Wednesday, December 23, 2015

Microsoft 70-486: Control application behavior by using MVC extensibility points

Exam Objectives


Implement MVC filters and controller factories; control application behavior by using action results, view engines, model binders, and route handlers

Quick Overview of Training Materials


I beat filters and action results to death in a previous post for Objective 3.3


Extensibility in the MVC pipeline


To really understand the gist of this objective, you first have to understand the overall MVC pipeline. The graphic from the Microsoft ASP.NET blog is really illuminating in this regard.  Not only does it cover the lifecycle of a request in great detail, but discusses the points along the pipeline where you may want to make changes.   Simon Chiaretta's blog on extensibility and accompanying chart is a bit dated but is also an excellent source for understanding the flow of a request through the MVC framework.  I covered a little bit of this flow in my post about modules and handlers, but here we'll need a much finer understanding of what is happening.

The two documents have some differences, in what they include, the level of granularity, order (particularly when model binding and auth occur), etc.  At a very high level, Simon's flow divides the process up like this:

  • Routing
    • sent to MvcHandler by default.  Using a custom handler bounces you out of MVC process flow.
  • Controller Creation
    • uses DefaultControllerFactory
  • Action Execution
    • ActionInvoker chooses action to invoke based on ActionNameSelectorAttribute and ActionMethodSelectorAttribute.  Can use custom ActionInvoker, which will remove you from MVC flow.
    • Model binding
    • Authorization filters
    • If authorized, action parameters populated and action executed
  • Result Execution
    • Result filters executed before and/or after view rendering
    • ViewResult creates HTML with various helper and validation classes
    • Response returned to browser

The ASP.NET diagram follows along these lines (I've noted the extensibility points, some of which are owing to Simon's diagram and blog... just full disclosure):

  • HttpRequest received by application, starts making it's way through various modules
  • The UrlRouting module resolves the route and chooses an IRouteHandler. By default this is MvcHandler, but can be a custom handler. This is the route handler extensibility point.
  • Controller creation.  MvcHandler uses the DefaultControllerFactory if a custom controller factory hasn't been registered as part of Application_Start in the Global.asax.cs file. This is the controller factory extensibility point.  
  • Invoke Authentication and Authorization filters.  If these fail, invokes an authentication challenge.
  • Model binding.  This can be customized by inheriting and overriding the DefaultModelBinder class or implementing the IModelBinder interface.  This is the modelbinder extensibility point.
  • Invoke actions with action filters.  If applicable, may also invoke authentication challenges.
  • Execute result with result filters.
    • Probes for view engine.  Existing view engines can be customized though inheritance and overrides, or by implementing the IViewEngine interface. This is the viewengine extensibility point.
    • View and PartialView action results are rendered by view engine. Other action results write directly to the HTTP response.
  • Dispose of controller and return response.

My interpretation of the flow of a request through the MVC pipeline, which is probably grossly oversimplified...

Simon's blog categorizes the various extensibility points based on the likely frequency they'll be required.  Templates, validation rules, and HTML Helpers are common points of extensibility; base controllers, AJAX/URL helpers, and Action/Result filters are less common but powerful productivity enhancers; routing, model binding and action result based extensibility is very case specific; and finally, changes to core MVC functionality like the controller factory, view engine, action invoker, and validation and metadata providers are very unlikely to be necessary.


Authorization Filters and Exception Filters


 My post on Objection 3.3 covered Action and Result filters, so I figured it would touch on Authorization and Exception filters here to round out my discussion of filters.  It's worth noting that MVC 5 includes Authentication filters, which execute before Authorization filters in the pipeline.  Both auth filters run before action execution.  Chapter 15 in the Professional MVC 5 book covers filters well, especially authentication filters.

Authentication filters allow more granular control of how authentication is configured.  While in many cases it's fine for authentication to be configured site wide (that is, there is exactly one way to authenticate with the application), this can create problems if one particular part of the site needs to authenticate differently.  A perfect example of this is authentication workflows using oauth2.

In an oauth2 flow, the user is redirected to the oauth provider (in my application it was Google but there are many other providers), along with a return url.  If the user grants the application access to the requested scopes (email, docs, whatever), they are redirected back to the return url along with an access_token, which is a bearer token.  In my application, the return url was a controller action that called the provider api for validating access tokens, parsing the email address, and either creating a Forms profile or logging the user in.

