[asp.net mvc 奇淫巧技] 03 - 枚举特性扩展解决枚举命名问题和支持HtmlHelper
一、需求
我们在开发中经常会遇到一些枚举,而且这些枚举类型可能会在表单中的下拉中,或者单选按钮中会用到等。
这样用是没问题的,但是用过的人都知道一个问题,就是枚举的命名问题,当然有很多人枚举直接中文命名,我是不推荐这种命名规则,因为实在不够友好。
那有没有可以不用中文命名,而且可以显示中文的方法呢。答案是肯定的。
二、特性解决枚举命名问题
那就是用特性解决命名问题,这样的话既可以枚举用英文命名,显示又可以是中文的,岂不两全其美。
/// <summary> /// 性别 /// </summary> public enum Gender { /// <summary> /// 女性 /// </summary> [Description("女性")] Female = 1, /// <summary> /// 男性 /// </summary> [Description("男性")] Male = 2, /// <summary> /// 未知 /// </summary> [Description("未知")] Unknown = 3, /// <summary> /// 人妖 /// </summary> [Description("人妖")] Demon = 4 }
1、新建枚举的特性类
首先我们需要新建枚举的特性,用来描述枚举,这样既可以解决枚举的命名问题,又可以解决枚举的显示问题。
我们在下拉框或者单选按钮上显示各个枚举项,可能会出现一些排序问题,所以在枚举的特性上不仅有显示的名称还有排序。
/// <summary> /// 枚举特性 /// </summary> [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class DescriptionAttribute : Attribute { /// <summary> /// 排序 /// </summary> public int Order { get; set; } /// <summary> /// 名称 /// </summary> public string Name { get; set; } /// <summary> /// 显示自定义描述名称 /// </summary> /// <param name="name">名称</param> public DescriptionAttribute(string name) { Name = name; } /// <summary> /// 显示自定义名称 /// </summary> /// <param name="name">名称</param> /// <param name="order">排序</param> public DescriptionAttribute(string name, int order) { Name = name; Order = order; } }
新建好枚举的特性类以后,我们就可以在枚举的字段上添加自定义的特性Description
/// <summary> /// 性别 /// </summary> public enum Gender { /// <summary> /// 女性 /// </summary> [Description("女性", 2)] Female = 1, /// <summary> /// 男性 /// </summary> [Description("男性", 1)] Male = 2, /// <summary> /// 未知 /// </summary> [Description("未知", 3)] Unknown = 3, /// <summary> /// 人妖 /// </summary> [Description("人妖", 4)] Demon = 4 }
特性第一个参数为名称,第二个为排序(int 类型,正序),这就是就是我们新建枚举时在需要显示和枚举名称不一样的枚举字段上添加即可。这个Gender枚举,在后面文章中会一直用到(Gender)。
2、新建枚举扩展方法获取枚举特性的描述
我们前面的工作已经把特性和在枚举上添加特性已经完成了,后面我们需要的就是要获取我们添加的描述和排序。
/// <summary> /// 枚举帮助类 /// </summary> public static class EnumTools { /// <summary> /// 获取当前枚举值的描述 /// </summary> /// <param name="value"></param> /// <returns></returns> public static string GetDescription(this Enum value) { int order; return GetDescription(value, out order); } /// <summary> /// 获取当前枚举值的描述和排序 /// </summary> /// <param name="value"></param> /// <param name="order"></param> /// <returns></returns> public static string GetDescription(this Enum value, out int order) { string description = string.Empty; Type type = value.GetType(); // 获取枚举 FieldInfo fieldInfo = type.GetField(value.ToString()); // 获取枚举自定义的特性DescriptionAttribute object[] attrs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); DescriptionAttribute attr = (DescriptionAttribute)attrs.FirstOrDefault(a => a is DescriptionAttribute); order = 0; description = fieldInfo.Name; if (attr != null) { order = attr.Order; description = attr.Name; } return description; } }
3、获取枚举描述和排序
至此:我们可以很容易获取到枚举添加的特性描述和排序。
var des = Gender.Male.GetDescription(); // des = “男性” var name = Gender.Male.ToString(); // name= "Male" var key = (int)Gender.Male; // key = 2 int order; var des1 = Gender.Female.GetDescription(out order); // des1 = “女性”, order= 2
这样我们就很好的解决了枚举命名问题, 可以很容易的获取到枚举的描述信息,也就是要显示的信息。但是我们需要的是一次性可以查询全部的枚举信息,以便我们进行显示。
三、获取所有枚举的描述和值,以便循环使用
我们已经可以很容易的获取到枚举的值,名称和描述了,所以后面的就很简单了。
/// <summary> /// 枚举帮助类 /// </summary> public static class EnumTools { /// <summary> /// 获取当前枚举的所有描述 /// </summary> /// <param name="value"></param> /// <returns></returns> public static List<KeyValuePair<int, string>> GetAll<T>() { return GetAll(typeof(T)); } /// <summary> /// 获取所有的枚举描述和值 /// </summary> /// <param name="type"></param> /// <returns></returns> public static List<KeyValuePair<int, string>> GetAll(Type type) { List<EnumToolsModel> list = new List<EnumToolsModel>(); // 循环枚举获取所有的Fields foreach (var field in type.GetFields()) { // 如果是枚举类型 if (field.FieldType.IsEnum) { object tmp = field.GetValue(null); Enum enumValue = (Enum)tmp; int intValue = Convert.ToInt32(enumValue); int order; string showName = enumValue.GetDescription(out order); // 获取描述和排序 list.Add(new EnumToolsModel { Key = intValue, Name = showName, Order = order }); } } // 排序并转成KeyValue返回 return list.OrderBy(i => i.Order).Select(i => new KeyValuePair<int, string>(i.Key, i.Name)).ToList(); } }
调用:这样我们就很容易的获取枚举所有字段的描述,如我们需要在cshtml中调用
<select class="form-control"> @{ var genders = EnumTools.GetAll<Gender>();} // 或者EnumTools.GetAll<>(Typeof(Gender)) @foreach (var item in genders) { <option value="@item.Key"> @item.Value </option> } </select>
生成的html为:
<select class="form-control"> <option value="2">男性</option> <option value="1">女性</option> <option value="3">未知</option> <option value="4">人妖</option> </select>
这样我们就已顺利的解决了枚举的命名以及排序显示等问题。
四、枚举特性扩展至HtmlHelper
我们已经解决了枚举的命名以及排序显示问题,但是我们想做的更好,比如每次都要写一个foreach获取所有的枚举然后在判断默认值和哪个相等,循环遍历,周而复始,重复造轮子,bad code。所以我们要进行封装,封装成与 @Html.DropDownList一样好用的HtmlHelper扩展。
/// <summary> /// 枚举下拉 /// </summary> /// <typeparam name="T">枚举类型</typeparam> /// <param name="html"></param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect<T>(this HtmlHelper html, object htmlAttributes = null) { return html.EnumToolsSelect(typeof(T), int.MaxValue, htmlAttributes); } /// <summary> /// 枚举下拉 /// </summary> /// <typeparam name="T">枚举类型</typeparam> /// <param name="html"></param> /// <param name="selectedValue">选择项</param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect<T>(this HtmlHelper html, int selectedValue, object htmlAttributes = null) { return html.EnumToolsSelect(typeof(T), selectedValue, htmlAttributes); } /// <summary> /// 枚举下拉 /// </summary> /// <typeparam name="T">枚举类型</typeparam> /// <param name="html"></param> /// <param name="selectedValue">选择项</param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect<T>(this HtmlHelper html, T selectedValue, object htmlAttributes = null) { return html.EnumToolsSelect(typeof(T), Convert.ToInt32(selectedValue), htmlAttributes); } /// <summary> /// 枚举下拉 /// </summary> /// <param name="html"></param> /// <param name="enumType">枚举类型</param> /// <param name="selectedValue">选择项</param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect(this HtmlHelper html, Type enumType, int selectedValue, object htmlAttributes = null) { // 创建标签 TagBuilder tag = new TagBuilder("select"); // 添加自定义标签 if (htmlAttributes != null) { RouteValueDictionary htmlAttr = htmlAttributes as RouteValueDictionary ?? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); tag.MergeAttributes(htmlAttr); } // 创建option集合 StringBuilder options = new StringBuilder(); foreach (var item in GetAll(enumType)) { // 创建option TagBuilder option = new TagBuilder("option"); // 添加值 option.MergeAttribute("value", item.Key.ToString()); // 设置选择项 if (item.Key == selectedValue) { option.MergeAttribute("selected", "selected"); } // 设置option option.SetInnerText(item.Value); options.Append(option.ToString()); } tag.InnerHtml = options.ToString(); // 返回MVCHtmlString return MvcHtmlString.Create(tag.ToString()); }
然后调用
@(Html.EnumToolsSelect<Gender>()) @(Html.EnumToolsSelect<Gender>(Gender.Unknown)) @(Html.EnumToolsSelect<Gender>(1)) @(Html.EnumToolsSelect<Gender>(Gender.Female, new { @class = "form-control" })) @(Html.EnumToolsSelect(typeof(Gender), 1)
这样就可以生成你所需要的下拉框的html,一行代码就可以解决复杂的枚举下拉。
你以为就这样结束了吗,很明显没有,因为不是我风格,我的风格是继续封装。
五、枚举特性扩展至HtmlHelper Model
这个可能有很多不会陌生,因为很多HtmlHelper都有一个For结尾的,如@Html.DropDownListFor等等,那我们也要有For结尾的,要不然都跟不上潮流了。
关于For的一些扩展和没有For的扩展的区别,简单来说带For就是和Model一起用的,如:@Html.TextBoxFor(i => i.Name)
这样就可以更加一步的封装,如Id,name,model的Name值以及验证等等。
话不多说,直接代码
/// <summary> /// 下拉枚举 /// </summary> /// <typeparam name="TModel">Model</typeparam> /// <typeparam name="TProperty">属性</typeparam> /// <param name="htmlHelper"></param> /// <param name="expression"></param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelectFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null) { // 获取元数据meta ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); Type enumType = modelMetadata.ModelType; // 设置id name的属性值 var rvd = new RouteValueDictionary { { "id", modelMetadata.PropertyName }, { "name", modelMetadata.PropertyName } }; // 添加自定义属性 if (htmlAttributes != null) { RouteValueDictionary htmlAttr = htmlAttributes as RouteValueDictionary ?? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); foreach (var item in htmlAttr) { rvd.Add(item.Key, item.Value); } } // 获取验证信息 IDictionary<string, object> validationAttributes = htmlHelper.GetUnobtrusiveValidationAttributes(modelMetadata.PropertyName, modelMetadata); // 添加至自定义属性 if (validationAttributes != null) { foreach (var item in validationAttributes) { rvd.Add(item.Key, item.Value); } } return htmlHelper.EnumToolsSelect(enumType, Convert.ToInt32(modelMetadata.Model), rvd); }
关于使用:
首先我们需要返回view时需要返回Model
public class HomeController : Controller { public ActionResult Index() { return View(new Person { Age = 1, Name = "Emrys", Gender = Gender.Male }); } } public class Person { public string Name { get; set; } public int Age { get; set; } public Gender Gender { get; set; } }
cshtm调用
@Html.EnumToolsSelectFor(i => i.Gender)
生成html代码
<select data-val="true" data-val-required="Gender 字段是必需的。" id="Gender" name="Gender"> <option selected="selected" value="2">男性</option> <option value="1">女性</option> <option value="3">未知</option> <option value="4">人妖</option> </select>
六、全部枚举特性和HtmlHelper代码
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Web.Mvc; using System.Web.Routing; namespace Emrys.EnumTools { /// <summary> /// 枚举帮助类 /// </summary> public static class EnumTools { /// <summary> /// 获取当前枚举值的描述 /// </summary> /// <param name="value"></param> /// <returns></returns> public static string GetDescription(this Enum value) { int order; return GetDescription(value, out order); } /// <summary> /// 获取当前枚举值的描述和排序 /// </summary> /// <param name="value"></param> /// <param name="order"></param> /// <returns></returns> public static string GetDescription(this Enum value, out int order) { string description = string.Empty; Type type = value.GetType(); // 获取枚举 FieldInfo fieldInfo = type.GetField(value.ToString()); // 获取枚举自定义的特性DescriptionAttribute object[] attrs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); DescriptionAttribute attr = (DescriptionAttribute)attrs.FirstOrDefault(a => a is DescriptionAttribute); order = 0; description = fieldInfo.Name; if (attr != null) { order = attr.Order; description = attr.Name; } return description; } /// <summary> /// 获取当前枚举的所有描述 /// </summary> /// <param name="value"></param> /// <returns></returns> public static List<KeyValuePair<int, string>> GetAll<T>() { return GetAll(typeof(T)); } /// <summary> /// 获取所有的枚举描述和值 /// </summary> /// <param name="type"></param> /// <returns></returns> public static List<KeyValuePair<int, string>> GetAll(Type type) { List<EnumToolsModel> list = new List<EnumToolsModel>(); // 循环枚举获取所有的Fields foreach (var field in type.GetFields()) { // 如果是枚举类型 if (field.FieldType.IsEnum) { object tmp = field.GetValue(null); Enum enumValue = (Enum)tmp; int intValue = Convert.ToInt32(enumValue); int order; string showName = enumValue.GetDescription(out order); // 获取描述和排序 list.Add(new EnumToolsModel { Key = intValue, Name = showName, Order = order }); } } // 排序并转成KeyValue返回 return list.OrderBy(i => i.Order).Select(i => new KeyValuePair<int, string>(i.Key, i.Name)).ToList(); } /// <summary> /// 枚举下拉 /// </summary> /// <typeparam name="T">枚举类型</typeparam> /// <param name="html"></param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect<T>(this HtmlHelper html, object htmlAttributes = null) { return html.EnumToolsSelect(typeof(T), int.MaxValue, htmlAttributes); } /// <summary> /// 枚举下拉 /// </summary> /// <typeparam name="T">枚举类型</typeparam> /// <param name="html"></param> /// <param name="selectedValue">选择项</param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect<T>(this HtmlHelper html, int selectedValue, object htmlAttributes = null) { return html.EnumToolsSelect(typeof(T), selectedValue, htmlAttributes); } /// <summary> /// 枚举下拉 /// </summary> /// <typeparam name="T">枚举类型</typeparam> /// <param name="html"></param> /// <param name="selectedValue">选择项</param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect<T>(this HtmlHelper html, T selectedValue, object htmlAttributes = null) { return html.EnumToolsSelect(typeof(T), Convert.ToInt32(selectedValue), htmlAttributes); } /// <summary> /// 枚举下拉 /// </summary> /// <param name="html"></param> /// <param name="enumType">枚举类型</param> /// <param name="selectedValue">选择项</param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelect(this HtmlHelper html, Type enumType, int selectedValue, object htmlAttributes = null) { // 创建标签 TagBuilder tag = new TagBuilder("select"); // 添加自定义标签 if (htmlAttributes != null) { RouteValueDictionary htmlAttr = htmlAttributes as RouteValueDictionary ?? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); tag.MergeAttributes(htmlAttr); } // 创建option集合 StringBuilder options = new StringBuilder(); foreach (var item in GetAll(enumType)) { // 创建option TagBuilder option = new TagBuilder("option"); // 添加值 option.MergeAttribute("value", item.Key.ToString()); // 设置选择项 if (item.Key == selectedValue) { option.MergeAttribute("selected", "selected"); } // 设置option option.SetInnerText(item.Value); options.Append(option.ToString()); } tag.InnerHtml = options.ToString(); // 返回MVCHtmlString return MvcHtmlString.Create(tag.ToString()); } /// <summary> /// 下拉枚举 /// </summary> /// <typeparam name="TModel">Model</typeparam> /// <typeparam name="TProperty">属性</typeparam> /// <param name="htmlHelper"></param> /// <param name="expression"></param> /// <param name="htmlAttributes"></param> /// <returns></returns> public static MvcHtmlString EnumToolsSelectFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null) { // 获取元数据meta ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); Type enumType = modelMetadata.ModelType; // 设置id name的属性值 var rvd = new RouteValueDictionary { { "id", modelMetadata.PropertyName }, { "name", modelMetadata.PropertyName } }; // 添加自定义属性 if (htmlAttributes != null) { RouteValueDictionary htmlAttr = htmlAttributes as RouteValueDictionary ?? HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); foreach (var item in htmlAttr) { rvd.Add(item.Key, item.Value); } } // 获取验证信息 IDictionary<string, object> validationAttributes = htmlHelper.GetUnobtrusiveValidationAttributes(modelMetadata.PropertyName, modelMetadata); // 添加至自定义属性 if (validationAttributes != null) { foreach (var item in validationAttributes) { rvd.Add(item.Key, item.Value); } } return htmlHelper.EnumToolsSelect(enumType, Convert.ToInt32(modelMetadata.Model), rvd); } } /// <summary> /// 枚举特性 /// </summary> [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public class DescriptionAttribute : Attribute { /// <summary> /// 排序 /// </summary> public int Order { get; set; } /// <summary> /// 名称 /// </summary> public string Name { get; set; } /// <summary> /// 显示自定义描述名称 /// </summary> /// <param name="name">名称</param> public DescriptionAttribute(string name) { Name = name; } /// <summary> /// 显示自定义名称 /// </summary> /// <param name="name">名称</param> /// <param name="order">排序</param> public DescriptionAttribute(string name, int order) { Name = name; Order = order; } } /// <summary> /// 枚举Model /// </summary> partial class EnumToolsModel { public int Order { get; set; } public string Name { get; set; } public int Key { get; set; } } }
最后望对各位有所帮助,本文原创,欢迎拍砖和推荐。
Github:https://github.com/Emrys5/Asp.MVC-03-Enum-rename-htmlhelper
系列课程
作者:Emrys
出处:http://www.cnblogs.com/emrys5/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。