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 too 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.

Integrating DropzoneJS into an ASP.NET MVC site

Dropzone allows you to easily handle file upload via drag and drop.

Here is a simple tutorial on how to integrate DropzoneJS into an existing ASP.NET MVC application. The instructions on the dropzone page are easy to follow but I include here a version tailored for ASP.NET MVC.

First off, there is a NuGet package but I prefer not to use it so that I can include only the necessary files and choose where those files are located.

The first step is to obtain the JavaScript file and include it into your /Scripts folder.

dropzone1

Optionally you can also download and use the CSS file. Put this one in your /Content folder.

dropzone2

Once these files are in place, it’s time to create some bundles. In App_Start/BundleConfig.cs add the following bundles:

bundles.Add(new ScriptBundle("~/bundles/dropzone")
            .Include("~/Scripts/dropzone.js"));

bundles.Add(new StyleBundle("~/Content/dropzone-css")
            .Include("~/Content/dropzone.css"));

On the page you want to use dropzone on, add calls to the Styles and Scripts render methods and also a form element with the dropzone class. The class name is how dropzone locates the control so it can make the appropriate modifications.

Remember that you can’t nest form elements so if your page already contains another form your will need to make sure they aren’t nested.

Here is how our page.cshtml:

@Styles.Render("~/Content/dropzone-css")

<div class="jumbotron">
    <h1>dropzone test</h1>
</div>

<div class="row">
    @using (Html.BeginForm("FileUpload", "Home", 
                           FormMethod.Post,
                           new
                           {
                               @class = "dropzone",
                               id = "dropzone-form",
                           }))
    {
        <div class="fallback">
            <input name="file" type="file" multiple />
        </div>
    }
</div>

@section scripts {
    @Scripts.Render("~/bundles/dropzone")

    <script type="text/javascript">
        Dropzone.options.dropzoneForm = {
            paramName: "file",
            maxFilesize: 20,
            maxFiles: 4,
            acceptedFiles: "image/*",
            dictMaxFilesExceeded: "Custom max files msg",
        };
    </script>
}

The form contains an optional fallback element for clients who don’t support JavaScript. Also note in the scripts section, custom configuration options are declared.

You may notice I have set a maxFilesize. This value is in MB. It is necessary to change the maxRequestLength attribute in your web.config file to a value at least as big as maxFilesize, otherwise IIS will reject files under the maxFilesize limit.

Here is how to change your maxRequestLength:

<system.web>
    <compilation debug="true" targetFramework="4.5.2" />
    <!--maxRequestLength increased to 20 MB-->
    <httpRuntime targetFramework="4.5.2" 
                 maxRequestLength="20480" />
</system.web>

After we have updated our web.config we can now move on to the controller.

[HttpPost]
public ActionResult FileUpload(HttpPostedFileBase file)
{
    try
    {
        var memStream = new MemoryStream();
        file.InputStream.CopyTo(memStream);

        byte[] fileData = memStream.ToArray();

        //save file to database using fictitious repository
        var repo = new FictitiousRepository();
        repo.SaveFile(file.FileName, fileData);
    }
    catch (Exception exception)
    {
        return Json(new { success = false, 
                          response = exception.Message });
    }

    return Json(new { success = true, 
                      response = "File uploaded." });
}

When uploading multiple files at the same time, this method will get called as many times as there are files.

If you want to handle the returned JSON, you need to handle events from dropzone, in this case the complete event.

Here is how the page looks after having uploaded two pictures:

dropzone3

ASP.NET MVC: Dynamically adding an existing View as a Partial View to a parent

Let’s say you have a pre-existing Model/View/Controller. For my purposes I will call it FirstController, FirstModel and the view, Details.

In this post I will explain how to reuse this form and model in another View/Controller which will become it’s parent. I will also show how to dynamically add this child view to the parent via Ajax. This way we can conditionally add the view based on the user’s input.

Here is our pre-existing MVC:

namespace ChildModel.Controllers
{
    public class FirstController : Controller
    {
        public ActionResult Details()
        {
            var model = new FirstModel { Name = "Gilles",
                                         Number = 42 };
            return View(model);
        }

        [HttpPost]
        public ActionResult Details(FirstModel model)
        {
            if (!ModelState.IsValid)
            {
                return View();
            }

            return RedirectToAction("Index", "Home");
        }
    }
}

namespace ChildModel.Models
{
    public class FirstModel
    {
        public string Name { get; set; }
        public int Number { get; set; }
    }
}

