January 2011 - Posts - Jon Galloway

January 2011 - Posts

Preventing Open Redirection Attacks in ASP.NET MVC

Summary

ASP.NET MVC 3 includes a new change in the the Account Controller to prevent open redirection attacks. After explaining how open redirection attacks work, I'll This tutorial explains how you can prevent open redirection attacks in your ASP.NET MVC applications. This tutorial discusses the changes that have been made in the AccountController in ASP.NET MVC 3 and demonstrates how you can apply these changes in your existing ASP.NET MVC 1.0 and 2 applications.

What is an Open Redirection Attack?

Any web application that redirects to a URL that is specified via the request such as the querystring or form data can potentially be tampered with to redirect users to an external, malicious URL. This tampering is called an open redirection attack.

Whenever your application logic redirects to a specified URL, you must verify that the redirection URL hasn’t been tampered with. The login used in the default AccountController for both ASP.NET MVC 1.0 and ASP.NET MVC 2 is vulnerable to open redirection attacks. Fortunately, it is easy to update your existing applications to use the corrections from the ASP.NET MVC 3 Preview.

To understand the vulnerability, let’s look at how the login redirection works in a default ASP.NET MVC 2 Web Application project. In this application, attempting to visit a controller action that has the [Authorize] attribute will redirect unauthorized users to the /Account/LogOn view. This redirect to /Account/LogOn will include a returnUrl querystring parameter so that the user can be returned to the originally requested URL after they have successfully logged in.

In the screenshot below, we can see that an attempt to access the /Account/ChangePassword view when not logged in results in a redirect to /Account/LogOn?ReturnUrl=%2fAccount%2fChangePassword%2f.

Since the ReturnUrl querystring parameter is not validated, an attacker can modify it to inject any URL address into the parameter to conduct an open redirection attack. To demonstrate this, we can modify the ReturnUrl parameter to http://bing.com, so the resulting login URL will be /Account/LogOn?ReturnUrl=http://www.bing.com/. Upon successfully logging in to the site, we are redirected to http://bing.com. Since this redirection is not validated, it could instead point to a malicious site that attempts to trick the user.

A more complex Open Redirection Attack

Open redirection attacks are especially dangerous because an attacker knows that we’re trying to log into a specific website, which makes us vulnerable to a phishing attack. For example, an attacker could send malicious e-mails to website users in an attempt to capture their passwords. Let’s look at how this would work on the NerdDinner site. (Note that the live NerdDinner site has been updated to protect against open redirection attacks.)

First, an attacker sends us a link to the login page on NerdDinner that includes a redirect to their forged page:

http://nerddinner.com/Account/LogOn?returnUrl=http://nerddiner.com/Account/LogOn

Note that the return URL points to nerddiner.com, which is missing an “n” from the word dinner. In this example, this is a domain that the attacker controls. When we access the above link, we’re taken to the legitimate NerdDinner.com login page.

Figure 02: NerdDinner login page with an open redirection

When we correctly log in, the ASP.NET MVC AccountController’s LogOn action redirects us to the URL specified in the returnUrl querystring parameter. In this case, it’s the URL that the attacker has entered, which is http://nerddiner.com/Account/LogOn. Unless we’re extremely watchful, it’s very likely we won’t notice this, especially because the attacker has been careful to make sure that their forged page looks exactly like the legitimate login page. This login page includes an error message requesting that we login again. Clumsy us, we must have mistyped our password.

When we retype our user name and password, the forged login page saves the information and sends us back to the legitimate NerdDinner.com site. At this point, the NerdDinner.com site has already authenticated us, so the forged login page can redirect directly to that page. The end result is that the attacker has our user name and password, and we are unaware that we’ve provided it to them.

Looking at the vulnerable code in the AccountController LogOn Action

The code for the LogOn action in an ASP.NET MVC 2 application is shown below. Note that upon a successful login, the controller returns a redirect to the returnUrl. You can see that no validation is being performed against the returnUrl parameter.

