Как я могу повторно использовать DropDownList в нескольких представлениях с помощью .NET MVC

Несколько просмотров из моего проекта имеют один и тот же раскрывающийся список...

Итак, в ViewModel из этого представления у меня есть:

public IEnumerable<SelectListItem> FooDdl { get; set; }

И в контроллере у меня есть:

var MyVM = new MyVM() {
    FooDdl = fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name)
}

До сих пор так хорошо... Но я делаю тот же код в каждом представлении/контроллере, у которого есть этот ddl...

Это лучший способ сделать это?

Спасибо

Ответы

Ответ 1

Мы также используем статический класс:

public static class SelectLists
{
        public static IList<SelectListItem> CompanyClasses(int? selected)
        {
            var session = DependencyResolver.Current.GetService<ISession>();

            var list = new List<SelectListItem>
                           {
                               new SelectListItem
                                   {
                                       Selected = !selected.HasValue,
                                       Text = String.Empty
                                   }
                           };

            list.AddRange(session.All<CompanyClass>()
                              .ToList()
                              .OrderBy(x => x.GetNameForCurrentCulture())
                              .Select(x => new SelectListItem
                                               {
                                                   Selected = x.Id == (selected.HasValue ? selected.Value : -1),
                                                   Text = x.GetNameForCurrentCulture(),
                                                   Value = x.Id.ToString()
                                               })
                              .ToList());

            return list;
        }
}

В представлении у нас нет ничего особенного:

@Html.DropDownListFor(x => x, SelectLists.CompanyClasses(Model))

И когда-то мы также создаем EditorTemplate, чтобы быстрее повторять использование этого

Модель:

[Required, UIHint("CompanyClassPicker")]
public int? ClassId { get; set; }

EditorTemplate:

@model int?

@if (ViewBag.ReadOnly != null && ViewBag.ReadOnly)
{
    var item = SelectLists.CompanyClasses(Model).FirstOrDefault(x => x.Selected);

    if (item != null)
    {
        <span>@item.Text</span>
    }
}
else
{
    @Html.DropDownListFor(x => x, SelectLists.CompanyClasses(Model))    
}

Ответ 2

Я бы сказал, что хорошо, если честно, поскольку это всего лишь повторение нескольких строк кода. Если он действительно беспокоит вас, но вы могли бы наследовать все свои контроллеры от BaseController (если они этого еще не сделали) и сохранить там метод, чтобы получить их все, например:

public IEnumerable<SelectListItem> GetFoos()
{
    return fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name);
}

Затем в ваших контроллерах вы можете:

var MyVM = new MyVM() {
    FooDdl = GetFoos()
}

Ответ 3

Если ваш DropDownList точно такой же, я бы использовал следующий подход:

1) В базовом контроллере или в классе помощника вы можете создать метод, который возвращает SelectList. Этот метод должен получить nullabe int, чтобы получить список выбора с предварительно выбранным значением.

2) Целесообразно кэшировать информацию, указанную в DDL, чтобы не запрашивать базу данных слишком часто.

Итак, для (1):

public SelectList GetMyDDLData(int? selectedValue){
    var data = fooRepository.GetAll().Select(x => new { Value = x.Id, Text = x.Name });
    return new SelectList(data, "Id","Name", selectedValue);
}

В модели просмотра:

var myVM = new MyVM();
myVM.DDLData = this.GetMyDDLData(null) // if it is in your BaseController.
myVM.DDLData = YourHelperClass.GetMyDDLData(null) // if it is in a helper static class

В ваших представлениях:

@Html.DropDownListFor(x => x.FooProp, Model.DDLData, "Select one...")

Для числа (2):

