在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!