With this simple view:

@model ChildModel.Models.FirstModel

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>First - Details</title>
</head>
<body>
<div>
<h1>First - Details</h1>
@using (Html.BeginForm("Details", "First"))
        {
            @Html.LabelFor(model => model.Name, "Name")
            @Html.TextBoxFor(model => model.Name)

            @Html.LabelFor(model => model.Number, "Number")
            @Html.TextBoxFor(model => model.Number)

            <input type="submit" value="Submit" />
        }</div>
</body>
</html>

first1

Here is the existing Model/View/Controller which will eventually become the parent:

namespace ChildModel.Controllers
{
    public class ParentController : Controller
    {
        public ActionResult Index()
        {
            var model = new ParentModel { IsChecked = true };
            return View(model);
        }

        [HttpPost]
        public ActionResult Submit(ParentModel model)
        {
            if (!ModelState.IsValid)
            {
                return View();
            }

            return RedirectToAction("Index", "Home");
        }
    }
}

namespace ChildModel.Models
{
    public class ParentModel
    {
        public bool IsChecked { get; set; }
    }
}
@model ChildModel.Models.ParentModel

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Parent</title>
</head>
<body>
<div>
<h1>Parent</h1>
@using (Html.BeginForm("Submit", "Parent"))
        {
            @Html.LabelFor(model => model.IsChecked, "Is Checked ?")
            @Html.CheckBoxFor(model => model.IsChecked)

            <input type="submit" value="Submit" />
        }</div>
</body>
</html>

parent1

Let’s start by including First into Parent. We will do the dynamic Ajax part afterwards.

Since we don’t want to include the form in our parent page, because our parent already has a form and submit button, we will take out the fields we want to reuse and put them in their own partial view.

_FirstFields.cshtml:

@model ChildModel.Models.FirstModel

@Html.LabelFor(model => model.Name, "Name")
@Html.TextBoxFor(model => model.Name)

@Html.LabelFor(model => model.Number, "Number")
@Html.TextBoxFor(model => model.Number)

And call this Partial View in First/Details.cshtml:

@using (Html.BeginForm("Details", "First"))
{
    Html.RenderPartial("_FirstFields");

    <input type="submit" value="Submit" />
}

In our ParentModel we will need to add a new property of type FirstModel:

public class ParentModel
{
    public bool IsChecked { get; set; }
    public FirstModel ChildModel { get; set; }
}

In our Parent/Index.cshtml view we can include the partial view. Notice the use of the ViewDataDictionary in RenderPartial. If we simply asked for the view, the model binder wouldn’t be able to get the data back on a Post. The HtmlFieldPrefix in our ViewDataDictionary is set to the name of our child model property.

This will have for effect of changing the Ids and names of the fields on the partial view allowing the model binder to correctly bind the fields.

@using (Html.BeginForm("Submit", "Parent"))
{
    @Html.LabelFor(model => model.IsChecked, "Is Checked ?")
    @Html.CheckBoxFor(model => model.IsChecked)

    Html.RenderPartial("../First/_FirstFields",
                       Model.ChildModel,
        new ViewDataDictionary(Html.ViewData)
        {
            TemplateInfo = new TemplateInfo
            {
                HtmlFieldPrefix = "ChildModel"
            }
        });

    <input type="submit" value="Submit" />
}

Now we have completed the first part by including a child model/view in our parent. Let’s add the Ajax part.

We will remove our RenderPartial and replace it with a div element which will serve to host the dynamic html code. We will also add an Ajax action link to call a new method on our ParentController.

@using (Html.BeginForm("Submit", "Parent"))
{
    @Html.LabelFor(model => model.IsChecked, "Is Checked ?")
    @Html.CheckBoxFor(model => model.IsChecked)

    @Ajax.ActionLink("Add Child Form", "GetChildElement",
                     "Parent",
                    new AjaxOptions
                    {
                        AllowCache = false,
                        InsertionMode =
                                InsertionMode.Replace,
                        HttpMethod = "Get",
                        UpdateTargetId = "childContainer",
                    })
<div id="childContainer"></div>
<input type="submit" value="Submit" />
}

In our ParentController we add the Ajax action method GetChildElement this time setting the prefix manually:

public PartialViewResult GetChildElement()
{
    ViewData.TemplateInfo.HtmlFieldPrefix = "ChildModel";
    return PartialView("../First/_FirstFields");
}

Don’t forget to add Ajax capabilities to your project like detailed here.

And everything should be working.

parent2

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.