private IEnumerable<YourModel> GetMyData()
{
    var dataItems = HttpContext.Cache["someKey"] as IEnumerable<YourModel>;
    if (dataItems == null)
    {
        // nothing in the cache => we perform some expensive query to fetch the result
        dataItems = fooRepository.GetAll().Select(x => new YourModel(){ Value = x.Id, Text = x.Name };

        // and we cache it so that the next time we don't need to perform the query
        HttpContext.Cache["someKey"] = dataItems ;
    }

    return dataItems;
}

"someKey" может быть чем-то конкретным и статическим, эти данные одинаковы для всех пользователей, или вы можете сделать "someKey" + User.Id, если данные специфичны для одного пользователя.

Если ваш репозиторий является абстрагированным слоем (а не непосредственно EntityFramework), вы можете разместить там этот код.

Ответ 4

Создайте объект с помощью getter для выпадающих значений:

public static class DropDowns
{
    public static List<SelectListItem> Items { 
       get
       {
           //Return values
       } 
    } 
}

Создание частичной дробилки:

@Html.DropDownListFor(m => "ChoosenItem", DropDowns.Items, "")

Вызвать частично:

@Html.RenderPartial("DropDownItems")

И, наконец, получите значение ChoosenItem в контроллере. Просто.

Ответ 5

Я использую IModelEnricher в сочетании с Automapper и атрибутами, которые определяют отношения между типом списка и поставщиком списка выбора. Я возвращаю объект и т.д., Используя специальный ActionResult, который затем запускает мой объект в ViewModel и обогащается данными, необходимыми для выбранных списков (и любых дополнительных данных). Кроме того, сохранение данных списка избранного как части вашего ViewModel не позволяет очистить ваш контроллер, модель и просмотр.

Определение ViewModel ernicher означает, что везде, где используется ViewModel, он может использовать один и тот же enricher для получения своих свойств. Таким образом, вы можете вернуть ViewModel в несколько мест, и он будет просто заполнен правильными данными.

В моем случае это выглядит примерно так в контроллере:

public virtual ActionResult Edit(int id)
{
    return AutoMappedEnrichedView<PersonEditModel>(_personRepository.Find(id));
}

[HttpPost]
public virtual ActionResult Edit(PersonEditModel person)
{
     if (ModelState.IsValid){
            //This is simplified (probably don't use Automapper to go VM-->Entity)
            var insertPerson = Mapper.Map<PersonEditModel , Person>(person);
            _personRepository.InsertOrUpdate(insertPerson);
            _requirementRepository.Save();
            return RedirectToAction(Actions.Index());
      }
     return EnrichedView(person);
 }

Этот вид ViewModel:

public class PersonEditModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public int FavouriteTeam { get; set; }
    public IEnumerable<SelectListItem> Teams {get;set;}
}

С этим видом Enricher:

public  class PersonEditModelEnricher :
IModelEnricher<PersonEditModel>
{
    private readonly ISelectListService _selectListService;

    public PersonEditModelEnricher(ISelectListService selectListService)
    {
        _selectListService = selectListService;
    }

    public PersonEditModelEnrich(PersonEditModel model)
    {
        model.Teams = new SelectList(_selectListService.AllTeams(), "Value", "Text")
        return model;
    }
} 

Еще один вариант заключается в том, чтобы украсить ViewModel атрибутами, которые определяют, как данные располагаются для заполнения списка выбора. Как:

  public class PersonEditModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public int FavouriteTeam { get; set; }
        [LoadSelectListData("Teams")]
        public IEnumerable<SelectListItem> Teams {get;set;}
    }

Теперь вы можете украсить соответствующий метод в своей службе выбора с помощью такого атрибута, как:

   [ProvideSelectData("Teams")]
   public IEnumerable Teams()
   {
        return _teamRepository.All.ToSelectList(a => a.Name, a => a.TeamId);
   }

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

Другими параметрами могут быть соглашения о конфигурации, в которых Enricher просматривает имя и тип свойства, например. IEnumerable<SelectListItem> PossibleFirstDivisionTeams {get; set;} затем соответствует этому, если он существует с именем поставщика списка выбора в классе, который говорит, реализует интерфейс-маркер ISelectListProvider. Мы отправили атрибут, основанный на одном и только что созданный Enums, представляющий различные списки, например. SelectList.AllFirstDivisionTeams. Также можно попробовать интерфейсы на ViewModel, которые просто имеют коллекцию свойств для selectlist. Мне не нравятся интерфейсы на моих моделях ViewModels, поэтому мы никогда этого не делали

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

Смотрите question. Также этот блог post и this. Также этот вопрос на форуме Automapper

Ответ 6

Первый вопрос: если список параметров принадлежит ViewModel. Год-два назад я сделал то же самое, но то, что я вижу в последнее время все больше и больше как "лучшая практика", заключается в том, что люди добавляют список в ViewBag/ViewData не в ViewModel. Это вариант, и я, как правило, делаю то же самое для раскрывающегося списка с одним выстрелом, но он не отвечает на вопрос о повторном использовании кода, с которым вы сталкиваетесь. Для этого я вижу два разных подхода (и еще два, которые я исключаю).

Общий шаблон редактора. Создайте шаблон редактора для типа, представленного выпадающим списком. В этом случае - потому что у нас нет списка возможных параметров в ViewModel или ViewBag - шаблон должен запрашивать параметры сервера. Это возможно, добавив метод действия (который возвращает json) в класс контроллера. Либо для общего "LookupsController" (возможно, ApiController), либо для контроллера, к которому принадлежит тип элементов списка.

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

Преимущество первого заключается в том, что хороший вызов @Html.EditorFor выполнит эту работу. Но мне не нравится зависимость ajax. Отчасти по этой причине я предпочел бы частичный вид.

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

Ответ 7

Вы можете поместить эту выборку в конструктор по умолчанию (null) MyVM, если вам не нужно изменять его содержимое.

Или вы можете использовать PartialView, который вы визуализируете в представлениях, которые нуждаются в t.

Ответ 8

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

Создайте частичный вид, _MyDropdownView.cstml, который использует вспомогательный класс, который вы выбрали из контроллеров с помощью следующего:

@using MyNamespace.MyHelperClass
<div id="myDropdown">@Html.DropDownListFor(model => model.Prop, MyVM as SelectList, "--Select a Property--")</div>

Затем в ваших представлениях:

@Html.RenderPartial("_MyDropdownView")

Ответ 9

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

@Html.DropDownListFor(x => x.Field, PathToController.GetDropDown())

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

public static List<SelectListItem> GetDropDown()
    {
        List<SelectListItem> ls = new List<SelectListItem>();
        lm = (call database);
        foreach (var temp in lm)
        {
            ls.Add(new SelectListItem() { Text = temp.name, Value = temp.id });
        }
        return ls;
    }

Надеюсь, это поможет.

Ответ 10

Методы расширения для спасения

public interface ISelectFoo {    
    IEnumerable<SelectListItem> FooDdl { get; set; }
}

public class FooModel:ISelectFoo {  /* implementation */ }     

public static void PopulateFoo(this ISelectFoo data, FooRepository repo)
{
    data.FooDdl = repo.GetAll().ToSelectList(x => x.Id, x => x.Name);
}


//controller
var model=new ViewModel(); 
model.PopulateFoo(repo);


 //a wild idea
public static T CreateModel<T>(this FooRepository repo) where T:ISelectFoo,new()
{
    var model=new T();
    model.FooDdl=repo.GetAll().ToSelectList(x => x.Id, x => x.Name);
    return model;
 }

//controller
 var model=fooRepository.Create<MyFooModel>();

Ответ 11

Как насчет метода Prepare в BaseController?

public class BaseController : Controller
{
    /// <summary>
    /// Prepares a new MyVM by filling the common properties.
    /// </summary>
    /// <returns>A MyVM.</returns>
    protected MyVM PrepareViewModel()
    {
        return new MyVM()
        {
            FooDll = GetFooSelectList();
        }
    }

    /// <summary>
    /// Prepares the specified MyVM by filling the common properties.
    /// </summary>
    /// <param name="myVm">The MyVM.</param>
    /// <returns>A MyVM.</returns>
    protected MyVM PrepareViewModel(MyVM myVm)
    {
        myVm.FooDll = GetFooSelectList();
        return myVm;
    }

    /// <summary>
    /// Fetches the foos from the database and creates a SelectList.
    /// </summary>
    /// <returns>A collection of SelectListItems.</returns>
    private IEnumerable<SelectListItem> GetFooSelectList()
    {
        return fooRepository.GetAll().ToSelectList(foo => foo.Id, foo => x.Name);
    }
}

Вы можете использовать эти методы в контроллере:

public class HomeController : BaseController
{
    public ActionResult ActionX()
    {
        // Creates a new MyVM.
        MyVM myVm = PrepareViewModel();     
    }

    public ActionResult ActionY()
    {
        // Update an existing MyVM object.
        var myVm = new MyVM
                       {
                           Property1 = "Value 1",
                           Property2 = DateTime.Now
                       };
        PrepareViewModel(myVm);
    }
}

Ответ 12

У вас есть интерфейс со всеми вашими свойствами, которые необходимо автоматически заполнить:

public interface ISelectFields
{
    public IEnumerable<SelectListItem> FooDdl { get; set; }
}

Теперь все ваши модели взглядов, которые хотят иметь эти свойства, реализуют этот интерфейс:

public class MyVM : ISelectFields
{
    public IEnumerable<SelectListItem> FooDdl { get; set; }
}

Имейте BaseController, переопределите OnResultExecuting, найдите ViewModel, который передается и вводит свойства в интерфейс:

public class BaseController : Controller
{
    protected override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var viewResult = filterContext.Result as ViewResult;
        if (viewResult != null)
        {
            var viewModel = viewResult.Model as ISelectFields;
            if (viewModel != null)
            {
                viewModel.FooDdl = fooRepository.GetAll().ToSelectList(x => x.Id, x => x.Name)
            }
        }
        base.OnResultExecuting(filterContext);
    }
}

Теперь ваши контроллеры очень просты, все строго набрано, вы придерживаетесь принципа DRY, и вы можете просто забыть о заполнении этого свойства, оно всегда будет доступно в ваших представлениях, пока ваши контроллеры наследуют от BaseController и ваш ViewModels реализует интерфейс.

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        MyVM vm = new MyVM();
        return View(vm);   //you will have FooDdl available in your views
    }
}

Ответ 13

Почему бы не использовать преимущества RenderAction: @(Html.RenderAction("ControllerForCommonElements", "CommonDdl"))

Создайте контроллер и действие, которое возвращает Ddl и просто ссылается на него в представлениях.

Ознакомьтесь с некоторыми советами здесь о том, как вы можете использовать его

Таким образом вы также можете кэшировать этот результат. На самом деле ребята, строящие StackOverflow, говорили о преимуществах использования этого в сочетании с различными правилами кэширования для разных элементов (т.е. Если ddl не должен быть на 100% до настоящего времени, вы можете кэшировать его на минутку или около того) в подкасте a while назад.