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
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的話就會知道非常的好用。
如果還沒使用過的話,不妨可以看看之前的文章,用用看囉。