Practical .NET

Managing Complex Web Requests

A complex ASP.NET MVC form can drive you to a big, ugly action method that handles all the functionality the page offers. The ActionMethodSelector provides a clean way to separate that logic over multiple methods, provided you understand a key distinction of which many ASP.NET MVC developers aren't aware.

In an earlier column, I responded to a reader who was looking for a way to have two Submit buttons on the same form perform two different processes back at the server: When the user clicked one Submit button, he wanted to save the user's current work state temporarily; when the user clicked the other Submit button, he wanted to save the user's work permanently. The solution I suggested added some code to his action method to analyze the data from the form and decide which processing to execute.

That's not a bad solution, provided you make two assumptions. The first assumption is that the logic necessary to call the right processing occurs in only one place (as was my reader's case). Second, the page is relatively simple -- my reader's sample page only supported two options, which had lots of overlap between the two sets of processing. The action method I suggested adding some code to wasn't going to be much more complicated after my changes.

But it's not hard to imagine more complex pages that make more demands on your controller's action methods. In that scenario, the logic to select the right processing might be very complicated, dominating the business-related processing in the method. Furthermore, a single action method that handles a complex page will itself be complex and, as a result, hard to maintain or extend.

As a side issue, it's also not hard to imagine that the logic to decide which processing to invoke might be needed in several places in the application (or even in multiple applications). If you're concerned about any of those issues, you'd want to find a way to simplify your method and, ideally, centralize and reuse the logic that selects the right processing.

Good news! ASP.NET has a way to address all of those concerns: the ActionMethodSelector attribute, which can be added to any action method to specify additional conditions that control when to execute the method. Effectively, an ActionMethodSelector allows you to remove the logic that selects the right processing from your action methods (and also use that logic in other places, if that makes sense). As a side benefit, instead of having a single action method performing multiple activities, you end up with multiple Action methods, each supporting a single processing option on the page.

Setting Up the Project
When designing an ActionMethodSelector, you'll want it to be sufficiently generalized so it can be used in multiple projects. Therefore, it makes sense to create your ActionMethodSelector attribute in a separate class library project from your project. You can then add your class library as a reference to any project where you need it. (I call my project PHVAttributes.) To use the code in this column, you'll need to add references to both System.Web and System.Web.Mvc to your attribute project.

The first step in creating your ActionMethodSelector attribute is to create a class that inherits from the .NET ActionMethodSelectorAttribute (this is the class that requires the System.Web reference). You can give your class any name you want, but it's a convention for attribute names to end with the word "Attribute." Within your class you must override the IsValidForRequest method in the ActionMethodSelectorAttribute class from which you inherit:

Public Class ActionMethodButtonSelectorAttribute
  Inherits ActionMethodSelectorAttribute

  Public Overrides Function IsValidForRequest(controllerContext As Web.Mvc.ControllerContext,
                                      methodInfo As Reflection.MethodInfo) As Boolean

  End Function
End Class

If the method your attribute is decorating is selected by ASP.NET, then ASP.NET will automatically call your attribute's IsValidForRequest method. In your IsValidForRequest method you check for whatever conditions you want to add to the normal process for selecting an Action method in ASP.NET. If your method returns True, the action method your attribute is decorating will be executed (no other attribute on the method standing in the way, of course); if your method returns False, ASP.NET will go looking for another Action method to execute (and, of course, may not find one, causing ASP.NET to return an error code).

For your ActionMethodSelector to be useful, therefore, you must have two or more action methods in your controller that look the same to ASP.NET -- methods that ASP.NET can't choose between without your attribute.

A Distinction That (Usually) Makes No Distinction
This brings up an interesting point in ASP.NET MVC of which many developers aren't consciously aware: Actions do not equal methods. You can think of Action names as the names you specify in your routing rules. Method names, on the other hand, are the names you define when you write your methods.

Typically, the distinction between Actions and methods isn't important: A controller usually has exactly one method for each Action. ASP.NET finds the appropriate method because the method has the same name as the Action … which, of course, leads to developers thinking of Actions being the same as methods.

The most frequent exception to this practice is when a developer creates two methods with the same name as an Action, but decorates one method with the HttpPost attribute and the other method with the HttpGet attribute. The following code shows two methods (a Get and a Post version) for a single Action called ProcessCustomer:

<HttpGet>
Public Function ProcessCustomer() As ActionResult
  Return View("DisplayCustomer")
End Function

<HttpPost>
Public Function ProcessCustomer(cust As Customer) As ActionResult
  Return View("UpdateSuccessful")
End Function

With this code, one method is used when the browser makes a Get request, the other method is used when the browser Posts data back to the server -- but there's still only one Action (ProcessCustomer). The .NET Framework, of course, doesn't mind there being two methods with the same name because the two methods have different signatures (one method accepts a parameter and the other method does not).

Usually, developers simply count on Actions and their corresponding methods having the same name, meaning that the Actions in your URLs are very tightly coupled to their implementations in your controller's methods. Again, most of the time this doesn't matter: It's very unusual to change either Action or method names (though, when you do change an Action name, you'll break any links to your site).

However, you don't need to have matching method and Action names: You can give your method any name you like and assign it to an Action by using the Action attribute. This code assigns two different methods to the same Action (in this case, "Index"):

<HttpPost>
<ActionName("Index")>
Public SaveWork(cust As Customer) As ActionResult
'...processing...
End Function

<HttpPost>
<ActionName("Index")>
Public CompleteWork(cust As Customer) As ActionResult
'...processing...
End Function

