Action Invoker
Action Invoker’s role is to call suitable action
depending upon the request directed by the controller. Controller consists of
different actions with different return types. There may be two or more
overloads of same actions inside the controller. The nature of those actions
may be different. Some actions are targeted for the GET request and some for
the POST request. Some actions may be disqualified as a valid action. It’s duty
of the action invoker to look after all these aspects before diverting the action
call. We gave little introduction about Action Invoker in “Controller Factory
and Action Invoker Part 1”. Now we discuss about the extensibility and
customizability of the action invoker.
Custom Action Invoker
Custom action invokers are rarely used unless we are
writing our own rules. Sometimes afar from other native request, we may come up
with our own action selector. Action selectors are attributes applied to any
action. These selectors convey special kind of desired behavior such as GET,
POST or PUT. We can customize action selector for our own purpose.
For example let us assume that there are two way to
validate the price of the purchased item. A store keeper can directly input the
price of the goods or he/she may use bar code reader. We can imagine that we
have two actions based on the nature of input. A store keeper’s typed input can
simply use POST request assisted action. However, for barcode reader
integration, we need a separate custom action selector. The action for barcode
reader contains the nifty codes to decode the price from an input image. Action
invoker is the exact location, where we can find smooth fitting for such codes.
Based on the nature of request, we can call correct override from the available
action name. However, for pure custom action invoker their perspective of implementation
is very hard to pin point.
To be useful as a custom action invoker, our
custom class must implement the interface “IActionInvoker”. This interface is responsible for invoking
the action rather than relying on the default configuration. The skeletal code
for this interface is as listed below:-
using System;
namespace
System.Web.Mvc
{
// Summary:
// Defines the contract for an action
invoker, which is used to invoke an action
// in response to an HTTP request.
public interface IActionInvoker
{
// Summary:
// Invokes the specified action by using the
specified controller context.
//
//
Parameters:
// controllerContext:
// The controller context.
//
// actionName:
// The name of the action.
//
// Returns:
// true if the action was found; otherwise,
false.
bool
InvokeAction(ControllerContext
controllerContext, string actionName);
}
}
Listing 1:- Skeletal code for the interface “IActionInvoker”
This interface defines a method named “InvokeAction” which is responsible for invoking the
custom behavior. The first parameter to this method is “controllerContext” of type “ControllerContext” which gives us access over the current or
requesting controller context. We can easily access controller specific
property through this parameter. The second parameter is “actionName” of type “string” which represents the name of the requested
action. This value can alternatively be obtained from the route value
dictionary through route data with the help of requesting controller context.
The return type of this method is boolean type. True value indicates that the
request has been processed. False value indicates the inability to process the
request.
We are going to use custom action invoker to
imitate the POST and GET request. In GET assisted request, we are rendering a
form which prompts for user input. In POST assisted request, we are addressing
a hello message to the users along with their entered inputs. We could have
done this just by using “[HttpPost]” and “[HttpGet]” attribute with the action methods, and
rendering a suitable view for every user request.
To proceed with the example of custom action
invoker, we create a class “MyActionInvoker” inside the “infrastructure” folder of the MVC
project. We implement interface “IActionInvoker” for this class. Inside the “InvokeAction”, we check for the request type and the
action name. Depending upon the request type and the action name, suitable
action is carried out. The code below lists out the “MyActionInvoker” class.
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using
ControllerExtensibility.Models;
using System.Text;
namespace
ControllerExtensibility.Infrastructure
{
public class MyActionInvoker
: IActionInvoker
{
#region IActionInvoker Members
public bool InvokeAction(ControllerContext
controllerContext, string actionName)
{
if
(controllerContext.HttpContext.Request.RequestType == "GET"
&& actionName == "item")
{
StringBuilder
view = new StringBuilder();
view.Append("<script type='text/javascript'
src='/Scripts/jquery-1.5.1.min.js'></script>");
view.Append("<form id='form' method='post' action='/test/item'>");
view.Append("<script type='text/javascript'>");
view.Append("$(document).ready(function () {");
view.Append("$('#submit').click(function () {");
view.Append("var name; var attribs;");
view.Append("name=$('#name').val();");
view.Append("attribs=$('#form').attr('action');");
view.Append("attribs=attribs+'?name='+name;");
view.Append("$('#form').attr('action',attribs);");
view.Append(" })");
view.Append(" })");
view.Append("</script>");
view.Append("<p>Your Name:</p>");
view.Append("<input id='name' type='text' value=''
name='name' id='name'>");
view.Append("<input type='submit' value='submit'
name='submit' id='submit'></form>");
controllerContext.HttpContext.Response.Write(view.ToString());
return
true;
}
else if
(controllerContext.HttpContext.Request.RequestType == "POST"
&& actionName == "item")
{
string
name=string.Empty;
if
(controllerContext.HttpContext.Request.QueryString["name"]
!= null)
{
name =
controllerContext.HttpContext.Request.QueryString["name"];
}
controllerContext.HttpContext.Response.Write("<p>Hello:<b>"
+ name + "</b></p>");
return
true;
}
else
{
return false;
}
}
#endregion
}
}
Listing 2:-“ MyActionInvoker” class
We checked the request type by the help of
HTTP context available through the Controller Context. For the “GET” request
which targets the “item” action, we rendered an HTML page. The html was
constructed by help of .net class “StringBuilder”. We have appended both JQuery/Javascript
and HTML code within the “StringBuilder” instance. By JQuery/javascript, we extracted an attribute “action” of the
“form” tag. To this attribute value, we appended the value entered inside the
textbox as a query string. All this is carried out on the click event of the
“submit” button. After successful completion of client event, form is submitted
along with updated value of the “action” attribute. Frankly, this is not an
appropriate way to code as you will observe compile time bug only during run
time. At compile time our hectic code is nothing but a string value. During the
“POST” request for the “item” action, we checked for query string “name”. The
“name” value which was submitted in the previous request is rendered here.
Similar to previous case, we rendered HTML by outputting the HTML content
directly from HTTP Context response’s write method.
To take care of these actions, we add a “Test” controller (regular MVC
controller). Below code lists the test controller.
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using
ControllerExtensibility.Infrastructure;
using
ControllerExtensibility.Models;
using
System.Web.Routing;
namespace
ControllerExtensibility.Controllers
{
public class TestController
: Controller
{
//
// GET: /Test/
public
TestController()
{
this.ActionInvoker
= new MyActionInvoker();
}
}
}
Listing 3:-“Test” controller
During the instantiation of the “Test” controller i.e. inside the “Test”
controller constructor, we set the “ActionInvoker” property of this controller to the
newly created instance of “MyActionInvoker”.
When we run the
project at this point, we can see the following outputs
Figure 1:- redirecting to the URL
“/test/item”
We are accessing the “GET” request portion of
custom action invoker when we redirect to this particular URL “/test/item”.
When we fill up the form
and click of submit/press enter,
Figure 2:- Upon submitting the form
Default Action Invoker
If we had directly used “GET” and “POST”
attributes along with action methods and views in the test controller, then it
would be one kind of default implementation. We are using this default action
invoker knowingly or unknowingly. For insight knowledge about functionality of
the default action invoker, we will explore default custom action invoker. To
make our class as a default implementation of the action invoker, we must
derive it from “ControllerActionInvoker”. “ControllerActionInvoker” is the action invoker used by every regular
MVC controller. If we check the definition of this default class, we can see
numerous virtual functions. We can override them with our own custom logics. “ControllerActionInvoker” implements “IActionInvoker” which is absolutely obvious.
With the help of default action invoker, we
are going to replicate above example of custom action invoker. We are going to
implement views instead of writing the HTML content directly. This makes our HTML
code manageable, easily interpretable, and maintainable.
While deriving from a class and overriding any
function, we must provide fall back support for other methods which are beyond
our interest. Whereas for “POST” and “GET” request for “item” action, we
implement our own logic. For this purpose we are overriding the method “InvokeAction”. We have already dealt with this method in
custom action invoker of type “IActionInvoker”. The return type and parameters of this method
has already been discussed in the custom action invoker segment.
We create class “MyDefaultActionInvoker” within the “infrastructure” folder of our application. “MyDefaultActionInvoker” class is derived from “ControllerActionInvoker”. The code listed below shows the same:-
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using
System.Web.UI.WebControls;
namespace
ControllerExtensibility.Infrastructure
{
public class MyDefaultActionInvoker
: ControllerActionInvoker
{
public override bool
InvokeAction(System.Web.Mvc.ControllerContext
controllerContext, string actionName)
{
if
(controllerContext.HttpContext.Request.RequestType == "GET"
&& actionName == "item")
{
ViewResult
viewResult = new ViewResult();
viewResult.View =
viewResult.ViewEngineCollection.FindView(controllerContext, "ItemGet", null).View;
InvokeActionResult(controllerContext, viewResult);
return
true;
}
else
if
(controllerContext.HttpContext.Request.RequestType == "POST"
&& actionName == "item")
{
ViewResult
viewResult = new ViewResult();
viewResult.View = viewResult.ViewEngineCollection.FindView(controllerContext,
"ItemPost", null).View;
InvokeActionResult(controllerContext, viewResult);
return
true;
}
else
{
return
base.InvokeAction(controllerContext,
actionName);
}
}
}
}
Listing 4:-“ MyDefaultActionInvoker” class
Within the “InvokeAction”, as in previous
case, we’ve split the logic into two parts for “GET” and “POST” request type
for the action “item”. An instance of type “ViewResult” is created in both portions. The view property of this instant is set to
the view property of the result returned by “FindView” method. This find view method searches over the collection of the view
from “ViewEngineCollection”. This collection
comprises of all the views of our application. The parameters to “FindView” are the current controller context, the name of the view and the name of
the master page. For the “GET” request we search for the view named “ItemGet.cshtml”
and for the “POST” request we search for the view named “ItemPost.cshtml”. These views are set as “ActionResult” (“ViewResult” are derived from “ActionResult”) by calling
method “InvokeActionResult”. The controller context
and the view result instance are passed as parameters to this method. If the
request is for any action other than “item”, we will call the base class method
“InvokeAction”. This way we will be supporting fall back architecture.
We make minor change in the “test” controller i.e. by updating reference to
action invoker to “MyDefaultActionInvoker” class instance. The code for the “test” controller is as listed below:-
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using
ControllerExtensibility.Infrastructure;
using
ControllerExtensibility.Models;
using
System.Web.Routing;
namespace
ControllerExtensibility.Controllers
{
public class TestController
: Controller
{
//
// GET: /Test/
public
TestController()
{
this.ActionInvoker
= new MyDefaultActionInvoker();
}
}
}
Listing 5:- “Test” controller
The newly added views “ItemGet.cshtml” and “ItemPost.cshtml” in
“/views/test” path is as listed below:-
@{
ViewBag.Title = "ItemGet";
}
<script type="text/javascript">
$(document).ready(function
() {
$("#submit").click(function () {
var
name;
var
attribs;
name = $('#name').val();
attribs = $('#form').attr('action');
attribs = attribs + '?name=' + name;
$('#form').attr('action', attribs);
})
})
</script>
@using (Html.BeginForm("item",
"test", FormMethod.Post,
new { id = "form"
}))
{
<p>
Your Name:</p>
<input id='name' type='text' value='' name='name' id='name' />
<input type='submit' value='submit' name='submit' id='submit' />
}
Listing 6:-“ItemGet.cshtml”
@{
ViewBag.Title = "ItemPost";
}
@{string name = string.Empty;}
@if (HttpContext.Current.Request.QueryString["name"] != null)
{
name = HttpContext.Current.Request.QueryString["name"].ToString();
}
<p>Hello:<b>@name</b>
Listing 7:-“ItemPost.cshtml”
The HTML code in
“ItemGet.cshtml” is proper than the preceding one in the custom action invoker
class. JQuery/Javascript is properly placed along with use of razor “Html”
helper methods.
When we run the
project, we can see the following outputs
Figure 3:- Redirecting to URL “/test/item”
Figure 4:- After submitting or posting the request
The both outputs in the custom and the default implementation looks
similar. What we need to put inside our head is that for custom implementation,
everything is hand written. While for default implementation, a view is used
instead. Nobody can tell the difference by only observing the screenshots. Even
the URL is same. Everything remains unchanged except the internal structure.
This is the flexibility of the MVC framework. We can get the job done without
letting others know about the changes we made. No can tell that we switched from
the custom action invoker to the default action invoker. We have maintained the
integrity of our application throughout the project. Only we know how much
vital is our change, but to the outside world they don’t feel the difference.
Discipline of the software engineering/development is to settle on the fact
what other assumes us to be and still be able to keep our achievement to
ourselves. We are rather happy and better off with clients who does feasibility
research on our behalf.