Web security in MVC4 .NET4.5 with C#: mass assignment aka overposting

This blog post will describe what mass assignment means and how you can protect your MVC4 web site against such an attack. Note that mass assignment is also called overposting.

Before we go into the details let’s set up our MVC4 project. Start Visual Studio 2012 and create an MVC4 internet application with .NET4.5 as the underlying framework.

Locate the Model folder and add a class called Customer. Add the following properties to it:

public class Customer
    {
        public String Name { get; set; }
        public int Age { get; set; }
        public String Address { get; set; }
        public int AccountBalance { get; set; }
    }

Let’s say that our business rules say that every new customer starts with an account balance of zero so the AccountBalance property should have its default value of 0 when creating a new customer.

Add a new controller to the Controllers folder called CustomerController. In the Add Controller window select the Empty MVC controller template. The only action you’ll see in this controller at first is “Index”.

In this controller we’ll simulate fetching our customers from the database. We of course do not have a database and it’s a very bad idea to read directly from the database in a controller but that’s not relevant for our discussion. Add the following method underneath the Index action:

private List<Customer> GetCustomersFromDb()
        {
            return new List<Customer>()
            {
                new Customer(){Address ="Chicago",
                    Age = 30, Name = "John", AccountBalance = 1000}
                , new Customer(){Address = "New York", 
                    Age = 35, Name = "Jill", AccountBalance = 500}
                , new Customer(){Address = "Stockholm",
                    Age = 25, Name = "Andrew", AccountBalance = 800}
            };
        }

This code is quite clear I suppose.

Right-click the Index action and select “Add view” from the context menu. In the Add View windows set the View name to “Index”, check the “Create a strongly-typed view” checkbox and select Customer as the Model class as follows:

Values in Add View to Customer

The corresponding view file Index.cshtml will be created in the Views/Customer folder. Open that file and modify its model declaration to use a List of Customer instead of a single customer as follows:

@model List<MassAssignment.Models.Customer>

In this view we only want to list our customers in an unordered list:

@model List<MassAssignment.Models.Customer>

@{
    ViewBag.Title = "Index";
}

<h2>Customers</h2>

<ul>
    @foreach (var customer in Model)
    {
        <li>Name: @customer.Name</li>
        <li>Address: @customer.Address</li>
        <li>Age: @customer.Age</li>
        <li>Account balance: @customer.AccountBalance</li>
    }
</ul>

We’d like to have a link to this view so open _Layout.cshtml in the Views/Shared folder and locate the ‘nav’ tag. The navigation will already have the following three links:

<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>

Add one more link to the list as follows:

<li>@Html.ActionLink("Customers", "Index", "Customer")</li>

Now run the application and click the Customers link on the opening screen. You should be directed to the View showing our customers as follows:

List of customers

It will probably not win the yearly CSS contest, but this will serve as a good starting point.

The next step will be to build the elements necessary to insert a new Customer.

We need a screen with the necessary fields to add a new Customer and an action that returns that view. Open CustomerController.cs and add the following method:

        [HttpGet]
        public ActionResult Create()
        {
            return View();
        }

This will listen to HTTP Get requests and return a View which we don’t have yet. Right click ‘Create’ and select Add view from the context menu. The View name can be Create. Check the Create a strongly-typed view checkbox and select the Customer class as the underlying model. This will insert the View file Create.cshtml in the Views/Customer folder. At first it’s quite empty:

@model MassAssignment.Models.Customer

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

Add the following HTML/Razor code underneath the h2 tag:

@using (Html.BeginForm())
{
    <fieldset>
        <legend>Customer</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Address)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Address)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Age)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Age)
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

We’re only adding the bare minimum that’s necessary for creating a new customer: labels and fields for all Customer properties bar one. Remember, that the business rules say that every new customer must start with an AccountBalance of 0, this property should not be set to any other value at creation time. Also this form lacks all types of data validation, but that’s not important for our purposes.

We need a link that leads to this View. Open Index.cshtml within the Customer folder and add the following Razor code below the unordered list:

@Html.ActionLink("Create new", "Create", "Customer")

Test the application now to see if you can navigate to the Create view. We need to add the code that will receive the user inputs. Go back to the Customer controller where we just added the Create method. The inputs on Create.cshtml will be posted against the Create view so we need a Create method that will listen to POST requests and accepts a Customer object as parameter.

