这是我的设计目标:
>视图模型应包含下拉列表的源数据
>限制Sil弦
>不使用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和Select表单元素的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(); }
解决方法
这是我的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,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属性替换隐藏的属性的名称。合理?
我希望这可以帮助你。