Had we wanted to provide a mixed authentication environment, where the parts of the application that required Google authentication used an oauth access token but the bulk of the site used a normal Forms authentication login, authentication filters would have been a viable option.

The Pro MVC 5 book emphasizes the point that authentication and authorization are not the same thing.  The example they use is a homepage that displays different information based on whether the user is authenticated.  Viewing the site does not require you to be logged in (anonymous, i.e. unauthenticated users can see the page), but the content is tailored to an authenticated user (might be as simple as "Hello, User" vs "Login", or could be custom content feeds, whatever).  Authentication is a necessary prerequisite to authorization.  If only certain people can see certain content, the system must know who you are before it can determine if you are permitted to access the resource.  Authentication is proving who you are, authorization is what permissions you are granted.

Authorization Filters run after authentication filters.  These include [Authorize], [ChildActionOnly], [RequireHttps], [ValidateAntiForgeryToken], and [ValidateInput].  If some set of preconditions needs to be met before entering the Action stage of the request life cycle, an authorization filter is one mechanism to do that.

Exception filters are pretty much just what they sound like.  They handle exceptions.  Logging, notifications, and redirection to an error page are all possible actions an exception filter might take.


Creating a custom controller factory


In situations where you need control over the way controllers are created, it is possible to create a custom controller factory to accomplish this.  Both the exam ref and the extensibility intro blog mention dependency injection and IoC as cases where creating a customized controller factory is warranted.  Simon's post lumps this under "core extensions", so it's not likely to be something you will need to do very often.  The exam ref glosses over the subject, noting only that you need to implement IControllerFactory and register it in Global.asax in Application_Start().  An example is fleshed out in the Custom Controller Factory in ASP.NET MVC article, which adds logger functionality.  It's not a bad walkthrough but it needed a couple tweaks.

First off, it jumps right in assuming you have a database to work with.  The quickest way to add one to a fresh MVC5 project is to select the "App_Data" folder, select "New Item" under the right click menu, and add a "Sql Database", which will create a localdb for you to work with (make sure you name it better than I did, yeesh...):


It should show up under Data Connections in Server Explorer.  Right click it, select "New Query", and run his schema creation query (minus the using statement):

The query and the resulting table in the database
Beyond that, it's mostly just a matter of cut and paste and making sure the namespaces all jive (depending of how closely you follow his example).  There were a couple small gotchas I ran in to.  The first was this code in the LoggerInfo.cs file:

/// <summary>
/// The class for Loggin the Request Information into the database usign ADO.NET EF
/// </summary>
public class RequestLogger : IRequestLogger
{
    Database1Entities1 objContext;
    public RequestLogger()
    {
        objContext = new Database1Entities1();
    }
 
    public void RecordLog(LoggerInformation logInfo)
    {
        objContext.AddToLoggerInformations(logInfo);
        objContext.SaveChanges();
    }
}

The problem here is that "AddToLoggerInformations()" is not defined.  So I just changed it to this:

/// <summary>
/// The class for Loggin the Request Information into the database usign ADO.NET EF
/// </summary>
public class RequestLogger : IRequestLogger
{
    Database1Entities1 objContext;
    public RequestLogger()
    {
        objContext = new Database1Entities1();
    }
 
    public void RecordLog(LoggerInformation logInfo)
    {
        objContext.LoggerInformations.Add(logInfo);
        objContext.SaveChanges();
    }
}

Also, since he isn't defining any views (at least not in the blog post), navigating to the EmployeeInfo controller will throw a "View not found" exception, so I changed it to return Json instead:

//
// GET: /EmployeeInfo/
public JsonResult Index()
{
    LogInfo();
    var Emps = from e in objDs.GetEmps()
               select e;
    filteredEmps = Emps.ToList();
    return Json(filteredEmps, JsonRequestBehavior.AllowGet);
}

Finally, because the default route asks CustomControllerFactory for the HomeController, it will break if the HomeController class doesn't have a contructor that takes an IRequestLogger, so I had to define one.  While I was at it, I added logging to the index page (needs refactoring but it works):

