Можно ли использовать репозиторий в модели просмотра?

Скажем, у меня сложная модель просмотра с большим количеством данных, таких как списки стран, продуктов, категорий и т.д., которые мне нужно извлекать из базы данных каждый раз, когда я создаю 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 моего TestModel пуст, потому что мне нужно получить категории из базы данных и соответствующим образом сопоставить их. Итак, это нормально, чтобы изменить мою модель представления, чтобы посмотреть что-то вроде этого:

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

    ...
}

Это позволит нам иметь очень чистый контроллер, но не вызовет ли он какие-либо проблемы с производительностью или архитектурой? Разве это не нарушит принцип единой ответственности для моделей взглядов? Должны ли ViewModels отвечать за выбор необходимых данных?

Ответ 1

Это не нормально. модель представления должна быть в основном DTO, заполненной сервисом/запросом или даже контроллером. Не было проблем с предыдущей версией, ваш контроллер - всего лишь пара строк кода.

Но ваш репозиторий не является репозиторием, а ORM. Правильный репозиторий (ну вот он будет просто некоторым объектом запроса) вернет непосредственно список категорий для модели представления.

О вашем атрибуте автоматической проверки, не изобретайте велосипед, кто-то другой (в этом случае я) сделал до.

Ответ 2

Нет, вы не должны указывать ссылку на репозиторий и логику в модели представления. Я полагаю, что единственное, что вам нужно - это возможность восстановить модель, если проверка не удалась. Вы можете попробовать одну из автоматизированных валидаций ModelState, например:

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

Ответ 3

Есть несколько потоков, которые я вижу с вашим подходом,

  • Используя репозиторий и выполняющий фактический запрос в контроллере,

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