ASP.NET MVC ViewModel с SelectList(Ы) лучшая практика

Я заметил, что в приложении NerdDinner, что если ModelState недействителен для обеда, он просто возвращает представление для модели:

        if (ModelState.IsValid) {
            ...
            return RedirectToAction("Details", new { id=dinner.DinnerID });
        }

        return View(dinner);

однако в моем приложении модель (модель представления в этой ситуации) содержит несколько списков выбора. На данный момент эти списки не создаются, поскольку эта модель представления была заполнена из представления формы. Что является рекомендуемым способом, чтобы заселить эту SelectLists, прежде чем отправить их обратно в пользователь?

это то, что я хочу, чтобы мой контроллер, чтобы делать:

public ActionResult Save(MyModel model)
{
    if (ModelState.IsValid)
    {
        businessClass.Save(model);
        return RedirectToAction("Index", "Home");
    }

    // This won't work because model has uninstantiated SelectLists
    return View("MyView", model);
}

Я не хочу отправлять модель в мою бизнес-логику, если ModelState недействителен, но, похоже, нет смысла помещать код заполнения SelectList в мой контроллер. Должен ли я создать общедоступный метод в моей бизнес-логике исключительно для выполнения такого рода вещей на моей модели(моделях) представления?

5 ответов


лично мне нравится держать его простым: -

[HttpGet]
public Edit(int id) {
     EditForm form = new EditForm();
     // Populate from the db or whatever...
     PopulateEditPageSelectLists(form);
     return View(form);
}

[HttpPost]
public Edit(EditForm form) {
     if (ModelState.IsValid) {
         // Do stuff and redirect...
     }
     PopulateEditPageSelectLists(form);
     return View(form);
}

public void PopulateEditPageSelectLists(form) {
     // Get lookup data from the db or whatever.
}

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


вы не говорите, сколько повторного использования вы хотели бы. Но лично мне нравятся вещи "четкие" (не вторгающиеся в контроллер) и по возможности повторяемые, а это в MVC означает - фильтры.

посмотри на это :

public class SupplyLanguagesAttribute : System.Web.Mvc.ActionFilterAttribute
{
    public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
    {
        filterContext.Controller.ViewData["languagesList"] =
            someService.LoadLanguagesAsDictionary();

        base.OnActionExecuting(filterContext);
    }
}

тогда вы просто используете его с каждым методом действий, где вам "могут" понадобиться языки:

[SupplyLanguages]
public ActionResult DoSomething()
{
...
}

а затем, учитывая, вы можете использовать данные непосредственно для DropDownList из ViewData, или вы даже можете "обернуть" это тоже (и избежать "волшебных строк" в views), с пользовательским многоразовым выпадающим списком:

public static MvcHtmlString LanguageDropDown(this HtmlHelper html, string name, object selectValue, bool defaultOption = false)
    {
        var languages = html.ViewData["languagesList"] as IDictionary<string,string>;

        if (languages == null || languages.Count() == 0)
            throw new ArgumentNullException("LanguageDropDown cannot operate without list of languages loaded in ViewData. Use SupplyLanguages filter.");

        var list = new SelectList(languages, "Key", "Value", selectValue);

        return SelectExtensions.DropDownList(html, name, list);
    }

мои контроллеры заполнить SelectLists на мою модель, если ModelState не действует.

после разделения проблем ваши бизнес-классы вообще ничего не должны знать о модели представления. Если вашему представлению нужен список сотрудников, контроллер получает список сотрудников из бизнес-слоя и создает список выбора, необходимый вашему представлению.

пример

public ActionResult Save(MyModel model) 
{ 
    if (ModelState.IsValid) 
    { 
        businessClass.Save(model); 
        return RedirectToAction("Index", "Home"); 
    } 
    model.PossibleEmployees 
             = _employeeRepository.All().Select(e => 
                                                new SelectListItem{Text=e.Name, 
                                                                   Value=e.Id});
    return View("MyView", model); 
} 

обновление

Если выбран код заполнения списка определяя, какие варианты представить, я думаю, вы, вероятно, должны переместить это в службу на своем бизнес-уровне. Если повторное использование является большой проблемой, ответ Руана выглядит так, как будто он имеет наибольшую возможность для повторного использования.


Я использую для заполнения списков, даже если модель недопустима. Еще одно возможное решение - это действие, возвращающее информацию json и создающее select через ajax. Иногда я также прибегал к статическим свойствам / кэшированным коллекциям. Я думаю, это всегда зависит от конкретного случая.

PS: вы можете использовать локальную модель в каждом действии, поэтому я могу оставить инициализацию внутри конструктора модели. (часто я переопределяю базовую модель с помощью [NonAction] утилиты, как ну.)

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

Я добавил некоторый метод утилиты в базовый контроллер для создания SelectListItems и подобных. Поскольку каждая модель наследуется от базы, я получил их почти везде в приложении. Конечно, коллекция заполняется через выделенный бизнес-объект.


У меня есть статическая функция в классе, которая возвращает SelectList. Метод принимает значение перечисления, которое определяет, какой SelectList возвращать. В представлении функции DropDownList или DropDownListFor вызывают эту функцию для получения списка выбора.

статическая функция выглядит так:

class HelperMethods
{
  enum LookupType {Users, Companies, States};

  public static SelectList CommonSelectList(LookupType type, int? filterValue = null)
    //filterValue can be used if the results need to be filtered in some way
    var db = new WhateverEntities();

    switch (type)
    {  
       case LookupType.Users:
         var list = db.Users.OrderBy(u => u.LastName).ToList()
         return new SelectList(list, "ID", "FullName")
         break;

       case LookupType.Companies
         var list = db.Companies.OrderBy(u => u.Name).ToList()
         return new SelectList(list, "ID", "Name")
         break;

       //and so on...
    }
  }
}

и представление содержит следующее:

@Html.DropDownListFor(m => m.UserID, HelperMethods.CommonSelectList(LookupType.Users))

таким образом, модель и контроллер не нуждаются в коде для настройки списка выбора для отправки Вид. Это упрощает повторное использование списка выбора, который уже был настроен. Кроме того, если представление должно перебирать список объектов, то эта же функция может использоваться для получения списка для этого. Это самый простой и удобный способ, который я нашел для этого.