Insert a method into CustomerController.cs that simulates saving the new Customer object in the database:

        private List<Customer> AddCustomerToDb(Customer customer)
        {
            List<Customer> existingCustomers = GetCustomersFromDb();
            existingCustomers.Add(customer);
            return existingCustomers;
        }

We can now add the overloaded Create method listening to POST requests:

        [HttpPost]
        public ActionResult Create(Customer customer)
        {
            List<Customer> updatedCustomers = AddCustomerToDb(customer);
            Session["Customers"] = updatedCustomers;
            return RedirectToAction("Index");
        }

The incoming Customer object will be populated using the values from the form we created on Create.cshtml. We add the customer to our ‘database’, save the updated list in a Session and redirect to the Index action to see the new list of Customers. Update the Index action to inspect the Session before checking the ‘database’:

public ActionResult Index()
        {
            List<Customer> customers = null;
            if (Session["Customers"] != null)
            {
                customers = Session["Customers"] as List<Customer>;
            }
            else
            {
                customers = GetCustomersFromDb();
            }
            return View(customers);
        }

Run the application and navigate to the Create customer view. Try to add a new Customer by filling out the Name, Address and Age fields. Remember that there is no validation so make sure you insert acceptable values, such as an integer in the Age field. Upon pressing the Create button you will be redirected to the Index view of the Customer controller and the new Customer will be added to the unordered list as follows:

Added a new customer

Note that the AccountBalance property was assigned the default value of 0 according to our business rules. So, we are perfectly safe, right? No-one ever could insert a customer with a positive or negative account balance. Well, it’s not so simple…

When the Create(Customer customer) action receives the POST request then the MVC binding mechanism tries its best to find all constituents of the Customer object. Run the application and navigate to the Create New Customer view. Check the generated HTML code; the form should look similar to this:

<form action="/Customer/Create" method="post">    <fieldset>
        <legend>Customer</legend>
        <div class="editor-label">
            <label for="Name">Name</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" id="Name" name="Name" type="text" value="" />
        </div>
        <div class="editor-label">
            <label for="Address">Address</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" id="Address" name="Address" type="text" value="" />
        </div>
        <div class="editor-label">
            <label for="Age">Age</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" data-val="true" data-val-number="The field Age must be a number." data-val-required="The Age field is required." id="Age" name="Age" type="number" value="" />
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
</form>

Check the ‘name’ attribute of each input tag. They match the names of the properties of the Customer object. These values will be read by the MVC model binding mechanism to build the Customer object in the Create method. An attacker can easily send a HTTP POST request to our action which includes the property value for AccountBalance. You could achieve this with C# as follows:

Uri uri = new Uri("http://validuri/Customer/Create");
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, uri);
List<KeyValuePair<String, String>> postValues = new List<KeyValuePair<string, string>>();
postValues.Add(new KeyValuePair<string, string>("name", "hacker"));
postValues.Add(new KeyValuePair<string, string>("address", "neverland"));
postValues.Add(new KeyValuePair<string, string>("age", "1000"));
postValues.Add(new KeyValuePair<string, string>("accountbalance", "5000000"));
message.Content = new FormUrlEncodedContent(postValues);
HttpClient httpClient = new HttpClient();
Task<HttpResponseMessage> responseTask = httpClient.SendAsync(message);
HttpResponseMessage response = responseTask.Result;

Not too difficult, right? There are all sorts of tools out there which can generate POST requests for you, such as Curl, SoapUI and many more, so you don’t even have to write any real code to circumvent the business rule.

This is a common technique to send all sorts of values that you would not expect in your application logic. Attackers will try to populate your models testing for combinations of property names. In a banking application it is quite feasible that a Customer object will have a property name called exactly AccountBalance. After all the developers and domain owners will most likely not give the AccountBalance property some completely unrelated name, such as “Giraffe” and then create a translation table where it says that Giraffe really means AccountBalance.

What can you do to protect your application against overposting?

Property blacklist

You can use the Bind attribute to build a black list of property names i.e. the names of properties that should be excluded from the model binding process:

public ActionResult Create([Bind(Exclude="AccountBalance")] Customer customer)

You can specify a comma-separated list of strings as the Exclude parameter. These properties will be excluded from the model binding process. Even if the key-value pair from the form includes the AccountBalance property it will be ignored.

Property whitelist

You can use the Bind attribute to build a white-list of property names as follows:

public ActionResult Create([Bind(Include="Age,Name,Address")] Customer customer)

