【问题标题】:MVC2 EditorTemplate for DropDownListDropDownList 的 MVC2 EditorTemplate
【发布时间】:2010-04-29 19:50:23
【问题描述】:

在过去一周的大部分时间里,我一直在深入研究 MVC2 中包含的新模板功能。我很难让 DropDownList 模板正常工作。我一直在努力解决的最大问题是如何将下拉列表的源数据获取到模板中。我看到很多示例,您可以将源数据放入 ViewData 字典 (ViewData["DropDownSourceValuesKey"]) 然后在模板本身中检索它们 (var sourceValues = ViewData["DropDownSourceValuesKey"];) 这有效,但我做到了不喜欢用一根愚蠢的绳子作为完成这项工作的关键。

以下是我想出的一种方法,并希望获得对此方法的意见:

这是我的设计目标:

  • 视图模型应包含下拉列表的源数据
  • 限制愚蠢的字符串
  • 不使用 ViewData 字典
  • 控制器负责用下拉列表的源数据填充属性

这是我的视图模型:

   public class CustomerViewModel
    {
        [ScaffoldColumn(false)]
        public String CustomerCode{ get; set; }

        [UIHint("DropDownList")]
        [DropDownList(DropDownListTargetProperty = "CustomerCode"]
        [DisplayName("Customer Code")]
        public IEnumerable<SelectListItem> CustomerCodeList { get; set; }

        public String FirstName { get; set; }
        public String LastName { get; set; }
        public String PhoneNumber { get; set; }
        public String Address1 { get; set; }
        public String Address2 { get; set; }
        public String City { get; set; }
        public String State { get; set; }
        public String Zip { get; set; }
    }

我的视图模型有一个 CustomerCode 属性,它是用户从值列表中选择的值。我有一个 CustomerCodeList 属性,它是可能的 CustomerCode 值的列表,并且是下拉列表的来源。我用 DropDownListTargetProperty 创建了一个 DropDownList 属性。 DropDownListTargetProperty 指向将根据用户从生成的下拉列表中的选择填充的属性(在本例中为 CustomerCode 属性)。

注意 CustomerCode 属性具有 [ScaffoldColumn(false)] 强制生成器跳过生成的输出中的字段。

我的 DropDownList.ascx 文件将使用来自 CustomerCodeList 属性的源数据生成一个下拉列表表单元素。生成的下拉列表将使用 DropDownList 属性中的 DropDownListTargetProperty 的值作为 Select 表单元素的 Id 和 Name 属性。所以生成的代码会是这样的:

<select id="CustomerCode" name="CustomerCode">
  <option>...
</select>

这很有效,因为当提交表单时,MVC 将使用下拉列表中的选定值填充目标属性,因为生成的下拉列表的名称目标属性。我将其可视化为 CustomerCodeList 属性是 CustomerCode 属性的扩展。我已将源数据耦合到属性。

这是我的控制器代码:

public ActionResult Create()
{
    //retrieve CustomerCodes from a datasource of your choosing
    List<CustomerCode> customerCodeList = modelService.GetCustomerCodeList();

    CustomerViewModel viewModel= new CustomerViewModel();
    viewModel.CustomerCodeList = customerCodeList.Select(s => new SelectListItem() { Text = s.CustomerCode, Value = s.CustomerCode, Selected = (s.CustomerCode == viewModel.CustomerCode) }).AsEnumerable();

    return View(viewModel);
}

这是我的 DropDownListAttribute 代码:

namespace AutoForm.Attributes
{
    public class DropDownListAttribute : Attribute
    {
        public String DropDownListTargetProperty { get; set; }
    }
}

这是我的模板代码 (DropDownList.ascx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %>
<%@ Import Namespace="AutoForm.Attributes"%>

<script runat="server">
    DropDownListAttribute GetDropDownListAttribute()
    {
        var dropDownListAttribute = new DropDownListAttribute();

        if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("DropDownListAttribute"))
        {
            dropDownListAttribute = (DropDownListAttribute)ViewData.ModelMetadata.AdditionalValues["DropDownListAttribute"];
        }

        return dropDownListAttribute;        
    }
</script>    

<% DropDownListAttribute attribute = GetDropDownListAttribute();%>

<select id="<%= attribute.DropDownListTargetProperty %>" name="<%= attribute.DropDownListTargetProperty %>">
    <% foreach(SelectListItem item in ViewData.Model) 
       {%>
        <% if (item.Selected == true) {%>
            <option value="<%= item.Value %>" selected="true"><%= item.Text %></option>
        <% } %>
        <% else {%>
            <option value="<%= item.Value %>"><%= item.Text %></option>
        <% } %>
    <% } %>
</select>

我尝试使用 Html.DropDownList 助手,但它不允许我更改生成的 Select 元素的 Id 和 Name 属性。

注意:您必须为 DropDownListAttribute 覆盖 DataAnnotationsModelMetadataProvider 的 CreateMetadata 方法。这是代码:

public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        var additionalValues = attributes.OfType<DropDownListAttribute>().FirstOrDefault();

        if (additionalValues != null)
        {
            metadata.AdditionalValues.Add("DropDownListAttribute", additionalValues);
        }

        return metadata;
    }
}

然后你必须在 Global.asax.cs 的 Application_Start 中调用新的 MetadataProvider:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    ModelMetadataProviders.Current = new MetadataProvider();
}

嗯,我希望这是有道理的,我希望这种方法可以为您节省一些时间。我想要一些关于这种方法的反馈。有更好的方法吗?

