Precompiling ASP.NET MVC views

In a traditional ASP.NET MVC application, Razor views are compiled by the server the first time they’re requested.

Further calls will result in the same compilation results being served again. These compiled view results are then available until IIS is restarted, the application pool is recycled or the application is shut down.

When that happens and the application is restarted, the next client to request a specific view will bear the compilation cost once more. In a high volume application, the application should be kept online as more requests arrive and compilation will thus happen infrequently.

In certain scenarios, for example when the application is accessed infrequently or when view compilation simply takes to much time, precompilation is a viable solution.

It allows us to precompile the views ahead of time which precludes IIS from needing to compile them at runtime.

You can precompile the views when you are building the application in Visual Studio, on the build server when doing a build or after the application is built but before it is deployed. This last one was possible with Web Forms but I have not investigated this avenue with MVC.

Let’s look at the pros and cons of the first two methods.

Precompiling on the developer’s machine

Pros:

  • Find potential view errors at compile time rather than runtime (see example below).
  • Speed up the application for developers by preventing view compilation when debugging.
  • Allows us to share views between ASP.NET MVC projects in the same solution.

Cons:

  • Cannot modify views when debugging or when the application is running. It needs to be stopped, compiled and started again for changes to take effect.
  • Compiled files will appear in merge conflicts. Note that you can simply ignore the conflicts and rebuild to solution to generate the files again.
  • Generated files will be checked in to source control, polluting the repository. On the plus side this allows other developers to reuse the precompilation results.
  • Need to install a Visual Studio extension and a NuGet package.

Precompiling on the build server

Pros:

  • No change in workflow for developers. They can continue modifying views at runtime, they do not need to install any tools.
  • No precompilation files in source control.

Cons:

  • Will not find view errors at compile time.
  • Will not speed up the application on the developer’s machines.
  • Requires a build server and build process (for those who aren’t using one).

In my last ASP.NET MVC project, I chose the first option for many reasons but primarily because I needed to share views between projects. To do this you need to have the views compiled and then copy the compiled views in a folder in the destination project. This process would require it’s own blog post so I will leave it for a possible future post.

Error example

Here’s an example of a precompilation error I mentionned earlier:

@model TestModel

@Model.First
@Model.Seond

<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

In our model we have two properties, First and Second. In the previous example an error has been introduced where the property is misspelled Seond. While this error will show up visually with a red squiggly line when you inspect the file, the application will compile just fine but throw an exception when you try to access this view.

mvcError

When working on a large project with dozens of views, it’s easy to miss these errors.

Activating precompilation in Visual Studio

To activate precompilation you need to install the following NuGet package in each project which will contain precompiled views: RazorGenerator.Mvc.

razorGeneratorMvc

You also need to install the following extension in Visual Studio: Razor Generator.

razorGeneratorExt

Finally you need to change the build tool for the views that need to be precompiled, often case this means all views, by setting the custom tool to RazorGenerator.

RazorGenerator

You can visit the RazorGenerator GitHub page for more information.

How to create an F# project under Linux

There isn’t a ton of info about starting out a new F# project under Linux so I’ve decided to document how I do it.

Install Mono

Following the instructions on the official F# site, install Mono and F#:

sudo apt-get install mono-complete fsharp

You can test that F# is installed by typing

fsharpc

to bring up the F# compiler.

Install Visual Studio Code

You could use any editor, I chose Visual Studio Code because it offers superb F# integration when combined with the Ionide plugin.

After installing VS Code press Ctr-Shift-X to open the Extension window and search for “Ionide” and install the following extensions:

  • Ionide-fsharp
  • Ionide-Paket
  • Ionide-FAKE

Start a new project

Using Ctr-P bring up the command window and type:

>f#:new project

Follow the instruction and select a class library project.

This will create the base project scaffolding including some files and folders. Now might be a good time to do a git init.

Paket

To build your project you must use the build.sh script. If you try this now, the command will fail because it won’t be able to find the Paket bootstrapper.

Paket is the package manager that downloads, installs and manages dependencies,  much like NuGet, Cargo or RubyGem on other platforms.

So head on over and download the latest Paket bootstrapper specifically paket.bootstrapper.exe and drop it in the .paket folder that was created by Ionide.

You can now run

./build.sh

And Paket will create a paket.dependencies and paket.lock.

Adding some code to the project

There should be a folder with the project name you specified to Ionide . Let’s add a new file to this project.

You have two choices here:

  1. Add it manually
  2. Use Ionide to automatically add the file to the project

Add it manually

To add the file manually you must create a new file with a .fs extension using the file system or VS Code.

Next you must add your file to the project by editing the .fsproj file.

Then open your .fsproj file and locate the ItemGroup section which includes the Compile Include tags. Similar to this:
<CompileInclude=”genesis2.fs”/>
<NoneInclude=”Script.fsx”/>
Add a new entry:
<CompileInclude=”NewFile.fs”/>
<CompileInclude=”genesis2.fs”/>
<NoneInclude=”Script.fsx”/>
Be careful, the order of the elements is important. If a file A is a dependency for file B, it must come before file B in this listing. In this example NewFile.fs can be a dependency for genesis2.fs but the reverse can’t be true.
The manual technique, even though it is tedious, is useful, particularly to debug your fsproj.

Use Ionide

The alternative is to use Ionide a to add a new file for you.

Create the new .fs file like before, but this time use Ctr-P, type in

>f#

And select, Add current file to project. Voila!

Adding a new dependency to our existing project

Let’s add a new dependency to our project, MathNet.Numerics, a library that provides methods and algorithms for numerical computations.

Again let’s see how to add it both manually and using the Ionide plugin.

