C# 在视图模型中使用存储库可以吗?

C# 在视图模型中使用存储库可以吗?,c#,asp.net-mvc,viewmodel,C#,Asp.net Mvc,Viewmodel,假设我有一个复杂的视图模型,其中包含很多数据,例如国家、产品、类别等的列表,每次创建视图模型时都需要从数据库中获取这些数据 我想解决的主要问题是,当我处理POST操作时,一些TestModel使用不正确的值发布,这导致ModelState.IsValid为false,然后我必须返回与当前发布的模型相同的视图。这迫使我再次获取类别列表,因为我是在get操作中这样做的。这会在控制器中添加大量重复代码,我想将其删除。目前我正在做以下工作: 我的模型和视图模型: public class HomeCon

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

我想解决的主要问题是,当我处理POST操作时,一些
TestModel
使用不正确的值发布,这导致
ModelState.IsValid
false
,然后我必须返回与当前发布的模型相同的视图。这迫使我再次获取类别列表,因为我是在get操作中这样做的。这会在控制器中添加大量重复代码,我想将其删除。目前我正在做以下工作:

我的模型和视图模型:

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
时执行操作,以尽可能保持控制器代码。到目前为止,我有以下用于删除
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");
}
这很好,但是现在我的
TestModel
Categories
属性为空,因为我必须从数据库中获取类别,并相应地映射它们那么可以修改我的视图模型,使其看起来像这样吗

public class TestModel
{
    private readonly Repository _repository = ObjectFactory.GetRepositoryInstance();

    ...

    public int SelectedCategory { get; set; }
    public IEnumerable<CategoryModel> Categories {
        get
        {
            return _repository.Categories.Select(c => new CategoryModel
            {
                Id = c.Id,
                Name = c.Name
            });
        }
    }

    ...
}
公共类测试模型
{
私有只读存储库_Repository=ObjectFactory.GetRepositoryInstance();
...
public int SelectedCategory{get;set;}
公共可数范畴{
得到
{
return\u repository.Categories.Select(c=>newcategoryModel
{
Id=c.Id,
Name=c.Name
});
}
}
...
}

这将允许我们拥有非常干净的控制器,但它不会导致某种性能或体系结构问题吗?它不会打破视图模型的单一责任原则吗?ViewModels应该负责获取所需的数据吗?

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

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


关于自动验证属性,不要重新发明轮子,是别人(在本例中是我)做的。

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


我可以从您的方法中看到一些流程

  • 使用存储库并在控制器中执行实际查询

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

    理想情况下,视图模型不会直接与存储库交互。如果您需要从存储库中填充模型,这将发生在您的控制器中。如果您不想在单独的控制器路由中重复类别填充,您可以尝试将此逻辑重构为单独的方法。。。
    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();
        }
    }