Rewriting Html.BeginForm() in MVC 3.0 and keeping unobtrusive javascript

Ciel picture Ciel · Feb 23, 2011 · Viewed 10.1k times · Source

This is going to seem like a bit of a silly endeavor, but it's something I want to learn nonetheless.

Right now, in ASP.NET MVC 3.0, you need to use the syntax @using (Html.BeginForm()) { and then later, the } to close a form block to get the fancy new 'unobtrusive javascript', lest you want to write all of it by hand (which is fine).

For some reason (Read: *OCD*) I don't like that. I'd really rather do this..

@Html.BeginForm()
<div class="happy-css">
</div>
@Html.EndForm()

Seem stupid yet? Yeah, well to each their own. I want to understand why it is working how it is and mold it to my liking. So I thought the first place I would start digging is the MVC 3.0 source itself. So I jumped into codeplex to find the BeginForm Extension method.

( http://aspnet.codeplex.com/SourceControl/changeset/view/63452#288009 )

So now I am a little confused as to how to begin achieving my goal. Reading through the code, I discovered that they all go down to a root method (not surprising, as most extension methods seem to be hierarchical methods all reaching down into a single one to avoid redundancy).

private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes) {
            TagBuilder tagBuilder = new TagBuilder("form");
            tagBuilder.MergeAttributes(htmlAttributes);
            // action is implicitly generated, so htmlAttributes take precedence.
            tagBuilder.MergeAttribute("action", formAction);
            // method is an explicit parameter, so it takes precedence over the htmlAttributes.
            tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);

            HttpResponseBase httpResponse = htmlHelper.ViewContext.HttpContext.Response;
            httpResponse.Write(tagBuilder.ToString(TagRenderMode.StartTag));
            return new MvcForm(htmlHelper.ViewContext.HttpContext.Response);
        }

What I am not seeing here is how this method relates to the unobtrusive javascript. If I simply type out ..

<form action="/Controller/Action" method="post">

and then put in my validation like so...

@Html.ValidationSummary(false)

I do not get the unobtrusive javascript. But if I use

@using (Html.BeginForm()) { then I do. I've even examined the generated markup and I really can't find the difference.

Now then it gets strange. If I just type in ...

@Html.BeginForm() and then put all of my form code, the form works and I get the unobtrusive javascript, but I have to manually type in </form> at the end. @Html.EndForm() doesn't work. But ontop of that, I now get the text System.Web.Mvc.Html.MvcForm written to the output stream right beneath the <form action="/Controller/Action" method="post"> html.

Can someone enlighten and/or help me?

Answer

marcind picture marcind · Feb 23, 2011

The answer to your underlying question (i.e. how to use BeginForm/EndForm syntax) is to do it in the following manner:

@{ Html.BeginForm(...); }
<div> content</div>
@{ Html.EndForm(); }

Unfortunately the Razor syntax right now is a bit more verbose when invoking helpers that write to the output (as opposed to the majority of helpers which just return an html snippet). You could probably make this easier by writing your own extension methods like so:

public static IHtmlString FormBegin(this HtmlHelper helper, ...) {
    helper.BeginForm(...);
    return new HtmlString("");
}

public static IHtmlString FormEnd(this HtmlHelper helper) {
    helper.EndForm();
    return new HtmlString("");
}