Feed Subscribe
Exception has been thrown by the target of an invocation.


When BeginForm is not BeginForm<T>

by ondrejsv 30. August 2010 16:03

or why the ASP.NET MVC 2 client validation fails when you use Microsoft.Web.Mvc (aka MvcFutures)?

There’s an incredibly useful set of extension methods in the MvcFutures called BeginForm<T>. These methods provide an alternative type-safe way of building MVC forms instead of the old string based BeginForm. Instead of:

<% using (Html.BeginForm("Submit", "ThreadedInputController")) { %>

you just write

<% using (Html.BeginForm<ThreadedInputController>(c => c.Submit()) { %>

No strings here, everything type-safe.

But when you enable fantastic client side validations with the BeginForm<T> constructed form you end with a JavaScript error “Object Required” somewhere deep inside the MicrosoftMvcValidation.js:

image

on the line:

formElement[Sys.Mvc.FormContext._formValidationTag] = this;

The cause of this is that the new BeginForm<T> and the old BeginForm do not have a common implementation; in fact they are completely separated. The client side validation model requires all HTML forms have an ID value and also the framework expects this ID value to be set in the FormContext.FormId property of the current ViewContext. The original BeginForm does exactly this. If you don’t provide the form ID yourself, it will generate one in the form “formN” where N is the sequence number of the form.

It’s nothing difficult to fix the implementation but you must rename your extension methods to avoid conflict with the MvcFutures implementation or directly fix it in the MvcFutures and build your own version.

Here’s the fix (I renamed methods to the BeginFormA<T>):

private static readonly object _lastFormNumKey = new object(); private static int IncrementFormCount(IDictionary items) { object lastFormNum = items[_lastFormNumKey]; int newFormNum = (lastFormNum != null) ? ((int)lastFormNum) + 1 : 0; items[_lastFormNumKey] = newFormNum; return newFormNum; } [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an Extension Method which allows the user to provide a strongly-typed argument via Expression")] public static MvcForm BeginFormA<TController>(this HtmlHelper helper, Expression<Action<TController>> action, FormMethod method, IDictionary<string, object> htmlAttributes) where TController : Controller { TagBuilder tagBuilder = new TagBuilder("form"); if (helper.ViewContext.ClientValidationEnabled && htmlAttributes["id"] == null) { // forms must have an ID for client validation int formNum = IncrementFormCount(helper.ViewContext.HttpContext.Items); var formId = String.Format(CultureInfo.InvariantCulture, "form{0}", formNum); tagBuilder.GenerateId(formId); } tagBuilder.MergeAttributes(htmlAttributes); string formAction = Microsoft.Web.Mvc.LinkExtensions.BuildUrlFromExpression(helper, action); tagBuilder.MergeAttribute("action", formAction); tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method)); helper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); var theForm = new MvcForm(helper.ViewContext); if (helper.ViewContext.ClientValidationEnabled) { helper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; } return theForm; }

You can download the whole static class with the extension methods.

Note: This bug is still not fixed in the ASP.NET MVC 3 Preview 1.

Tags: ,

Pingbacks and trackbacks (1)+

Comments are closed