Sunday, February 2, 2014

Custom Controller and Custom Action Result Types



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.

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