【问题标题】:MVC Templated Helper - DropDownMVC 模板化助手 - DropDown
【发布时间】:2010-06-29 13:10:28
【问题描述】:

在 MVC2.0 中使用模板化助手时,我遇到了一个难题,即如何让项目填充下拉列表。 我正在使用[UIHint(BadgesDropDown)] 属性,但是如何在不违反 MVC 模式的情况下获取列表项,控制器是否应该将它们放在 ViewData 中? BadgesDropDown.ascx 是否应该调用 Helper 来获取它们?

现在我要去:

BadgesDropDown.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= Html.DropDownList("", ViewData["Badges"] as IEnumerable<SelectListItem>)%>

控制器

ViewData["Badges"] = new SelectList(SiteRepository.GetBadges(), "RowKey", "BadgeName");

这是要走的路吗?

【问题讨论】:

    标签: templates asp.net-mvc-2 drop-down-menu


    【解决方案1】:

    最近有很多关于这个话题的讨论。日期、日期范围和多选复选框列表也会遇到类似的障碍。任何您可能想要使用一组丰富的 html 控件的地方。我一直在尝试子 ViewModel 的概念,我认为该解决方案比我尝试过的其他方法更干净。

    基本概念是您定义一个与自定义 EditorTemplate 紧密耦合的小型视图模型。

    在您的示例中,我们将从特定于单个选择列表的(子)ViewModel 开始:

    public class SelectModel
    {
      #region SelectModel(string value, IEnumerable<SelectListItem> items)
      public SelectModel(string value, IEnumerable<SelectListItem> items)
      {
        _value = value;
        Items = new List<SelectListItem>(items);
    
        _Select();
      } 
      #endregion
    
      // Properties
    
      public List<SelectListItem> Items { get; private set; }
    
      public string Value
      { 
        get { return _value; }
        set { _value = value; _Select();}
      }
      private string _value;
    
      // Methods
    
      private void _Select()
      {
        Items.ForEach(x => x.Selected = (Value != null && x.Value == Value));
      }
    }
    

    在想要使用下拉菜单的视图模型中,您可以编写选择模型(我们都在使用视图模型,对吧?):

    public class EmailModel
    {
      // Constructors
    
      public EmailModel()
      {
        Priority = new SelectModel("normal", _ToPrioritySelectItems());
      }
    
      // Properties
    
      public SelectModel Priority { get; set; }
    
      // Methods
    
      private IEnumerable<SelectListItem> _ToPrioritySelectItems()
      {
        List<SelectListItem> result = new List<SelectListItem>();
    
        result.Add(new SelectListItem() { Text = "High", Value = "high" });
        ...
      }
    

    请注意,这是一个带有一组固定下拉项的简单示例。如果它们来自域层,控制器会将它们传递给 ViewModel。

    然后在 Shared/EditorTemplates 中添加一个编辑器模板 SelectModel.ascx

    <%@ Control Inherits="System.Web.Mvc.ViewUserControl<SelectModel>" %>
    
    <div class="set">
      <%= Html.LabelFor(model => model) %>
      <select id="<%= ViewData.ModelMetadata.PropertyName %>_Value" name="<%=ViewData.ModelMetadata.PropertyName %>.Value">
      <% foreach (var item in Model.Items) { %>
        <%= Html.OptionFor(item) %>
      <% } %>
      </select>
    </div>
    

    注意:OptionFor 是一个很明显的自定义扩展

    这里的技巧是使用默认 ModelBinder 期望的复合格式设置 id 和 name。在我们的示例“Priority.Value”中。因此,直接设置定义为 SelectModel 一部分的基于字符串的 Value 属性。如果我们需要重新显示表单,setter 会负责更新 Items 列表以设置默认选择选项。

    这种“子视图模型”方法真正闪耀的地方是更复杂的“控制标记的 sn-ps”。我现在有子视图模型,它们对多选列表、开始/结束日期范围和日期+时间组合遵循类似的方法。

    一旦你沿着这条路走下去,下一个明显的问题就是验证。

    我最终让我所有的子 ViewModel 都实现了一个标准接口:

    public interface IValidatable
    {
      bool HasValue { get; }
      bool IsValid { get; }
    }
    

    然后,我有一个自定义的 ValidationAttribute:

    public class IsValidAttribute : ValidationAttribute
    {
      // Constructors
    
      public IsValidAttribute()
      {
        ErrorMessage = "(not valid)";
      }
    
      // Properties
    
      public bool IsRequired { get; set; }
    
      // Methods
    
      private bool Is(object value)
      {
        return value != null && !"".Equals(value);
      }
    
      public override bool IsValid(object value)
      {
        if (!Is(value) && !IsRequired)
          return true;
    
        if (!(value is IValidatable))
          throw new InvalidOperationException("IsValidAttribute requires underlying property to implement IValidatable");
    
        IValidatable validatable = value as IValidatable;
        return validatable.IsValid;
      }
    }
    

    现在您可以像任何标量属性一样将属性放在基于子 ViewModel 的属性上:

    [IsValid(ErrorMessage = "Please enter a valid start date/time")]
    public DateAndTimeModel Start { get; set; }
    

    【讨论】:

      【解决方案2】:

      在 MVC 2 中,一个很棒的新方法......如果使用它依赖于所有属性数据。

      <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
      Inherits="System.Web.Mvc.ViewPage<glossaryDB.EntityClasses.AssociationEntity>" %>
      
      <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
          Association: Edit
      </asp:Content>
      
      <asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
          <h3>Association: Edit</h3>
          <% using (Html.BeginForm()) { %>
              <fieldset style="padding: 1em; margin: 0; border: solid 1px #999;">
                  <%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>
                  <%= Html.EditorForModel() %>
                  <input type="submit" value="  Submit  " />
              </fieldset>
          <% } %>
          <p><%= Html.ActionLink("Details", "Index") %></p>
      </asp:Content>
      

      为此,有 2 个选项。 UIHint 必须提供数据源或控制器必须提供。如果 UIHint 确实如此,则提供给下拉列表的数据是固定的。另一个选项是控制器,它允许我们根据需要使用不同的数据集切换下拉数据。

      我找到了一些相关的例子:

      书呆子晚餐

      [1]:搜索 codeclimber.net.nz 和 how-to-create-a-dropdownlist-with-asp.net-mvc [2]:bradwilson.typepad.com 和 templates-part-5-master-page-templates

      【讨论】:

        【解决方案3】:

        我按照上面的例子实现了解决方案。需要注意的一件事是,助手应该只使用提供给他们的数据,请参阅View dependency

        最好的做法是写 Html 助手不知道控制器和 上下文。他们应该做好自己的工作 仅基于提供的数据 调用者。

        我同意上述说法。只是与常规的 ASP.Net 开发相比,还有很多工作要做。

        【讨论】:

          猜你喜欢
          • 2014-01-03
          • 1970-01-01
          • 2015-10-23
          • 2011-10-22
          • 2015-01-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多