DropDownList 的 MVC2 编辑器模板
过去一周的大部分时间我都在深入研究 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 会使用下拉列表中选定的值填充目标属性,因为生成的下拉列表的名称 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 帮助器,但它不允许我更改生成的 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();
}
嗯,我希望这是有道理的,我希望这种方法可以为您节省一些时间。我想要一些关于这种方法的反馈。有更好的方法吗?
I've spent the majority of the past week knee deep in the new templating functionality baked into MVC2. I had a hard time trying to get a DropDownList template working. The biggest problem I've been working to solve is how to get the source data for the drop down list to the template. I saw a lot of examples where you can put the source data in the ViewData dictionary (ViewData["DropDownSourceValuesKey"]) then retrieve them in the template itself (var sourceValues = ViewData["DropDownSourceValuesKey"];) This works, but I did not like having a silly string as the lynch pin for making this work.
Below is an approach I've come up with and wanted to get opinions on this approach:
here are my design goals:
- The view model should contain the source data for the drop down list
- Limit Silly Strings
- Not use ViewData dictionary
- Controller is responsible for filling the property with the source data for the drop down list
Here's my View Model:
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; }
}
My View Model has a CustomerCode property which is a value that the user selects from a list of values. I have a CustomerCodeList property that is a list of possible CustomerCode values and is the source for a drop down list. I've created a DropDownList attribute with a DropDownListTargetProperty. DropDownListTargetProperty points to the property which will be populated based on the user selection from the generated drop down (in this case, the CustomerCode property).
Notice that the CustomerCode property has [ScaffoldColumn(false)] which forces the generator to skip the field in the generated output.
My DropDownList.ascx file will generate a dropdown list form element with the source data from the CustomerCodeList property. The generated dropdown list will use the value of the DropDownListTargetProperty from the DropDownList attribute as the Id and the Name attributes of the Select form element. So the generated code will look like this:
<select id="CustomerCode" name="CustomerCode">
<option>...
</select>
This works out great because when the form is submitted, MVC will populate the target property with the selected value from the drop down list because the name of the generated dropdown list IS the target property. I kinda visualize it as the CustomerCodeList property is an extension of sorts of the CustomerCode property. I've coupled the source data to the property.
Here's my code for the controller:
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);
}
Here's my code for the DropDownListAttribute:
namespace AutoForm.Attributes
{
public class DropDownListAttribute : Attribute
{
public String DropDownListTargetProperty { get; set; }
}
}
Here's my code for the template (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>
I tried using the Html.DropDownList helper, but it would not allow me to change the Id and Name attributes of the generated Select element.
NOTE: you have to override the CreateMetadata method of the DataAnnotationsModelMetadataProvider for the DropDownListAttribute. Here's the code for that:
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;
}
}
Then you have to make a call to the new MetadataProvider in Application_Start of Global.asax.cs:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ModelMetadataProviders.Current = new MetadataProvider();
}
Well, I hope this makes sense and I hope this approach may save you some time. I'd like some feedback on this approach please. Is there a better approach?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我想我找到了一个解决方案,可以在使用 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:
<% } %>
我的 WebAppSolutions.Helpers 中有一些用于 HtmlFieldNameFor 和 HtmlDisplayName 的客户代码。这些帮助器从应用于视图模型中的属性的属性中检索数据。
使用 EditorModelFor() 使其正常工作的关键是(应该在上面的 Object.ascx 中的第 20 行左右):
prop.PropertyName 是 ViewModel 中的属性,包含将成为 DropDownList 的数据列表。 htmlFieldName 是 DropDownList 属性正在替换的隐藏属性的名称。有道理吗?
我希望这对你有帮助。
I think I found a solution to make it work when using Html.EditorForModel(); When using EditorForModel(), MVC uses Object.ascx to loop through all properties of the model and calls the corresponding template for each property in the model. ASP.Net MVC out of the box has Object.ascx in code, but you can create your own Object.ascx. Just create an EditorTemplates subfolder in your Shared View folder. Create an Object.ascx file there. (read this post for more information: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html)
Here's my Object.ascx:
<% } %>
I have some custome code in my WebAppSolutions.Helpers for HtmlFieldNameFor and HtmlDisplayName. These helpers retrieve data from attributes applied to properties in the view model.
The key to getting this to work using EditorModelFor() is this (should be line 20 or so in Object.ascx above):
prop.PropertyName is the property in the ViewModel containing the list of data that will become the DropDownList. htmlFieldName is the name of the property that's hidden that the DropDownList property is replacing. Make sense?
I hope this helps you.
完美的。这就是我正在寻找的。谢谢!
但您的示例模型是简单模型。将像 OwnerName 这样的复杂视图模型
设置为下拉列表怎么样,但它不是视图模型的直接元素,而是 ServicePackageWithOwnerName 的子元素,而 ServicePackageWithOwnerName 是视图模型的元素。在这种情况下,无法在控制器中设置 OwnerName 值,如何解决这个问题?欣赏!
问候
杰克
Perfect. This is what I'm looking for. Thanks!
But your example model is simple model. How about a complex viewmodel like
The OwnerName is set to a dropdownlist, but it is not a direct element of the viewmodel instead it's a child element of ServicePackageWithOwnerName which is the element of the viewmodel. In such condition, there's no way to set the OwnerName value in the controller, how to fix this? Appreciate!
Regards
Jack
这是我在代码项目中的这篇文章中的方法:
One Editor Template for all DropDownLists in ASP.Net MVC
This is my approach from this post in Code Project:
One Editor Template for all DropDownLists in ASP.Net MVC