This is the first in a twice weekly series of blog posts that we’re going to share from the Microsoft Web Development blog to showcase some of the really cool and mature features available in ASP.NET Web Forms for developers to use in their projects. Web Forms is the development model that was deployed first with ASP.NET in 2001, and has been improved in every ASP.NET release since. Throughout this series of posts, we will build a sample application to track our travel information. Each update will provide a link to the source code so that you can follow along.
In this first post, we’re going to look at the model binding feature in web forms and see how it has been improved to provide for asynchronous operations in the new ASP.NET 4.6 framework in Visual Studio 2015.
In the beginning
Many long-time ASP.NET developers are familiar with the various ways that you can deliver data to user interface components in web forms. We call that databinding to a control, and there are two primary techniques you can use:
- Declarative data-binding with data controls
- Code-behind, or “manual” data-binding on the server
Declarative data-binding is where all of our database code is placed into a SqlDataSource object on the ASPX page and a grid or some other user interface control references the id of the SqlDataSource to know how to work with a data resource. Let’s start our first code sample with a simple grid bound to a table of trip information:
The advantage of this technique is that all of the code for the user interface, server-side operations, and database commands are all in one file. The disadvantage with this approach is that when any of your database resources changes or matures, you need to touch all of your ASPX files to update and maintain those objects. This is particularly a problem as a database evolves over time.
A manual code-behind method would connect data access to controls through a series of events that are appropriate to each control. The advantage of this approach is that custom code can be written to centralize data access using a repository class strategy. The disadvantage of this technique is that a lot more code needs to be written to connect these classes and I need to ensure that the data content of the grid is loaded in the correct event during the page lifecycle. I could put the contents of my grid into ViewState, but that would bloat the size of the page, a major faux pas in today’s web.
Along came Model Binding
With the release of ASP.NET 4.5 in 2012, a simpler and friendlier data binding solution was introduced called Model Binding. This was an adjustment of a similar feature that was developed for ASP.NET MVC so that the Web Forms framework could experience the productivity gains of this simplified programming model. The idea with Model Binding is that each data-bound control has four simple methods for each of the CRUD operations:
These methods are available to the control as a property or an attribute that can be specified at design-time. With some editor auto-completion magic, Visual Studio will generate the appropriate signature for these methods in the code-behind of the page you are working on. Let’s take a look at the GridView control demonstrated in the previous code listings and update it to use model binding for the SelectMethod:
That looks fairly simple, as we moved the code to read the data into the myGrid_GetData method in the code behind. The advantage here is that all of my data access code is in one method in the code-behind and that method will be called by the page when it is needed. You can use a repository model with this technique, and really take advantage of that design strategy by implementing the OnCallingDataMethods event handler. Inside the arguments passed through this event is a property called DataMethodsObject that contains the instance of the repository object that houses the methods you want ASP.NET to call for your control. Let’s take a look at a page with a FormView control so that we can manage the details of a trip, and add SelectMethod, InsertMethod, and UpdateMethod attributes:
What’s with the [QueryString] markup in the myForm_GetItem method on line 11? That is a hint that indicates to the ASP.NET page where it can acquire a value to pass in to the SelectMethod if it has not been explicitly called. In the case of this page, the id is delivered in the querystring parameter “id”.
Next question: where does the id argument come from in the UpdateMethod? There is no querystring argument on this method, but the id is declared as a DataKeyNames value on the FormView. This tells ASP.NET that the primary key for the trip object is the id property and to use that in submitting updates. The argument name on the UpdateMethod is the same name as the name of the property in the DataKeyNames attribute in my ASPX markup.
Last question: In the InsertMethod and UpdateMethod there is a call to TryUpdateModel. What is that method call? This is a helper method that will attempt to pass values that were submitted to the page into the model object that is passed into TryUpdateModel. This prevents that silly mapping code that you always ended up writing like this:
Too cool! I don’t have to write all of that annoying code and I can now move my data-access methods for this control to a class library or even use dependency injection to pass an interface for a concrete class:
The major disadvantage to this approach is that it runs synchronously within the page lifecycle and you can’t really take advantage of async and await methods to access the datastore.
Async Model Binding in ASP.NET 4.6
Web Forms has had the ability to run forms asynchronously since ASP.NET 3.5 back in 2008. However, you needed to construct your own threads and map the callback through an event to be processed by the form before the content was delivered to your visitors. My mind melted every time I tried to debug a page that I built with this technique. It worked great, but was real hard to maintain.
With C# 5 and ASP.NET MVC 5, asynchronous programming became a lot easier to follow and build with the async and await keywords and delivering Task objects. The promise was simple: use these keywords and the host application will release threads while awaiting for background processing. This makes great sense for use in database interactions, as we do not want to block web server requests while awaiting database processing.
I can adapt the gridview to use asynchronous processing with the database in three simple steps.
Step 1: Add an async=true modified to the @Page directive:
<%@ Page Language="C#" Async="true" Title="My Trips" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="TravelDemo.Trips.Default" MasterPageFile="~/Site.Master" %>
Step 2: Add an async keyword to my select method and wrap the return object in a Task
public async Task<IEnumerable<Models.Trip>> myGrid_GetData()
Step 3: Await the appropriate async version of the data access method
return await ctx.Trips.OrderBy(t => t.DepartureDateTimeUtc).ToListAsync();
That’s it for our grid. The FormView and its data modification methods can be updated similarly in their repository class:
Data access has come a long way with ASP.NET, and with the latest updates we can be extremely productive with high-end multi-threading capabilities with small changes to our existing single-threaded code base. Unfortunately, with great code power comes great responsibility. If you make every database call on your application async, that means that the web server will release threads and handle more volume for you while database processing occurs. Cool! However, the database will feel the direct force of every query with no processing boundary between the web browser and the query. Your DBA may see traffic surge, so use these techniques wisely.
The source code branch for this project in Visual Studio 2015 is available on GitHub. Take a look at the before and AsyncModelBinding branches to understand how the project evolved from the 2001 data access model to the async programming model of today.