By adding the ActionName attribute to every action method you could vary your method names freely without disconnecting them from their actions. I'm not suggesting you implement this as a best practice, by the way -- it's a lot of work to eliminate a problem that doesn't occur very often. However, by using the Action attribute I now have two methods that ASP.NET can't distinguish between because both methods are tied to the Index Action and they're both to be used on a Post. To choose between them, I'll need an ActionMethodSelector.

Implementing a Button Selector
The key issue in your ActionMethodSelector is how you'll decide in the attribute's IsValidForRequest method if the method the attribute is decorating should be executed. To help you make that decision, the IsValidForRequest method is passed one of the ubiquitous ASP.NET context objects -- a ContollerContext object, in this case. Like all of the ASP.NET context objects, a ControllerContext has numerous properties giving you access to a wide variety of information about the request that ASP.NET is processing currently (the request that's looking for an Action method to execute). Also passed to the IsValidForRequest method is a MethodInfo object containing information about the Action method that the attribute is decorating.

To apply this solution to my reader's problem, I want to check to see if a particular Button has been sent from the browser to the server and choose which Action method to execute based on that result. When a user clicks a Submit button in an HTML Form, the button's name and text (caption) are sent as a name/value pair to the server, along with the rest of the data on the page, No other Submit button on the page is sent. This means that I can detect which button was clicked in the browser by checking to see if the button's name has been sent from the browser. My best choice for checking for the button name is the Request object's Form collection, which holds all the name/value pairs sent from the browser. And (more good news) the Request object is available from the Request property on the ControllerContext object passed to your method.

Therefore, I just need to check to see if a particular button's name is present in the Form collection in my code. One solution is to establish a naming convention for different kinds of buttons and hardcode that naming convention into my attribute. As one example of that, I could return True when the Request object's Form collection contains a name that matches the name of the Action method to which the attribute is attached.

The following sample form has two methods with their name attributes set to the names of action methods in my controller:

@Html.BeginForm()
  ...rest of form...
  <input type="submit" name="SaveWork" id="ButtonSave" value="Save Your Work" />
  <input type="submit" name="CompleteWork" id="ButtonFinish" value="Complete" />
</form>

The next step is to create an ActionMethodSelector that will tie these buttons to the appropriate action method. The following code uses the Name attribute on the MethodInfo object (this will be the name of the Action method your attribute is decorating) to retrieve a value from the Request object's Form collection:

Public Class ActionMethodButtonSelectorAttribute
  Inherits ActionMethodSelectorAttribute

  Public Overrides Function IsValidForRequest(…
    If controllerContext.HttpContext.Request.Form(methodInfo.Name) IsNot Nothing Then
      Return True
    Else
      Return False
    End If

If the Form collection contains a name/value pair with the same name as the method, the Form collection will return the value assigned to the name in the browser. In this case, I don't care what the value is (I just want to know the value is present, indicating the button was clicked) so I just check to see that the result isn't Nothing to return True.

Listing 1 shows my attribute applied to my two otherwise indistinguishable Action methods.

Listing 1: Using an ActionMethodSelector to Choose Between Action Methods
<HttpPost>
<ActionName("Index")>
<ActionMethodButtonSelector>
Public SaveWork(cust As Customer) As ActionResult
  '...processing...
End Function

<HttpPost>
<ActionName("Index")>
<ActionMethodButtonSelector>
Public CompleteWork(cust As Customer) As ActionResult
  '...processing...
End Function

Enhancing the Solution
There's at least one disadvantage to creating an ActionMethodSelector attribute rather than putting the code in the action method: The logic for selecting action methods is now hidden inside the attribute rather than available in the action method. Without reading the documentation about the attribute, it's not clear how a developer who didn't have access to its code would know how my ActionMethodButtonSelector attribute works. And, even if the developer did know how the attribute worked, the developer might not appreciate the naming convention the attribute requires.

A more flexible solution is to allow the developer using the attribute to specify the button name for which to look. You can do that by adding a constructor to your attribute that accepts whatever parameters you're willing to allow the developer to specify. Listing 2 shows a version of my ActionMethodButtonSelector attribute that allows the developer to specify the button name.

Listing 2: An Action Selector Driven by HTML Attributes
Public Class ActionMethodButtonSelector
  Inherits ActionMethodSelectorAttribute

  Private ButtonName As String

  Public Sub New(ButtonName As String)
    Me.ButtonName = ButtonName
  End Sub

  Public Overrides Function IsValidForRequest(…
    If controllerContext.HttpContext.Request.Form(ButtonName) IsNot Nothing Then
      Return True
    Else
      Return False
    End If
  End Function
End Class

The IntelliSense support for the constructor will now prompt the developer for a ButtonName, giving the developer some clue about how this attribute works. My controller now looks like Listing 3.

Listing 3: Using a Better ActionMethodSelector to Choose Between Action Methods
<HttpPost>
<ActionName("Index")>
<ActionMethodButtonSelector("SaveWork")>
Public SaveWork(cust As Customer) As ActionResult
  '...processing...
End Function

<HttpPost>
<ActionName("Index")>
<ActionMethodButtonSelector("CompleteWork">
Public CompleteWork(cust As Customer) As ActionResult
  '...processing...
End Function

In all of these cases, the logic for selecting between methods has been pretty simplistic. However, the combination of context information, method information, parameters provided by the developer, and your own procedural code should allow you to support any set of complex decision-making code you could need -- and make your action methods shorter and more focussed, as well.

To put it another way: There's no reason for your action methods to be as complex as the page they support.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube