上篇说到HtmlHelper,今天继续这个话题。
我们在自己建立的MVC项目中,随便打开一个.cshtml文件,找到一个HtmlHelper调用,例如:@Html.TextBox("CustomerName"),把光标放在Html上,按F12进入其定义,即代码段1中的语句【public new HtmlHelper<TModel> Html { get; set; }】。可见,@Html是HtmlHelper<TModel>类的一个实例,并且它是泛型类WebViewPage<TModel>的一个属性,由于.cshtml文件中的类继承于泛型类WebViewPage<TModel>,所以我们在Razor中可以直接使用@Html。
#region 程序集System.Web.Mvc.dll, v4.0.30319 // C:\Program Files(x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Web.Mvc.dll #endregion using System; namespace System.Web.Mvc { // 摘要: // 表示呈现使用 ASP.NET Razor 语法的视图所需的属性和方法。 // // 类型参数: // TModel: // 视图数据模型的类型。 public abstract classWebViewPage<TModel> : WebViewPage { // 摘要: // 初始化 System.Web.Mvc.WebViewPage<TModel> 类的新实例。 protected WebViewPage(); // 摘要: // 获取或设置 System.Web.Mvc.AjaxHelper 对象,该对象用于使用 Ajax 呈现 HTML 标记。 // // 返回结果: // 用于使用 AJAX 呈现 HTML 标记的System.Web.Mvc.AjaxHelper 对象。 public AjaxHelper<TModel> Ajax { get; set; } // // 摘要: // 获取或设置 System.Web.Mvc.HtmlHelper 对象,该对象用于呈现 HTML 元素。 // // 返回结果: // 用于呈现 HTML 元素的 System.Web.Mvc.HtmlHelper 对象。 public HtmlHelper<TModel> Html {get; set; } // // 摘要: // 获取关联的 System.Web.Mvc.ViewDataDictionary 对象的 Model属性。 // // 返回结果: // 关联的 System.Web.Mvc.ViewDataDictionary 对象的 Model属性。 public TModel Model { get; } // // 摘要: // 获取或设置一个字典,其中包含在控制器和视图之间传递的数据。 // // 返回结果: // 一个字典,其中包含在控制器和视图之间传递的数据。 public ViewDataDictionary<TModel>ViewData { get; set; } // 摘要: // 初始化 System.Web.Mvc.AjaxHelper、System.Web.Mvc.HtmlHelper和 System.Web.Mvc.UrlHelper // 类。 public override void InitHelpers(); // // 摘要: // 设置视图数据。 // // 参数: // viewData: // 视图数据。 protected override void SetViewData(ViewDataDictionaryviewData); } }
代码段 1
好了,现在我们进入泛型类HtmlHelper<TModel>的定义(代码段2),在这里可以看到两个大家肯定会感兴趣的属性,即ViewData和ViewBag。没错,这就是我们用于保持不同Http请求之间状态的那两个东东。
public class HtmlHelper<TModel> :HtmlHelper { private DynamicViewDataDictionary_dynamicViewDataDictionary; privateViewDataDictionary<TModel> _viewData; public HtmlHelper(ViewContextviewContext, IViewDataContainer viewDataContainer) : this(viewContext,viewDataContainer, RouteTable.Routes) { } public HtmlHelper(ViewContextviewContext, IViewDataContainer viewDataContainer, RouteCollectionrouteCollection) : base(viewContext,viewDataContainer, routeCollection) { _viewData = new ViewDataDictionary<TModel>(viewDataContainer.ViewData); } public new dynamic ViewBag { get { if (_dynamicViewDataDictionary== null) { _dynamicViewDataDictionary= new DynamicViewDataDictionary(() => ViewData); } return_dynamicViewDataDictionary; } } public newViewDataDictionary<TModel> ViewData { get { return _viewData; } } }
代码段 2
大家看ViewData和ViewBag属性的定义中用了new关键字,这是为什么?我们知道,关键字new除了常用来创建对象,还有一个作用是覆写父类的同名成员。由此可知,在泛型类HtmlHelper<TModel>的父类中,肯定也存在一个叫ViewData的成员。(注:本篇文章第二自然段中的语句【public newHtmlHelper<TModel> Html { get; set; }】也是这个意思。)我们进入泛型类HtmlHelper<TModel>的父类HtmlHelper,果然不出所料,确实有ViewData属性和ViewBag属性,见代码段3,其中ViewData是一个字典,ViewBag是一个动态类型(dynamic)。怎么样?这就是ViewData和ViewBag的庐山真面目。
public ViewDataDictionary ViewData { get { returnViewDataContainer.ViewData; } } public dynamic ViewBag { get { if (_dynamicViewDataDictionary== null) { _dynamicViewDataDictionary= new DynamicViewDataDictionary(() => ViewData); } return _dynamicViewDataDictionary; } }
代码段 3
我们在HtmlHelper类中并没有找到常用的ActionLink、TextBox等Helper,回想一下上篇文章中关于ActionLink的分析,可以知道MVC框架内置的HtmlHelper是以扩展方法的形式进行定义的,这些扩展方法存在于MVC源码中的“System.Web.MVC/Html”路径下,名称包含Extention的类中,例如InputExtensions、LabelExtensions、ChildActionExtensions、FormExtensions、NameExtensions、PartialExtensions、SelectExtensions、TextAreaExtensions等等。
下面我们着重说一下FormExtensions类。在这个类中,我们可以找到常用的@Html.BeginForm的定义,没错,这就是Razor中用来定义Web表单的Helper。用过这个Helper的人都会注意到一个细节,就是BeginForm的用法是【@using (Html.BeginForm()){}】,和普通的Helper不同。这种用法可以让我们不用调用EndForm方法,但这是为什么呢?
见到using关键字,我们都知道在这里它的作用是在代码块结束之前,自动调用Dispose方法,当然,using代码块中的对象必须实现了IDisposable接口。找到BeginForm的定义(代码段4),发现这个方法的返回值是一个MvcForm对象。
public static MvcForm BeginForm(thisHtmlHelper htmlHelper) { // generates <formaction="{current url}" method="post">...</form> string formAction =htmlHelper.ViewContext.HttpContext.Request.RawUrl; return FormHelper(htmlHelper, formAction,FormMethod.Post, new RouteValueDictionary()); }
代码段 4
跟踪进入MvcForm类的定义,可以看到这个类果然实现了IDisposable接口,狂喜!在MvcForm类的定义中,找到Dispose方法的实现(代码段5),豁然开朗吧?原来在Dispose方法中调用了FormExtensions.EndForm方法。
public void Dispose() { Dispose(true /* disposing */); GC.SuppressFinalize(this); } protected virtual void Dispose(booldisposing) { if (!_disposed) { _disposed = true; FormExtensions.EndForm(_viewContext); } }
代码段 5
继续进入FormExtensions.EndForm方法的定义(代码段6),更加豁然开朗吧?继续狂喜!EndForm方法的作用正是向浏览器发送Web表单的结束标记</form>。
internal static voidEndForm(ViewContext viewContext) { viewContext.Writer.Write("</form>"); viewContext.OutputClientValidation(); viewContext.FormContext = null; }
代码段 6