在ASP.NET MVC应用程序开发中,经常需要处理与数据库中外键关联的下拉列表(DropDownList)。通常情况下,开发者会使用Html.DropDownList(...)或Html.DropDownListFor(...)辅助方法,并在控制器中从数据存储中提取数据项,然后将其放入ViewData或ViewBag中,最后在辅助方法中使用这些值。本文将介绍一种通过自定义属性和过滤器来自动化这一过程的方法。
几乎所有的编辑视图模型(EditViewModel)至少需要一个DropDownList来处理模型或数据库中的外键。ASP.NET MVC程序员几乎都会使用Html.DropDownList(...)或Html.DropDownListFor(...)辅助方法。为了填充这个DropDownList,他们会从控制器中的数据存储拉取项目,并将它们放入ViewData或ViewBag,然后在辅助方法中使用这些值。但是,现在将使用一个编辑器模板(EditorTemplate)来处理应用程序中的所有DropDownLists。
目标是创建一个通用的DropDownList编辑器模板。首先,定义一个带有DropDown属性的视图模型类:
public class PersonEditor {
public int Id { get; set; }
public string Name { get; set; }
[DropDown("GetServices", 1)]
public int? ServiceId { get; set; }
}
然后,在控制器中处理这个导航属性,并在视图执行之前传递任何添加的参数:
[FillDropDowns]
public ActionResult Edit() {
var viewModel = new PersonEditor { ServiceId = 3 };
return View(viewModel);
}
需要一个属性来装饰导航属性,并需要一个在视图渲染之前执行的动作过滤器(ActionFilter),以填充ViewData字典中的项。
假设有一个简单的DropDownList服务层:
public class DropDownListService {
// ...
public IEnumerable GetServices(int typeId) {
return context.Services
.Where(m => m.Type == typeId)
.Select(m => new SelectListItem { Text = m.Name, Value = m.Id.ToString() });
}
}
然后,需要在视图渲染之前调用GetServices方法,并将结果存储在某个ViewData键中,以便在视图中检索。
接下来,定义一个自定义属性DropDownAttribute,它继承自UIHintAttribute,以便获取编辑器模板的名称。还需要服务类型,它将为提供IEnumerable
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public class DropDownAttribute : UIHintAttribute {
private readonly Type _serviceType;
private readonly string _methodName;
private readonly object[] _arguments;
public DropDownAttribute(string methodName, params object[] arguments)
: this(methodName, "DropDown", typeof(DropDownListService), arguments) { }
public DropDownAttribute(string methodName, string templateName, Type serviceType, params object[] arguments)
: base(templateName) {
_serviceType = serviceType;
_comboBoxServiceMethods = methodName;
_arguments = arguments;
}
public IEnumerable GetMethodResult() {
if (_serviceType == null)
throw new NoNullAllowedException("Service class type is needed.");
try {
var serviceInstance = Activator.CreateInstance(_serviceType);
var methodInfo = _serviceType.GetMethod(_methodName);
return methodInfo.Invoke(serviceInstance, _arguments) as IEnumerable;
} catch (Exception) {
throw;
}
}
}
这个属性类定义了需要的方法名和参数,并且添加了params关键字,以便按需传递参数。GetMethodResult方法将由ActionFilter调用以执行并获取结果。
接下来,定义一个动作过滤器FillDropDowns,它将在结果执行时查找视图模型中所有带有DropDownAttribute的属性,并在ViewData字典中填充这些列表。
public class FillDropDowns : ActionFilterAttribute {
public override void OnResultExecuting(ResultExecutingContext filterContext) {
var viewModel = filterContext.Controller.ViewData.Model;
if (viewModel != null)
setLists(viewModel.GetType(), filterContext.Controller.ViewData);
base.OnResultExecuting(filterContext);
}
private static void setLists(Type viewModelType, IDictionary viewData) {
foreach (var property in viewModelType.GetProperties()) {
if (!(property.PropertyType.IsClass && !(property.PropertyType == typeof(string)))) {
var att = (DropDownAttribute)GetCustomAttribute(property, typeof(DropDownAttribute));
if (att != null) {
var viewDataKey = "DDKey_" + property.Name;
viewData[viewDataKey] = viewData[viewDataKey] ?? att.GetMethodResult();
}
} else {
setLists(property.PropertyType, viewData);
}
}
}
}
动作过滤器会检查ViewModel中的属性是否带有DropDownAttribute,并在ViewData字典中填充这些列表。如果已经存在,则不会重复填充。
最后,需要在EditorTemplates文件夹中添加一个新的视图,作为所有DropDown导航属性的编辑器。由于属性带有UIHintAttribute,所以编辑器模板文件应该命名为"DropDown"。以下是它的简单代码:
@model object
@Html.DropDownListFor(m => m, Html.GetAutomatedList(m => m).SetSelected(Model))
接下来,需要两个帮助方法来完成工作:
需要两个帮助方法来获取工作完成:
public static class HtmlExtensions {
public static IEnumerable SetSelected(this IEnumerable selectList, object selectedValue) {
selectList = selectList ?? new List();
if (selectedValue == null)
return selectList;
var vlaue = selectedValue.ToString();
return selectList.Select(m => new SelectListItem {
Selected = string.Equals(vlaue, m.Value),
Text = m.Text,
Value = m.Value
});
}
public static IEnumerable GetAutomatedList(this HtmlHelper htmlHelper, Expression> expression) {
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
return ((IEnumerable)htmlHelper.ViewData["DDKey_" + metadata.PropertyName]);
}
}