ASP.NET MVC ViewModel с наилучшей практикой SelectList (s)

Я заметил, что в приложении 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 в мой контроллер. Должен ли я создать публичный метод в моей бизнес-логике исключительно для того, чтобы делать такие вещи на моей модели (-ях) просмотра?

Ответы

Ответ 1

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

[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.
}

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

Ответ 2

Вы не говорите, сколько повторного использования вы хотите. Но лично мне нравятся вещи "clear" (dont invading controller) и, насколько это возможно, возможно, и что в 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, или вы можете даже "обернуть" это тоже (и избегать "магических строк" ​​в представлениях), с пользовательским многоразовым DropDown:

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

Ответ 3

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

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

Пример

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

Update

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

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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