Sunday, January 30, 2011

ASP.Net MVC - Multi Form and Multi Submit Button Handling

Introduction
This article is inspired by the blog that showed excelled approach for using ActionNameSelector attribute. I have tried to take this approach little further to support multiple forms and multiple submit buttons without providing any hardcoded value to the attribute but just by specifying name of the form and button this action intended to handle. This makes sense when you don’t want to specify any Value directly inside code, as in case of localization when Value of buttons is not fixed.
Let’s design our approach for both the scenarios.

1. Multiple forms on same page

We will have an attribute to handle this scenario that will look for the form name from which the request has been posted. To have this we need to store form name in hidden field. So let’s create extension method of BeginForm to handle this.

public static MvcForm BeginForm(this HtmlHelper htmlHelper, string formName)
{
   return BeginForm(htmlHelper, null, formName);
}

public static MvcForm BeginForm(this HtmlHelper htmlHelper, MvcForm form, string formName)
{
   if(String.IsNullOrEmpty(formName))
       throw new ArgumentNullException("formName");
   if (form == null)
       form = htmlHelper.BeginForm();
   htmlHelper.ViewContext.Writer.WriteLine(htmlHelper.Hidden("n.__formName", formName));
   return form;
}

Now we have our hidden field rendered along with form name in output inside the current form tag. Below is the output of the page for View page

@using (Html.BeginForm("LogOn"))
{  }
@using (Html.BeginForm("ChangePassword"))
{  }

View Output

<form action="/" method="post"><input name="n.__formName" type="hidden" value="LogOn" />
</form>
<form action="/" method="post"><input name="n.__formName" type="hidden" value="ChangePassword" />
</form>

Now let’s design ActionNameSelectorAttribute

[AttributeUsage(AttributeTargets.Method)]
public class FormActionSelectorAttribute : ActionNameSelectorAttribute
{
   private readonly string[] _formName;
   public FormActionSelectorAttribute(params string[] formName)
   {
       if (formName == null)
           throw new ArgumentNullException("formName");
       _formName = formName;
   }
   public string[] FormName
   { get { return _formName; }
   }
   public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
   {
       return _formName.Contains(controllerContext.RequestContext.HttpContext.Request.Form["n.__formName"]);
   }
}

Implementation

[HttpPost]
[FormActionSelector("LogOn")]
public ActionResult Index(LogOnModel logOn)
{
   var account = new Account {LogOn = logOn};
   return View(account);
}
[HttpPost]
[FormActionSelector("ChangePassword")]
public ActionResult Index(ChangePasswordModel changePasswordModel)
{
   var account = new Account {ChangePassword = changePasswordModel};
   return View(account);
}


2. Multiple submit buttons inside same form

As we don’t want to do check using value of button we need to follow same approach that we used in form names. But in this scenario our value of hidden filed should be decided on click of that button. So now we will have to handle this from client side. Rather than adding this hidden field right from we will use small script to create only when needed. You can do this just like we did in multi form scenario, but I prefer it this way. Here I’m going to add new attribute to submit button to attach our function on click of it.

Read more: Codeproject