public class HomeController : Controller
{
            
    IRequestLogger logger;
    /// <summary>
    /// The Constructor with the Logger parameter. 
    /// </summary>
    /// <param name="log"></param>
    public HomeController(IRequestLogger log)
    {
        logger = log;
    }
 
    public ActionResult Index()
    {
        LogInfo();
        return View();
    }
 
    private void LogInfo()
    {
        LoggerInformation logInfo = new LoggerInformation();
        logInfo.UserName = this.Request.LogonUserIdentity.Name;
        logInfo.RequestUrl = this.Request.Url.AbsoluteUri;
        logInfo.Browser = this.Request.Browser.Browser;
        logInfo.RequestType = this.Request.RequestType;
        logInfo.UserHostAddress = this.Request.UserHostAddress;
        logger.RecordLog(logInfo);
    }
}

Played around with it a bit, the "Display Data" menu item on Server Explorer kept showing me just one row, so I thought it was broken... which is why I have so many entries lol:



Creating a custom ActionResult


While I covered the built-in ActionResults in a previous post, it's worth touching on the capability of creating customized ActionResults.  This is accomplished by inheriting from the ActionResult class and overriding the ExecuteResult method.  The CodeProject page Custom ASP.NET MVC ActionResults has a decent example, the one major change I made when implementing it in my demo project was to factor the duplicated logic in the encoding switch into it's own private method, so this:

 switch (Encoding)
            {
                case EncodingType.UTF8:
                    doc.Declaration = new XDeclaration("1.0""utf-8"null);
                    context.HttpContext.Response.Charset = "utf-8";
                    xmldecl.Encoding = "UTF-8";
                    XmlElement root = xmldoc.DocumentElement;
                    xmldoc.InsertBefore(xmldecl, root);
                    context.HttpContext.Response.BinaryWrite(
                      System.Text.UTF8Encoding.Default.GetBytes(xmldoc.OuterXml));
                    break;
                case EncodingType.UTF16:
                    doc.Declaration = new XDeclaration("1.0""utf-16"null);
                    context.HttpContext.Response.Charset = "utf-16";
                    xmldecl.Encoding = "UTF-16";
                    root = xmldoc.DocumentElement;
                    xmldoc.InsertBefore(xmldecl, root);
                    context.HttpContext.Response.BinaryWrite(
                      System.Text.UnicodeEncoding.Default.GetBytes(xmldoc.OuterXml));
                    break;
                case EncodingType.UTF32:
                    doc.Declaration = new XDeclaration("1.0""utf-32"null);
                    context.HttpContext.Response.Charset = "utf-32";
                    xmldecl.Encoding = "UTF-32";
                    root = xmldoc.DocumentElement;
                    xmldoc.InsertBefore(xmldecl, root);
                    context.HttpContext.Response.BinaryWrite(
                      System.Text.UTF32Encoding.Default.GetBytes(xmldoc.OuterXml));
                    break;
            }  

became this:

    switch (Encoding)
    {
        case EncodingType.UTF8:
            WriteResponse(context, doc, xmldoc, xmldecl, "utf-8");
            break;
        case EncodingType.UTF16:
            WriteResponse(context, doc, xmldoc, xmldecl, "utf-16");
            break;
        case EncodingType.UTF32:
            WriteResponse(context, doc, xmldoc, xmldecl, "utf-32");
            break;
    }
 
    ...
}
 
private void WriteResponse(ControllerContext context, XDocument doc, XmlDocument xmldoc, XmlDeclaration xmldecl, String encoding)
{
    doc.Declaration = new XDeclaration("1.0", encoding, null);
    context.HttpContext.Response.Charset = encoding;
    xmldecl.Encoding = encoding.ToUpper();
    XmlElement root = xmldoc.DocumentElement;
    xmldoc.InsertBefore(xmldecl, root);
    context.HttpContext.Response.BinaryWrite(
      System.Text.UTF8Encoding.Default.GetBytes(xmldoc.OuterXml));
    //return root;
}

... much nicer.  I did, also, run into a hiccup with the controller factory implemented above.  I was expecting the XmlController to be picked up automatically, forgetting that the custom controller factory uses a configuration file ("Controllers.xml").  So I gave the controller a default constructor that hard coded the logger and just switched back to the default controller factory.  I added a "Book.xml" file in the base directory which I populated with data from Microsoft, and the provided code behaved as expected.


