• 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=" 100px;"/></a>

    方法1:分部视图

    在做后台表格的时候经常要修改这样的问题,于是首先想到的就是做一个分部视图,叫tableimg。

    @model string
    @if (!string.IsNullOrEmpty(Model))
    {
    <a href="@Model" target="_blank"> <img src="@Model" style=" 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=" 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("{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='{1}px;'/></a>", url, length);
       return MvcHtmlString.Create(str);

    调用的时候传入参数:

    @Html.Image(Model.Img,100) 

    结果显示ok:

    方法1:分部视图

    在做后台表格的时候经常要修改这样的问题,于是首先想到的就是做一个分部视图,叫tableimg。

    @model string
    @if (!string.IsNullOrEmpty(Model))
    {
    <a href="@Model" target="_blank"> <img src="@Model" style=" 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=" 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("{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='{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)

    结果一样,且可以扩展样式,匹配选中。

    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("{0}px", length));
    
                tagA.InnerHtml = img.ToString();
    
                //return string.Format("<a href='{0}' target='_blank'> <img src='{0}' style='{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!

  • 相关阅读:
    python中不同文件中函数和类的调用
    python中使用queue实现约瑟夫环(约瑟夫问题)求解
    C语言中几个常用数学计算函数ceil(), floor(), round()的用法
    python中stack在实际中的简单应用之进制转换
    python中stack在实际中的简单应用之平衡符号
    python中两种栈实现方式的性能对比
    python实现stack并测试
    昨天的面试的一点思考
    从历代帝王的的创业经历看哪些人适合创业
    python chr()和ord()的含义和使用方法
  • 原文地址:https://www.cnblogs.com/bubugao/p/sdd3.html
Copyright © 2020-2023  润新知