2015-07-20 38 views
10

Załóżmy, że mam złożony model widoku z dużą ilością danych, takich jak listy krajów, produkty, kategorie itp., Które muszę pobrać z bazy danych za każdym razem, gdy utworzę ViewModel.Czy można używać repozytorium w widoku modelu?

Głównym problemem chcę naprawić to, że kiedy uchwyt działania i po kilku TestModel została wysłana z nieprawidłowymi wartościami, co powoduje ModelState.IsValid być false, potem muszę wrócić ten sam widok z aktualnie zamieszczonych modelu. To zmusza mnie do ponownego uzyskania listy kategorii, ponieważ robiłem to w akcji GET. Dodaje to dużo powielonego kodu do kontrolera i chcę go usunąć. Obecnie robię następujące:

mój model i widok modele:

modelu, jednostka przechowywane w bazie danych:

public class Category 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 

    public IEnumerable<Category> SubCategories { get; set; } 
} 

Zobacz modele:

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

Moje kontroler:

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

Chcę usunąć zduplikowane kategorie pobierania i mapowanie, w zasadzie ten kod:

.Categories = _repository.Categories.Select(c => new CategoryModel 
      { 
       Id = c.Id, 
       Name = c.Name 
      }) 

od kontrolera. Również chcę usunąć sprawdzanie poprawności ModelState, chcę wykonać akcję tylko, jeśli ModelState.IsValid, aby zachować kod kontrolera AS CLEAN AS MOŻLIWE. Do tej pory mam następujące rozwiązanie do usuwania czek ModelState ważności:

tworzyć niestandardowe 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, 
     }; 
    } 
} 

Teraz model jest sprawdzane przed wykonuje działanie. W przypadku błędów sprawdzania poprawności używamy tego samego widoku z tym samym ostatnio opublikowanym modelem. Dlatego akcja kontroler POST wygląda następująco:

[HttpPost] 
[ValidateModelAttribute] 
public ActionResult Index(TestModel model) 
{ 
    // Do some important stuff with posted data 
    return RedirectToAction("Success"); 
} 

To jest ładne, ale teraz mój Categories własność moich TestModel jest pusta, bo muszę sprowadzić kategorie z bazy danych i map je odpowiednio. Więc jest to OK, aby zmodyfikować model moim zdaniem wyglądać mniej więcej tak:

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

    ... 
} 

Pozwoli to nam na bardzo czysty kontroler, ale nie będzie to powodować jakieś wydajności lub zagadnień architektonicznych? Czy nie złamałoby to zasady pojedynczej odpowiedzialności dla modeli widoku? Czy ViewModels powinien być odpowiedzialny za pobieranie potrzebnych danych?

+2

Idealnie nie, Twoje modele widoków nie wchodzi bezpośrednio w interakcje z repozytorium. Jeśli chcesz zapełnić swój model z repozytorium, stanie się to w twoim kontrolerze. Jeśli nie chcesz powielać kategorii ludności w oddzielnych trasach kontrolera, możesz spróbować przekonwertować tę logikę na osobną metodę. – timothyclifford

Odpowiedz

3

To nie jest w porządku. model widoku powinien być głównie DTO wypełnionym przez usługę/zapytanie, a nawet kontroler. Nie było problemu z poprzednią wersją, twój kontroler to tylko kilka linii kodu.

Ale twoje repozytorium nie jest tak naprawdę repozytorium, to ORM.Właściwe repozytorium (dobrze tutaj byłby tylko jakiś obiekt zapytania) zwróciłoby bezpośrednio listę kategorii dla modelu widoku.

O atrybucie automatycznego sprawdzania poprawności, nie wymyślaj ponownie koła, ktoś inny (w tym przypadku ja) zrobił to before.

3

Nie, nie należy umieszczać referencji repozytorium i logiki w modelach widoku. Przypuszczam, że jedyną rzeczą, której potrzebujesz, jest możliwość odbudowania modelu, jeśli sprawdzanie poprawności zakończy się niepowodzeniem. Można spróbować jednego z automatycznego sprawdzania poprawności ModelState, na przykład:

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

0

Istnieje kilka przepływy widzę ze swoim podejściu

  1. Korzystanie z repozytorium i robi rzeczywistego zapytań w kontroler,

    var model = new TestModel 
    { 
        Categories = _repository.Categories.Select(c => new CategoryModel 
        { 
         Id = c.Id, 
         Name = c.Name 
        }) 
    }; 
    

lepszym rozwiązaniem jest albo MO w tym do repozytorium lub jeszcze lepiej umieść go na bardziej logicznym poziomie, takim jak usługi.

Dzięki nowemu rozwiązaniu jest jeszcze gorzej, gdy kierujesz repozytorium do modelu widoku. Idealnie byłoby zrobić to tak,

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