【问题标题】:Is it OK to use repository in view model?在视图模型中使用存储库可以吗?
【发布时间】:2015-10-09 13:26:11
【问题描述】:

假设我有一个复杂的视图模型,其中包含大量数据,例如国家、产品、类别等列表,每次创建 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 时执行操作以保持控制器代码尽可能清洁。到目前为止,我有以下解决方案来删除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");
}

这很好,但现在我的TestModelCategories 属性为空,因为我必须从数据库中获取类别,并相应地映射它们。 那么可以将我的视图模型修改为如下所示

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

    ...
}

这将使我们拥有非常干净的控制器,但它不会导致某种性能或架构问题吗?它不会破坏视图模型的单一责任原则吗? ViewModel 是否应该负责获取它需要的数据?

【问题讨论】:

  • 理想情况下不会,您的视图模型不会直接与您的存储库交互。如果您需要从存储库中填充模型,这将发生在您的控制器中。如果您不想在单独的控制器路由中复制类别填充,您可以尝试将此逻辑重构为单独的方法。
  • 很高兴知道我不是唯一做错的人......

标签: c# asp.net-mvc viewmodel


【解决方案1】:

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

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

关于你的自动验证属性,不要重新发明轮子,其他人(在这种情况下是我)做到了before

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-10-31
        • 2019-12-18
        • 2015-05-04
        • 2010-10-05
        • 2011-12-29
        • 2021-04-30
        • 2016-06-25
        相关资源
        最近更新 更多