This white-list will include all property names that you want to be bound.

Use the UpdateModel/TryUpdateModel methods

You can use the overloads of UpdateModel or TryUpdateModel methods within the Create method as follows:

UpdateModel(customer, "Customer", new string{"Age","Name","Address"});

You can include the names of the properties in the string array that you want to use in the binding process.

Create a ViewModel

Probably the most elegant solution is to create an interim object – a viewmodel – that will hold the properties you want to bind upon a HTTP POST request. Insert a class called CustomerViewModel in the Models folder:

public class CustomerViewModel
    {
        public String Name { get; set; }
        public int Age { get; set; }
        public String Address { get; set; }
    }

Modify the Create method as follows:

        [HttpPost]
        public ActionResult Create(CustomerViewModel customer)
        {
            Customer newCustomer = new Customer()
            {
                AccountBalance = 0
                ,Address = customer.Address
                ,Age = customer.Age
                ,Name = customer.Name
            };
            List<Customer> updatedCustomers = AddCustomerToDb(newCustomer);
            Session["Customers"] = updatedCustomers;
            return RedirectToAction("Index");
        }

This way you can be sure that you only receive the properties that you expect and nothing else.

You can view the list of posts on Security and Cryptography here.

Web security in .NET4.5 MVC4 with C#: Cross site request forgery

In this post we’ll discuss what a cross-site request forgery means and how you can protect your MVC4 web application against such an attack. The term is abbreviated as CSRF – pronounced ‘see-surf’.

A CSRF is an attack a malicious user can execute using the identity of an unsuspecting user who is logged onto the web application. The effects of the attack can range from annoyances, such as logging out the user from the site, to outright dangerous actions such as stealing data.

Imagine the following: a user with no administrative rights wants to do something on your site that only admins are allowed to do, such as create a new customer in the database. Let’s say that your website is called http://www.mysite.com. This malicious user will then build a site which a user who is logged on to http://www.mysite.com will be tricked into using. Let’s say that the malicious user builds a simple site called http://www.fakesite.com. The attacker will have to know at least a little bit about the HTML of the target site, but that’s easy. He can simply select View Source in IE and similar commands in FF and Chrome and there it is.

Within http://www.mysite.com there will be a page where the user can enter the details of the new customer. The attacker will have to inspect the form element on that site, copy its raw HTML source and paste it to http://www.fakesite.com. He won’t need to build a fully functioning web page: a single-page app will suffice. Let’s say that this single page is called nicecars.html. Let’s imagine that the attacker will actually place some pictures of good cars on this webpage so that it looks real.

Imagine that the original source HTML of the rendered page on http://www.mysite.com looks as follows:

<form action="/Customers/Create" method="post">
    <input id="Name" name="Name" type="text" value="" />
    <input id="City" name="City" type="text" value="" />
    <input id="Country" name="Country" type="text" value="" />
</form>

The attacker will now take this HTML, paste it in http://www.fakesite.com/nicecars.html and insert his own values as follows:

<form action="/Customers/Create" method="post">
    <input id="Name" name="Name" type="text" value="You have been tricked" />
    <input id="City" name="City" type="text" value="Hello" />
    <input id="Country" name="Country" type="text" value="Neverland" />
</form>

The attacker is aiming to insert these values into the database through an authenticated administrator on http://www.mysite.com. In reality these values may be more dangerous such as transferring money from one account to another.

The attacker will also dress up the form a little bit:

<form id="fakeForm" style="display: none;" action="http://www.mysite.com/Customers/Create" method="post">
    <input id="Name" name="Name" type="text" value="You have been tricked" />
    <input id="City" name="City" type="text" value="Hello" />
    <input id="Country" name="Country" type="text" value="Neverland" />
</form>

Note the inline style element: the idea is not to show this form to the administrator. The form must stay invisible on nicecars.html. It’s enough to build a form that has the same structure that http://www.mysite.com expects. Also, as http://www.fakesite.com has nothing to do with Customers, the attacker will not want to post back to his own fake website. Instead he will want to post the data against http://www.mysite.com, hence the updated ‘action’ attribute of the form tag.

You may be wondering how this form will be submitted as there is no submit button. The good news for the attacker is that there’s no need for a button. The form can be submitted using a little JavaScript right underneath the form:

<script>
    setTimeout(function () { window.fakeForm.submit(); }, 2000);
</script>