Create Custom Route Handler


Chapter 14 in the O'Reilly book has a section on "Extending Routing" which includes a rather sophisticated example of using a custom route handler to facilitate the use of Component Object Model (COM) objects in an MVC application.  The aim of the example is to execute the request in a Single Treaded Apartment (STA) so that any COM objects called by the request will run in the same thread, allowing for greater parallelism.

The text doesn't do a very good job of providing the example all in one piece, so I stitched it together and got it working (after fixing some minor typo's).  One issue the book doesn't address is a question of configuration.  At least in MVC 5, the following value must be included in the Web.config appSettings section or you'll throw an exception:

  <appSettings>
    <!-- other junk -->
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" />
  </appSettings>

The AspCompatHandler class implements IRouteHandler, which has one method, GetHttpHandler().  All this method does is return an instance of the inner class AspCompatHandlerImpl, which inherits from System.Web.UI.Page and implements IHttpAsyncHandler.  This inner class has an OnInit() method that basically mimics the standard MvcHandler, finding the appropriate controller based on the route and passing the request to that controller for execution.  IHttpAsyncHandler requires two methods, BeginProcessRequest and EndProcessRequest, and these are delegated to the AspCompat version inherited from Page.  Full source is on GitHub.

Once the route handler is created, all that is left is to create the route in App_Start/RouteConfig.cs.  This initially tripped me up because I forgot that order matters and the default route kept trying to pick up the request.  In the process I tried two different ways to add the route and once I figured out that the problem was the order, I tested both and both worked as expected.  Because I left the default route and added a route specifically to use the custom route handler,  I can see either version without needing to change the code:

public static void RegisterRoutes(RouteCollection routes)
 {
     routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
     routes.Add(new Route(
         url: "AspCompat/{controller}/{action}",
         defaults: new RouteValueDictionary(
             new { controller = "Home", action = "ThreadState" }),
         routeHandler: new AspCompatHandler()));
 
     //routes.MapRoute(
     //    name: "AspCompatRoute",
     //    url: "AspCompat/{controller}/{action}",
     //    defaults: new { controller = "Home", action = "ThreadState" }
     //).RouteHandler = new AspCompatHandler();
 
     routes.MapRoute(
         name: "Default",
         url: "{controller}/{action}/{id}",
         defaults: new { controller = "Home", 
             action = "Index", id = UrlParameter.Optional }
     );
 }

All the "ThreadState" action does is return the tread status as a string, either "MTA" or "STA".  Here you can see the difference:





Create Custom View Engine


Imran Baloch's blog posts on creating a custom view engine focus on subclassing and selectively overriding behavior from existing view engines.  In the post from 2011 he's adding a parameter to the view path that incorporates the controller namespace.  There was some discussion in the comments about using an approach like this for a multi-tenant application, but there weren't any code examples of the modified view engine in practice.  The post from 2015 is using bleeding edge versions of .NET and MVC so I couldn't make it work as it was in my MVC 5 app.  This one looked for the view in a randomly selected "Theme" folder, and the concepts would be easy enough to adapt to the older example.

The Code Project article Creating your own MVC View Engine For MVC Application outlines a basic toy example of creating a custom view engine.  Before getting into the code it points out how the framework has the flexibility built in to use a variety of different view engines, and mentions a few open source projects.  This example, while super simple, was at least instructive in that it included a soup to nuts example (so I could actually see output from the example code).  The viewengine inherits from VirtualPathProviderViewEngine, and replaces curly brace fields with matching data from the ViewData dictionary (so {message} will be replaced with the value in ViewData[message]).

The Exam Ref also briefly covers creating a custom view engine.  The author discusses the use case of overriding the Razor view engine to include debugging information on the bottom of the page when a flag is set.  Surprisingly, he doesn't bother to demonstrate what that might look like, but instead provides a blatantly plagiarized code from the Code Project article (CP example was from Dec 2011, exam ref published 2013, so...) smdh.   Now, I will say that the idea of debug info being rendered to the page was fascinating, and seemed like something I could pull off with relatively little effort, so I implemented that:

Having got this to work, I have to say that I don't think displaying debug info is a strong case for a view engine tweak, as I think a simple ActionFilter appending data to the Response would have worked just as well and been much simpler to implement.  So there are several parts to making this work:

  • Create a subclass of RazorViewEngine that returns our custom view with debugging info if a flag is set, otherwise just return the normal Razor view.
  • Create a subclass of RazorView with the Render method overwritten so we can append our debug data to the end
  • Create an ActionFilter that copies the value of the "debugModeOn" query string param into a ViewData param (not strictly necessary, just gives you finer control over when debug is available).
  • Register custom view engine in Global.asax.

I won't post all of the code here (This post is already way too long), just Render in the RazorView subclass, since this is where the magic happens:

public override void Render(ViewContext viewContext, TextWriter writer)
{
    StringBuilder sb = new StringBuilder();
    StringWriter sw = new StringWriter(sb);
    base.Render(viewContext, sw);
 
    string contents = sb.ToString();
 
    contents = Regex.Replace(contents, "</body>""<p>DEBUG INFO:</p><p>" + 
        Environment.StackTrace + "</p></body>");
 
    writer.Write(contents);
    writer.Flush();
}

The rest is on GitHub: CustomViewEngine.  Here is the result:




Create Custom Model Binder


The CodeProject article ASP.NET MVC Custom Model Binder gives a pretty basic example of creating a custom model binder that takes in values for a day, month, and year, and binds a formatted date string to the model.  It covers two ways to create the model binder: implementing the IModelBinder interface, or subclassing the DefaultModelBinder class and overriding the BindModel method.  I tried the IModelBinder approach because it was simpler, though you get a lot for free (validation) with the inheritance approach.

In his example, the formatted date string is just the provided numbers concatenated together with slashes, so providing day=5, month=13, and year=99999 just returns 5/13/99999.  This was making my eye twitch a bit, so I went ahead and added a bit of rudimentary validation by first parsing the concatenated string with DateTime.Parse, and then returning the long date string:

public class HomeCustomBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext,
                            ModelBindingContext bindingContext)
    {
        HttpRequestBase request = controllerContext.HttpContext.Request;
 
        string title = request.Form.Get("Title");
        string day = request.Form.Get("Day");
        string month = request.Form.Get("Month");
        string year = request.Form.Get("Year");
 
        string parsedDate = "Invalid Date Parameters";
        try
        {
            parsedDate = DateTime.Parse(month + "/" + day + "/" + year).ToLongDateString();
        }
        catch (Exception ex) {}
 
 
        return new HomePageModels
        {
            Title = title,
            Date = parsedDate
        };
    }
} 

If the date parameters are invalid, then instead of a nonsense "date" string, the error message is returned instead.  You probably wouldn't do it this way in a real environment, but at least it no longer made blood shoot out of my eyes.


The article demonstrates two ways to use the custom model binder:  Using the [ModelBinder(typeof(CLASS))] attribute on the controller action parameter, or registering the model binder in the Global.asax file.  The article actually uses both, which is unnecessary; after trying both individually I got the exact same results.

Attribute:
[HttpPost]
public ActionResult Index([ModelBinder(typeof(HomeCustomBinder))] HomePageModels home)
{ 
    // do stuff 
}

Global.asax
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
 
        //register custom model binder
        ModelBinders.Binders.Add(typeof(HomePageModels), new HomeCustomBinder());
    }
}


3 comments:

  1. Hi Troy, just wanted to say thanks for an excellent and well thought out set of notes on the exam content. I' studying for 70-486 at the moment and keep coming back to these for a bit more depth and alternative perspectives on the material outside of the 'official' guides.
    Just a note that the link at the top of this article is broken
    (I found it on http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application though)
    Thanks !

    ReplyDelete
    Replies
    1. Thanks Jason, and you are welcome. It's been an excellent learning experience for me to put these posts together and I'm glad they are proving valuable to you. I fixed the broken link, thank you for pointing that out!

      Delete
  2. Free online simulation of the Microsoft 70-486 exam is available here if someone needs https://exambraindumps.com/70-486.html. Really good to check your knowledge.

    ReplyDelete