Software Engineer Problem Solver

Leo Reading

One Moment, Please...

Find My Articles

Share This Page

B l o g

Blog

Leo Reading

Software Engineer and Problem Solver From the USA

Download Resume

My Blog A collection of technical and non-technical mishaps, achievements and stories.

Programming

Creating Static Error Pages Dynamically in MVC

Ben Foster has a great article on setting up custom error pages in an MVC project that I typically refer to each time I create a new one.  Recently, I've finally decided to work smarter, not harder, by setting up my own MVC seed project.  Of course, this should include some pre-defined error pages.  Unfortunately, error pages aren't exceedingly straight-forward.

 

Caveats

  • There are two types of error pages that we need to consider: Errors handled by MVC, and errors handled by IIS
  • This method will not correctly set the status codes (it will return 200 since you are being served a file)
  • This solution hard-codes the URL.  That may or may not be acceptable for your application.
  • Requires .NET 4.5.2+

Why Static HTML Files?

In the event of an error in the MVC pipeline, IIS will not be able to serve an MVC page.  If this is the case, your application will get stuck in a bit of an error loop, and bad things will happen.  We don't want that, now do we?  We do, however, want the ability to create nice looking error pages with Razor syntax and all the fun parts of MVC.  

 

Create Some MVC Error Pages

Go ahead and create a controller and some actions to serve up error pages using MVC.  In this case, I have 1 view, a viewmodel, and an action method for each type of error I want to handle.

ErrorPageViewModel

public class ErrorPageViewModel
{
    public string Title { get; set; }
    public string Message { get; set; }
}

ErrorController

public ActionResult Error400()
{
    var vm = new ErrorPageViewModel
    {
        Message = "Bad Request",
        Title = "400"
    };
    return View("Error", vm);
}

Error.cshtml

 

@model APISeed.Web.Models.ErrorPageViewModel


<h1>@Model.Title</h1>
<h2>@Model.Message</h2>

 

Generate the Static HTML Files

Now that our MVC site is capable of generating the HTML output for these views, we can leverage that to generate static HTML files for each of the error types.  Because I'm a lazy programmer, I don't want to update this each time I decide I need to have a pretty page for a different type of error message, so we're going to use some reflection to solve this problem.

StaticErrorGenerator

Our Constructor:

public StaticErrorGenerator(string errorController, Type globalAsaxType, string baseUrl)
{
    _baseUrl = baseUrl;
    _errorController = errorController;
    _globalAsaxType = globalAsaxType;
    _actionNames = GetControllerActions();
}

You've probably guessed that the magic is happening in GetControllerActions, and you're absolutely right!  Following advice found in this StackOverflow answer, we are able to dynamically generate a list of all of the Action names in the specified errorController.

private IEnumerable<string> GetControllerActions()
{
    var assembly = Assembly.GetAssembly(_globalAsaxType);
    return assembly.GetTypes()
        .Where(type => typeof(System.Web.Mvc.Controller).IsAssignableFrom(type))
        .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
        .Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
        .Where(x => x.DeclaringType.Name == _errorController + "Controller" && x.ReturnType.Name == "ActionResult")
        .Select(x => x.Name);
}

Now that we have the action method names and the controller name, we can output our files to where ever we'd like.  The method to do that looks like this (using RestSharp):

public void GenerateStaticErrorPages(string errorPageFolderPhysicalPath)
{
    if (!Directory.Exists(errorPageFolderPhysicalPath)) Directory.CreateDirectory(errorPageFolderPhysicalPath);
    var client = new RestClient(_baseUrl);

    foreach (var action in _actionNames)
    {
        // Get the HTML, and write it to file using the same name as the action method
        var request = new RestRequest(_errorController + "/" + action, Method.GET);
        var responseHtml = client.Execute(request).Content;
        var fileName = errorPageFolderPhysicalPath + "\\" + action + ".html";
        File.WriteAllText(fileName, responseHtml);
    }
}

 

Where Do I Run It?

At this point, we have a working solution, but it needs to be invoked somehow.  The obvious answer is the Global.asax's application_start method, but the obvious problem with that is that the application is not going to respond to requests while it is in the application_start, rendering our little module entirely useless.

 .NET 4.5.2 to the rescue!

With .NET 4.5.2, we were given this wonderful HostingEnvironment.QueueBackgroundWorkItem method. I'm a huge fan of this, since it really gives developers the ability to truly utilize a web application for scheduled tasks.  With this method, we are guaranteed 30 seconds to complete the current task before the App Pool shuts down, which is amazing.  I digress...

If we use this in the application_start method, it will be sent to the background, and the MVC pipeline will continue.  Because of that, the first request may hang for a couple of seconds while the app finishes starting, but it will make it through as soon as the app is ready and continue plowing through our other messages. (Feel free to try this without it running as a background process, just don't blame me when your app doesn't start! You've been warned...)

Here's what my Global.asax application_start looks like:


HostingEnvironment.QueueBackgroundWorkItem(cancellation =>
{
    var errorGenerator = new StaticErrorGenerator("Error", typeof(WebApiApplication), "http://localhost:61163/");
    errorGenerator.GenerateStaticErrorPages(HostingEnvironment.ApplicationPhysicalPath + "\\ErrorPages");
});

 A Note on httpErrors

The httpErrors element in the system.webServer element is what serves up files for IIS pipeline errors.  One detail that was lacking (at least for me), is that the path to your file is a relative path to the physical location.  If you have tracing enabled (for IIS Express, C:\Users\<username>\Documents\IISExpress\TraceLogFiles\<project name>\), you can see the request trace logs.  Because I have my error page files in a separate folder, I instinctively wanted to use a forward slash (/) since this is a web application.  If you cause an IIS pipeline error (try going to localhost:1234/aasdfkj.asdf), you can look at the trace logs and see what IIS is doing for you.

The key point here is that you'll find what file it's attempting to serve, and you will notice that it's a file path, not a url.  Because of this, we need to use back-slashes to point to the correct relative location.


<Data Name="FileNameOrURL">C:\Github\APISeed\APISeed.Web\ErrorPages\Error404.html</Data>

 

 

Hopefully you've found this interesting and/or helpful.  Please feel free to comment with any questions or if you'd have done it differently!

Leo Reading

Leo Reading is a US based software engineer and problem solver. Known as a jack of all trades and master of few, Leo is constantly learning new technology and expanding his understanding of all things 'nerd'

You Might Also Like

Xamarin Studio - The Following Add-Ins Could Not Start

Today I embark on a rather ambitious journey into the world of mobile development paired with a cloud-based back-end.  I've had plenty of ideas that I've tossed around as far as developing something new, something that's needed, and most importantly, something that will be used.  This idea actually has potential, and it...

New Website

Facebook and Twitter are a great place to share personal thoughts, pictures, etc with your friends and family.  A big part of who I am is my inner nerd.  I wanted a place to share this part of me with the world.  I've begun development on this site so that I can do just that.  I plan to share some of the technological str...

One Moment, Please

Close