Asp.Net MVC 擴展 Html.ImageFor 方法詳解

背景:

在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;
        }
    }
View Code

代碼已更新到:https://github.com/stoneniqiu/Portal.MVC

小結:回顧這四種方法,分部視圖最直接,但不夠靈活,ImageFor調用很簡單,也最靈活,實現複雜點但可用來去擴展更多方法。若是要實現一個功能,須要強制性改動幾個地方,依賴多個地方,天然就失去了靈活性,最後實現了EnumToDropDownList的方法仍是很方便的,不須要依賴於什麼模板,也不須要再自定義什麼特性。 最後但願對你有幫助。tks!

相關文章
相關標籤/搜索