This code will submit the form after 2 seconds.

If the attacker will try to run his own malicious form then he will be redirected to the Login page of http://www.mysite.com as only admins are allowed to add customers. However, he has no admin rights, so what can he do? He can copy the link to the webpage with the malicious form, i.e. http://www.fakesite.com/nicecars.html, and send it to some user with admin rights on http://www.mysite.com. Examples: insert a link in an email, on Twitter, on Facebook, etc., and then hope that the user will click the link thinking they will see some very nice cars. If the admin actually clicks the link then the damage is done: the values in the malicious form of http://www.fakesite.com/nicecars.html will be posted to http://www.mysite.com/Customers/Create and as the user has admin rights, the new Customer object will be inserted into the database.

The attacker can make it even less obvious that http://www.mysite.com is involved in any way: the form can be submitted with Ajax. The attacker uses the administrative user as a proxy to submit his own malicious data.

This will work seamlessly if the administrative user is logged onto http://www.mysite.com when they click the link to http://www.fakesite.com. Why? When the admin is logged on then the browser will send the authentication cookie .ASPXAUTH along with every subsequent request – even to http://www.fakesite.com. So http://www.fakesite.com will have access to the cookie and send it back to http://www.mysite.com when the malicious form is submitted.

It is clear that the usual authentication and authorisation techniques will not be sufficient against such an attack. You will also want to make sure that the form data that is sent to your application originated from your application and not from an external one. Fortunately this is easy to achieve in MVC4 through the AntiForgeryToken object.

You will need to place this token in two places.

1. On the Controller action that accepts the form data. Example:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CustomerViewModel customerViewModel)

If you now try to enter a new customer by posting against the Create method you’ll receive the following exception:

“The required anti-forgery token from field “_RequestVerificationToken” is not present.”

The reason is that the form that posts against the Create method must have an anti-forgery token which can be verified. You can add this token using a simple Html helper in the .cshtml Razor view. This token will hold a cryptographically significant value. This value will be added to a cookie which the browser will carry along when the form is posted. The value in this cookie will be verified before the request is allowed to go through.

Even if a malicious attacker manages to have an admin click the link to http://www.fakesite.com/nicecars.html they will not be able to set the right cookie value. Even if the attacker knew the exact value of the verification token websites don’t allow setting cookies for another website. This is how an anti-forgery token will prevent a CSRF attack.

2. We set up the verification token on the form as well.

This is easy to do using a Html helper as follows:

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <fieldset>

    </fieldset>
}

With very little effort you can protect your website against a cross-site request forgery.

You can view the list of posts on Security and Cryptography here.

Exception handling in async methods in .NET4.5 MVC4 with C#

In this post we’ll take a look at how to handle exceptions that are thrown by actions that are awaited. My previous post already included some exception handling techniques in MVC4 but here we will concentrate on exceptions thrown by await actions. Check my previous 3 posts for the full story behind the code examples shown here.

We will simulate some problems by intentionally throwing an exception in GetResultAsync and GetDataAsync:

public async Task<String> GetDataAsync(CancellationToken ctk)
        {
            StringBuilder dataBuilder = new StringBuilder();
            dataBuilder.Append("Starting GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            ctk.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            throw new Exception("Something terrible has happened!");
            dataBuilder.Append("Results from the database. ").Append(Environment.NewLine);
            dataBuilder.Append("Finishing GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return dataBuilder.ToString();
        }
public async Task<String> GetResultAsync(CancellationToken ctk)
        {
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.Append("Starting GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            ctk.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            throw new Exception("The service is down!");
            resultBuilder.Append("This is the result of a long running calculation. ");
            resultBuilder.Append("Finishing GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return resultBuilder.ToString();
        }

Also, increase the timeout value of the Index action so that we do not get a Timeout Exception:

[AsyncTimeout(4000)]
[HandleError(ExceptionType = typeof(TimeoutException), View = "Timeout")]
public async Task<ActionResult> Index(CancellationToken ctk)

Before you run the application change the ‘mode’ attribute of the customErrors element in the web.config to “Off” as we want to see the debug data.

It does not come as a surprise that we run into an exception:

Intentional exception YSOD

If you had worked with the TPL library before then you may have expected an AggregateException that wraps all exceptions encountered during the parallel calls. However, TPL behaves slightly differently in conjunction with the await keyword. It is still an AggregateException that is instantiated behind the scenes but the .NET runtime will only throw the first exception that was encountered during the method execution.

This is good news: we can set up our try-catch structures as usual; we don’t need to worry about inspecting an AggregateException anymore.

View the list of MVC and Web API related posts here.

Timeout exceptions with async/await in .NET4.5 MVC4 with C#

This post will discuss timeouts that occur with await and async in .NET4.5. For clarity on async and await in MVC4 check out my previous two blog posts: Await and async in .NET4.5 and Async controllers and actions in .NET4.5 MVC4

As await operations may involve some seriously long running actions, such as calling a slow web service, it can be a good idea to specify a timeout. We may not want to make the visitor wait 60 seconds just to see an error message afterwards. If your experience tells you that a web service normally responds within 5 seconds at most then it may be pointless waiting 50-60 seconds as you can be sure something has gone wrong. ASP.NET has a default request timeout of 90 seconds – correct me here if I’m wrong – but we can specify other values directly in code with an attribute: AsyncTimeout that takes the timeout value in milliseconds as parameter.

In addition to the AsyncTimeout attribute you’ll also need to supply an additional parameter of type CancellationToken to the async action. This parameter can be used by the long running services to check if the user has requested a cancellation. The CancellationToken has an IsCancellationRequested property which provides exactly this type of information. In our example we’ll pass this token to the service calls and use it to throw an exception if the request has been cancelled. As our services are not real service calls, there is no clean-up work to do but imagine that if an IO operation is interrupted by a user then the cancellation token can throw an exception and you can clean up all open resources or roll back the database operations in a catch clause.

You can read more about cancellation tokens on MSDN: Cancellation token on MSDN

Update service methods:

public async Task<String> GetDataAsync(CancellationToken ctk)
        {
            StringBuilder dataBuilder = new StringBuilder();
            dataBuilder.Append("Starting GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            ctk.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            dataBuilder.Append("Results from the database. ").Append(Environment.NewLine);
            dataBuilder.Append("Finishing GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return dataBuilder.ToString();
        }
public async Task<String> GetResultAsync(CancellationToken ctk)
        {
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.Append("Starting GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            ctk.ThrowIfCancellationRequested();
            await Task.Delay(2000);
            resultBuilder.Append("This is the result of a long running calculation. ");
            resultBuilder.Append("Finishing GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return resultBuilder.ToString();
        }

We know that the Index() action needs about 2 seconds to complete so let’s try something more aggressive to see what happens:

[AsyncTimeout(1000)]
        public async Task<ActionResult> Index(CancellationToken ctk)
        {
            DateTime startDate = DateTime.UtcNow;

            HomePageViewModel viewModel = new HomePageViewModel();
            viewModel.AddMessage(string.Concat("Starting Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            CalculationService calcService = new CalculationService();
            DatabaseService dataService = new DatabaseService();

            Task<String> calculationResultTask = calcService.GetResultAsync(ctk);
            Task<String> databaseResultTask = dataService.GetDataAsync(ctk);

            await Task.WhenAll(calculationResultTask, databaseResultTask);

            viewModel.AddMessage(calculationResultTask.Result);
            viewModel.AddMessage(databaseResultTask.Result);

            DateTime endDate = DateTime.UtcNow;
            TimeSpan diff = endDate - startDate;

            viewModel.AddMessage(string.Concat("Finishing Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            viewModel.AddMessage(string.Concat("Action processing time: ", diff.TotalSeconds));
            return View(viewModel);
        }

It is no surprise that we get a timout exception upon running the application:

Timeout YSOD

The yellow screen of death is great for debugging but not so nice in a production environment. To turn on custom error messages you must change web.config: locate the customErrors tag under system.web and change the mode attribute to “On” for the production environment. If your web.config does not have this tag then add it:

<system.web>
    <customErrors mode="On"></customErrors>

There is a default view in the Shared folder within Views called Error.cshtml. After modifying the web.config file the user will be redirected to that view upon an unhandled exception:

Error.cshtml screen

You can of course create custom views for errors and then specify which error view to show using attributes. Example:

[AsyncTimeout(1000)]
        [HandleError(ExceptionType = typeof(TimeoutException), View = "Timeout")]
        public async Task<ActionResult> Index(CancellationToken ctk)

This way you can specify error views for specific types of unhandled exceptions.

The next post will look at exception handling in async methods.

View the list of MVC and Web API related posts here.

Async controllers and actions in .NET4.5 MVC4 with C#

In the previous post we looked at the basics of async and await in a Console app. Now we’re ready to implement these new features in MVC4 to create async controllers.

The way to build asynchronous controllers has been completely changed compared to how it was done in MVC3 with the AsyncController superclass. This class and the inherent complexity in using it are gone in MVC4.

Start Visual Studio 2012 and create an MVC4 Web Application and choose the Internet template. Create a folder called “Services” in the solution. Insert two classes in this folder: DatabaseService and CalculationService. They represent long running calls to external services and have the following content:

DatabaseService.cs:

public class DatabaseService
    {
        public string GetData()
        {
            StringBuilder dataBuilder = new StringBuilder();
            dataBuilder.Append("Starting GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            Thread.Sleep(2000);
            dataBuilder.Append("Results from the database. ").Append(Environment.NewLine);
            dataBuilder.Append("Finishing GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return dataBuilder.ToString();
        }
    }

CalculationService.cs:

public class CalculationService
    {
        public string GetResult()
        {
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.Append("Starting GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            Thread.Sleep(2000);
            resultBuilder.Append("This is the result of a long running calculation. ");
            resultBuilder.Append("Finishing GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return resultBuilder.ToString();
        }
    }

There should be nothing complicated in either class implementation.

Create another folder in the solution called ViewModels. Add a class called HomePageViewModel in that folder:

public class HomePageViewModel
    {
        public List<String> Messages { get; set; }

        public void AddMessage(string message)
        {
            if (Messages == null)
            {
                Messages = new List<string>();
            }
            Messages.Add(message);
        }
    }

Navigate to the Index action of the Home controller and modify it as follows:

public ActionResult Index()
        {
            DateTime startDate = DateTime.UtcNow;

            HomePageViewModel viewModel = new HomePageViewModel();
            viewModel.AddMessage(string.Concat("Starting Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            CalculationService calcService = new CalculationService();
            DatabaseService dataService = new DatabaseService();

            viewModel.AddMessage(calcService.GetResult());
            viewModel.AddMessage(dataService.GetData());

            DateTime endDate = DateTime.UtcNow;
            TimeSpan diff = endDate - startDate;

            viewModel.AddMessage(string.Concat("Finishing Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            viewModel.AddMessage(string.Concat("Action processing time: ", diff.TotalSeconds));
            return View(viewModel);
        }

Nothing complicated here either: we’re just adding messages to the view model, show the thread ids and measure the time it takes to complete the action.

Modify Index.cshtml of the Home view as follows:

@model Mvc4.ViewModels.HomePageViewModel
@{
    ViewBag.Title = "Home Page";
}

<ul>
    @foreach (String message in Model.Messages)
    {
        <li>@message</li>
    }
</ul>

So when you run the web page you should see an output similar to the following:

Screen with no async

It’s easy to see the following:

  • The Index() action blocks the thread when it calls CalculationService and DatabaseService
  • The total processing time took about 4 seconds in total
  • All involved methods executed on the same thread

Now our goal is to make this process more efficient: as it stands now the main thread is only sitting idle for most of the processing time. This can be a serious problem if we’re intending to build a scalable and responsive application.

We need to make a couple of changes to our code:

  • The Index() action needs to return a Task of type ActionResult and turned to an async method: we do not directly return an ActionResult but a Task that represents an ActionResult
  • Remember that if an action is of type async then it needs to have its pair ‘await’ somewhere in the method body
  • We have two long running method calls within the Index() action, so we’ll instruct MVC4 to await them
  • Since we’ll await those two method calls we need to insert async versions of DatabaseService.GetData() and CalculationService.GetResult() as well: they in turn must also return Tasks of type string instead of plain string

Using our experience from the Console app in the previous post we’ll include an async version of GetResult() in CalculationService.cs:

public async Task<String> GetResultAsync()
        {
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.Append("Starting GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            await Task.Delay(2000);
            resultBuilder.Append("This is the result of a long running calculation. ");
            resultBuilder.Append("Finishing GetResult on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return resultBuilder.ToString();
        }

We’ll also insert a GetDataAsync() in DatabaseService.cs:

public async Task<String> GetDataAsync()
        {
            StringBuilder dataBuilder = new StringBuilder();
            dataBuilder.Append("Starting GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(". ");
            await Task.Delay(2000);
            dataBuilder.Append("Results from the database. ").Append(Environment.NewLine);
            dataBuilder.Append("Finishing GetData on thread id ").Append(Thread.CurrentThread.ManagedThreadId)
                .Append(".");
            return dataBuilder.ToString();
        }

Update the Index action of the Home controller to call the async method versions of the services:

public async Task Index()
        {
            DateTime startDate = DateTime.UtcNow;

            HomePageViewModel viewModel = new HomePageViewModel();
            viewModel.AddMessage(string.Concat("Starting Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            CalculationService calcService = new CalculationService();
            DatabaseService dataService = new DatabaseService();

            string calculationResult = await calcService.GetResultAsync();
            string databaseResult = await dataService.GetDataAsync();

            viewModel.AddMessage(calculationResult);
            viewModel.AddMessage(databaseResult);

            DateTime endDate = DateTime.UtcNow;
            TimeSpan diff = endDate - startDate;

            viewModel.AddMessage(string.Concat("Finishing Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            viewModel.AddMessage(string.Concat("Action processing time: ", diff.TotalSeconds));
            return View(viewModel);
        }

Run the web app now and you may see an output similar to the following:

MVC screen with async in place

Note the following:

  • The Index action started on thread id 10
  • The same thread enters GetResultAsync
  • The main thread exits GetResultAsync at the await keyword and thread 9 takes over
  • Thread 9 enters GetDataAsync and exits at the await keyword and thread 5 takes over
  • Thread 5 finished Index()
  • The total processing time is still about 4 seconds, but remember from the previous post: if you want to couple asynchronous methods with concurrency you need to include the TPL as well. This will be resolved later in this post.

The output on your screen may be different when you run the sample. It is not guaranteed that 3 different threads will be involved throughout the lifetime of the Index() action. It depends on the availability of threads, may only be 2 in total.

If you are working with WCF services and add a service reference to your project then you’ll have the option to generate the Async() versions of the service calls automatically. Make sure to select the ‘Allow generation of asynchronous operations’ checkbox and the ‘Generate task-based operations’ radiobutton in the Service Reference Settings window.

.NET4.5 is now interspersed with built-in async versions of long running and/or remote methods. Typical examples include: HttpClient.SendAsync that returns a Task of type HttpResponseMessage, or HttpContent.ReadAsStringAsync that returns a Task of type String.

We can now introduce TPL to make the service calls run in parallel. As it stands now the Index action first waits for GetResultAsync to finish before it goes on with GetDataAsync. Ideally Index should wait for both actions to complete in parallel and not one after the other. We will basically hold a reference to the Task values returned by the services and await them both together.

Update the Index action as follows:

public async Task<ActionResult> Index()
        {
            DateTime startDate = DateTime.UtcNow;

            HomePageViewModel viewModel = new HomePageViewModel();
            viewModel.AddMessage(string.Concat("Starting Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            CalculationService calcService = new CalculationService();
            DatabaseService dataService = new DatabaseService();

            Task<String> calculationResultTask = calcService.GetResultAsync();
            Task<String> databaseResultTask = dataService.GetDataAsync();

            await Task.WhenAll(calculationResultTask, databaseResultTask);

            viewModel.AddMessage(calculationResultTask.Result);
            viewModel.AddMessage(databaseResultTask.Result);

            DateTime endDate = DateTime.UtcNow;
            TimeSpan diff = endDate - startDate;

            viewModel.AddMessage(string.Concat("Finishing Action on thread id ", Thread.CurrentThread.ManagedThreadId));
            viewModel.AddMessage(string.Concat("Action processing time: ", diff.TotalSeconds));
            return View(viewModel);
        }

Note the following:

  • We do not await the two service calls one by one
  • Both of them will be awaited using Task.WhenAll
  • Task.WhenAll accepts an array of Task objects that should run in parallel
  • Task.WhenAll will block until all tasks in the array have finished
  • The await keyword will make sure that Index will wait upon all tasks to complete in the array
  • To retrieve the returned value from the service calls just use the Result property of the Task object: this will be populated if the Task has a return value i.e. it is a Task of some type

When you run the updated Index page you may see something like this:

Screen with async and TPL

Again, your results will almost certainly differ. Which thread is allocated to which task is up to the thread scheduler. Refresh the page a couple of times to see some different results.

In the next post we’ll wrap up the discussion with some miscellaneous issues such as timeouts or exception handling in conjunction with asynchronous controllers.

View the list of MVC and Web API related posts here.

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: