Лучший способ заполнить SelectList для ViewModel на GET/POST

У меня есть следующая ViewModel:

public class EditViewModel
{
    public int FooType { get; set; }
    public IEnumerable<SelectListItem> FooTypes { get; set; }
}

Первоначально я заполнил его в своем действии редактирования следующим образом:

public ActionResult Edit(int id)
{
    EditViewModel model = new EditViewModel();
    model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");

    return View(model);
}

Когда я создал действие для POST значений, мне пришлось повторить тот же код:

public ActionResult Edit(int id, EditViewModel model)
{
    if( !ModelState.IsValid )
    {
        model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");

        return View(model);
    }

    return RedirectToAction("Index");
}

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


person Dismissile    schedule 29.09.2011    source источник


Ответы (3)


Учитывая, что C# является объектно-ориентированным языком, доступно множество вариантов.

Проще всего было бы просто обернуть его в метод внутри контроллера:

private SelectList GetFooTypesList()
{
    return new SelectList(repository.GetFooTypes(), "Id", "Value);
}

и вызовите его при настройке вашей модели

или, если вы используете его в нескольких классах, вы можете создать вспомогательный метод в другом классе, который принимает репозиторий или IEnumerable в качестве параметра.

Если вы хотите стать действительно продвинутым, вы можете использовать ModelFactory для создания модели FooType для вас с предварительно заполненным свойством FooType, поэтому контроллеру вообще не нужно беспокоиться об этом.

Вариантов много, нужно только выбрать тот, который подходит именно вам.

Моим личным предпочтением является простой вспомогательный метод в контроллере.

person lomaxx    schedule 29.09.2011

Я делал это в модели раньше (когда это было практикой кодирования для этой проектной группы), но это зависит от вашей философии в отношении того, что такое «бизнес-логика» и что такое «доступ к данным», и что относится к модели против контроллера. Существуют разные и обоснованные мнения.

Модель, где вам нужен обнуляемый тип для FooType:

public class EditViewModel
{
    public int? FooType { get; set; }
    public IEnumerable<SelectListItem> GetFooTypes(object selectedFooType = null)
    {
        return new SelectList(repository.GetFooTypes(), "Id", "Value", selectedFooType);
    }
}

Контроллер «Get», где вам нужно сначала создать модель, чтобы убедиться, что свойство Model доступно в представлении:

public ActionResult Edit(int id)
{
    EditViewModel model = new EditViewModel();

    return View(model);
}

Вид (без Барбары Вава):

@Html.DropDownListFor(m => m.FooType, Model.GetFooTypes(Model.FooType))

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

Модель:

public class EditViewModel
{
    public int? FooType { get; set; }
    public IEnumerable<int?> FooTypes
    {
        get
        {
            // declare/define repository in your model somewhere    
            return repository.GetFooTypes();
        }
    }
}

Вид:

@Html.DropDownListFor(m => m.FooType, new SelectList(Model.FooTypes, "Id", "Value", Model.FooType))
person nekno    schedule 30.09.2011

В ответе «nekno» (ответил 30 сентября в 22:19) есть две альтернативы ViewModel, которые либо возвращают «IEnumerable‹SelectListItem›», либо «IEnumerable‹int?›». Обе эти альтернативы используют репозиторий, но фактически не создают его, поэтому я хотел бы немного расширить пример кода и выбрать вторую альтернативу, то есть класс со свойством, типизированным 'IEnumerable‹int?›':

using Microsoft.Practices.ServiceLocation; // ServiceLocator , http://commonservicelocator.codeplex.com/
using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
    public int? FooType { get; set; }

    public IEnumerable<int?> FooTypes
    {
        get
        {
            return Repository.GetFooTypes();
        }
    }

    private IRepository Repository
    {
        get
        {
            return ServiceLocator.Current.GetInstance<IRepository>();
        }
    }   
}

Приведенный выше вид кода с «Поиском зависимостей» теперь использует зависимость от сторонней библиотеки, в данном случае библиотеки Common Service Locator.

Мой вопрос: как можно заменить приведенный выше код на «Внедрение зависимостей»? Саму ViewModel действительно было бы очень просто реализовать, вот так:

using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
    private readonly IRepository _repository;

    public EditViewModel(IRepository repository) 
    {
        _repository = repository;
    }

    public int? FooType { get; set; }

    public IEnumerable<int?> FooTypes
    {
        get
        {
            return _repository.GetFooTypes();
        }
    }
}

Проблема заключается в том, как заставить ViewModel внедряться в реализацию, когда среда ASP.NET MVC создаст экземпляр «EditViewModel» и отправит его в качестве параметра в метод Action, такой как подпись метода tihs:

public ActionResult Edit(int id, EditViewModel model)  {
// How do we make the framework instantiate the above 'EditViewModel' with an implementation of 'IRepository' when the Action method is invoked ???

Официальный учебник по MVC, насколько я вижу, не предлагает хорошего решения. В разделе «Обработка правок» (методы «public ActionResult Edit(...)» ) на страницах ниже они дублируют создание параметров так же, как в постере этого вопроса о стеке, который вы сейчас читаете.

http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-5

http://mvcmusicstore.codeplex.com/SourceControl/changeset/view/d9f25c5263ed#MvcMusicStore/Controllers/StoreManagerController.cs

Если есть решение о том, как заставить фреймворк вводить модель представления с помощью ваших средств извлечения данных (например, репозитория), то я полагаю, что это может быть использование некоторой реализации либо «IModelBinderProvider», либо «IModelBinder», но я экспериментировал с ними без настоящий успех...

Итак, может ли кто-нибудь предоставить ссылку на полный рабочий пример с кодом ASP.NET MVC 3, который позволяет внедрять средство извлечения данных в конструктор модели представления, которую создает среда и которая будет отправляться в качестве параметра в метод действия?

Обновление от 01 01 2012. Для тех, кого интересует решение этого конкретного вопроса о внедрении конструктора экземпляра ViewModel, когда платформа создает его экземпляр и отправляет в качестве параметра параметру метода действия MVC, я создали новый вопрос с более конкретной темой, и, таким образом, более вероятно, что кто-то с решением найдет его и опубликует хороший ответ: Внедрение конструктора экземпляра модели представления, используемого в качестве параметра метода действия

person user310457    schedule 29.12.2011