2010-04-29 91 views
16

我已经花了上周的大部分时间膝盖深入新的模板功能烘焙到MVC2。我很难尝试使用DropDownList模板。我一直在努力解决的最大问题是如何将下拉列表的源数据提供给模板。我看到很多例子,你可以将源数据放入ViewData字典(ViewData [“DropDownSourceValuesKey”]),然后在模板本身中检索它们(var sourceValues = ViewData [“DropDownSourceValuesKey”];)这有效,但我不像有一个愚蠢的字符串作为使这项工作的关键。MVC2 EditorTemplate为DropDownList

下面是一个方法,我想出来的,并希望得到这一做法的意见:

这里是我的设计目标:

  • 视图模型应包含降源数据下拉列表
  • 限制傻字符串
  • 不使用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的值作为选择表单元素的Id和Name属性。所以生成的代码如下所示:

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

这工作了巨大的,因为提交表单时,MVC会从下拉列表填充所选值目标属性,因为产生的下拉列表中的名称IS的目标属性。我有点想象它,因为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帮手,但它不会让我改变ID和产生的选择元素的名称属性。

注意:您必须覆盖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(); 
} 

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

回答

1

完美。这是我正在寻找的。谢谢!

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

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值,该如何解决?欣赏!

问候

杰克

+0

,想到的第一件事是创建ServicePackageWithOwnerName自定义编辑模板。 ServicePackageWithOwnerName自定义模板将为OwnerName属性发出一个下拉框。在MaintainServicePackageViewModel中,您将使用UIHint(“[ServicePackageWithOwnerName Tempalte Name]”)装饰CurrentServicePackage。我正在计划探索这个概念,但还没有时间这样做。 – tschreck 2010-05-12 20:53:59

+0

杰克 - 看看这个链接,它可能是你需要的: http://dotnetslackers.com/articles/aspnet/asp-net-mvc-2-0-templating.aspx – tschreck 2010-05-14 04:31:12

+1

此外,请检查此链接。 Brad Wilson创建了一个非常好的模板系列。该链接处理嵌套的复杂类型。 http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html – tschreck 2010-05-14 15:04:57

2

我想我找到了一个解决方案,使其使用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和一些custome代码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是在视图模型的属性包含将成为DropDownList的数据列表。 htmlFieldName是DropDownList属性所替代的隐藏属性的名称。合理?

我希望这可以帮助你。

+0

您有一个对PropertyExtendedMetaDataAttribute属性的引用propertyExtendedMetaDataAttribute = GetPropertyExtendedMetaDataAttribute(modelMetaData );',但我无法在任何地方找到该类型。那是你创建的其他东西,还是像MvcContrib这样的第三方库? – TLS 2011-02-24 20:21:54

+0

如果您在接受答案时遇到困难,请标记此帖以供版主注意。 – 2011-04-26 15:03:25