[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; }
    }
}
View Code

最后望對各位有所幫助,本文原創,歡迎拍磚和推薦。   

Github:https://github.com/Emrys5/Asp.MVC-03-Enum-rename-htmlhelper

 

系列課程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM