假设我有复杂的视图模型,其中包含许多数据,例如国家/地区,产品,类别等列表,我每次创建ViewModel时都需要从数据库中获取这些数据。
我想解决的主要问题是,当我处理POST操作时,某些TestModel
发布的值不正确,导致ModelState.IsValid
为false
,那么我必须返回与当前发布的模型相同的视图。这迫使我再次获取我的类别列表,因为我在GET操作中这样做。这在控制器中添加了大量重复的代码,我想删除它。目前我正在做以下事情:
我的模型和视图模型:
模型,存储在数据库中的实体:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Category> SubCategories { get; set; }
}
查看模型:
public class CategoryModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TestModel
{
[Required]
[MaxLength(5)]
public string Text { get; set; }
public int SelectedCategory { get; set; }
public IEnumerable<CategoryModel> Categories { get; set; }
public SelectList CategoriesList
{
get
{
var items = Categories == null || !Categories.Any()
? Enumerable.Empty<SelectListItem>()
: Categories.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
});
return new SelectList(items, "Value", "Text");
}
}
}
我的控制器:
public class HomeController : Controller
{
private readonly Repository _repository = ObjectFactory.GetRepositoryInstance();
public ActionResult Index()
{
var model = new TestModel
{
Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
})
};
return View(model);
}
[HttpPost]
public ActionResult Index(TestModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Succes");
}
model.Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
});
return View(model);
}
public ActionResult Succes()
{
return View();
}
}
我想删除重复的类别提取和映射,基本上是这段代码:
.Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
})
来自控制器。此外,我想删除ModelState
有效性检查,我只想在ModelState.IsValid
执行操作时保持控制器代码 AS CLEAN AS POSSIBLE 。到目前为止,我有以下解决方案来删除ModelState
有效性检查:
创建自定义ValidateModelAttribute
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var viewData = filterContext.Controller.ViewData;
if(viewData.ModelState.IsValid) return;
viewData.Model = filterContext.ActionParameters["model"];
filterContext.Result = new ViewResult
{
ViewData = viewData,
};
}
}
现在,在执行操作之前验证模型。在验证错误的情况下,我们使用相同的视图与最近发布的相同模型。因此,控制器POST操作如下所示:
[HttpPost]
[ValidateModelAttribute]
public ActionResult Index(TestModel model)
{
// Do some important stuff with posted data
return RedirectToAction("Success");
}
这很好,但现在我的Categories
的{{1}}属性为空,因为我必须从数据库中获取类别,并相应地映射它们。 所以可以修改我的视图模型看起来像这样:
TestModel
这将使我们拥有非常干净的控制器,但它不会导致某种性能或架构问题吗?它不会打破视图模型的单一责任原则吗? ViewModels应该负责获取它需要的数据吗?
答案 0 :(得分:3)
不行。视图模型应该主要是由服务/查询或甚至控制器填充的DTO。以前的版本没有问题,你的控制器只是几行代码。
但是你的存储库并不是真正的存储库,而是一个ORM。一个适当的存储库(在这里它只是一些查询对象)将直接返回视图模型的类别列表。
关于您的自动验证属性,请勿重新发明轮子,其他人(在本例中为我)执行此操作before。
答案 1 :(得分:3)
不,您不应该将存储库引用和逻辑放入视图模型中。我想你唯一需要的是能够在验证失败时重建模型。您可以尝试其中一种自动ModelState验证,例如:
http://benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc
答案 2 :(得分:0)
我的方法可以看到一些流程,
使用存储库并在控制器中进行实际查询,
var model = new TestModel
{
Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
})
};
更好的方法是将其移至存储库,或者更好地将其置于更合理的层面,例如服务。
使用您的新解决方案,当您在视图模型中引用存储库时,情况会更糟。理想情况下,我会这样做,
public class TestService : ITestService{
private IReposotory repo;
public TestService(IReposotory repo){
this.repo = repo;
}
public TestModel GetModel()
{
return new TestModel()
{
Categories = _repository.Categories.Select(c => new CategoryModel
{
Id = c.Id,
Name = c.Name
})
};
}
}
public class HomeController : Controller
{
private readonly ITestService _service;
public HomeController (ITestService service){
_service = service;
}
public ActionResult Index()
{
return View(_service.GetModel());
}
[HttpPost]
public ActionResult Index(TestModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Succes");
}
return View(model);
}
public ActionResult Succes()
{
return View();
}
}