ASP.NET MVC視圖中的@Html.xxx(...)

問題

在視圖頁中@Html.xxx(...)是什麼?如何被執行?css

以下圖所示:
  
html

解疑

視圖頁中@Html.xxx(...)涉及的內容有:ios

  • 視圖頁被編譯後的類繼承自 WebViewPage<T>:WebViewPage:WebPageBase:WebPageRenderingBase:WebPageExecutingBase
  • 在進行View呈現過程當中,建立視圖頁對象實例,此時 初始化了一個HtmlHelper對象,並賦值給其父類的一個名爲Html的屬性,@Html.xxx(...)中的Html就是該屬性
  • 視圖頁中的@Html.xxx(...),通過編譯以後,則變成該視圖頁對象Execute方法中的一段代碼,即:this.Write(this.Html.xxx(xxx));
  • xxx是HtmlHelper類的擴展方法

以@Html.TextBox(...)爲例進行詳細分析:web

  在ASP.NET MVC 中,視圖頁都被編譯成繼承自WebViewPage<T>的類,而在進行 View呈現 的過程當中,須要經過反射建立視圖頁類的實例(Object類型),而後再將該實例轉換爲WebViewPage類型,並進行一些初始化操做,就在初始化時,建立了HtmlHelper對象,並賦值給其屬性Html。express

public abstract class BuildManagerCompiledView : IView
{
    public void Render(ViewContext viewContext, TextWriter writer)
    {
        if (viewContext == null)
        {
            throw new ArgumentNullException("viewContext");
        }
 
        object instance = null;
        //獲取被編譯的視圖頁(cshtml文件)的類型                                                                 
        Type type = BuildManager.GetCompiledType(ViewPath);
        if (type != null)
        {
            //經過SingleServiceResolver利用反射和緩存的原理建立視圖頁的實例,過程相似SingleServiceResolver類對Controller的激活。
            instance = ViewPageActivator.Create(_controllerContext, type);
        }
 
        if (instance == null)
        {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    MvcResources.CshtmlView_ViewCouldNotBeCreated,
                    ViewPath));
        }
        //執行RenderView方法
        RenderView(viewContext, writer, instance);
    }
}
public class RazorView : BuildManagerCompiledView
{
    protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
    {
        if (writer == null)
        {
            throw new ArgumentNullException("writer");
        }
        //將視圖頁實例轉換爲WebViewPage對象。
        WebViewPage webViewPage = instance as WebViewPage;
        if (webViewPage == null)
        {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    MvcResources.CshtmlView_WrongViewBase,
                    ViewPath));
        }
        webViewPage.OverridenLayoutPath = LayoutPath;//母板路徑(在第二步中,建立RazorView對象時,由構造函數導入)
        webViewPage.VirtualPath = ViewPath;          //該視圖路徑
        webViewPage.ViewContext = viewContext;       //視圖上下文,其中有TempData、controllerContext、ViewData等
        webViewPage.ViewData = viewContext.ViewData;
        
        //WebViewPage對象初始化,執行建立3個xxHelper對象AjaxHelper、HtmlHelper、UrlHelper。
        webViewPage.InitHelpers();
 
        if (VirtualPathFactory != null)
        {
            webViewPage.VirtualPathFactory = VirtualPathFactory;
        }
        if (DisplayModeProvider != null)
        {
            webViewPage.DisplayModeProvider = DisplayModeProvider;
        }
 
        WebPageRenderingBase startPage = null;
        if (RunViewStartPages)
        {
            startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
        }
        //按層次結構處理視圖頁的內容。
        webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
    }
}
建立視圖頁對象-->轉換爲WebViewPage類型
public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild
{
    //省略其餘代碼...
    public AjaxHelper<object> Ajax
    {
        get;
        set;
    }
    
    public HtmlHelper<object> Html
    {
        get;
        set;
    }
    
    public UrlHelper Url
    {
        get;
        set;
    }
    
    public ViewContext ViewContext
    {
        get;
        set;
    }
    
    public virtual void InitHelpers()
    {
        this.Ajax = new AjaxHelper<object>(this.ViewContext, this);
        this.Html = new HtmlHelper<object>(this.ViewContext, this);
        this.Url = new UrlHelper(this.ViewContext.RequestContext);
    }
}
WebViewPage

 「_Index.cshtml」經編譯後的結果爲:緩存

  如上圖所示,「@Html.TextBox(‘txtUser’)」 通過編譯後,就是去執行base.Html.TextBox("txtUser"),又由類繼承關係可知,base.Html的值就是一個HtmlHelper對象(視圖頁對象轉換爲WebViewPage類型後,初始化時建立的)。而TextBox("txtUser"),則是HtmlHelper的擴展方法!app

public static class InputExtensions
{
    //省略其餘代碼...
    
    // TextBox
    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name)
    {
        return TextBox(htmlHelper, name, value: null);
    }

    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value)
    {
        return TextBox(htmlHelper, name, value, format: null);
    }

    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, string format)
    {
        return TextBox(htmlHelper, name, value, format, htmlAttributes: (object)null);
    }

    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
    {
        return TextBox(htmlHelper, name, value, format: null, htmlAttributes: htmlAttributes);
    }

    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, string format, object htmlAttributes)
    {
        return TextBox(htmlHelper, name, value, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, IDictionary<string, object> htmlAttributes)
    {
        return TextBox(htmlHelper, name, value, format: null, htmlAttributes: htmlAttributes);
    }

    public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, string format, IDictionary<string, object> htmlAttributes)
    {
        return InputHelper(htmlHelper,
                           InputType.Text,
                           metadata: null,
                           name: name,
                           value: value,
                           useViewData: (value == null),
                           isChecked: false,
                           setId: true,
                           isExplicitValue: true,
                           format: format,
                           htmlAttributes: htmlAttributes);
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        return htmlHelper.TextBoxFor(expression, format: null);
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format)
    {
        return htmlHelper.TextBoxFor(expression, format, (IDictionary<string, object>)null);
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        return htmlHelper.TextBoxFor(expression, format: null, htmlAttributes: htmlAttributes);
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format, object htmlAttributes)
    {
        return htmlHelper.TextBoxFor(expression, format: format, htmlAttributes: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
    {
        return htmlHelper.TextBoxFor(expression, format: null, htmlAttributes: htmlAttributes);
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
    public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format, IDictionary<string, object> htmlAttributes)
    {
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        return TextBoxHelper(htmlHelper,
                             metadata,
                             metadata.Model,
                             ExpressionHelper.GetExpressionText(expression),
                             format,
                             htmlAttributes);
    }

    private static MvcHtmlString TextBoxHelper(this HtmlHelper htmlHelper, ModelMetadata metadata, object model, string expression, string format, IDictionary<string, object> htmlAttributes)
    {
        return InputHelper(htmlHelper,
                           InputType.Text,
                           metadata,
                           expression,
                           model,
                           useViewData: false,
                           isChecked: false,
                           setId: true,
                           isExplicitValue: true,
                           format: format,
                           htmlAttributes: htmlAttributes);
    }

    // Helper methods
    private static MvcHtmlString InputHelper(HtmlHelper htmlHelper, InputType inputType, ModelMetadata metadata, string name, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes)
    {
        string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
        if (String.IsNullOrEmpty(fullName))
        {
            throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
        }

        TagBuilder tagBuilder = new TagBuilder("input");
        tagBuilder.MergeAttributes(htmlAttributes);
        tagBuilder.MergeAttribute("type", HtmlHelper.GetInputTypeString(inputType));
        tagBuilder.MergeAttribute("name", fullName, true);

        string valueParameter = htmlHelper.FormatValue(value, format);
        bool usedModelState = false;

        switch (inputType)
        {
            case InputType.CheckBox:
                bool? modelStateWasChecked = htmlHelper.GetModelStateValue(fullName, typeof(bool)) as bool?;
                if (modelStateWasChecked.HasValue)
                {
                    isChecked = modelStateWasChecked.Value;
                    usedModelState = true;
                }
                goto case InputType.Radio;
            case InputType.Radio:
                if (!usedModelState)
                {
                    string modelStateValue = htmlHelper.GetModelStateValue(fullName, typeof(string)) as string;
                    if (modelStateValue != null)
                    {
                        isChecked = String.Equals(modelStateValue, valueParameter, StringComparison.Ordinal);
                        usedModelState = true;
                    }
                }
                if (!usedModelState && useViewData)
                {
                    isChecked = htmlHelper.EvalBoolean(fullName);
                }
                if (isChecked)
                {
                    tagBuilder.MergeAttribute("checked", "checked");
                }
                tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue);
                break;
            case InputType.Password:
                if (value != null)
                {
                    tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue);
                }
                break;
            default:
                string attemptedValue = (string)htmlHelper.GetModelStateValue(fullName, typeof(string));
                tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(fullName, format) : valueParameter), isExplicitValue);
                break;
        }

        if (setId)
        {
            tagBuilder.GenerateId(fullName);
        }

        // If there are any errors for a named field, we add the css attribute.
        ModelState modelState;
        if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
        {
            if (modelState.Errors.Count > 0)
            {
                tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
            }
        }

        tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

        if (inputType == InputType.CheckBox)
        {
            // Render an additional <input type="hidden".../> for checkboxes. This
            // addresses scenarios where unchecked checkboxes are not sent in the request.
            // Sending a hidden input makes it possible to know that the checkbox was present
            // on the page when the request was submitted.
            StringBuilder inputItemBuilder = new StringBuilder();
            inputItemBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));

            TagBuilder hiddenInput = new TagBuilder("input");
            hiddenInput.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
            hiddenInput.MergeAttribute("name", fullName);
            hiddenInput.MergeAttribute("value", "false");
            inputItemBuilder.Append(hiddenInput.ToString(TagRenderMode.SelfClosing));
            return MvcHtmlString.Create(inputItemBuilder.ToString());
        }

        return tagBuilder.ToMvcHtmlString(TagRenderMode.SelfClosing);
    }

    private static RouteValueDictionary ToRouteValueDictionary(IDictionary<string, object> dictionary)
    {
        return dictionary == null ? new RouteValueDictionary() : new RouteValueDictionary(dictionary);
    }
}
InputExtensions

  代碼中,TextBox等擴展方法最終調用InputHelper方法,從而生成對應字符串類型的html標籤(如:<input type='text"...>),而後將標籤封裝到一個MvcHtmlString對象中,並返回。即:base.Html.TextBox("txtUser")的執行結果就是一個封裝了html標籤內容的MvcHtmlString對象。以後執行this.Write(base.Html.TextBox("txtUser"))時,會執行MvcHtmlString對象的 ToHtmlString()方法獲取html標籤,最終將標籤內容寫入到響應給客戶端的內容中!ide

public interface IHtmlString
{
    string ToHtmlString();
}
IHtmlString
public class HtmlString : IHtmlString
{
    private string _htmlString;
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public HtmlString(string value)
    {
        this._htmlString = value;
    }
    public string ToHtmlString()
    {
        return this._htmlString;
    }
    public override string ToString()
    {
        return this._htmlString;
    }
}
HtmlString
public sealed class MvcHtmlString : HtmlString
{
    [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "MvcHtmlString is immutable")]
    public static readonly MvcHtmlString Empty = Create(String.Empty);

    private readonly string _value;

    public MvcHtmlString(string value)
        : base(value ?? String.Empty)
    {
        _value = value ?? String.Empty;
    }

    public static MvcHtmlString Create(string value)
    {
        return new MvcHtmlString(value);
    }

    public static bool IsNullOrEmpty(MvcHtmlString value)
    {
        return (value == null || value._value.Length == 0);
    }
}
MvcHtmlString

 如下展現如何從MmvHtmlString對象中獲得被封裝的html標籤(如:<input type='text"...>),並寫入到Http響應內容中!函數

 最終,將生成的html標籤以字符串的形式寫入到Http響應內容中!ui

 以上只對HtmlHelper的擴展方法TextBox進行了介紹,其餘的擴展方法童鞋們自行補腦了!上述若有不適之處,請指正!!!

擴展:

  能夠經過建立一個HtmlHelper的擴展方法,從而實現一個自定義控件!

相關文章
相關標籤/搜索