ASP.NET MVC 學習筆記(十三)-自訂DataAnnotationsModelMetadataProvider讓UIHint在MVC中可以傳遞參數

ASP.NET MVC 學習筆記(十三)-自訂DataAnnotationsModelMetadataProvider讓UIHint在MVC中可以傳遞參數

將近一年前,寫過一篇 ASP.NET MVC學習筆記(十一)-超好用的Templates

那時候就覺得 MVC 的 Templates 功能非常的好用,但在MVC中用 UIHint屬性 選擇樣板的時候

無法傳遞參數,讓Templates的使用上會多了一點限制。

(在Dynamic Data中,UIHint是可以傳遞參數的,但在MVC的預設Provider沒有支援,只能使用ViewData傳遞)

最近剛好跟朋友又在討論這個問題,搜尋了一下發現已經有人分享出解決辦法了。

參考網址:Using UIHint With ControlParameters in MVC

所以自己就照著這個概念實作了自己的版本,以後當成自己的 Library 使用。

p.s 如果還不清楚 mvc 的 templates 要怎麼使用,請先看一下我之前的文章會比較清楚這篇在講什麼

 

第一步:

繼承 DataAnnotationsModelMetadataProvider 類別,

自訂一個自己的Provider,然後override CreateMetadata方法

    public class ExtendDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider
    {
        protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
        {
            //不希望覆寫太多東西,所以大部分還是用base的CreateMetadata方法
            ModelMetadata metadata = base.CreateMetadata(attributes,
                                                          containerType,
                                                          modelAccessor,
                                                          modelType,
                                                          propertyName);

            List<Attribute> attributeList = new List<Attribute>(attributes);

            IEnumerable<UIHintAttribute> uiHintAttributes = attributeList.OfType<UIHintAttribute>();
            UIHintAttribute uiHintAttribute = uiHintAttributes.FirstOrDefault(a => String.Equals(a.PresentationLayer, "MVC", StringComparison.OrdinalIgnoreCase))
                                            ?? uiHintAttributes.FirstOrDefault(a => String.IsNullOrEmpty(a.PresentationLayer));

            //如果有UIHint屬性,就將他的參數塞到ModelMetadata.AdditionalValues裡面
            //Key是UIHintTemplateControlParameters
            if (uiHintAttribute != null)
            {
                if (metadata.AdditionalValues.ContainsKey("UIHintTemplateControlParameters"))
                    throw new ArgumentException("Metadate.AdditionalValues已存在 \"UIHintTemplateControlParameters\"這個Key,請更換擴充UIHintAttribute的Key值。");

                metadata.AdditionalValues.Add("UIHintTemplateControlParameters", uiHintAttribute.ControlParameters);
            }
            return metadata;
        }
    }


第二步:

在MVC中的 Globol.asax 的 Application_Start中,指定 ModelMetadataProviders

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterRoutes(RouteTable.Routes);

            ModelMetadataProviders.Current = new ExtendDataAnnotationsModelMetadataProvider();
        }

 

使用方式:

先定義自己的Model,並套上要使用的屬性

    public class TestModel
    {
        public string Name { get; set; }

        //第一個參數是指定templates名稱,如果要依型別找templates的話可以不填
        //第二個參數是表示用在哪,這邊可以填MVC或不填
        //第三個參數之後就是自訂的參數,用key/value的方式
        //一個key,一個value的方式依序傳遞
        [UIHint("", "MVC", "Width", 25, "Color", "red")]
        public string Age { get; set; }

        [UIHint("DateTime", "MVC", new Object[] { "DateFormat", "yyyy-MM-dd HH:mm:ss" })]
        public DateTime Birthday { get; set; }

        [DisplayName("結婚紀念日")]
        public DateTime WeddingDate { get; set; }
    }

 

 

 

覆寫預設的Templates

image

String.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<%
    var txtWidth = Html.GetUIHintParametersValue<int>("Width");
    txtWidth = txtWidth == 0 ? 100 : txtWidth;
    var color = Html.GetUIHintParametersValue<string>("Color","blue");
%>

<%=Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { style=String.Format("width:{0}px;color:{1}",txtWidth,color) })%>

DateTime.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<% var dateFormat = Html.GetUIHintParametersValue<string>("DateFormat", "yyyy-MM-dd"); %>

<%=Html.TextBox("", String.Format("{0:"+dateFormat+"}", ViewData.TemplateInfo.FormattedModelValue), new { @class = "datepicker" })%>

 

String.ascx這個Template還是出現一個TextBox,但可以設定寬度及顏色

DateTime.ascx則是加入了jQuery的datepicker,並且可以設定日期的format

另外為了方便抓出存在metadata中的參數,我另外寫了一個Helper來使用

    public static class UIHientHelper
    {
        /// <summary>
        /// 擴充HtmlHelper
        /// 如果ModelMetadata.AdditionalValues中有UIHintTemplateControlParameters這個Key,就回傳Dictionary
        /// </summary>
        public static Dictionary<string, object> GetUIHintParametersDictionary(this HtmlHelper helper)
        {
            var additionalValues = helper.ViewContext.ViewData.ModelMetadata.AdditionalValues;

            if (additionalValues.ContainsKey("UIHintTemplateControlParameters"))
            {
                Dictionary<string, object> dic = (Dictionary<string, object>)additionalValues["UIHintTemplateControlParameters"];
                return dic;
            }
            return null;
        }

        /// <summary>
        /// 擴充HtmlHelper
        /// 用指定的key去抓設定在UIHintTemplateControlParameters Dictionary中的Value
        /// </summary>
        public static T GetUIHintParametersValue<T>(this HtmlHelper helper,string key)
        {
            return GetUIHintParametersValue<T>(helper,key,default(T));
        }

        /// <summary>
        /// 擴充HtmlHelper
        /// 用指定的key去抓設定在UIHintTemplateControlParameters Dictionary中的Value
        /// 並在沒有設定時傳回預設值
        /// </summary>
        public static T GetUIHintParametersValue<T>(this HtmlHelper helper, string key, T defaultValue)
        {
            var dic = GetUIHintParametersDictionary(helper);
            if (dic != null && dic.ContainsKey(key))
            {
                var value = dic[key].ToConvertOrDefault<T>(defaultValue);
                return value;
            }
            return defaultValue;
        }
    }

擴充object,一個小小的型別轉換器

    public static T ToConvertOrDefault<T>(this object obj, T defaultValue)
    {
        try
        {
            return (T)Convert.ChangeType(obj, typeof(T));
        }
        catch
        {
            return defaultValue;
        }
    }

 

測試結果

Controller

        public ActionResult Index()
        {
            TestModel model = new TestModel()
            {
                Name = "CodingRoad",
                Age = "26",
                Birthday = new DateTime(1985, 6, 28, 12, 5, 0),
                WeddingDate = new DateTime(2013, 1, 4)
            };

            return View(model);
        }

View

   <%=Html.LabelFor(p => p.Name)%>:<%=Html.EditorFor(p=>p.Name) %>
   <br />
   <br />
   <br />
   <%=Html.LabelFor(p=>p.Age) %>:<%=Html.EditorFor(p=>p.Age) %>
   <br />
   <br />
   <br />
   <%=Html.LabelFor(p=>p.Birthday) %>:<%=Html.EditorFor(p=>p.Birthday) %>
   <br />
   <br />
   <br />
   <%=Html.LabelFor(p => p.WeddingDate)%>:<%=Html.EditorFor(p=>p.WeddingDate) %>

結果

東西有點多,但有在使用Templates的話就會知道非常的好用。

如果還沒使用過的話,不妨可以看看之前的文章,用用看囉。

範例Source Code