在視圖頁中@Html.xxx(...)是什麼?如何被執行?css
以下圖所示:
html
視圖頁中@Html.xxx(...)涉及的內容有:ios
以@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); } }
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); } }
「_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); } }
代碼中,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(); }
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; } }
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); } }
如下展現如何從MmvHtmlString對象中獲得被封裝的html標籤(如:<input type='text"...>),並寫入到Http響應內容中!函數
最終,將生成的html標籤以字符串的形式寫入到Http響應內容中!ui
以上只對HtmlHelper的擴展方法TextBox進行了介紹,其餘的擴展方法童鞋們自行補腦了!上述若有不適之處,請指正!!!
擴展:
能夠經過建立一個HtmlHelper的擴展方法,從而實現一個自定義控件!