簡體   English   中英

如何在.NET MVC的多個視圖中重用DropDownList

[英]How can I reuse a DropDownList in several views with .NET MVC

我項目中的幾個視圖都有相同的下拉列表...

所以,在該視圖的ViewModel中,我有:

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

在控制器中我有:

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

到目前為止一切都那么好......但是我在每個具有該ddl的視圖/控制器中執行相同的代碼...

這是最好的方法嗎?

謝謝

我說老實說沒關系,因為它只是重復幾行代碼。 如果它真的困擾你,你可以讓你的所有控制器從BaseController繼承(如果它們還沒有)並在那里存儲一個方法來獲取它們,例如:

public IEnumerable<SelectListItem> 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<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

如果您的存儲庫是一個abstractin層(不是直接的EntityFramework),您可以將此代碼放在那里。

我們還使用靜態類:

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

使用getter為您的下拉列值創建對象:

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

創建Razor部分:

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

呼叫部分:

@Html.RenderPartial("DropDownItems")

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

我使用IModelEnricher結合IModelEnricher和屬性來定義列表類型和選擇列表提供程序之間的關系。 我使用特定的ActionResult返回一個實體等,然后將我的實體自動化為ViewModel,並豐富選擇列表所需的數據(以及所需的任何其他數據)。 同時將選擇列表數據保留為ViewModel的一部分可以使您的控制器,模型和查看職責保持清晰。

定義ViewModel ernicher意味着在使用ViewModel的任何地方,它都可以使用相同的richher來獲取其屬性。 因此,您可以在多個位置返回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查看屬性名稱和類型,例如IEnumerable<SelectListItem> 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)構造函數中。

或者您可以使用您渲染到需要的視圖中的PartialView。

如果您真的不想復制代碼,請將控制器中的代碼放入幫助程序類中,然后在共享視圖(如_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")

我喜歡在helper類中經常使用靜態類,我可以從任何視圖調用它。

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

希望它有所幫助。

救援的延伸方法

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

BaseControllerPrepare方法怎么樣?

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

有一個界面,其中包含需要自動填充的所有屬性:

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原則並且您可以忘記填充該屬性,只要您的控制器繼承自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%更新,你可以在一分鍾左右緩存它)一段時間播客前。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM