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.

Script

/// <reference path="jquery-1.4.4.js" />
(function ($) {
    if ($) {
        $(document).ready(function () {
            $.each(document.forms, function () {
                var theForm = this;

                $('input[multiSumbit]', theForm).click(function () {
                    var __sender = $("input[name='n.__sender']", theForm);

                    if (__sender.length == 0) {
                        __sender = $("<input name=\"n.__sender\" value=\"" + $(this).attr('name') + "\" type=\"hidden\"/>");
                        theForm.appendChild(__sender[0]);
                    }

                    __sender[0].value = $(this).attr('name');
                });
            });
        });
    }
})($);  

Let’s create extension method for creating submit input tag for our case.

public static MvcHtmlString SubmitButton(this HtmlHelper htmlHelper, string name, string value = "", Dictionary<string,> htmlAttribute = null)
{
    if (String.IsNullOrEmpty(name))
        throw new ArgumentNullException("name");

    var button = new TagBuilder("input");

    if (htmlAttribute != null) 
        button.MergeAttributes(htmlAttribute);

    button.MergeAttribute("name", name, true);
    if (!String.IsNullOrEmpty(value)) button.MergeAttribute("value", value, true);
    button.MergeAttribute("multiSumbit","true", true);
    button.MergeAttribute("type", "submit", true);

    return new MvcHtmlString(button.ToString(TagRenderMode.SelfClosing));
}</string,>

Usage

@using (Html.BeginForm())
{
    @Html.SubmitButton("btnSumbit", "Log On")
    @Html.SubmitButton("btnCancel", "Cencel")
}

View Output

<form action="/" method="post">
<input multiSumbit="true" name="btnSumbit" type="submit" value="Log On" />
<input multiSumbit="true" name="btnCancel" type="submit" value="Cencel" />
</form>

As you see only thing that we have added is multiSumbit attribute.

Now let’s design ActionNameSelectorAttribute

[AttributeUsage(AttributeTargets.Method)]
public class SubmitButtonActionSelectorAttribute : ActionNameSelectorAttribute
{
    private readonly string[] _buttonNames;

    public SubmitButtonActionSelectorAttribute(params string[] buttonNames)
    {
        if (buttonNames == null)
            throw new ArgumentNullException("buttonNames");

            _buttonNames = buttonNames;
    }

    public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
    {
        return _buttonNames.Contains(controllerContext.RequestContext.HttpContext.Request.Form["n.__sender"]);
    }
}

Implementation

[HttpPost]
[FormActionSelector("LogOn")]
[SubmitButtonActionSelector("btnSumbit")]
public ActionResult Index(LogOnModel logOn)
{
    var account = new Account {LogOn = logOn};
    bool isValid = ModelState.IsValid;

    return View(account);
}

[HttpPost]
[ActionName("Index")]
[FormActionSelector("LogOn")]
[SubmitButtonActionSelector("btnCancel")]
public ActionResult IndexCancel()
{
    var account = new Account();
    return View(account);
}

You can also specify multiple button names in same attribute if required as button name parameter I have used is params string[]

Vote and comment if it really helped you :)

History 

  • 1st Release: 29 Jan 2011
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"