如何在.NET MVC的多个视图中重用DropDownList

我项目中的几个视图都有相同的下拉列表…

所以,在该视图的ViewModel中我有:

public IEnumerable FooDdl { get; set; } 

在控制器中我有:

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

到目前为止一切都那么好……但我在每个具有该ddl的视图/控制器中执行相同的代码…

这是最好的方法吗?

谢谢

我们还使用静态类:

 public static class SelectLists { public static IList CompanyClasses(int? selected) { var session = DependencyResolver.Current.GetService(); var list = new List { new SelectListItem { Selected = !selected.HasValue, Text = String.Empty } }; list.AddRange(session.All() .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) { @item.Text } } else { @Html.DropDownListFor(x => x, SelectLists.CompanyClasses(Model)) } 

我说老实说没关系,因为它只是重复几行代码。 如果它真的困扰你,你可以让你的所有控制器inheritance自BaseController (如果它们还没有)并在那里存储一个方法来获取它们,例如:

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

然后在您的控制器中,您可以:

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

如果您的DropDownList与我将使用的方法完全相同:

1)在Base Controller或Helper类中,您可以创建一个返回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 GetMyData() { var dataItems = HttpContext.Cache["someKey"] as IEnumerable; 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

如果您的存储库是一个abstractin图层(不是直接的EntityFramework),您可以将此代码放在那里。

使用getter为您的下拉列值创建对象:

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

创建Razor部分:

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

呼叫部分:

 @Html.RenderPartial("DropDownItems") 

最后在控制器中接收ChoosenItem值。 只是。

我使用IModelEnricher结合IModelEnricher和属性来定义一种列表和选择列表提供程序之间的关系。 我使用特定的ActionResult返回实体等,然后将我的实体自动化为ViewModel,并丰富选择列表所需的数据(以及所需的任何其他数据)。 同时将选择列表数据保留为ViewModel的一部分可以使您的控制器,模型和查看职责保持清晰。

定义ViewModel ernicher意味着在使用ViewModel的任何地方,它都可以使用相同的richr来获取其属性。 因此,您可以在多个位置返回ViewModel,它只会填充正确的数据。

在我的情况下,这在控制器中看起来像这样:

 public virtual ActionResult Edit(int id) { return AutoMappedEnrichedView(_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(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 Teams {get;set;} } 

有了这种Enricher:

 public class PersonEditModelEnricher : IModelEnricher { 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 Teams {get;set;} } 

现在,您可以使用以下属性在选择服务中修饰适当的方法:

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

然后对于没有复杂浓缩的简单模型,只需通用浓缩过程就可以处理它。 如果你想做更复杂的事情,你可以定义一个更丰富的,如果它存在,它将被使用。

其他选项可以是配置方法的约定,其中Enricher查看属性名称和类型,例如IEnumerable PossibleFirstDivisionTeams {get; set;}然后匹配它,如果它与类中的选择列表提供者名称一起表示实现标记接口ISelectListProvider 。 我们去了一个属性,刚刚创建了代表各种列表的枚举,例如SelectList.AllFirstDivisionTeams 。 也可以在ViewModel上尝试只有选择列表的属性集合的接口。 我真的不喜欢我的ViewModel上的接口所以我们从来没有这样做过

这完全取决于您的应用程序的规模以及跨多个模型需要相同类型的选择列表数据的频率。 您需要澄清的任何具体问题或要点让我知道

看到这个问题 。 这篇博文也是这个 。 还有关于Automapper论坛的这个问题

第一个问题是options-list是否属于ViewModel。 一两年前我做了同样的事情,但我最近看到越来越多的“最佳实践”是人们将列表添加到ViewBag / ViewData而不是ViewModel。 这是一个选项,我倾向于对一次性下拉列表做同样的事情,但它没有回答你所面临的代码重用问题。 为此,我看到了两种不同的方法(我还排除了两种方法)。

共享编辑器模板。 为下拉列表所代表的类型创建编辑器模板。 在这种情况下 – 因为我们没有ViewModel或ViewBag中可能的选项列表 – 模板必须找到服务器的选项。 这可以通过向控制器类添加一个action方法(返回json)来实现。 要么是共享的“LookupsController”(可能是ApiController),要么是list-items’类型所属的控制器。

局部视图 。 下拉值属于某种类型。 该类型的Controller可以具有返回局部视图的action方法。

第一个好处是,一个不错的@Html.EditorFor调用将完成这项工作。 但我不喜欢ajax依赖。 部分由于这个原因,我更喜欢局部视图。

还有第三个: 儿童行动 ,但我不认为这是一个好的模式。 您可以谷歌查看子操作和部分视图之间的区别,对于这种情况,子操作是错误的选择。 我也不会推荐辅助方法 。 我相信它们也不是为这个用例而设计的。

如果您不需要更改其内容,可以将该提取放在MyVM的默认(null)构造函数中。

或者您可以使用您渲染到需要t的视图中的PartialView。

我喜欢在helper类中经常使用静态类,我可以从任何视图调用它。

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

然后在你的控制器中有一个像这样构建的方法

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

希望它有所帮助。

如果您真的不想复制代码,请将控制器中的代码放入辅助类中,然后在共享视图(如_Layout.cshtml)中呈现下拉列表,然后由RenderPartial将其实现到您的视图中。

创建一个局部视图_MyDropdownView.cstml,它使用助手类从控制器中输入代码,如下所示:

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

然后,在你的意见中:

 @Html.RenderPartial("_MyDropdownView") 

救援的延伸方法

 public interface ISelectFoo { IEnumerable 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(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(); 

BaseControllerPrepare方法怎么样?

 public class BaseController : Controller { ///  /// Prepares a new MyVM by filling the common properties. ///  /// A MyVM. protected MyVM PrepareViewModel() { return new MyVM() { FooDll = GetFooSelectList(); } } ///  /// Prepares the specified MyVM by filling the common properties. ///  /// The MyVM. /// A MyVM. protected MyVM PrepareViewModel(MyVM myVm) { myVm.FooDll = GetFooSelectList(); return myVm; } ///  /// Fetches the foos from the database and creates a SelectList. ///  /// A collection of SelectListItems. private IEnumerable 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); } } 

有一个界面,其中包含需要自动填充的所有属性:

 public interface ISelectFields { public IEnumerable FooDdl { get; set; } } 

现在所有想要拥有这些属性的视图模型都实现了该接口:

 public class MyVM : ISelectFields { public IEnumerable 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原则并且您可以忘记填充该属性,只要您的控制器inheritance自BaseControllerViewModels实现,它将始终在您的视图中可用界面。

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

为什么不使用RenderAction的优点: @(Html.RenderAction("ControllerForCommonElements", "CommonDdl"))

创建一个控制器,以及一个返回Ddl的动作,并在视图中引用它。

请参阅此处有关如何使用它的一些提示

这样您也可以缓存此结果。 实际上,构建StackOverflow的人谈到了使用它的优点,结合不同的缓存规则用于不同的元素(即如果ddl不需要100%更新,你可以在一分钟左右缓存它)一段时间播客前。