在Asp.net MVC中定義模型的時候,DataType有DataType.ImageUrl這個類型,但htmlhelper卻沒法輸出一個img,當用腳手架自動生成一些form或表格的時候,這些Url字段老是須要再手動改一次,特別是我想在img上面包裹一個a標籤。並限定大小,好比:html
<a href="url" target="_blank"> <img src="url" style="width: 100px;"/></a>
方法1:分部視圖git
在作後臺表格的時候常常要修改這樣的問題,因而首先想到的就是作一個分部視圖,叫tableimg。github
@model string @if (!string.IsNullOrEmpty(Model)) { <a href="@Model" target="_blank"> <img src="@Model" style="width: 100px;"/></a> }
使用的時候:express
@Html.Partial("tableimg",Model.Img)
方即是方便了些,但仍是不夠靈活。寬度是寫死的;並且還要記住這個視圖,若是這樣的片斷多了都不知道誰是誰了;和腳手架生成的代碼TextBoxFor,DisplayFor等風格也不同;若是要增長參數呢,還得去改模型。後端
方法2:UIHint數據結構
這個方法和分部視圖類似,也是使用模板,須要先在shared文件夾下建立一個EditorTemplates文件夾,而後新建一個視圖。這裏命名爲ImageLink。內容和上面同樣。ide
@model string @if (!string.IsNullOrEmpty(Model)) { <a href="@Model" target="_blank"> <img src="@Model" style="width: 100px;"/></a> }
只是調用方法不同:post
[DataType(DataType.ImageUrl)] [UIHint("ImageLink")] public string Img { get; set; }
在視圖裏面經過EditorFor調用:ui
@Html.EditorFor(model => model.Img)
這修改的地方比較多,感受不太舒服。能不能一勞永逸呢?固然是能夠的,這須要自定義一個ModelMetadataProvider,來告訴MVC這個數據類型的屬性就用這個模板顯示。this
public class ImageModelMetadataProvider : DataAnnotationsModelMetadataProvider { protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var meta= base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); if (meta.DataTypeName==DataType.ImageUrl.ToString() && string.IsNullOrEmpty(meta.TemplateHint)) { meta.TemplateHint = "ImageLink"; } return meta; } }
ModelMetadata是用來描述模型數據結構的數據,好比數據類型、約束、顯示名稱等,而ModelMetadataProvider就是用來提供Model的模型元數據的。
而後全局註冊:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ModelMetadataProviders.Current = new ImageModelMetadataProvider(); }
模型的定義裏面,再也不須要加UiHint了
[DataType(DataType.ImageUrl)] public string Img { get; set; }
視圖裏面調用的時候,須要用EditorFor。回頭看一下,這種方式仍是不夠靈活,要實現一個效果,首先要增長一個模板,而後註冊模型元數據提供器,而後每個要顯示計劃效果的模型還要強制的使用DataType特性以及Html.EditorFor輸出,這讓人有點束縛的感受。
可不能夠只改一個地方呢?因而想到擴展htmlhelper
方法3:Html.Image
新建一個靜態類,Htmlhelpers,增長一個Image的擴展方法,有url和length兩個參數。用tagbuilder建立標籤,增長屬性。
public static MvcHtmlString Image(this HtmlHelper helper, string url, int length) { var tagA = new TagBuilder("a"); tagA.MergeAttribute("href", url); tagA.MergeAttribute("target", "_blank"); var img = new TagBuilder("img"); img.MergeAttribute("src", url); img.MergeAttribute("style", string.Format("width:{0}px", length)); tagA.InnerHtml = img.ToString(); return MvcHtmlString.Create(tagA.ToString()); }
最後返回MvcHtmlString ,但上面體現不了tagbuilder的好處。若是以爲寫tag比較麻煩,能夠這樣:
var str= string.Format("<a href='{0}' target='_blank'> <img src='{0}' style='width:{1}px;'/></a>", url, length); return MvcHtmlString.Create(str);
調用的時候傳入參數:
@Html.Image(Model.Img,100)
結果顯示ok:
但若是要增長寬度以及更多的樣式,想將這個img的id指定爲模型屬性的名字呢 ,那就得用ImageFor了。
方法4:Html.ImageFor
開始不會寫,就想到參考MVC源碼,因而用強大的ILSpy(直接把System.Web.MVC.dll拖進來)找到了System.Web.MVC.HTML中的源碼,直接能夠看到LabelExtension和DisplayExtension等,經常使用的TextBoxFor位於InputExtension。
因此這裏我借鑑了上面的方法,先產生一個img,在用a表情包裹着。這裏若是還用string.Format那就太糟糕了。
internal static MvcHtmlString ImageHelper(HtmlHelper html, ModelMetadata metadata, IDictionary<string, object> htmlAttributes = null) { //屬性值 var value = metadata.Model.ToString(); //屬性名 if (string.IsNullOrEmpty(value)) { return MvcHtmlString.Empty; } var img = new TagBuilder("img"); img.Attributes.Add("src", value); img.Attributes.Add("id", metadata.PropertyName);
img.MergeAttributes(htmlAttributes, true);
var tagA = new TagBuilder("a"); tagA.MergeAttribute("href",value); tagA.MergeAttribute("target", "_blank"); tagA.InnerHtml = img.ToString(); return MvcHtmlString.Create(tagA.ToString()); } public static MvcHtmlString ImageFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) { ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); //var propertyName = ExpressionHelper.GetExpressionText(expression); //也能獲取到屬性名 var htmlAttributes2 = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); return ImageHelper(html, modelMetadata, htmlAttributes2); } public static MvcHtmlString ImageFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) { return ImageFor(html, expression, null); }
ImageHelper方法用來負責生產本身想要的標籤。包含三個參數,htmlhelper、modelmetadata、htmlAttributes。htmlhelper不用多說,頁面上就是用它來生成各類元素,但這裏沒有使用它。modelmetadata就是模型元數據,它描述了Model的數據結構,以及Model的每一個數據成員的一些特性。正是有了Model元數據的存在,才使模板化HTML的呈現機制成爲可能。這裏主要用來獲取模型的值,也就是對應的url值。經過斷點咱們能夠了解到它包含了寫什麼:
詳情能夠移步Artech大神的博客:ASP.NET MVC Model元數據及其定製: 初識Model元數據 。htmlAttributes就一目瞭然了。就是樣式字典。但咱們在寫的時候,都是傳入的是object,好比:
@Html.ImageFor(n=>Model.Img,new{width = "100px"} )
這後面的new{wdith='100px'}本質上就是一個匿名對象,匿名對象的最大的好處就是屬性能夠自定義,想加什麼樣式就加什麼樣式,而後經過htmlhelper的方法轉換爲IDictionary<string, object> htmlAttributes 結構
var htmlAttributes2 = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
在看下這個源碼裏面是如何實現的
經過類型解釋器拿到匿名對象的全部屬性的屬性解釋器。再添加到集合裏面去。這樣tagbuilder的MergeAttribute方法就好處理這些樣式或者屬性鍵值對了。
而模型元數據經過處理Lambda表達式和獲得:
ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
內部是由ModelMetadataProvider實現,ModelMetadataProvider是一個抽象類,提供了三個抽象方法:
public abstract class ModelMetadataProvider { protected ModelMetadataProvider(); public abstract IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType); public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName); public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType); }
AssociatedMetadataProvider繼承ModelMetadataProvider,上面用到的DataAnnotationsModelMetadataProvider是繼承AssociatedMetadataProvider。這裏artech講的比較多,詳情請移步:ASP.NET MVC的Model元數據提供機制的實現 更多深刻知識暫且打住。這個時候咱們的ImageFor方法已經能夠用了。
@Html.ImageFor(n=>Model.Img) <br/> @Html.ImageFor(n=>Model.Img,new{width = "100px"} ) <br/>
生成的html:
<a href="http://photocdn.sohu.com/20160629/Img456995877.jpeg" target="_blank"><img id="Img" src="http://photocdn.sohu.com/20160629/Img456995877.jpeg"></a> <a href="http://photocdn.sohu.com/20160629/Img456995877.jpeg" target="_blank"><img id="Img" src="http://photocdn.sohu.com/20160629/Img456995877.jpeg" width="100px"></a>
這樣就自在多了。由此咱們也能夠擴展其餘的For方法。
Html.EnumToDropDownList
有了這個思路,順手把枚舉類型的問題也解決下,你們曉得的,給枚舉類型加Display特性形同虛設。咱們通常是但願枚舉類型可以顯示中文,值是枚舉就行。好比有枚舉:
public enum QuestionType { [Display(Name = "單選")] Single,
[Display(Name = "多選")] Multiple, [Display(Name = "判斷")] Jude, [Display(Name = "填空")] Blank, [Display(Name = "問答")] Question }
若是視圖上這樣寫:
@Html.DropDownListFor(n => n.QuestionType, new SelectList(Enum.GetValues(typeof(QuestionType))))
只能獲得英文的下拉框:
網上還有用方法二解決枚舉類型顯示問題的例子。其實擴展htmlhelp方法最簡單,定義一個EnumToDropDownList的方法,參數是枚舉和name。
public static MvcHtmlString EnumToDropDownList(this HtmlHelper helper, Enum eEnum,string name) { var selectList = new List<SelectListItem>(); var enumType = eEnum.GetType(); foreach (var value in Enum.GetValues(enumType)) { var field = enumType.GetField(value.ToString()); var option = new SelectListItem() { Value = value.ToString() }; var display = field.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; option.Text = display != null ? display.Name : value.ToString(); option.Selected = Equals(value, eEnum); selectList.Add(option); } return helper.DropDownList(name, selectList); }
先經過Enum.GetValues方法獲得枚舉類型的各個值,而後經過反射獲得DisplayAttribute特性。而後將獲取到name做爲下拉框option的Text。調用:
@Html.EnumToDropDownList(Model.QuestionType, "QuestionType")
EnumToDropDownListFor實現起來就簡單啦,關鍵是找到類型。
public static MvcHtmlString EnumToDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes=null) { ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); var htmlAttributes2 = AnonymousObjectToHtmlAttributes(htmlAttributes); var enumType = modelMetadata.ModelType; var selectList = new List<SelectListItem>(); foreach (var value in Enum.GetValues(enumType)) { var field = enumType.GetField(value.ToString()); var option = new SelectListItem() { Value = value.ToString() }; var display = field.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; option.Text = display != null ? display.Name : value.ToString(); option.Selected = Equals(value, modelMetadata.Model); selectList.Add(option); } return html.DropDownList(modelMetadata.PropertyName, selectList,htmlAttributes2);
調用更加簡單:
@Html.EnumToDropDownListFor(model => model.QuestionType)
結果同樣,且能夠擴展樣式,匹配選中。若是要再後端顯示枚舉的文字,也很簡單了:
public string GetEnumTxt(Enum eEnum) { var enumType = eEnum.GetType(); var field = enumType.GetField(eEnum.ToString()); var display = field.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; return display != null ? display.Name : eEnum.ToString(); }
helper代碼
public static class HtmlHelpers { public static MvcHtmlString Image(this HtmlHelper helper, string url, int length) { var tagA = new TagBuilder("a"); tagA.MergeAttribute("href", url); tagA.MergeAttribute("target", "_blank"); var img = new TagBuilder("img"); img.MergeAttribute("src", url); img.MergeAttribute("style", string.Format("width:{0}px", length)); tagA.InnerHtml = img.ToString(); //return string.Format("<a href='{0}' target='_blank'> <img src='{0}' style='width:{1}px;'/></a>", url, length); return MvcHtmlString.Create(tagA.ToString()); } public static MvcHtmlString EnumToDropDownList(this HtmlHelper helper, Enum eEnum,string name) { var selectList = new List<SelectListItem>(); var enumType = eEnum.GetType(); foreach (var value in Enum.GetValues(enumType)) { var field = enumType.GetField(value.ToString()); var option = new SelectListItem() { Value = value.ToString() }; var display = field.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; option.Text = display != null ? display.Name : value.ToString(); option.Selected = Equals(value, eEnum); selectList.Add(option); } return helper.DropDownList(name, selectList); } public static MvcHtmlString EnumToDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes=null) { ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); var htmlAttributes2 = AnonymousObjectToHtmlAttributes(htmlAttributes); var enumType = modelMetadata.ModelType; var selectList = new List<SelectListItem>(); foreach (var value in Enum.GetValues(enumType)) { var field = enumType.GetField(value.ToString()); var option = new SelectListItem() { Value = value.ToString() }; var display = field.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; option.Text = display != null ? display.Name : value.ToString(); option.Selected = Equals(value, modelMetadata.Model); selectList.Add(option); } return html.DropDownList(modelMetadata.PropertyName, selectList,htmlAttributes2); } public static MvcHtmlString A(this HtmlHelper helper, string text, string url, int id) { var tagA = new TagBuilder("a"); tagA.MergeAttribute("href", url); tagA.MergeAttribute("data-id", id.ToString()); tagA.InnerHtml = text; return MvcHtmlString.Create(tagA.ToString()); } internal static MvcHtmlString ImageHelper(HtmlHelper html, ModelMetadata metadata, IDictionary<string, object> htmlAttributes = null) { //屬性值 var value = metadata.Model.ToString(); if (string.IsNullOrEmpty(value)) { return MvcHtmlString.Empty; } var img = new TagBuilder("img"); img.Attributes.Add("src", value); //屬性名 img.Attributes.Add("id", metadata.PropertyName); img.MergeAttributes(htmlAttributes, true); var tagA = new TagBuilder("a"); tagA.MergeAttribute("href",value); tagA.MergeAttribute("target", "_blank"); tagA.InnerHtml = img.ToString(); return MvcHtmlString.Create(tagA.ToString()); } public static MvcHtmlString ImageFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) { ModelMetadata modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); // var propertyname = ExpressionHelper.GetExpressionText(expression); var htmlAttributes2 = AnonymousObjectToHtmlAttributes(htmlAttributes); return ImageHelper(html, modelMetadata , htmlAttributes2); } public static MvcHtmlString ImageFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) { return ImageFor(html, expression, null); } private static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes) { RouteValueDictionary routeValueDictionary = new RouteValueDictionary(); if (htmlAttributes != null) { foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(htmlAttributes)) { routeValueDictionary.Add(propertyDescriptor.Name.Replace('_', '-'), propertyDescriptor.GetValue(htmlAttributes)); } } return routeValueDictionary; } } public class ImageModelMetadataProvider : DataAnnotationsModelMetadataProvider { protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var meta= base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); if (meta.DataTypeName==DataType.ImageUrl.ToString() && string.IsNullOrEmpty(meta.TemplateHint)) { meta.TemplateHint = "ImageLink"; } return meta; } }
代碼已更新到:https://github.com/stoneniqiu/Portal.MVC
小結:回顧這四種方法,分部視圖最直接,但不夠靈活,ImageFor調用很簡單,也最靈活,實現複雜點但可用來去擴展更多方法。若是要實現一個功能,須要強制性改動幾個地方,依賴多個地方,天然就失去了靈活性,最後實現了EnumToDropDownList的方法仍是很方便的,不須要依賴於什麼模板,也不須要再自定義什麼特性。 最後但願對你有幫助。tks!