前天,我在开发中碰到一个奇怪的问题。和往常一样,我在视图中这样写:

@Html.TextBoxFor(m => m.LoginName)

因为Login模型是有Required标注的,那么输出的HTML理当带有对应的data-val-required属性,从而启用客户端验证。但在页面上却没有验证信息,查看HTML源码发现,期望的属性并未生成。这是为什么呢?

我的第一个反应是去确认ClientValidationEnabled和UnobtrusiveJavaScriptEnabled设置是否被关闭了,可是并没有。我从来未曾、也没有理由关闭这些设置。那么问题在哪呢?

求之不得,只好请问google了。Stackoverflow上的一个类似的问题引起了我的关注,帖中提到原因可能是因为控件不在FormContext的上下文中。这提醒了我,我的form是手写的html,像这样:

<form action="@Url.Content("Login")" method="post">
</form>

而不是用Html.BeginForm()。在我印象里,BeginForm()的作用只是输出一段HTML,最终结果和手写标签应该是完全等效的。难道这个方法还有什么其他特殊的行为吗?为了确认,反汇编了一下ASP.NET MVC的源代码,发现还真的是这样。

 

 

TextBoxFor()是一个扩展方法,它的实现调用了HtmlHelper.InputHelper辅助方法,其中有这么一行:

tagBuilder.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));

很明显,该方法设置了客户端验证需要的属性。来看看方法的实现:

public IDictionary<string, object> GetUnobtrusiveValidationAttributes(string name, ModelMetadata metadata)
{
    Dictionary<string, object> dictionary = new Dictionary<string, object>();
    if (!this.ViewContext.UnobtrusiveJavaScriptEnabled)
    {
        return dictionary;
    }
    FormContext formContextForClientValidation = this.ViewContext.GetFormContextForClientValidation();
    if (formContextForClientValidation == null)
    {
        return dictionary;
    }
        ......

可以看到,方法会在ViewContext中查找FormContext,如果找不到的话则直接返回,而不会设置任何属性。那么这个FormContext又是在哪设置的呢?最直接的想法是,它应该在Html.BeginForm()当中。

来看看BeginForm()方法,它调用了FormHelper方法,其实现大概是这样的:

private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
    TagBuilder tagBuilder = new TagBuilder("form");
    tagBuilder.MergeAttributes<string, object>(htmlAttributes);
    tagBuilder.MergeAttribute("action", formAction);
    tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
    bool flag = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;
    if (flag)
    {
        tagBuilder.GenerateId(htmlHelper.ViewContext.FormIdGenerator());
    }
    htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
    MvcForm result = new MvcForm(htmlHelper.ViewContext);
    if (flag)
    {
        htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
    }
    return result;
}

前面的代码似乎和FormContext没有太大关系,但是后面就调用了ViewContext.FormContext,这说明FormContext必定是在这一行之前创建的。是不是在于new MvcForm(...)这一句呢?再来看看MvcForm的构造方法:

public MvcForm(ViewContext viewContext)
{
    if (viewContext == null)
    {
        throw new ArgumentNullException("viewContext");
    }
    this._viewContext = viewContext;
    this._writer = viewContext.Writer;
    this._originalFormContext = viewContext.FormContext;
    viewContext.FormContext = new FormContext();
}

果然!

 

结论:要让ASP.NET MVC客户端生效,不仅ClientValidationEnabled和UnobtrusiveJavaScriptEnabled应该设置为true,而且要创建的input控件必须用BeginForm()/EndForm()包围起来,否则不会生成data-val属性。所以,建议你使用Html.BeginForm()辅助方法,而应该避免手写form标签,以免碰到和我类似的bug。

 

 

posted on 2012-08-30 10:24  夏之韵  阅读(3834)  评论(4编辑  收藏  举报