15

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

因此,在该视图的 ViewModel 中,我有:

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

在控制器中我有:

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

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

这是最好的方法吗?

谢谢

4

13 回答 13

3

如果您的 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<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),你可以把这段代码放在那里。

于 2013-10-08T14:53:51.460 回答
3

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

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

然后在您的控制器中,您可以执行以下操作:

var MyVM = new MyVM() {
    FooDdl = GetFoos()
}
于 2013-10-02T19:58:03.807 回答
2

我们还使用一个静态类:

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

编辑器模板:

@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))    
}
于 2013-10-09T21:50:33.550 回答
2

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

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

创建剃刀部分:

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

调用部分:

@Html.RenderPartial("DropDownItems")

最后在控制器中接收ChoosenItem值。简单地。

于 2013-10-14T12:31:07.287 回答
1

我使用IModelEnricher结合 Automapper 和属性来定义列表类型和选择列表提供程序之间的关系。我使用特定的 ActionResult 返回实体等,然后将我的实体自动映射到 ViewModel 并丰富选择列表所需的数据(以及所需的任何其他数据)。此外,将选择列表数据作为 ViewModel 的一部分保持您的控制器、模型和视图职责清晰。

定义 ViewModel ernicher 意味着在使用 ViewModel 的任何地方,它都可以使用相同的丰富器来获取其属性。因此,您可以在多个位置返回 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;}
}

有了这种浓缩器:

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 会查看属性名称和类型,例如IEnumerable<SelectListItem> PossibleFirstDivisionTeams{get;set;} 然后如果它与一个选择列表提供程序名称存在于一个类中,该类表示实现了一个标记接口,则将其匹配ISelectListProvider。我们采用了基于属性的方法,刚刚创建了表示各种列表的 Enums Eg SelectList.AllFirstDivisionTeams。也可以尝试 ViewModel 上只有一个用于选择列表的属性集合的接口。我真的不喜欢我的 ViewModel 上的接口,所以我们从来没有这样做过

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

看到这个问题。还有这篇博这个。Automapper论坛上的这个问题

于 2013-10-08T13:36:27.700 回答
1

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

共享编辑器模板。为下拉列表表示的类型创建一个编辑器模板。在这种情况下——因为我们在 ViewModel 或 ViewBag 中没有可能的选项列表——模板必须向服务器提供选项。这可以通过向控制器类添加一个操作方法(返回 json)来实现。共享的“LookupsController”(可能是 ApiController)或列表项类型所属的控制器。

部分观点。下拉值属于某种类型。该类型的控制器可以有一个返回部分视图的操作方法。

第一个的好处是一个漂亮的@Html.EditorFor电话就可以完成这项工作。但我不喜欢 ajax 依赖。部分出于这个原因,我更喜欢部分观点。

还有第三个:child action,但我认为这不是一个好的模式。您可以在谷歌上搜索子操作和部分视图之间的区别,因为这种情况下子操作是错误的选择。我也不推荐辅助方法。我相信它们也不是为这个用例设计的。

于 2013-10-14T19:09:24.487 回答
0

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

创建一个控制器和一个返回 Ddl 并在视图中引用它的操作。

在此处查看有关如何使用它的一些提示

这样你也可以缓存这个结果。实际上,构建 StackOverflow 的人在播客中谈到了将其与针对不同元素的不同缓存规则结合使用的优点(即,如果 ddl 不需要 100% 更新,则可以将其缓存一分钟左右)前。

于 2013-10-12T15:38:16.583 回答
0

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

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
    }
}
于 2013-10-09T21:12:28.000 回答
0

如果您不需要更改其内容,则可以将该 fetch 放入 MyVM 的默认(空)构造函数中。

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

于 2013-10-02T19:59:07.263 回答
0

a 中的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);
    }
}
于 2013-10-09T20:09:48.230 回答
0

Extension methods to the rescue

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>();
于 2013-10-08T13:25:07.117 回答
0

我喜欢在可以从任何视图调用的辅助类中经常使用静态类。

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

希望它有所帮助。

于 2013-10-02T20:18:14.303 回答
0

如果您真的不想复制代码,请将控制器中的代码放入帮助程序类中,然后在共享视图(如 _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")
于 2013-10-02T20:11:34.763 回答