Before installing any packages make sure that the .paket/paket.exe file that was previously downloaded by the bootstrapper  is executable. To do so, change the permissions via the command line or the GUI.

Add it manually

First open the paket.dependencies file in your project root and add the following line:

nuget MathNet.Numerics
And then in your project folder (for each of the projects where you want to install the dependencies) locate the paket.references file and add this line:
MathNet.Numerics
Then run:
.paket/paket.exe install
To check that everything is working you can by adding the following in one of your source file:
open MathNet.Numerics.Distributions
And build your project again.

Use Ionide

Open the .fsproj file of the project you want to add the dependency to, type in Ctr-P and then:

> paket: Add NuGet to current project

Debugging the project

Twice I’ve had the project refusing to build because of the paket dependencies after a Mono upgrade. In this case your best bet is to delete the dependency info in the fsproj and add them again.

Error handling in ASP.NET MVC

This is a basic error handling strategy for an ASP.NET MVC app. It will handle all ASP.NET errors, allow somewhere to put logging code and redirect to a generic error page.

This will miss exceptions not handled by ASP.NET such as navigating to an invalid URL but will be sufficient to handle and log application errors in your controllers.

First start by creating an ErrorController (this one is basic but could be expanded):

using System.Web.Mvc;

namespace BasicErrorHandling.Controllers
{
    public class ErrorController : Controller
    {
        // GET: Error
        public ActionResult Index(string message)
        {
            TempData["errorMessage"] = message;
            return View();
        }
    }
}

Don’t forget to create a view for said controller:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Error</title>
</head>
<body>
    <div> 
        <h1>Generic Error Page</h1>
        <p>@TempData["errorMessage"]</p>
        
    </div>
</body>
</html>

For testing purposes, let’s introduce an exception in one of our actions:

public ActionResult About()
{
    ViewBag.Message = "Your application description page.";

    // throw an error to test exception handling
    throw new Exception("Fictious error");

    return View();
}

The method that will handle our errors and redirect to our error page is in our Global.asax.cs:

protected void Application_Error(object sender, EventArgs e)
{
    // get error and clear it 
    var exception = Server.GetLastError();
    Server.ClearError();

    // log error
    // ...

    // redirect to error page
    string message = Regex.Replace(exception.Message, @"\t|\n|\r", ""); // replace newlines which will not work with query string
    Response.Redirect("/Error/?message=" + message);
}

Here we should log or handle the error in the manner appropriate to our application. Finally we redirect to an error page supplying an optional message parameter. We could also redirect to this error controller from somewhere else than Global.asax.

To make everything work nicely we need to add a new route in our RouteConfig.cs:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Error",
        url: "Error/{message}", 
        defaults: new { controller = "Error", 
                        action = "Index", 
                        message = UrlParameter.Optional } 
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", 
                        action = "Index", 
                        id = UrlParameter.Optional }
    );
}

 
You can find the whole thing on GitHub.

Simple Ajax scenario in ASP.NET MVC Part 3: graceful degradation

This follows part 1 and part 2.

Let’s continue improving our previous example by adding more requirements. This time supporting clients without JavaScript aka graceful degradation for our Ajax feature.

Graceful degradation in web design refers to supporting less capable clients correctly. In our case, using Ajax will currently only work in browsers with JavaScript enabled.

While that is most browsers, the best scenario possible is to support all browsers, even those who have disabled JavaScript or do not support it.

Supporting clients without JavaScript is sometimes required for conforming to standards or for accessibility reasons.

Changing our previous example

If we disable JavaScript and try our current version of our example application our filtering feature doesn’t work anymore (as well as our jQuery DataTables plugin).

jsdisabled

We are currently using the onclick event on a checkbox. Since this won’t submit the form without JavaScript we need to show the input element we had hidden in Part 2 when JS is disabled.

We still want to have that element hidden if JavaScript is enabled so instead of relying on style=”display: none” we will use JavaScript to hide the input element.

using (Ajax.BeginForm("FilterDepartmentsAjax",
                       new AjaxOptions
                       {
                           HttpMethod = "POST",
                           InsertionMode = 
                               InsertionMode.Replace,
                           UpdateTargetId = 
                               "department-table",
                           OnSuccess = "tableUpdated",
                       }))
{
    <label>Filter: </label>
    @Html.CheckBoxFor(model => model.Filtered, 
                      new { onclick = "$(this).parents('form:first').find(':submit')[0].click();" })
    <input id="input-filter" type="submit" value="Submit" />
}

<div id="department-table">
    @{ Html.RenderPartial("DepartmentsTablePartial", Model); }    
</div>

@section Scripts
{
    <script type="text/javascript">
        $(document).ready(function () {
            $('#input-filter').hide();
            $('#deparment-table').DataTable();
        });

        function tableUpdated() {
            $('#deparment-table').DataTable().draw();
        }
    </script>    
}

Now our button is shown only when JavaScript is disabled.

The next problem is that we want to return a PartialView when we are dealing with an Ajax request and a View when we are dealing with a regular request.

Otherwise we will either get only the partial view with JavaScript disabled or loose our jQuery DataTables functionality when we return a whole view.

The correct this we change the FilterDepartmentsAjax method return type to ActionResult and use if (Request.IsAjaxRequest()) like so:

[HttpPost]
public ActionResult FilterDepartmentsAjax(
                       DepartmentsModel model)
{
    var updatedModel = new DepartmentsModel
    {
        Filtered = model.Filtered,
        Departments = GetDepartments(model.Filtered),
    };

    if (Request.IsAjaxRequest())
        return PartialView("DepartmentsTablePartial", 
                           updatedModel);

    return View("Index", updatedModel);
}

Problem solved.

You can find the whole project on GitHub and the specific changeset for this blog post.