【问题讨论】:

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


    【解决方案1】:

    我想我找到了一个解决方案,让它在使用 Html.EditorForModel(); 时工作。在使用 EditorForModel() 时,MVC 使用 Object.ascx 循环遍历模型的所有属性,并为模型中的每个属性调用相应的模板。开箱即用的 ASP.Net MVC 代码中有 Object.ascx,但您可以创建自己的 Object.ascx。只需在您的共享视图文件夹中创建一个 EditorTemplates 子文件夹。在那里创建一个 Object.ascx 文件。 (阅读这篇文章了解更多信息:http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html

    这是我的 Object.ascx:

    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
    <%@ Import Namespace="WebAppSolutions.Helpers" %>
    <% if (ViewData.TemplateInfo.TemplateDepth > 1) { %>
    <%= ViewData.ModelMetadata.SimpleDisplayText%>
    <% }
    else { %>    
    <% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %>
    
        <% var htmlFieldName = Html.HtmlFieldNameFor(prop.PropertyName);%>
    
        <% if (prop.HideSurroundingHtml) { %>
            <%= Html.Editor(htmlFieldName)%>
        <% }
           else { %>
            <div id="<%= htmlFieldName %>Container" class="editor-field">
                <% if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString())) { %>
                    <%= Html.Label(prop.PropertyName, Html.HtmlDisplayName(prop.PropertyName), prop.IsRequired)%>
                <% } %>
                <%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
                <%= Html.ValidationMessage(prop.PropertyName, "*") %>
            </div>
        <% } %>
    <% } %>
    

    我的 WebAppSolutions.Helpers 中有一些用于 HtmlFieldNameFor 和 HtmlDisplayName 的自定义代码。这些助手从应用于视图模型中的属性的属性中检索数据。

        public static String HtmlFieldNameFor<TModel>(this HtmlHelper<TModel> html, String propertyName)
        {
            ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
            return GetHtmlFieldName(modelMetaData, propertyName);
        }
    
        public static String HtmlDisplayName<TModel>(this HtmlHelper<TModel> html, String propertyName)
        {
            ModelMetadata modelMetaData = GetModelMetaData(html, propertyName);
            return modelMetaData.DisplayName ?? propertyName;
        }
        private static ModelMetadata GetModelMetaData<TModel>(HtmlHelper<TModel> html, String propertyName)
        {
            ModelMetadata modelMetaData = ModelMetadata.FromStringExpression(propertyName, html.ViewData);
            return modelMetaData;
        }
    
        private static String GetHtmlFieldName(ModelMetadata modelMetaData, string defaultHtmlFieldName)
        {
            PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData);
            return propertyExtendedMetaDataAttribute.HtmlFieldName ?? defaultHtmlFieldName;
        }
    

    使用 EditorModelFor() 让它工作的关键是这个(应该是上面 Object.ascx 中的第 20 行左右):

    <%= Html.Editor(prop.PropertyName, "", htmlFieldName)%>
    

    prop.PropertyName 是 ViewModel 中的属性,包含将成为 DropDownList 的数据列表。 htmlFieldName 是 DropDownList 属性正在替换的隐藏属性的名称。有意义吗?

    希望对你有帮助。

    【讨论】:

    • 您引用了PropertyExtendedMetaDataAttribute propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData);,但我无法在任何地方找到该类型。这是您创建的其他东西还是在 MvcContrib 之类的第 3 方库中?
    • 如果您在接受您的回答时遇到困难,请标记此帖子以引起版主注意。
    【解决方案2】:

    完美。这就是我要找的。谢谢!

    但是您的示例模型是简单模型。像

    这样的复杂视图模型怎么样?
    public class MaintainServicePackageViewModel
    {
        public IEnumerable<ServicePackageWithOwnerName> ServicePackageWithOwnerName { get; set; }
        public ServicePackageWithOwnerName CurrentServicePackage { get; set; }
        public IEnumerable<ServiceWithPackageName> ServiceInPackage { get; set; }
    }
    
    public class ServicePackageWithOwnerName : ServicePackage
    {
        [UIHint("DropDownList")]
        [DropDownList(DropDownListTargetProperty = "Owner")]
        [DisplayNameLocalized(typeof(Resources.Globalization), "OwnerName")]
        public IEnumerable<SelectListItem> OwnerName { get; set; }
    }
    

    OwnerName 设置为下拉列表,但它不是视图模型的直接元素,而是视图模型元素 ServicePackageWithOwnerName 的子元素。在这种情况下,无法在控制器中设置 OwnerName 值,如何解决?欣赏!

    问候

    杰克

    【讨论】:

    • 首先想到的是为 ServicePackageWithOwnerName 创建一个自定义编辑器模板。 ServicePackageWithOwnerName 自定义模板将为 OwnerName 属性发出一个下拉框。在 MaintainServicePackageViewModel 中,您将使用 UIHint("[ServicePackageWithOwnerName 模板名称]") 装饰 CurrentServicePackage。我正计划探索这个概念,但还没有时间。
    • 杰克-看看这个链接,它可能是你需要的:dotnetslackers.com/articles/aspnet/…
    • 另外,请查看此链接。 Brad Wilson 创建了一个非常好的模板系列。此链接处理嵌套的复杂类型。 bradwilson.typepad.com/blog/2009/10/…
    【解决方案3】:

    这是我在代码项目中这篇文章的方法:

    One Editor Template for all DropDownLists in ASP.Net MVC

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-28
      • 2021-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-22
      相关资源
      最近更新 更多