Essential guide to ASP.NET MVC3 performance

by Leon Cullens 24. december 2011 15:18

The .NET CLR can give your web application a significant performance boost compared to other platforms such as PHP due to it's compiled nature. Not only does your .NET code perform better because it's more low-level, it also comes with good support for parallel programming. This guide will not explain the basics of a responsive and fast web application by talking about CSS minifying, sprites, content delivery networks, etc. Instead this guide will be an essential guide for making sure you don't miss any features that can enhance the performance of your ASP.NET MVC3 application.

Output caching

Perhaps the most useful feature of MVC3 (performance-wise) is output caching. The biggest performance hits actually occur when your application really has to fetch data, do calculations on it and return the data. Output caching can cache these results so they can be returned directly without even touching the database. Especially when executing complex queries this can drop the load on your server significantly (in fact you could drop the load on your server by a whooping 90% by carefully inplementing caching in your web application).

Output caching is quite simple actually; all you have to do is to add an attribute to the methods that you want to cache, set the amount of seconds to store the result of that method and optionally set the location of the cache (client, server, combination, downstream, etc.). Below is an extremely simplified example to show you how it works:

public class FooController : Controller
{
    [OutputCache(Duration = 30, Location = OutputCacheLocation.ServerAndClient)]
    public ActionResult Foo()
    {
        // Fetch some data

        return View();
    }

    [OutputCache(Duration = 30)]
    public ActionResult Bar()
    {
        // Do some calculations, return some data

        return View();
    }
}

Sessionless controllers

MVC3 controllers come with session support by default. This means that controllers are able to store data values across requests. The process of creating and maintaining session state however consumes server memory and/or storage. Also it will waste extra CPU cycles that, probably, can be put to better use for most applications. As an added benefit it will be easier to run your web application across multiple server if you disable session state.

Again, disabling session state is quite easy, all you have to do is to mark your controller with the SessionState attribute as follows:

[SessionState(SessionStateBehavior.Disabled)]
public class FooController : Controller
{
    public ActionResult Foo()
    {
        // Fetch some data

        return View();
    }
}

Asynchronous controllers

If your application is I/O or network limited, it's a good option to consider implementing AsyncControllers, meaning that your controllers will be able to process multiple actions at the same time. One of the things you could do is to call a web service or fetch some data from the database. When you put this operation onto a separate work queue, the worker thread can immediately return to the worker pool and service other requests, making sure your application stays responsive. A good and easy way to implement asynchronous actions is by using the Task Parallel Library (TPL). In the example below I will give a brief explanation of this.