[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        if (MembershipService.ValidateUser(model.UserName, model.Password))
        {
            FormsService.SignIn(model.UserName, model.RememberMe);
            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Now let’s look at the changes to the ASP.NET MVC 3 LogOn action. This code has been changed to validate the returnUrl parameter by calling a new method in the System.Web.Mvc.Url helper class named IsLocalUrl().

[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        if (MembershipService.ValidateUser(model.UserName, model.Password))
        {
            FormsService.SignIn(model.UserName, model.RememberMe);
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

This has been changed to validate the return URL parameter by calling a new method in the System.Web.Mvc.Url helper class, IsLocalUrl().

Protecting Your ASP.NET MVC 1.0 and MVC 2 Applications

We can take advantage of the ASP.NET MVC 3 changes in our existing ASP.NET MVC 1.0 and 2 applications by adding the IsLocalUrl() helper method and updating the LogOn action to validate the returnUrl parameter.

The UrlHelper IsLocalUrl() method actually just calling into a method in System.Web.WebPages, as this validation is also used by ASP.NET Web Pages applications. 

public bool IsLocalUrl(string url) {
    return System.Web.WebPages.RequestExtensions.IsUrlLocalToHost(
        RequestContext.HttpContext.Request, url);
}

The IsUrlLocalToHost method contains the actual validation logic, as shown below.

public static bool IsUrlLocalToHost(this HttpRequestBase request, string url) {
    if (url.IsEmpty()) {
        return false;
    }
 
    Uri absoluteUri;
    if (Uri.TryCreate(url, UriKind.Absolute, out absoluteUri)) {
        return String.Equals(request.Url.Host, absoluteUri.Host, StringComparison.OrdinalIgnoreCase);
    }
    else {
        bool isLocal = !url.StartsWith("http:", StringComparison.OrdinalIgnoreCase)
            && !url.StartsWith("https:", StringComparison.OrdinalIgnoreCase)
            && Uri.IsWellFormedUriString(url, UriKind.Relative);
        return isLocal;
    }
}

In our ASP.NET MVC 1.0 or 2 application, we’ll add a IsLocalUrl() method to the AccountController, but you’re encouraged to add it to a separate helper class if possible. We will make two small changes to the ASP.NET MVC 3 version of IsLocalUrl() so that it will work inside the AccountController. First, we’ll change it from a public method to a private method, since public methods in controllers can be accessed as controller actions. Second, we’ll modify the call that checks the URL host against the application host. That call makes use of a local RequestContext field in the UrlHelper class. Instead of using this.RequestContext.HttpContext.Request.Url.Host, we will use this.Request.Url.Host. The following code shows the modified IsLocalUrl() method for use with a controller class in ASP.NET MVC 1.0 and 2 applications.

//Note: This has been copied from the System.Web.WebPages RequestExtensions class
private bool IsLocalUrl(string url)
{
    if (string.IsNullOrEmpty(url))
    {
        return false;
    }

    Uri absoluteUri;
    if (Uri.TryCreate(url, UriKind.Absolute, out absoluteUri))
    {
        return String.Equals(this.Request.Url.Host, absoluteUri.Host, StringComparison.OrdinalIgnoreCase);
    }
    else
    {
        bool isLocal = !url.StartsWith("http:", StringComparison.OrdinalIgnoreCase)
            && !url.StartsWith("https:", StringComparison.OrdinalIgnoreCase)
            && Uri.IsWellFormedUriString(url, UriKind.Relative);
        return isLocal;
    }
}

Now that the IsLocalUrl() method is in place, we can call it from our LogOn action to validate the returnUrl parameter, as shown in the following code.

[HttpPost] 
public ActionResult LogOn(LogOnModel model, string returnUrl) 
{ 
    if (ModelState.IsValid) 
    { 
        if (MembershipService.ValidateUser(model.UserName, model.Password)) 
        { 
            FormsService.SignIn(model.UserName, model.RememberMe); 
            if (IsLocalUrl(returnUrl)) 
            { 
                return Redirect(returnUrl); 
            } 
            else 
            { 
                return RedirectToAction("Index", "Home"); 
            } 
        } 
        else 
        { 
            ModelState.AddModelError("", "The user name or password provided is incorrect."); 
        } 
    }
}

Now we can test an open redirection attack by attempting to log in using an external return URL. Let’s use /Account/LogOn?ReturnUrl=http://www.bing.com/ again.

After successfully logging in, we are redirected to the Home/Index Controller action rather than the external URL.

Taking Additional Actions when an open redirect attempt is detected

The LogOn action can take additional actions in the case an open redirect is detected. For instance, you may want to log this as a security exception using ELMAH and display a custom logon message that lets the user know that they've been logged in but that the link they clicked may have been malicious. That logic goes in the "else" block in the LogOn action.

[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        if (MembershipService.ValidateUser(model.UserName, model.Password))
        {
            FormsService.SignIn(model.UserName, model.RememberMe);
            if (IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                // Actions on for detected open redirect go here. 
                string message = string.Format("Open redirect to to {0} detected.", returnUrl);
                ErrorSignal.FromCurrentContext().Raise(new System.Security.SecurityException(message));
                return RedirectToAction("SecurityWarning", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or password provided is incorrect.");
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Recap

Open redirection attacks can occur when redirection URLs are passed as parameters in the URL for an application. The ASP.NET MVC 3 template includes code to protect against open redirection attacks. You can add this code with some modification to ASP.NET MVC 1.0 and 2 applications. To protect against open redirection attacks when logging into ASP.NET 1.0 and 2 applications, add an IsLocalUrl() method and validate the returnUrl parameter in the LogOn action.

Note: This information (minus the "additional actions" bit) is available in the Security Tutorials section on the ASP.NET website.

Posted by Jon Galloway | 25 comment(s)
Filed under: ,

FIX: WCF Data Service with Entity Framework Code-First DbContext doesn’t accept updates

Summary

The Entity Framework Code First DbContext doesn’t expose the interfaces to support updates when exposed via WCF Data Services. Attempting to save changes results in a fault with the message "The data source must implement IUpdatable or IDataServiceUpdateProvider to support updates." The fix is to alter your WCF Data Service to expose the DbContext's underlying ObjectContext and to disable proxy generation.

Background

Jesse Liberty and I have stumbled into some frontier country. Our work on a Windows Phone + WCF Data Services + EF Code First + MVC 3 solution for the The Full Stack has put us in that delightful developer place where the combination of two or more pre-release or newly-released bits can feel like you're in primordial-ooze-technical-preview stage. Honestly, it's our job to get there before you do, and we love it.

I've been pushing Entity Framework Code-First to anyone who will listen. My kids are sick of hearing about it... "yes, dad, POCO object, no config, we GET it!"

We'd figured out how to connect all the pieces together so that data in an ASP.NET MVC 3 using SQL CE and EF Code First could expose data to a Windows Phone client via a WCF Data Service. That sounds like a lot of moving parts, but it actually went pretty smoothly once we figured out the steps, as documented here.

The problem - the service was read-only

Our goal is to build out a contact manager application that allows you to quickly store and look up contacts by description, e.g. short, long hair, works for Microsoft, met at MIX 2010.

Our phone client did a fine job of reading the data from the service, but our attempts to save changes back to the service gave some errors that were hard to troubleshoot. Based on some help from Chris "Woody" Woodruff, we changed our update method to stop using batching, and we started seeing the specific error message: "The data source must implement IUpdatable or IDataServiceUpdateProvider to support updates."

That error message led me to a comment on a post by Rowan Miller on using WCF Data Services against a DbContext, which is exactly what we were doing. Rowan points out that if you're going by experience or documentation on using Entity Framework (before Code First), you'd create a simple WCF Data Service and point it at your Context class, and everything would work. However:

Now what if your BlogContext derives from DbContext instead of ObjectContext? In the current CTP4 you can’t just create a DataService of a derived DbContext, although you can expect this to work by the time there is an RTM release.

But there is some good news, DbContext uses ObjectContext under the covers and you can get to the underlying context via a protected member.

Aha, you say - that was for CTP4, and there's a newer release. Read on...

DbContext as a lightweight, convention-based wrapper over ObjectContext

DbContext is really easy to work with. It does the basic things you'd expect from ObjectContext, like tracking changes to your entities, batching changes, etc. The DbContext also adds some other convention-based goodness on top, though, which does things like infer what the database connection should be based on the the entity name, creating the database if it doesn't exist, etc.

However, the DbContext abstracts away / hides some capabilities in the ObjectContext. Usually, those capabilities aren't things you'll miss, but occasionally you will. In this case, we need IUpdatable support, as the MSDN documentation for DataService<T> explains:

The type of the DataService<T> must expose at least one property that returns an entity set that is an IQueryable<T> collection of entity types. This class must also implement the IUpdatable interface to enable updates to be made to entity resources.

The workaround - Expose the DbContext's base ObjectContext

Rowan Miller explains the workaround, which involves two short steps. Fortunately, since he posted that info (valid for CTP4), it's become even easier, since DbContext directly exposes the ObjectContext with your having to write a property to expose it. Prior to EF Code First CTP 5, if you wanted to call into the base ObjectContext, you had to expose the underlying context via a property, like this:

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public ObjectContext UnderlyingContext
    {
        get { return this.ObjectContext; }
    }
}

In CTP5, the DbContext implements IObjectContextAdapter, which exposes the ObjectContext. So, putting that together, if you want to set up a WCF Data Service that exposes a DbContext named PersonContext, you’ll need to change from this:

public class PersonTestDataService : DataService<PersonContext>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }
}

to this:

public class PersonTestDataService : DataService<ObjectContext>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }

    protected override ObjectContext CreateDataSource()
    {
        var ctx = new PersonContext();
        var objectContext = ((IObjectContextAdapter)ctx).ObjectContext;
        objectContext.ContextOptions.ProxyCreationEnabled = false;
        return objectContext;
    }
}

Here’s the summary of what we did:

  • The DataService is typed as ObjectContext rather than the class which implemented DbContext (PersonContext).
  • We override CreateDataSource to get at the PersonContxt’s underlying ObjectContext, returning the ObjectContext as the result.
  • We disable Proxy Creation, which apparently does something magical. I just got it from Rowan Miller’s post and it works, so I’m not going to complain.

Updating the client proxy

If you’d previously created a client service proxy (via DataSvcUtil) for the service, you’ll need to regenerate it because the ObjectContext based WCF Service now implements IUpdatable and thus exposes some new additional methods. In our test case - a flat object model with one class holding simple strings - the changes to the client proxy class were to set up property change notifications:

  • public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
  • protected virtual void OnPropertyChanged(string property)

Fun with IObjectContextAdapter

I’ve run into other circumstances where I needed access to the DbContext’s ObjectContext – for instance working with the ObjectStateManager. It’s nice to be able to cast a DbContext to an IObjectContextAdapter to get at the ObjectContext, then go to town with the base ObjectContext.

Posted by Jon Galloway | 2 comment(s)
Filed under: , ,

Web Camps India

I had the opportunity to help lead two Web Camps in India with James Senior in November.

Web Camps? Web Camps are an international series of free one and two day training events which showcase the Microsoft web platform. The camp is targeted at a broad range of developer background and experience. Content builds from 101 level introductory material to 200-300 level coverage. We start with an overview of the Microsoft web platform, then move on to building an application with ASP.NET MVC 3, Entity Framework, and jQuery, and finish the day with some advanced topics. The second day features proctored hands on labs and one-on-one question and answers.

I’m a veteran Web Camper, having been to seven before the India trip: Toronto, San Francisco, London, Munich, Chicago, New York, and Los Angeles. I blogged about the first Web Camp in Toronto.

The Web Camps team has a lot of events scheduled through June. Those currently announced include 7 large two day events and 42 one day events. The full schedule is here: http://webcamps.ms/upcoming-web-camps.aspx

Updated Presentation Content

We updated all the content for this trip – slides and demos – to cover ASP.NET MVC 3, NuGet, Entity Framework 4 (Code First), and Microsoft's recent contributions to jQuery. The agenda was as follows:

  • Microsoft Web Platform Overview - Web Platform, ASP.NET, WebPI, WebMatrix
  • ASP.NET MVC 3 Fundamentals – Models, Views, Controllers, Razor Syntax
  • HTML Helpers, Forms, Unobtrusive Ajax
  • Data Access & Modelling with Entity Framework Code First
  • Validation in MVC 3 – Data annotations, unobtrusive jQuery
  • jQuery Contributions – Templating, Data-Linking and Globalization
  • Advanced ASP.NET MVC 3 – Dependency Injection, Global Action Filters, Custom Validation and more as time and questions permit

Observations on presenting with the bleeding edge stack

The new stack (MVC3 / NuGet / EF Code First / etc.) is a lot of fun to build with, and attendees seemed to like it pretty well. The tradeoff with presenting technical demos running on pre-release bits, of course, is that it’s harder to make everything work well together.

Code / demo recycle time

  • Internet Explorer 9 Beta / Page Not Found on first page load after build slows down the flow in demonstrations where you're doing a lot of code/run cycles. Note: this is a known issue, and workarounds are available. This is an IE9 beta issue that’s been reported in connect, so I’ll bet you $8 that it’ll be fixed momentarily.
  • SQL Server CE 4 database "file exists" errors which go away when you refresh the application. This appears to be an issue with CTP releases our demos were built on, as I haven’t seen it pop up with the latest SQL Server CE 4 CTP. If you are seeing these, upgrade to the latest SQL Server CE CTP.

The combined impact of these wasn't obvious when building the demos, but was compound when presenting them, as the errors slow down the flow and distract from the demo. I presented one of these demonstrations the following week not using IE9 and dropping the database backing during the validation portion of the demo, and the small incremental difference in each cycle made that presentation a lot smoother.

NuGet Local Repository

I pulled down a local copy of the NuGet repository for the demonstration, which worked really well. We didn't need to rely on the internet connection (hotel wifi at one event), and we didn't need to worry about an update to the NuGet feed breaking our demo (note: I've noticed this happen to other folks). I used Steve Michelotti's Powershell script for that, but the feed’s changed a bit since then. I’ve got an updated script I’ll post soon.

Learning from other presentations

I referred to Scott Hanselman’s PDC10: Building a Blog with Microsoft "Unnamed Package of Web Love" presentation when things went wrong when we were building the demos. Captain Obvious tells us that speakers (myself included, too, I guess) make choices in the flow of their demos so that they… well, work. In some cases this might be to work around known issues with the early bits, or maybe they just got lucky and didn’t run down the dark alleys (note: this is not me – I have a knack for hitting every potential dead end in pre-release bits). Those few places where our demos didn’t run as smoothly as they could have, I noticed that other demos avoided the problems through the order or combination of how things were built. Note that in general I think the whole stack is working really well and is feeling just about baked by now, but there’s a reason for CTP / beta / RC releases.

The Camps – Bangalore and Hyderabad

The event planners put a lot of effort into making this a top-notch event, and it really showed. The stage setup included three screens - a center screen which showed the live video of the presenter, and two side screens showing the computer monitor display.

Web Camps - Bangalore - Screens

The center screen video was fed by a videographer who filmed the entire event, which was also live-streamed to 10,000 live viewers throughout India. James and I kept things pretty interactive and informal, with a lot of live coding and on the fly coding in response to questions. Scott Hanselman showed up via live video chat for both events, which was very well received by the crowd.

Web Camps - Hyderabad - 07 Web Camps - Photos - Bangalore - 09 Web Camps - Photos - Bangalore - 07 Web Camps - Photos - Bangalore - 06

Lab Content

There were two different options for attendees during the Lab day. Attendees who wanted to review ASP.NET MVC basics and application building worked through the MVC Music Store.

Those that wanted to dig into specific topics in more detail used the Web Camps Training Kit. James' team has done a complete update of the Web Camps Training Kit for MVC 3, and most of these labs build on the MVC Music Store tutorial, which seemed to work really well as a common reference point. Available labs included:

  • ASP.NET MVC 3 Fundamentals
  • ASP.NET MVC 3 Razor
  • ASP.NET MVC 3 Models and Data Access
  • ASP.NET MVC 3 Helpers, Forms and Validation
  • ASP.NET MVC 3 Testing
  • ASP.NET MVC 3 Custom Validation
  • ASP.NET MVC 3 Custom Action Filters
  • ASP.NET MVC 3 Global and Dynamic Action Filters
  • ASP.NET MVC 3 Dependency Injection
  • ASP.NET MVC 3 NuGet

These labs were very well received. James and I have previously been kept very busy proctoring labs at previous Web Camps, but the new lab content is so thorough that the lab day was pretty quiet. We were able to dig into some in-depth questions and discussions which wouldn’t have been possible previously. The Training Kit material is available online at http://trainingkit.webcamps.ms and is highly recommended.

Web Camps - Hyderabad - 01

Attendance

The Web Camps India events were a big hit. We had about 550 attendees in person, and the events were live streamed throughout India bringing total attendance to 10,680 developers. The combination of the live event plus regional live streaming seems like a great way to involve as many people as possible.

Web Camps - Hyderabad - 02

After James and I left, local Developer Evangelists continued the Web Camps tour to Chennai (1st December, 2010) and Pune (3rd December, 2010) – with a cumulative attendance of 460 Developers and Technology Decision Makers.

Response

“Event exceeded my expectations … conduct these type of sessions regularly when new technologies are released/announced … “
(Kasha Kullai | Hyderabad)

"@JonGalloway & @JSenior just finished a beautiful session on Entity Framework Code First #Microsoft #Webcamps #Hyderabad"
(@kashyapa | Hyderabad)

“Conduct more such events to help developers learn new concepts.“
(Nithya A | Bangalore)&

"It was great to have both of you presenting. Fabulous Presentations :)"
(@uma_kanth‎ | Bangalore)

Attendees were very engaged and asked a lot of questions.

Web Camps TV #10 - Update from Web Camp India

While in Bangalore, James and I had the opportunity to talk with Harish Ranganathan on Web Camps TV about what's new in the Microsoft Web Platform and how the Web Camps were going.

Web Camps - Hyderabad - 05

Get Microsoft Silverlight

Observations

A lot of the questions – both during the sessions and labs – came from local trainers and educators. My guess is that the Web Camp served as a touch point with Microsoft that filled a void since PDC and TechEd are out of travelling range from India for a lot of people. It was really cool to work directly with people who were going to be passing the information along to others.

The technology demographic seemed pretty similar to what I've seen at Web Camps and Europe.

  • A lot of folks are doing Web Forms development, and have a lot of questions about how to adapt from control oriented development to the MVC pattern. The most common questions continue to be on Grids and Charts, so it's nice to have the new Web Helpers for both of those scenarios.
  • Those that are doing ASP.NET MVC development are generally at the intermediate to advanced intermediate level. For instance, we had a lot of great questions on how some Microsoft frameworks integrate with and compare to open source options.

The local Microsoft team in India was well organized and really engaged with the community. They put a lot of effort into the event, and it really paid off.

This was a fabulous opportunity to work with developers from a dramatically different culture who have a shared passion for web development. Namaste!

ASP.NET MVC 3–What’s in it for you?

ASP.NET MVC 3 was released on January 13. You've probably seen the blog posts, especially:

You can get the feature summary, including an in-depth MVC 3 feature overview, at http://asp.net/mvc/mvc3. I coordinate that page and make sure the MVC 3 Release Notes get htmlized, so we could talk bullet points all day long. But let's try to boil it down to the proverbial elevator pitch. Why should you care that ASP.NET MVC 3 is release enough to start using it today? Why would your pointy-haired boss consider using MVC 3 on your next project?

Beyond Bullet Points - The Elevator Pitch

I've been doing training events and writing tutorial/sample code using ASP.NET MVC 3 for a while, and I've been thinking about the overall "feel" of ASP.NET MVC 3 from the viewpoint of a developer. I just finished a full rewrite of the MVC Music Store tutorial for MVC 3 (using Razor views layouts, NuGet, and Entity Framework Code-First), and that provided a great opportunity to compare the overall development experience in building the exact same application twice in two different versions of ASP.NET MVC.

Note: I didn't write the tutorial update with this in mind, so I'd like to think this comparison, despite other possible faults, is at least an honest one.

Looking back at the ASP.NET MVC releases, I'd summarize them as follows:

  • ASP.NET MVC 1 - The best of ASP.NET and .NET development with the benefits of the maintainable, testable, extensible MVC pattern
  • ASP.NET MVC 2 - Templating and attribute-based validation which make it easier to display information and forms
  • ASP.NET MVC 3 - Razor makes views simpler, NuGet makes takes the risk and pain out of open source, and advanced features make extensibility painless

Of course, all three releases  have included a lot of advanced features and aren’t really focused on a single theme. We tried to pin Phil down to a theme for the ASP.NET MVC 2 release when we interviewed him on Herding Code, and he refused to go along with it. MVC releases have an agile development feel – each release includes the top features that will bring the most value to the ASP.NET MVC community community, so as in any agile project I’ve worked on, the theme for each release is “the best software we could possibly give you today.”

So I focused instead on how I’d summarize each release to someone who was interested, but not convinced. I really tried to distill the feel of MVC 3 down to a single phrase, and I just couldn't do it. Let’s look at the three joys of ASP.NET MVC 3 from a developer perspective.

Razor makes views feel more like HTML, less like code

When you’re writing View code, your focus is on generating HTML to represent your model. You’ve got two contexts to consider – static HTML and code that writes HTML based on your model. Switching between those contexts is distracting. Razor is a new view engine for ASP.NET MVC that makes your view code feel more like a view and less like code. Here's the Store/Browse view in ASP.NET MVC 3 with Razor:

@model MvcMusicStore.Models.Genre

@{
    ViewBag.Title = "Browse Albums";
}

<div class="genre">
    <h3><em>@Model.Name</em> Albums</h3>

    <ul id="album-list">
        @foreach (var album in Model.Albums)
        {
            <li>
                <a href="@Url.Action("Details", new { id = album.AlbumId })">
                    <img alt="@album.Title" src="@album.AlbumArtUrl" />
                    <span>@album.Title</span>
                </a>
            </li>
        }
    </ul>
</div>

Here's that same view in ASP.NET MVC 2 using the Web Forms View Engine:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcMusicStore.ViewModels.StoreBrowseViewModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Browse Albums
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <div class="genre">
        <h3><em><%: Model.Genre.Name %></em> Albums</h3>

        <ul id="album-list">
            <% foreach (var album in Model.Albums) { %>

            <li>
                <a href="<%: Url.Action("Details", new { id = album.AlbumId }) %>">
                    <img alt="<%: album.Title %>" src="<%: album.AlbumArtUrl %>" />
                    <span><%: album.Title %></span>
                </a>
            </li>

            <% } %>
        </ul>

    </div>

</asp:Content>

This is a relatively simple view, so there's not a huge amount of code in either case. I this specific case, we dropped about 6 lines of code in a 27 line view. Lines of code can be abused as a metric, but that's nearly a 25% savings on a short view. I've been noticing that's largely because:

  • Context switches between code and markup are lightweight, and
  • The Razor layout system requires a lot less of the view code - there's no need to use <asp:Content> blocks to indicate where content should go

Line count doesn't tell the full story, though. For me, as I was writing the code, I noticed that there was a lot less friction in the context switching within the lines of code as well. It was time to look at the character count changes, and I decided to do that using PowerShell.

I'm still working on my relationship with PowerShell. I like what it does, but there's something funky about the syntax that makes it a little hard for me to adsorb. After a few quick searches, I had this short command which gives me the character count for all files in a directory:

dir -Include *.* -Recurse | get-content | measure-object -character -word -line -ignoreWhiteSpace

That gives me the following results:

  Lines Words Characters
MVC 2 593 1718 17496
MVC 3 582 1370 13897

Even that doesn't tell the full story - a 20% character count reduction is nice, but that doesn't get close to the qualitative feel in moving from the Web Forms view engine to Razor. It feels like I'm writing half the code in each view, because it flows so much more smoothly.

Looking at the Metrics

Next, I looked at how this affected the overall code count and complexity using the Code Metrics feature in Visual Studio (available in Premium and Ultimate editions). This is a great way to get a rollup view of how complex and maintainable your code is - and you can drill in to find problem areas, export the results, etc.

Here's how things looked with the MVC 2 release of MVC Music Store:

MVC Music Store - Code Metrics - MVC 2

And in MVC 3

MVC Music Store - Code Metrics - MVC 3

The obvious change here is in the Models folder, which shouldn't be surprising since we moved to Entity Framework Code-First with this new release.

But let's think about that a bit more. Even if EF Code First had been further along when I was doing the MVC 2 version, I'm not sure I would have used it. I'm hugely excited about the technology, but there are a few too many moving parts to explain in getting EF Code set up and integrated in a project to dig into that, even in an intermediate level post.

So why did I use EF Code First in this beginner level tutorial? Because I could install it using NuGet.

NuGet makes annoying things embarrassingly easy

I've been a fan of using additional libraries in my .NET projects for a long, long time - sometimes prerelease bits, sometimes open source libraries. It's something that's been a big effort at times - worthwhile, but still an effort. Here's the general flow:

  1. Search around for something that sounds like it might work
  2. Find the website and look around for a download
  3. Figure out how to install and configure the library
  4. See if it worked
  5. Repeat steps 2-4 until it works or you give up

None of that qualifies as rocket science, but it's just difficult enough that:

  • Deciding to use a library requires weighing the benefit that the library brings against the uncertainty and pain of getting it to work
  • You have to consider if other people who will be using the code will be able to get it to work and fix it when they run into trouble

NuGet flips that on its head by making it extremely easy to both install and uninstall software libraries. So the decision to use EF Code First was a lot simpler, because folks who are following the tutorial can add EF Code First to their project by following some very simple steps, and without ever leaving Visual Studio.

MVC 3 makes hard things embarrassingly easy

We don't leverage the new service location features in the MVC 3 for dependency injection in the MVC Music Store tutorial, but it's not much of a stretch. It's simple enough that it's included in the first day of the Web Camps content for for MVC 3, and attendees at Web Camps have no trouble with the DI lab on Day 2 of Web Camps. That's a big deal, as Dependency Injection has generally been just difficult enough to implement that only advanced developers working with mature teams on long-running projects ever made use of it. When I was helping to write the MVC 3 content for the current wave of Web Camps, I was really happy with how smoothly and simply Dependency Injection fit into code demonstrations we'd be showing to beginning and intermediate level developers.

I think that's a remarkable achievement - a framework that makes hard things easy, and easy things even easier.

Entity Framework Code-First, OData & Windows Phone Client

Entity Framework Code-First is the coolest thing since sliced bread, Windows  Phone is the hottest thing since Tickle-Me-Elmo and OData is just too great to ignore.

As part of the Full Stack project, we wanted to put them together, which turns out to be pretty easy… once you know how.  

EF Code-First CTP5 is available now and there should be very few breaking changes in the release edition, which is due early in 2011. 

Note: EF Code-First evolved rapidly and many of the existing documents and blog posts which were written with earlier versions, may now be obsolete or at least misleading.

 

Code-First?

With traditional Entity Framework you start with a database and from that you generate “entities” – classes that bridge between the relational database and your object oriented program.

With Code-First (Magic-Unicorn) (see Hanselman’s write up and this later write up by Scott Guthrie) the Entity Framework looks at classes you created and says “if I had created these classes, the database would have to have looked like this…” and creates the database for you! By deriving your entity collections from DbSet and exposing them via a class that derives from DbContext, you "turn on" database backing for your POCO with a minimum of code and no hidden designer or configuration files.

POCO == Plain Old CLR Objects

Your entity objects can be used throughout your applications - in web applications, console applications, Silverlight and Windows Phone applications, etc. In our case, we'll want to read and update data from a Windows Phone client application, so we'll expose the entities through a DataService and hook the Windows Phone client application to that data via proxies.  Piece of Pie.  Easy as cake.

The Demo Architecture

To see this at work, we’ll create an ASP.NET/MVC application which will act as the host for our Data Service.  We’ll create an incredibly simple data layer using EF Code-First on top of SQLCE4 and we’ll expose the data in a WCF Data Service using the oData protocol.  Our Windows Phone 7 client will instantiate  the data context via a URI and load the data asynchronously.

Setting up the Server project with MVC 3, EF Code First, and SQL CE 4

Create a new application of type ASP.NET MVC 3 and name it DeadSimpleServer. 

We need to add the latest SQLCE4 and Entity Framework Code First CTP's to our project. Fortunately, NuGet makes that really easy. Open the Package Manager Console (View / Other Windows / Package Manager Console) and type in "Install-Package EFCodeFirst.SqlServerCompact" at the PM> command prompt. Since NuGet handles dependencies for you, you'll see that it installs everything you need to use Entity Framework Code First in your project.

PM> install-package EFCodeFirst.SqlServerCompact
'SQLCE (≥ 4.0.8435.1)' not installed. Attempting to retrieve dependency from source...
Done
'EFCodeFirst (≥ 0.8)' not installed. Attempting to retrieve dependency from source...
Done
'WebActivator (≥ 1.0.0.0)' not installed. Attempting to retrieve dependency from source...
Done
You are downloading SQLCE from Microsoft, the license agreement to which is available at http://173.203.67.148/licenses/SQLCE/EULA_ENU.rtf. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'SQLCE 4.0.8435.1'
You are downloading EFCodeFirst from Microsoft, the license agreement to which is available at http://go.microsoft.com/fwlink/?LinkID=206497. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'EFCodeFirst 0.8'
Successfully installed 'WebActivator 1.0.0.0'
You are downloading EFCodeFirst.SqlServerCompact from Microsoft, the license agreement to which is available at http://173.203.67.148/licenses/SQLCE/EULA_ENU.rtf. Check the package for additional dependencies, which may come with their own license agreement(s). Your use of the package and dependencies constitutes your acceptance of their license agreements. If you do not accept the license agreement(s), then delete the relevant components from your device.
Successfully installed 'EFCodeFirst.SqlServerCompact 0.8'
Successfully added 'SQLCE 4.0.8435.1' to EfCodeFirst-CTP5
Successfully added 'EFCodeFirst 0.8' to EfCodeFirst-CTP5
Successfully added 'WebActivator 1.0.0.0' to EfCodeFirst-CTP5
Successfully added 'EFCodeFirst.SqlServerCompact 0.8' to EfCodeFirst-CTP5

Note: We're using SQLCE 4 with Entity Framework here because they work really well together from a development scenario, but you can of course use Entity Framework Code First with other databases supported by Entity framework.

Creating The Model using EF Code First

Now we can create our model class. Right-click the Models folder and select Add/Class. Name the Class Person.cs and add the following code:

using System.Data.Entity;

namespace DeadSimpleServer.Models
{
   public class Person
   {
      public int ID { get; set; }
      public string Name { get; set; }
   }

   public class PersonContext : DbContext
   {
      public DbSet<Person> People { get; set; }
   }
}

Notice that the entity class Person has no special interfaces or base class. There's nothing special needed to make it work - it's just a POCO. The context we'll use to access the entities in the application is called PersonContext, but you could name it anything you wanted. The important thing is that it inherits DbContext and contains one or more DbSet which holds our entity collections.

Adding Seed Data

We need some testing data to expose from our service. The simplest way to get that into our database is to modify the CreateCeDatabaseIfNotExists class in AppStart_SQLCEEntityFramework.cs by adding some seed data to the Seed method:

protected virtual void Seed( TContext context )
{
   var personContext = context as PersonContext;
   personContext.People.Add( new Person { ID = 1, Name = "George Washington" } );
   personContext.People.Add( new Person { ID = 2, Name = "John Adams" } );
   personContext.People.Add( new Person { ID = 3, Name = "Thomas Jefferson" } );
   personContext.SaveChanges();
}

The CreateCeDatabaseIfNotExists class name is pretty self-explanatory - when our DbContext is accessed and the database isn't found, a new one will be created and populated with the data in the Seed method. There's one more step to make that work - we need to uncomment a line in the Start method at the top of of the AppStart_SQLCEEntityFramework class and set the context name, as shown here,

public static class AppStart_SQLCEEntityFramework {
    public static void Start() {
        DbDatabase.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");

        // Sets the default database initialization code for working with Sql Server Compact databases
        // Uncomment this line and replace CONTEXT_NAME with the name of your DbContext if you are 
        // using your DbContext to create and manage your database
        DbDatabase.SetInitializer(new CreateCeDatabaseIfNotExists<PersonContext>());
    }
}

Now our database and entity framework are set up, so we can expose data via WCF Data Services.

Note: This is a bare-bones implementation with no administration screens. If you'd like to see how those are added, check out The Full Stack screencast series.

Creating the oData Service using WCF Data Services

Add a new WCF Data Service to the project (right-click the project / Add New Item / Web / WCF Data Service).

We’ll be exposing all the data as read/write.  Remember to reconfigure to control and minimize access as appropriate for your own application.

Open the code behind for your service. In our case, the service was called PersonTestDataService.svc so the code behind class file is PersonTestDataService.svc.cs.

using System.Data.Services;
using System.Data.Services.Common;
using System.ServiceModel;
using DeadSimpleServer.Models;

namespace DeadSimpleServer
{
   [ServiceBehavior( IncludeExceptionDetailInFaults = true )]
   public class PersonTestDataService : DataService<PersonContext>
   {
      // This method is called only once to initialize service-wide policies.
      public static void InitializeService( DataServiceConfiguration config )
      {
         config.SetEntitySetAccessRule( "*", EntitySetRights.All );
         config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
         config.UseVerboseErrors = true;
      }
   }
}

We're enabling a few additional settings to make it easier to debug if you run into trouble. The ServiceBehavior attribute is set to include exception details in faults, and we're using verbose errors. You can remove both of these when your service is working, as your public production service shouldn't be revealing exception information.

You can view the output of the service by running the application and browsing to http://localhost:[portnumber]/PersonTestDataService.svc/:

<service xml:base="http://localhost:49786/PersonTestDataService.svc/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns="http://www.w3.org/2007/app">
 <workspace>
  <atom:title>Default</atom:title>
   <collection href="People">
    <atom:title>People</atom:title>
   </collection>
  </workspace>
</service>

This indicates that the service exposes one collection, which is accessible by browsing to http://localhost:[portnumber]/PersonTestDataService.svc/People

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
<feed xml:base=http://localhost:49786/PersonTestDataService.svc/ 
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 
xmlns="http://www.w3.org/2005/Atom">
  <title type="text">People</title>
  <id>http://localhost:49786/PersonTestDataService.svc/People</id>
  <updated>2010-12-29T01:01:50Z</updated>
  <link rel="self" title="People" href="People" />
  <entry>
    <id>http://localhost:49786/PersonTestDataService.svc/People(1)</id>
    <title type="text"></title>
    <updated>2010-12-29T01:01:50Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="Person" href="People(1)" />
    <category term="DeadSimpleServer.Models.Person"
      scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:ID m:type="Edm.Int32">1</d:ID>
        <d:Name>George Washington</d:Name>
      </m:properties>
    </content>
  </entry>
  <entry>
    ...
  </entry>
</feed>

Let's recap what we've done so far.

Entity Framework Code First, OData, and Windows Phone - Server Side

But enough with services and XML - let's get this into our Windows Phone client application.

Creating the DataServiceContext for the Client

Use the latest DataSvcUtil.exe from http://odata.codeplex.com. As of today, that's in this download: http://odata.codeplex.com/releases/view/54698

You need to run it with a few options:

/uri - This will point to the service URI. In this case, it's http://localhost:59342/PersonTestDataService.svc  Pick up the port number from your running server (e.g., the server formerly known as Cassini).

/out - This is the DataServiceContext class that will be generated. You can name it whatever you'd like.

/Version - should be set to 2.0

/DataServiceCollection - Include this flag to generate collections derived from the DataServiceCollection base, which brings in all the ObservableCollection goodness that handles your INotifyPropertyChanged events for you.

Here's the console session from when we ran it:

E:\WhoIsThatPhone\WhoIsThatPhone>D:\oData\DataSvcUtil.exe 

/out:ContactService.cs /Version:2.0 /DataServiceCollection /uri:http://localhost:59342/PersonTestDataService.svc Microsoft (R) DataSvcUtil version 1.0.0.0 Copyright (C) 2008 Microsoft Corporation. All rights reserved. Writing object layer file... Generation Complete -- 0 errors, 0 warnings E:\WhoIsThatPhone\WhoIsThatPhone>

Next, to keep things simple, change the Binding on the two TextBlocks within the DataTemplate to Name and ID,

 <ListBox
    x:Name="MainListBox"
    Margin="0,0,-12,0"
    ItemsSource="{Binding}"
    SelectionChanged="MainListBox_SelectionChanged">
    <ListBox.ItemTemplate>
       <DataTemplate>
          <StackPanel
             Margin="0,0,0,17"
             Width="432">
             <TextBlock
                Text="{Binding Name}"
                TextWrapping="Wrap"
                Style="{StaticResource PhoneTextExtraLargeStyle}" />
             <TextBlock
                Text="{Binding ID}"
                TextWrapping="Wrap"
                Margin="12,-6,12,0"
                Style="{StaticResource PhoneTextSubtleStyle}" />
          </StackPanel>
       </DataTemplate>
    </ListBox.ItemTemplate>
 </ListBox>

Getting The Context

In the code-behind you’ll first declare a member variable to hold the context from the Entity Framework. This is named using convention over configuration.

The db type is Person and the context is of type PersonContext, You initialize it by providing the URI, in this case using the URL obtained from the Cassini web server,

PersonContext context = 
  new PersonContext( 
  new Uri( "http://localhost:49786/PersonTestDataService.svc/" ) );

Create a second member variable of type DataServiceCollection<Person> but do not initialize it,

DataServiceCollection<Person> people;

In the constructor you’ll initialize the DataServiceCollection using the PersonContext,

public MainPage()
{
   InitializeComponent();
   people = new DataServiceCollection<Person>( context );

Finally, you’ll load the people collection using the LoadAsync method, passing in the fully specified URI for the People collection in the web service,

 people.LoadAsync( 
    new Uri( 
    "http://localhost:49786/PersonTestDataService.svc/People" ) );

Note that this method runs asynchronously and when it is finished the people  collection is already populated. Thus, since we didn’t need or want to override any of the behavior we don’t implement the LoadCompleted.

Windows Phone - Databound

You can use the LoadCompleted event if you need to do any other UI updates, but you don't need to. The final code is as shown below:

using System;
using System.Data.Services.Client;
using System.Windows;
using System.Windows.Controls;
using DeadSimpleServer.Models;
using Microsoft.Phone.Controls;

namespace WindowsPhoneODataTest
{
   public partial class MainPage : PhoneApplicationPage
   {
      PersonContext context = new PersonContext( 
        new Uri( "http://localhost:49786/PersonTestDataService.svc/" ) );
      DataServiceCollection<Person> people;

      // Constructor
      public MainPage()
      {
         InitializeComponent();

         // Set the data context of the listbox control to the sample data
         // DataContext = App.ViewModel;
         people = new DataServiceCollection<Person>( context );
         people.LoadAsync( 
            new Uri( "http://localhost:49786/PersonTestDataService.svc/People" ) );
         DataContext = people;
         this.Loaded += new RoutedEventHandler( MainPage_Loaded );
      }

      // Handle selection changed on ListBox
      private void MainListBox_SelectionChanged( object sender, SelectionChangedEventArgs e )
      {
         // If selected index is -1 (no selection) do nothing
         if ( MainListBox.SelectedIndex == -1 )
            return;

         // Navigate to the new page
         NavigationService.Navigate( 
            new Uri( "/DetailsPage.xaml?selectedItem=" 
            + MainListBox.SelectedIndex, UriKind.Relative ) );

         // Reset selected index to -1 (no selection)
         MainListBox.SelectedIndex = -1;
      }

      // Load data for the ViewModel Items
      private void MainPage_Loaded( object sender, RoutedEventArgs e )
      {
         if ( !App.ViewModel.IsDataLoaded )
         {
            App.ViewModel.LoadData();
         }
      }
   }
}

With people populated we can set it as the DataContext and run the application; you’ll find that the Name and ID are displayed in the list on the Mainpage. Here's how the pieces in the client fit together:

Entity Framework Code First, OData, and Windows Phone - Client Side

Complete source code available here

More Posts