ASP.NET MVC陷阱:Html.BeginForm()隱式設置了FormContext


前天,我在開發中碰到一個奇怪的問題。和往常一樣,我在視圖中這樣寫:

@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。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM