Custom Controllers and Custom Action Result Types - By Narendra Shrestha
Every request we send to the MVC framework are attended and acknowledged by the controllers. With every arriving request, controllers select corresponding action. Controller is responsible for processing these requests by rendering suitable view and performing operation on the domain and UI data model.
Every request we send to the MVC framework are attended and acknowledged by the controllers. With every arriving request, controllers select corresponding action. Controller is responsible for processing these requests by rendering suitable view and performing operation on the domain and UI data model.
To
fully qualify as a controller, the controller class should derive from “Controller” class located in the name space “namespace
System.Web.Mvc”. Inside “Controller” class, several
other helper methods exists such as “PartialViewResult
PartialView(),JsonResult Json(object data) etc.”. These are familiar helper methods and need no
explanation. It should be apparent now to all of us why we are able to perform
statements like “return View()” in action method without even
bothering about namespace. Don’t confuse the term helper methods with
extensible helper methods.
If
you go down further and explore, you can see that “Controller”
class is derived from “ControllerBase” and it also implements
several other interfaces. “ControllerBase” in turn further implements “IController”.
When we implement this interface in a class, MVC framework recognizes that
class as one of the valid controller. The skeletal code for “IController” is listed as below.
public interface IController
{
// Summary:
// Executes the specified request context.
//
//
Parameters:
// requestContext:
// The request context.
void
Execute(RequestContext requestContext);
}
Listing 1:-Skeletal
code for interface “IController”
It
all rest upon the “IController” interface to customize
the controller. The default controller available in the MVC framework is a
complete package. However, some crude developers who like everything inside their
territory rather seem to avoid this short-cut. To develop something from
scratch, we need to be certain in so many things. Most common issue that will
circulate in your own implementation includes:-
Testability
Your controller should be unit
testable. Higher flexibility comes with less complexity while mocking any
classes or methods
Reliability
Your controller should receive
full confidence from your side and from your team.
Easiness in
implementation
Your controller should be
easily usable and extensible. Developers with lower programming experiences
should have no problem interpreting your methods.
Avoid confusion
Your controller’s method names
and signature should be meaningful and shouldn’t clash with the names provided
by native framework. For example in MVC “View(“MyView”)” renders view called
“MyView”. Whereas, you shouldn’t devise something like “View(“StringValue”)”
that renders a partial view and takes string value as the strongly type model
value. The practice should be consistent.
Let
us move to an example to follow up the customization feature of the
controllers. In order to add your custom controllers, right click the
“controllers” folder and select Add>Class…
While
naming your class make sure that you append “Controller” to your class name.
This is the general naming convention for the controllers. The following
listing is for the “BasicController.cs”.
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using
System.Web.Routing;
using System.Net;
using System.IO;
using System.Text;
namespace
ActionController.Controllers
{
public class BasicController
: IController
{
#region IController Members
public void Execute(RequestContext
requestContext)
{
string
actionName=requestContext.RouteData.Values["action"].ToString().ToLower();
switch(actionName)
{
case
"share":
requestContext.HttpContext.Server.Execute("~/Share.htm");
break;
default:
requestContext.HttpContext.Response.Write("<h2>action
not yet available</h2>");
break;
}
}
#endregion
}
}
Listing 2:- “BasicController.cs”
Within “Execute” method which receives
parameter of type “RequestContext”, we extracted the value of the action from the route data route value
dictionary. Based on that action name, the request is targeted towards the
cases of “switch” statement. Upper Case and
lower case independency is achieved by changing the entire action name to the
lower case. For “share” action, we render
the page “Share.htm”. Otherwise, we respond by informing that action is not
available yet.
The code for the
page “Share.htm” is as listed below which is a pure html page. This page is
situated right inside the project root folder.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Share</title>
</head>
<body>
<iframe id="iframe_Share" src="http://www.nepalsharemarket.com/nepalsharemarket/nepse/analysis/Today.aspx"
scrolling="no"
style="height: 700px; border: 0px none; width: 776px; margin-top: -192px; margin-left: -171px;
">
<p>
Your browser does not support
iframes.
</p>
</iframe>
<div id="my"></div>
</body>
</html>
Listing 2: “Share.htm”
This page does nothing more than displaying the share
market affair of Nepalese banks from the website
We only display the list of banks by
carefully concealing other parts of the page through CSS styling.
When our solution is run, we get the
following outputs.
- When we redirect to URL “/basic/share”
Figure 1: When redirecting to URL “/basic/share”, file
“Share.htm” is rendered
- When redirecting to any “/basic/{action}” type URL other than “/basic/share”
Figure 2:-When redirecting to URL “/basic/anyaction”
Not only controllers can be
customized, the action return types can also be customized. Thanks to the
flexibility of the MVC. This can be done by deriving our custom return type
class from “ActionResult”. We generally derive it from the class “ActionResult”. When we
need more specific approach, only then we will derive it from other specific
action return types such as “ViewResult”, “FileContentResult” etc.
Since all the return types are derived from “ActionResult”, there is no harm in sticking to the more general type. In our
implementation, we created a return type called “JpegImageResult” class inside infrastructure folder. Like mentioned in our previous
threads, infrastructure folder is where our customizing code goes. We didn’t do
so during customization of controllers because MVC assumes controllers to be
inside “Controllers” folder. This is the advantage we achieve of naming
convention of the controllers.
When MVC framework receives an “ActionResult” object from an action method, it calls the “ExecuteResult” method within “ActionResult”. This method accepts
a parameter of type “ControllerContext”. As a result, we get access to various classes such as “HttpContext”,” RouteData”, “RequestContext” etc. “ActionResult” object deals with request by generating the output which you desire. The
following code shows the “JpegImageResult” class for our case.
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using System.IO;
namespace
ActionController.Infrastructure
{
public class JpegImageResult
: ActionResult
{
public HttpPostedFileBase image { get; set; }
public override void
ExecuteResult(ControllerContext context)
{
if
(image != null && image.ContentType == "image/jpeg")
{
byte[]
buffer = new byte[image.ContentLength];
image.InputStream.Read(buffer,
0, image.ContentLength);
context.HttpContext.Response.ContentType = image.ContentType;
context.HttpContext.Response.BinaryWrite(buffer);
}
else
{
context.HttpContext.Response.Write("<h2>invalid
jpeg image</h2>");
}
}
}
}
Listing 3:-“JpegImageResult.cs”
The purpose of “JpegImageResult” class is to return the image if the uploaded/posted image file is a valid JPEG image. We checked whether the posted images file of type “HttpPostedFileBase” is a JPEG image or
something else. When we receive a valid image, we save that image in a buffer
array of type “byte[]”. After specifying the image content type, we rewrite the
image back by using “BinaryWrite” method. In case of
invalid image or other file type, we respond by informing about the invalid JPEG image. You must mention the image content type while rewriting it back to
the browser. Just for sake of fun and learning, you can skip this statement and
witness the strange output.
In order to be able to use the above functionality, change your home
controller to the code listed as below.
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using
ActionController.Infrastructure;
namespace
ActionController.Controllers
{
public class HomeController
: Controller
{
//
// GET:
/Home/
[HttpGet]
public ActionResult Index()
{
return
View();
}
[HttpPost]
public JpegImageResult Index(HttpPostedFileBase
myImage)
{
return
new JpegImageResult
{ image = myImage };
}
}
}
Listing 4:-“HomeController.cs”
Inside the home controller, there is an action called “Index” that receives
no parameter. It generates the view called “Index.cshtml” for the GET request
of the index action.
Whereas, another action “Index” for the POST request receives “HttpPostedFileBase” parameter from file
upload html-controller in the view “Index.cshtml”. We then instantiate “JpegImageResult” object, by setting “image” property of “JpegImageResult” with the value of posted file “myImage”.
The return type of our action “Index” for POST request is of type “JpegImageResult”, i.e. it leaves up to “JpegImageResult” class “ExecuteResult” to generate the output.
Only thing that remains is the view “Index.cshtml” whose code is as listed
below.
@model ActionController.Infrastructure.JpegImageResult
@{
ViewBag.Title = "Index";
}
<h2>
Choose a file</h2>
@using (Html.BeginForm("Index",
"Home", FormMethod.Post,
new { enctype = "multipart/form-data"
}))
{
<input type="file" name="myImage" />
<br />
<input type="submit" name="upload" value="upload" />
}
Listing 5:-“Index.cshtml”
When posting back files, we must use the overload of Html helper method “BeginForm”. We specified the name of the controller, action and form method. The
last parameter is an anonymous object where we have set anonymous object property
“enctype” as “multipart/form-data”. This declaration of anonymous type is mandatory while posting back a
file and to be recognized as a file.
When we run the project and redirecting to “/Home/Index” URL, we can see
the following output
Figure 3:-“Index.cshtml”
Browse and select any “JPEG” image file and click on upload. We can
see output as below:-
Figure 4:-after selecting the image and clicking on upload
In case you have selected any file except “JPEG” image following output
will be displayed
Figure 5:- Invalid file selection and upload
We always have many choices while working with MVC framework. Likewise we
can happily be satisfied with default functionality or may take a sharp turn
and implement something from the scratch. While doing so we must always take in
account of previously discussed common issues. These common issues when
considered seriously can bring up standard and bug free application. These
common issues apply not only to controllers but to any other customized classes
for MVC implementation. When we strictly adhere to these common issues, we can
come up with something productive i.e. testable, reusable, extensible and maintainable.
This is the power felt in the flexibility of the MVC framework.
No comments:
Post a Comment