asp.net mvc源码分析-BeginForm方法 和ClientValidationEnabled 属性
在上篇文章asp.net mvc源码分析-RenderAction和RenderPartial我们提到了一个常用的RenderAction方法,除了它我们还会经常遇到表单提交,这时我们通常会用到BeginForm。让我们来看看你BeginForm是如何使用的
运行结果就是生成form表单
一般我们的表单提交都涉及到强类型,所以一般需要@model MvcApp.Controllers.UserInfo指令,那我们来看看你用@using (Html.BeginForm()) 和Html.BeginForm();、Html.EndForm();这两种用法有什么区别。
我们找到BeginForm返回的是一个MvcForm,而MvcForm的一定如下: public class MvcForm : IDisposable
可见使用using最后调用的是MvcForm的Dispose方法:
protected virtual void Dispose(bool disposing) {
if (!_disposed) {
_disposed = true;
_writer.Write("</form>");
// output client validation and restore the original form context
if (_viewContext != null) {
_viewContext.OutputClientValidation();
_viewContext.FormContext = _originalFormContext;
}
}
}
这里的_disposed默认是false,_writer是viewContext.Writer或则httpResponse.Output都表示当前的输出流。那么我们再来看看EndForm吧:
public static void EndForm(this HtmlHelper htmlHelper) {
htmlHelper.ViewContext.Writer.Write("</form>");
htmlHelper.ViewContext.OutputClientValidation();
}
可见EndForm和MvcForm的Dispose方法完全等价。
我们来看看你BeginForm是如何实现的,
public static MvcForm BeginForm(this HtmlHelper htmlHelper) {
// generates <form action="{current url}" method="post">...</form>
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return FormHelper(htmlHelper, formAction, FormMethod.Post, new RouteValueDictionary());
}
public static MvcForm BeginForm(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, FormMethod method, IDictionary<string, object> htmlAttributes) {
string formAction = UrlHelper.GenerateUrl(null /* routeName */, actionName, controllerName, routeValues, htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, true /* includeImplicitMvcValues */);
return FormHelper(htmlHelper, formAction, method, htmlAttributes);
}
这里的FormHelper方法比较简单,里面有一句需要注意一下 bool traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;这里ClientValidationEnabled 和UnobtrusiveJavaScriptEnabled默认都是true,所以traditionalJavascriptEnabled 为false。
上面有个GenerateUrl方法,这个方法也很简单,关键代码就3句。
RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
VirtualPathData vpd = routeCollection.GetVirtualPathForArea(requestContext, routeName, mergedRouteValues);
string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext,vpd.VirtualPath);
看看这里我们就用到了VirtualPathData的VirtualPath属性了。
在FormExtensions中有一个BeginRouteForm方法,该方法的使用方式和BeginForm方法差不多,就跳过了。现在我们来看看ClientValidationEnabled 和UnobtrusiveJavaScriptEnabled默认为什么是true?这2个属性都是调用ScopeCache实例的对应属性,而获取ScopeCache时通过ScopeCache的Get方法来完成的。该Get方法代码很简单:
public static ScopeCache Get(IDictionary< object , object > scope, HttpContextBase httpContext) { if (httpContext == null && System.Web.HttpContext.Current != null ) { httpContext = new HttpContextWrapper(System.Web.HttpContext.Current); } ScopeCache result = null ; scope = scope ?? ScopeStorage.CurrentScope; if (httpContext != null ) { result = httpContext.Items[_cacheKey] as ScopeCache; } if (result == null || result._scope != scope) { result = new ScopeCache(scope); if (httpContext != null ) { httpContext.Items[_cacheKey] = result; } } return result; } } |
而ScopeStorage的CurrentScope属性是又是怎么来的了?
public static IScopeStorageProvider CurrentProvider {
get { return _stateStorageProvider ?? _defaultStorageProvider; }
set { _stateStorageProvider = value; }
}
public static IDictionary<object, object> CurrentScope {
get {
return CurrentProvider.CurrentScope;
}
}
默认来自于StaticScopeStorageProvider的CurrentScope,该属性默认返回的是一个没有成员的IDictionary<object, object>实例。那么这里的CurrentProvider 默认是个说明东西了,在System.Web.WebPages项目中的PreApplicationStartCode有这么一句 ScopeStorage.CurrentProvider = new AspNetRequestScopeStorageProvider(); 在AspNetRequestScopeStorageProvider的够着函数有这么一句ApplicationScope = new ApplicationScopeStorageDictionary(); 在ApplicationScopeStorageDictionary的构造函数中有 public ApplicationScopeStorageDictionary() : this(new WebConfigScopeDictionary()) { },WebConfigScopeDictionary的够着函数又有 public WebConfigScopeDictionary() :this(WebConfigurationManager.AppSettings) { }这么一句.
不知道大家是否还记得在ControllerBase的Execute方法中有这么一句 using (ScopeStorage.CreateTransientScope()) { ExecuteCore(); }
public static IDisposable CreateTransientScope() {
return CreateTransientScope(new ScopeStorageDictionary(baseScope: CurrentScope));
}
public static IDisposable CreateTransientScope(IDictionary<object, object> context) {
var currentContext = CurrentScope;
CurrentProvider.CurrentScope = context;
return new DisposableAction(() => CurrentProvider.CurrentScope = currentContext); // Return an IDisposable that pops the item back off
}
现在我们知道了IScopeStorageProvider 的CurrentProvider是什么东西了,是一个AspNetRequestScopeStorageProvider,里面的数据有一部分从WebConfigurationManager.AppSettings这里取出来的。
我想大家现在该明白为什么这个ClientValidationEnabled 默认是true了吧。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构