public class FooController : AsyncController
{
    public void FooAsync()
    {
        AsyncManager.OutstandingOperations.Increment(2);

        Task.Factory.StartNew(() => {
            // Perform some expensive operation

            AsyncManager.Parameters["data"] = data;
            AsyncManager.OutstandingOperations.Decrement();
        });
        Task.Factory.StartNew(() => {
            // Perform another expensive operation

            AsyncManager.Parameters["moredata"] = data;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ActionResult FooCompleted(string data, string moredata)
    {
        Foo foo = new Foo { Bar = data, Baz = moredata };

        return View(foo);
    }
}

As you can see I've splitted the action method into two separate methods (an async method that performs the operation(s) and a completed method that returns the data). It is important to name these methods as I did; start with the name of the action method, ending with 'Async' or 'Completed'.

In the Async method we start 2 new 'outstanding operations', and then we create 2 news tasks that do some processing, store the results of the operations by sending parameters to the AsyncManager and finally decreasing the amount of outstanding operations (marking the operation as finished). The parameters that we've sent to the AsyncManager will be automatically mapped to the parameters of the Completed method by looking at the names of the parameters. Finally the Completed method can return this data to the view that called the action method.

Client side validation

A very easy way to decrease the amount of hits on your web server is by implementing client side validation. Without client side validation, every time a user posts a form, the web server will receive a request, no matter if the request is valid or not. When using client side validation, the web server only receives a request when a form is filled in correctly, saving quite a few requests when working with enterprise applications that heavily rely on forms. Client side validation can't be any easier when working with MVC3, because all you have to do is to create a model that is annotated with some validation attributes and to include the JQuery validation library into your web application. The following example will demonstrate a basic example of this:

public class ChangePasswordModel
{
    [Required]
    [DataType(DataType.Password)]
    [Display(Name = "Current password")]
    public string OldPassword { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "New password")]
    public string NewPassword { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm new password")]
    [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
}

The model is annotated with Required attribute and a couple of other attributes that specify the rules that have to be met before the data can be submitted. Finally we just include the JQuery validation library in our _Layout.cshtml file:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title</title>
        <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
        <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
        <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    </head>
<body>

View lookup performance

Another interesting thing to keep in mind is that you can disable any other view engines. This means that the list of locations that will be scanned for your view will be shorter, resulting in better performance. You can disable any another view engines and re-register Razor by putting the following code in the Application_Start() method of your Global.asax file:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());

What this does: it obviously clears any existing view engines, and registers the Razor view engine, resulting in a 'cleaner' lookup directory list. (Thanks to Charandeep Singh for mentioning it)

Deploying

When deploying your web application, make sure you disable debug mode in your Web.Config as follows:

<compilation debug="false" targetFramework="4.0">

Or you can override this setting in IIS Manager .NET Compilation Tool when using IIS 7.x or higher.

Conclusion

The steps above are the essential steps to get most out of your ASP.NET MVC3 web application. Output caching will definately deliver the biggest performance gain, with operation parallelization being another good candidate for improving the performance significantly. One thing you should always keep in mind is that you should always deploy your application in release mode, so it's best to write a deploy script to do this automatically for you, or by overriding this in your IIS Manager.

Tags: , , , , ,

ASP.NET MVC

Comments (9) -

Jonas Hovgaard
Jonas Hovgaard Denmark
6-1-2012 21:26:56 #

Thanks for a great article!

I haven't spent much time with the new async controllers, but I as an async noob I have to ask you; the place where you want me to do "the expensive work" I would normally call some expensive method that returns some values, right? Should this be a normal C# method or should the method also be "async optimized" in some way?

Thanks in advance Smile

Reply

Leon Cullens
Leon Cullens Netherlands
6-1-2012 22:12:39 #

Hi Jonas,

Usually your 'task' will call some method. It doesn't really matter what kind of operation this is; it can be a call to some web service, a query against the database, some expensive calculation. It's important that all these tasks can be processed in parallel, so they shouldn't have to rely on each other to be completed.

Correct me if I'm wrong here, I didn't dive too deep into parallel programming yet, but afaik this is how it works Smile

Let me know if this answers your question.

Reply

Jonas Hovgaard
Jonas Hovgaard Denmark
6-1-2012 22:24:26 #

Hi Leon,

Thanks for your answer. Yes it did answer my question - kind of Laughing

As I understand you, my expensive method should never call another expensive method. But I can't help asking why not?

Or did you mean that my first expensive method (first increase of OutstandingOperations), should not rely on my second expensive method (second increase of OutstandingOperations). I see how that makes perfectly sense.

Reply

Leon Cullens
Leon Cullens Netherlands
7-1-2012 0:10:47 #

Hi Jonas,

Your second interpretation is correct. Of course one expensive method can call another expensive method, but every task should be able to be performed asynchronously. For example: inserting a new customer and retrieving the last inserted ID (the customer's ID) can't be done asynchronous, they have to be processed after each other.

Hope that helps, you can read more about Asynchronous controllers at msdn.microsoft.com/en-us/library/ee728598.aspx

Reply

Jonas Hovgaard
Jonas Hovgaard Denmark
7-1-2012 0:16:06 #

Alright, I understand.

Yes, it was actually that MSDN article that made me ask the question. If you notice in the example, they call a function called "newsService.GetHeadlinesAsync(city);".

Enough about that - thanks for your time! Smile

Reply

Charandeep Singh
Charandeep Singh India
9-1-2012 18:17:32 #

Good stuff! Thanks!

Another thing I would like to consider here is View Lookup Performance blogs.msdn.com/.../...view-lookup-performance.aspx

We can set only ViewEngine that we will be using. If we use only Razor, we do following:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());


So this saves from looking up for views of ASPX view engine (View.aspx View.asax)

Reply

Leon Cullens
Leon Cullens Netherlands
9-1-2012 19:30:22 #

Thanks for the addition.

As far as I know this only has a benefit when you have multiple view engines right?

Reply

Charandeep Singh
Charandeep Singh India
9-1-2012 19:45:32 #

No, its actually opposite.

As you may know, there are several ViewEngines that we can use with ASP.NET MVC. ASP.NET MVC 3 by default registers two ViewEngines ASPX & Razor. But mostly developers are using Razor now.

So, having two ViewEngines registered by default. The View Lookup goes like this:
~/Views/Home/Index.aspx
~/Views/Home/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml


Now that we know we only going to use Razor for Views. We can eliminate ASPX views lookup by unregistered it from the ViewEngines list. So having only RazorViewEngine registered, MVC will only look for Razor views. So, lookups will be eliminated to:
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml

Reply

Leon Cullens
Leon Cullens Netherlands
9-1-2012 21:09:06 #

Great info, thanks for sharing Smile

Reply

Pingbacks and trackbacks (3)+

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

about

Name: Leon Cullens
Country: The Netherlands
Job: Software Engineer / Entrepreneur
Studied: Computer Science 
Main skills: Microsoft technology (Azure, ASP.NET MVC, Windows 8, C#, SQL Server, Entity Framework), software architecture (enterprise architecture, design patterns), Marketing, growth hacking, entrepreneurship

advertisements

my apps