在视图模型中使用存储库是否可以?

时间:2015-07-20 12:13:27

标签: c# asp.net-mvc viewmodel

假设我有复杂的视图模型,其中包含许多数据,例如国家/地区,产品,类别等列表,我每次创建ViewModel时都需要从数据库中获取这些数据。

我想解决的主要问题是,当我处理POST操作时,某些TestModel发布的值不正确,导致ModelState.IsValidfalse,那么我必须返回与当前发布的模型相同的视图。这迫使我再次获取我的类别列表,因为我在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应该负责获取它需要的数据吗?

3 个答案:

答案 0 :(得分:3)

不行。视图模型应该主要是由服务/查询或甚至控制器填充的DTO。以前的版本没有问题,你的控制器只是几行代码。

但是你的存储库并不是真正的存储库,而是一个ORM。一个适当的存储库(在这里它只是一些查询对象)将直接返回视图模型的类别列表。

关于您的自动验证属性,请勿重新发明轮子,其他人(在本例中为我)执行此操作before

答案 1 :(得分:3)

不,您不应该将存储库引用和逻辑放入视图模型中。我想你唯一需要的是能够在验证失败时重建模型。您可以尝试其中一种自动ModelState验证,例如:

http://benfoster.io/blog/automatic-modelstate-validation-in-aspnet-mvc

答案 2 :(得分:0)

我的方法可以看到一些流程,

  1. 使用存储库并在控制器中进行实际查询,

    var model = new TestModel
    {
        Categories = _repository.Categories.Select(c => new CategoryModel
        {
            Id = c.Id,
            Name = c.Name
        })
    };
    
  2. 更好的方法是将其移至存储库,或者更好地将其置于更合理的层面,例如服务。

    使用您的新解决方案,当您在视图模型中引用存储库时,情况会更糟。理想情况下,我会这样做,

    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();
        }
    }