Asp.Net MVC 扩展 Html.ImageFor 方法详解
背景:
在Asp.net MVC中定义模型的时候,DataType有DataType.ImageUrl这个类型,但htmlhelper却无法输出一个img,当用脚手架自动生成一些form或表格的时候,这些Url字段总是需要再手动改一次,特别是我想在img上面包裹一个a标签。并限定大小,比如:
<a href="url" target="_blank"> <img src="url" style="width: 100px;"/></a>

方法1:分部视图
在做后台表格的时候经常要修改这样的问题,于是首先想到的就是做一个分部视图,叫tableimg。
@model string
@if (!string.IsNullOrEmpty(Model))
{
<a href="@Model" target="_blank"> <img src="@Model" style="width: 100px;"/></a>
}
使用的时候:
@Html.Partial("tableimg",Model.Img)
方便是方便了些,但还是不够灵活。宽度是写死的;而且还要记住这个视图,如果这样的片段多了都不知道谁是谁了;和脚手架生成的代码TextBoxFor,DisplayFor等风格也不一样;如果要增加参数呢,还得去改模型。
方法2:UIHint
这个方法和分部视图相似,也是使用模板,需要先在shared文件夹下创建一个EditorTemplates文件夹,然后新建一个视图。这里命名为ImageLink。内容和上面一样。
@model string
@if (!string.IsNullOrEmpty(Model))
{
<a href="@Model" target="_blank"> <img src="@Model" style="width: 100px;"/></a>
}
只是调用方法不一样:
[DataType(DataType.ImageUrl)]
[UIHint("ImageLink")]
public string Img { get; set; }
在视图里面通过EditorFor调用:
@Html.EditorFor(model => model.Img)
这修改的地方比较多,感觉不太舒服。能不能一劳永逸呢?当然是可以的,这需要自定义一个ModelMetadataProvider,来告诉MVC这个数据类型的属性就用这个模板显示。
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!
Asp.Net MVC 扩展 Html.ImageFor 方法详解的更多相关文章
- Asp.Net MVC学习总结之过滤器详解(转载)
来源:http://www.php.cn/csharp-article-359736.html 一.过滤器简介 1.1.理解什么是过滤器 1.过滤器(Filters)就是向请求处理管道中注入额外的 ...
- ASP.NET MVC学习之模型验证详解
ASP.NET MVC学习之模型验证篇 2014-05-28 11:36 by y-z-f, 6722 阅读, 13 评论, 收藏, 编辑 一.学习前的一句话 在这里要先感谢那些能够点开我随笔的博友们 ...
- Asp.Net MVC part2 View、Controller详解
View详解Razor视图引擎简介HtmlHelper强类型页面 Razor视图引擎简介强大的@:表示使用C#代码,相当于aspx中的<%%>可以完成输出功能当遇到html标签时会认为C# ...
- 【部分】ASP.NET MVC的Controller接收输入详解
原文:https://blog.csdn.net/lxrj2008/article/details/79455360 ASP.NET mvc的Controller要正确的响应用户发出的请求就要获取到用 ...
- asp.net文本编辑器FCKeditor使用方法详解
文本编辑器的使用: 1.FCKeditor的官方网站是:http://www.fckeditor.net/download 目前最新的FCKeditor.Net_2.6.9版本. 请在此页下载:ht ...
- ASP.NET 窗体间传值实现方法详解
假设ParentForm.aspx 页面上有TextBox1文本框和Open按钮点击Open按钮弹出SubForm.aspx,SubForm.aspx页面上有TextBox1文本框和Close按钮点击 ...
- MVC 扩展 Html.ImageFor
Asp.Net MVC 扩展 Html.ImageFor 方法详解 背景: 在Asp.net MVC中定义模型的时候,DataType有DataType.ImageUrl这个类型,但htmlhelpe ...
- ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解
原文 ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解 在教程的这一部分,我们将研究一下自动生成的 Details 和Delete 方法. Details 方法 打开M ...
- 【译】ASP.NET MVC 5 教程 - 11:Details 和 Delete 方法详解
原文:[译]ASP.NET MVC 5 教程 - 11:Details 和 Delete 方法详解 在教程的这一部分,我们将研究一下自动生成的 Details 和Delete 方法. Details ...
随机推荐
- Fuzzy Probability Theory---(3)Discrete Random Variables
We start with the fuzzy binomial. Then we discuss the fuzzy Poisson probability mass function. Fuzzy ...
- 不支持C++11 decltype的噩耗
前言:因为公司现在使用vs2008,所以很多c++11的新特性还未能使用,导致写了很多冤枉代码. 最初引擎的数学库非常简单,使用起来也不方便,例如: float FastLerp(const floa ...
- JIT动态编译器的原理与实现之Interpreter(解释器)的实现(三)
接下来,就是要实现一个虚拟机了.记得编码高质量的代码中有一条:不要过早地优化你的代码.所以,也本着循序渐进的原则,我将从实现一个解释器开始,逐步过渡到JIT动态编译器,这样的演化可以使原理看起来更清晰 ...
- loop 循环次数
在汇编中可以使用 loop 段地址:偏移地址 并配合 cx 达到循环执行的目的,但是在一些资料中看到说,cx 是循环的次数,我觉得这是不对的. 比如下面这段代码的作用是使得最终的 ax 中的值为 3 ...
- Linux echo, sort, sed 等一些命令总结
linux echo, sort, sed是初学linux shell script 的一些常用的命令.基本上来说,如果能够掌握了这些命令,我们就能写出一些不错的linux脚本.以下是我遇到的以下常用 ...
- 阿里云RDS for MySQL备份文件+binlog恢复过程中碰到的一些问题
1.一开始通过官方下载有的压缩包安装,碰到各种依赖问题,最后采用YUM安装 1.通过yum安装percona-Xtrabackup 1.1 先安装依赖: yum install perl-DBI yu ...
- 如何获取URL中的参数
获取URL中的某个参数或者所有参数以便我们后续去修改这个地址,在程序中是非常必要的.网上有很多这样的代码片段,为了以后查阅方便,顺便整理思路,下面使用2种方法来获取URL中的某个参数. 方法一 ,sp ...
- 微信小程序 wx.uploadFile在安卓手机上面the same task is working问题解决
微信小程序上传图片的时候,如果是多图片上传,一般都是直接用一个循环进行wx.uploadFile 这个在电脑上面测试与苹果手机上面都不会有什么问题 但当用安卓测试的时候,你会发现小程序会提示一个the ...
- [UCSD白板题] Fractional Knapsack
Problem Introduction Given a set of items and total capacity of a knapsack,find the maximal value of ...
- [UE4]自定义MovementComponent组件
自定义Movement组件 目的:实现自定义轨迹如抛物线,线性,定点等运动方式,作为组件控制绑定对象的运动. 基类:UMovementComponent 过程: 1.创建UCustomMovement ...