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

10.1k views Asked by At

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?

2

There are 2 answers

2
marcind On BEST ANSWER

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("");
}
0
Jason Goemaat On

The reason it works I believe is that the BeginForm method returns an MvcForm object and not html, it writes the html directly to the page. When the using block ends it is disposed and it writes the closing end tag. That's the reason you see the text System.Web.Mvc.Html.MvcForm appear in your output. You have to put the closing tag in there manually because the MvcForm object isn't disposed.

The using syntax is like doing this:

@{ MvcForm mf = Html.BeginForm(); /* writes directly to html stream */ }
<div class="happy-css">
</div>
@{ mf.Dispose(); /* like calling mf.EndForm() */ }