Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Windows Azure, PHP, ...

About the author

Maarten Balliauw is currently employed as a Technical Evangelist at JetBrains. His interests are mainly web applications developed in ASP.NET (C#) or PHP and the Windows Azure cloud platform.
More about me More about me
Send mail E-mail me


ASP.NET MVC Quickly Pro NuGet Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn
Maarten Balliauw - MVP - Most Valuable Professional
Maarten Balliauw - ASPInsider

Search

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright Maarten Balliauw 2013


CarTrackr on Windows Azure - Part 4 - Membership and authentication

This post is part 4 of my series on Windows Azure, in which I'll try to convert my ASP.NET MVC application into a cloud application. The current post is all about implementing authentication in CarTrackr.

Other parts:

Picking a solution...

In my opening post on this series, I defined some tasks which I would probably have to do prior to being able to run CarTrackr on Azure. For membership and authentication, I defined 2 solutions:  Cloudship or Windows Live ID.

At first, Cloudship looked really nice as it is just an implementation of ASP.NET's provider model based on Azure. Some thinking cycles later, this did not feel right for CarTrackr... For CarTrackr, authentication only would be enough, membership would be real overkill.

The solution I'll be using in CarTrackr is Windows Live ID. Luckily, there's some ASP.NET MVC code for that in an older release of the MVC Membership Starter Kit.

Adding Live ID to CarTrackr

First of all, add the WindowsLiveLogin.cs class from the MVC Membership Starter Kit. Also ,ake sure it can configure itself by adding the Live ID settings in web.config:

<appSettings>
    <!-- See: http://msdn2.microsoft.com/en-us/library/bb676633.aspx and https://msm.live.com/app/default.aspx -->
    <add key="wll_appid" value="001600008000AF26"/>
    <add key="wll_secret" value="mvcmembershipstarterkit"/>
    <add key="wll_securityalgorithm" value="wsignin1.0"/>
</appSettings>

Now, I always like removing code. Actually, a lot of methods can be removed from the AuthenticationController due to the fact that Live ID will take care of lost password e-mails and stuff like that. After these stripping actions, my AccountController looks like the following:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.UI;
using CarTrackr.Core;
using CarTrackr.Repository;
using CarTrackr.Models;
using CarTrackr.Filters;

namespace CarTrackr.Controllers
{

    [HandleError]
    [OutputCache(Location = OutputCacheLocation.None)]
    [LiveLogin]
    public class AccountController : Controller
    {
        private IUserRepository UserRepository;

        public AccountController()
            : this(null, null)
        {
        }

        public AccountController(IFormsAuthentication formsAuth, IUserRepository userRepository)
        {
            FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
            UserRepository = userRepository;
        }

        public IFormsAuthentication FormsAuth
        {
            get;
            private set;
        }

        [Authorize]
        public ActionResult Index()
        {
            return RedirectToAction("Login");
        }

        public ActionResult Login()
        {
            return View("Login");
        }

        public ActionResult Logout()
        {
            FormsAuth.SignOut();

            // Windows Live ID logout...
            HttpCookie loginCookie = new HttpCookie( "webauthtoken" );
            loginCookie.Expires = DateTime.Now.AddYears( -10 );
            Response.Cookies.Add( loginCookie );

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

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity is WindowsIdentity)
            {
                throw new InvalidOperationException("Windows authentication is not supported.");
            }
        }

        #region Live ID

        public ActionResult WindowsLiveAuthenticate()
        {
            // initialize the WindowsLiveLogin module.
            WindowsLiveLogin wll = new WindowsLiveLogin(true);

            // communication channels
            HttpRequestBase request = this.HttpContext.Request;
            HttpResponseBase response = this.HttpContext.Response;

            // extract the 'action' parameter from the request, if any.
            string action = request["action"] ?? "";

            /*
              If action is 'logout', clear the login cookie and redirect
              to the logout page.

              If action is 'clearcookie', clear the login cookie and
              return a GIF as response to signify success.

              By default, try to process a login. If login was
              successful, cache the user token in a cookie and redirect
              to the site's main page.  If login failed, clear the cookie
              and redirect to the main page.
            */
            if (action == "logout")
            {
                return RedirectToAction("Logout");
            }
            else if (action == "clearcookie")
            {

                HttpCookie loginCookie = new HttpCookie("webauthtoken");
                loginCookie.Expires = DateTime.Now.AddYears(-10);
                response.Cookies.Add(loginCookie);

                string type;
                byte[] content;
                wll.GetClearCookieResponse(out type, out content);
                response.ContentType = type;
                response.BinaryWrite(content);
                response.End();
                return new EmptyResult();

            }
            else
            {
                WindowsLiveLogin.User wllUser = wll.ProcessLogin(request.Form);

                HttpCookie loginCookie = new HttpCookie("webauthtoken");
                if (wllUser != null)
                {
                    loginCookie.Value = wllUser.Token;

                    if (wllUser.UsePersistentCookie)
                    {
                        loginCookie.Expires = DateTime.Now.AddYears(10);
                    }
                }
                else
                {
                    loginCookie.Expires = DateTime.Now.AddYears(-10);
                }

                // check for user in repository
                CarTrackr.Domain.User user = UserRepository.RetrieveByUserName(wllUser.Id);
                if (user == null)
                {
                    user = new CarTrackr.Domain.User();
                    user.UserName = wllUser.Id;
                    UserRepository.Add(user);
                }

                // log user in
                response.Cookies.Add(loginCookie);
                FormsAuthentication.SetAuthCookie(user.UserName, false);

                return RedirectToAction("Login");
            }
        }

        #endregion
    }

    // The FormsAuthentication type is sealed and contains static members, so it is difficult to
    // unit test code that calls its members. The interface and helper class below demonstrate
    // how to create an abstract wrapper around such a type in order to make the AccountController
    // code unit testable.

    public interface IFormsAuthentication
    {
        void SetAuthCookie(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationWrapper : IFormsAuthentication
    {
        public void SetAuthCookie(string userName, bool createPersistentCookie)
        {
            FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
        }
        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }
}

Yes, that's almost no code left compared to the original! Remember, Live ID will take care of all user-account-related stuff for me. The only thing I'm doing here is accepting the authentication ticket Live ID provides to CarTrackr. Yes, I'm actually registering the user on my cloud storage too, because I want to track how much users actually use CarTrackr...

One thing to notice: I've created an action filter attribute (hence the [LiveLogin] attribute on the AccountController class). The LiveLogin action filter looks like the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using CarTrackr.Core;
using CarTrackr.Models;

namespace CarTrackr.Filters
{
    public class LiveLogin : ActionFilterAttribute
    {
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            WindowsLiveLogin wll = new WindowsLiveLogin(true);

            SignInViewData viewData = new SignInViewData
            {
                AppId = wll.AppId,
                UserId = GetUserId(wll, filterContext.HttpContext.Request)
            };

            filterContext.Controller.ViewData["WindowsLiveLogin"] = viewData;
        }

        public static string GetUserId(WindowsLiveLogin wll, HttpRequestBase request)
        {
            HttpCookie loginCookie = request.Cookies["webauthtoken"];

            if (loginCookie != null)
            {
                string token = loginCookie.Value;

                if (!string.IsNullOrEmpty(token))
                {
                    WindowsLiveLogin.User user = wll.ProcessToken(token);

                    if (user != null)
                    {
                        return user.Id;
                    }
                }
            }

            return null;
        }
    }
}

What hapens in this code is actually checking for the Live ID context we are in. This context can be used in any view of CarTrackr since it is stored in the ViewData dictionary by this action filter: ViewData["WindowsLiveLogin"]. This context is used by a simple LiveIdControl (code in download later on) to render the Live ID sign in / sign out link.

Reminder for deployment...

One reminder left when deploying this to Azure: I'll have to make sure that Live ID posts the authentication ticket to the correct URL in CarTrackr. This can be done later in the Azure project management interface:

Live ID settings in Azure

Conclusion

This was a quite easy task compared to configuring TableStorage. Thank's to the MVC Membership Starter Kit, the Live ID integration was easy.

Stay tuned for the final part: deployment on Azure! I'll also provide a download link and a live link to the project.

kick it on DotNetKicks.com


Categories: ASP.NET | Windows Azure | C# | General | MVC

CarTrackr on Windows Azure - Part 3 - Data storage

This post is part 3 of my series on Windows Azure, in which I'll try to convert my ASP.NET MVC application into a cloud application. The current post is all about implementing cloud storage in CarTrackr.

Other parts:

Types of Azure storage

Windows Azure offers 3 types of cloud storage: blobs, tables and queues. Blob Storage stores sets of binary data, organized in containers of your storage account. Table Storage offers structured storage in the form of tables. The Queue service stores an unlimited number of messages, each of which can be up to 8 KB in size.

Windows Azure Storage Account

CarTrackr will use table storage to store cars and refuellings. Table Storage is one of the simplest way to store data in Azure. All tables are accessed using a Uri in the form of http://<applicationname>.tables.core.windows.net. You need to have a storage cccount if you want to use the table storage. Waiting for an invitation? The Azure SDK contains a development storage tool which simulates all cloud table storage features on your local host: http://127.0.0.1:10002

Each storage account has a Table containg an Entity. An Entity contains Columns. Entity can be considered to be the row and Columns as values. Each Entity always contains these properties: PartitionKey, RowKey and Timestamp . The PartitionKey and RowKey identify a row or an Entity. (source)

Implementing Azure TableStorage in CarTrackr

Configuring TableStorage

First of all, we have to specify we are going to use storage in the CarTrackr application. This can be achieved by adding some configuration to the CarTrackr_Azure's ServiceConfiguration.cscfg:

<?xml version="1.0"?>
<ServiceConfiguration serviceName="CarTrackr_Azure" xmlns=""http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"">http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  <Role name="Web">
    <Instances count="1" />
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="TableStorageEndpoint" value=""http://127.0.0.1:10002"/">http://127.0.0.1:10002"/>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

When deploying, we should of course change the TableStorageEndpoint to the Azure TableStorage endpoint. Account name and key should also be modified at that moment.

Development storage

Making sure all tables exist in TableStorage

To make sure all tables exist in TableStorage, a TableStorageDataServiceContext should be defined. This TableStorageDataServiceContext should contain some TableStorageEntity items. We can easily make all domain objects in CarTrackr of type TableStorageEntity. Make sure to build the samples in the Azure SDK folder and add a reference to the StorageClient.dll in that folder.

Creating a class that is TableStorage aware is easy now: inherit the Microsoft.Samples.ServiceHosting.StorageClient.Microsoft.Samples.ServiceHosting.StorageClient class, modify the constructor and you're done. I've also modified the domain classes, making RowKey the identifier for the current object.

namespace CarTrackr.Domain
{
    public partial class Car : TableStorageEntity, IRuleEntity
    {
        public Car()
            : base()
        {
            PartitionKey = Guid.NewGuid().ToString();
            RowKey = Guid.NewGuid().ToString();
        }

        // ... other code ...
    }
}

Also add the CarTrackrCloudContext class to the CarTrackr.Data namespace. This CarTrackrCloudContext class will implement a TableStorageDataServiceContext, defining which tables are in the TableStorage.

using System.Data.Services.Client;
using CarTrackr.Domain;
using Microsoft.Samples.ServiceHosting.StorageClient;

namespace CarTrackr.Data
{
    public class CarTrackrCloudContext : TableStorageDataServiceContext
    {
        public DataServiceQuery<Car> Cars
        {
            get
            {
                return CreateQuery<Car>("Cars");
            }
        }

        public DataServiceQuery<Refuelling> Refuellings
        {
            get
            {
                return CreateQuery<Refuelling>("Refuellings");
            }
        }

        public DataServiceQuery<User> Users
        {
            get
            {
                return CreateQuery<User>("Users");
            }
        }
    }
}

This CarTrackrCloudContext tells the application there are 3 types of tables, represented by 3 domain class types. Note that the class has been inherited from TableStorageDataServiceContext, which automatically connects to the TableStorage endpoint with the account information we stored previously in the Service Configuration file.

To make sure tables exist and are up to date when the application is started, add the following to CarTrackr's Global.asax:

protected static bool tablesRegistered = false;
protected static object syncLock = "";

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if (!tablesRegistered)
    {
        lock (syncLock)
        {
            if (!tablesRegistered)
            {
                try {
                    StorageAccountInfo account = StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration();
                    TableStorage.CreateTablesFromModel(typeof(CarTrackr.Data.CarTrackrCloudContext), account);
                    tablesRegistered = true;
                }
                catch { }
            }
        }
    }
}

The TableStorage client will create all requried tables based on the CarTrackrCloudContext class's IQueryable properties. Note that I'm using double-check locking here to make sure tables are only created once (performance). 

Querying data

Luckily, my repository code is not subject to much changes. Linq queries just keep working on the Azure TableStorage. Only Insert, Update and Delete are a little bit different. The CarTrackrCloudContext class represents the runtime context of ADO.NET Data Services and enables to use AddObject(), DeleteObject(), UpdateObject(), followed by SaveChanges().

Here's an example on adding a Car (CarRepository):

public void Add(Car car)
{
    if (car.OwnerId == Guid.Empty)
        car.OwnerId = User.UserId;

    car.EnsureValid();

    DataSource.DataContext.AddObject("Cars", car);
    DataSource.DataContext.SaveChanges();
}

Storage conclusions

I have actually modified my Linq to SQL classes and repository code as TableStorage currently only supports Binary, Bool, DateTime, Double, Guid, Int, Long and String. No decimals and custom types anymore... There's also some other missing features regarding ordering of data and joins which required me to change a lot of repository code. But hey, it's still only the repository code I needed to change!

Next post will be about membership and authentication. Stay tuned!

kick it on DotNetKicks.com


Categories: ASP.NET | Windows Azure | C# | General | MVC

CarTrackr on Windows Azure - Part 2 - Cloud-enabling CarTrackr

This post is part 2 of my series on Windows Azure, in which I'll try to convert my ASP.NET MVC application into a cloud application. The current post is all about enabling the CarTrackr Visual Studio Solution file for Windows Azure.

Other parts:

Adding CarTrackr_WebRole

For a blank Azure application, one would choose the Web Cloud Service type project (installed with teh Azure CTP), which brings up two projects in the solution: a <project> and <project>_WebRole. The first one is teh service definition, the latter is the actual application. Since CarTrackr is an existing project, let's add a new CarTrackr_Azure project containing the service definition.

Right-click the CarTrackr solution and add a new project. From the project templates, pick the "Cloud Service -> Blank Cloud Service" item and name it "CarTrackr_Azure". The CarTrackr_Azure project will contain all service definition data used by Windows Azure to determine the application's settings and environment.

 Creating CarTrackr_Azure

CarTrackr solution

Great! My solution explorer now contains 3 projects: CarTrackr, CarTrackr.Tests and the newly created CarTrackr_Azure. Next thing to do is actually defining the CarTrackr project in CarTrackr_Azure as the WebRole project. Right-click "Roles", "Add", and notice... we can not promote CarTrackr to a WebRole project. Sigh!

Edit the CarTrackr.csproj file using notepad and merge the differences in (ProjectTypeGuids, RoleType and ServiceHostingSDKInstallDir):


<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>

    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

    <ProductVersion>9.0.30729</ProductVersion>

    <SchemaVersion>2.0</SchemaVersion>

    <ProjectGuid>{E536FB25-134E-4819-9BAC-0D276D851FB8}</ProjectGuid>

    <ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

    <OutputType>Library</OutputType>

    <AppDesignerFolder>Properties</AppDesignerFolder>

    <RootNamespace>CarTrackr</RootNamespace>

    <AssemblyName>CarTrackr</AssemblyName>

    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>

    <RoleType>Web</RoleType>

    <ServiceHostingSDKInstallDir Condition=" '$(ServiceHostingSDKInstallDir)' == '' ">$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\ServiceHosting\v1.0@InstallPath)</ServiceHostingSDKInstallDir>

  </PropertyGroup>

  <!-- ... -->

</Project>

Visual Studio will prompt to reload the project, allow this by clicking the "Reload" button. Now we can right-click "Roles", "Add", "Web Role Project in Solution" and pick CarTrackr. Note that the 2 files in CarTrackr_Azure now have been updated to reflect this.

ServiceDefinition.csdef now contains the following:


<?xml version="1.0" encoding="utf-8"?>

<ServiceDefinition name="CarTrackr_Azure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">

  <WebRole name="Web">

    <InputEndpoints>

      <InputEndpoint name="HttpIn" protocol="http" port="80" />

    </InputEndpoints>

  </WebRole>

</ServiceDefinition>

This file will later instruct the Azure platform to run a website on a HTTP endpoint, port 80. Optionally, I can also add a HTTPS endpoint here if required. For now, this definition wil do.

ServiceConfiguration.csdef now contains the following:


<?xml version="1.0"?>

<ServiceConfiguration serviceName="CarTrackr_Azure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">

  <Role name="Web">

    <Instances count="1" />

    <ConfigurationSettings />

  </Role>

</ServiceConfiguration>

This file will inform Azure of the required environment for CarTrackr. First of all, one instance will be hosted. If it becomes a popular site and more "servers" are needed, I can simply increase this number and have more power in the cloud. The ConfigurationSettings element can contain some other configuration settings, for example where data will be stored. I think I'll be needing this in a future blog post, but for now, this will do.

 

Service Configuration

It's in the cloud!

After doing all configuration steps, I can simply start the CarTrackr application in debug mode.

CarTrackr in the cloud!

Nothing fancy here, everything still works! I just can't help the feeling that Windows Azure will not know my local SQL server for data storage... Which will be the subject of a next blog post!

kick it on DotNetKicks.com


Categories: ASP.NET | Windows Azure | C# | General | MVC

Track your car expenses in the cloud! CarTrackr on Windows Azure - Part 1 - Introduction

As you may see in the title, I will be starting a series on modifying my CarTrackr sample application to a cloud-based, Windows Azure application. At this point, I don't know if it's easy nor do I know what it takes to achieve this goal. I only have some assumtions on how CarTrackr can be converted to a cloud application.

This post is part 1 of the series, in which I'll describe the architecture of Windows Azure and what I think it takes to convert my ASP.NET MVC application into a cloud application.

Other parts:

Microsoft Azure

Azure Services PlatformAt Microsoft PDC 2008, the Azure Services Platform was announced in the opening keynote. Azure is the name for Microsoft’s Software + Services platform, an operating system in the cloud providing services for hosting, management, scalable storage with support for simple blobs, tables, and queues, as well as a management infrastructure for provisioning and geo-distribution of cloud-based services, and a development platform for the Azure Services layer.

You can currently download the Windows Azure SDK from www.azure.com and play with it on your local computer. Make sure to sign-up at the Azure site: you might get lucky and receive a key to test the real thing.

CarTrackr

From my previous blog post: "CarTrackr is a sample application for the ASP.NET MVC framework using the repository pattern and dependency injection using the Unity application block. It was written for various demos in presentations done by Maarten Balliauw. CarTrackr is an online software application designed to help you understand and track your fuel usage and kilometers driven."

CarTrackr, cloud version That being said: what will it take to port this onto the Azure platform? First of all, a new logo applies. I now want a logo with clouds in it. Since it's still no official release, I'll also keep the "beta" label in place. Looks nice, eh? :-)

Seriously, here's what I think needs to be done:

Concept Current implementation Azure implementation
Data store Repository pattern on top of Linq to SQL. Repository pattern (whew!) on top of Azure TableStorage.
Membership ASP.NET membership Windows Live ID or Cloudship

In addition to the above table, I'll also have to make the CarTrackr solution aware of Azure. Next thing: make Azure aware of ASP.NET MVC... I'll also have to deploy this application in the cloud at the end. Stay tuned!

kick it on DotNetKicks.com


Categories: ASP.NET | Windows Azure | C# | General | MVC

ASP.NET MVC XForms released on CodePlex

Just noticed there's a new project on CodePlex related to the ASP.NET MVC framework: MVC XForms. MVC XForms is a simple UI framework for ASP.NET MVC based on the W3C XForms specification. It provides a set of form controls that allow updating of complex model objects.

Picked these project goals from Jon Curtis' blog:

  • To allow automatic form population, deserialization and validation based on the (arbitrarily complex) model.
  • To produce semantic HTML forms using the logic of XForms.
  • To output clean, terse HTML.
  • No javascript, unless necessary and always optional and unobtrusive.
  • To enable clean, terse view code.
  • To make the framework as extensible and customisable as possible without compromising simplicity or the above goals.
  • Use convention over configuration and a fluent API.

Great story, but how does it work?

I haven't gone into any advanced scenario's, but have instead used a simple case to demonstrate some of the MVC XForms basics. First of all, I've created a Person class with an Id (int), Name (string) and BirthDate (DateTime). This class is used by a specific view in my application, of which the view markup looks like this:

<% Html.XForm(ViewData.Model).Form(form => { %>
  <%=form.Input(p => p.Name).Label("Name") %>
  <%=form.Input(p => p.BirthDate).Label("Birth date") %>
<% }); %>

This is all there is to creating an MVC XForm. Note that I'm creating a XForm based on my model, and that I want an input field for the Name and BirthDate properties, each with a label supplied.

On to rendering: the HTML generated by MVC XForm looks like the following:

<form action="" method="post" class="xf xform">
  <div class="xf input text">
    <label for="Name">Name</label>
    <input id="Name" type="text" name="Name" value="Maarten Balliauw"/>
  </div>
  <div class="xf input date">
    <label for="BirthDate">Birth date</label>
    <input id="BirthDate" type="text" name="BirthDate" value="21-12-1983"/>
  </div>
</form>
<script type="text/javascript">
  $(document).ready(function(){mvc.xforms.init({"id":"","cons":[]})});
</script>

That's actually nice, clean HTML markup! Note the CSS classes that are applied on certain fields. For example, the div element for BirthDate has a CSS class "date", which can be used by, for example, jQuery to enable a date picker on that field.

More information

In the CodePlex download (http://www.codeplex.com/mvcxforms), there is a sample project which makes use of all simple and advanced ideas in MVC XForms. You can also check out Jon Curtis' blog posts on MVC XForms to read some more samples.

kick it on DotNetKicks.com


Using the ASP.NET MVC ModelBinder (screencast)

A new screencast has just been uploaded to the MSDN Belgium Chopsticks page. Don't forget to rate the video!

Using ASP.NET MVC ModelBinder

Abstract: "This screencast demonstrates how code can be made more maintainable and testable by delegating binding to client data to the ASP.NET MVC model binder architecture."

kick it on DotNetKicks.com


Partial page updates with ASP.NET MVC and jQuery (and action filters)

When building an ASP.NET MVC application, chances are that you are using master pages. After working on the application for a while, it's time to spice up some views with jQuery and partial updates.

Let's start with an example application which does not have any Ajax / jQuery. Our company's website shows a list of all employees and provides a link to a details page containing a bio for that employee. In the current situation, this link is referring to a custom action method which is rendered on a separate page.

Example application

Spicing things up with jQuery

The company website could be made a little sexier... What about fetching the employee details using an Ajax call and rendering the details in the employee list? Yes, that's actually what classic ASP.NET's UpdatePanel does. Let's do that with jQuery instead.

The employees list is decorated with a CSS class "employees", which I can use to query my DOM using jQuery:

$(function() {
    $("ul.employees > li > a").each(function(index, element) {
        $(element).click(function(e) {
            $("<div />").load( $(element).attr('href') )
                        .appendTo( $(element).parent() );
            e.preventDefault();
        })
    });
});

What's this? Well, the above code instructs jQuery to add some behaviour to all links in a list item from a list decorated with the employees css class. This behaviour is a click() event, which loads the link's href using Ajax and appends it to the list.

Now note there's a small problem... The whole site layout is messed up, because the details page actually renders itself using a master page.

Messed-up employees list after performing an Ajax call

Creating a jQueryPartial action filter

The solution to this broken page is simple. Let's first create a new, empty master page, named "Empty.Master". This master page should have as many content placeholder regions as the original master page, and no other content. For example:

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Empty.Master.cs" Inherits="jQueryPartialUpdates.Views.Shared.Empty" %>

<asp:ContentPlaceHolder ID="MainContent" runat="server" />

We can now apply this master page to the details action method whenever an Ajax call is done. You can implement this behaviour by creating a custom action filter: jQueryPartial.

public class jQueryPartial : ActionFilterAttribute
{
    public string MasterPage { get; set; }

    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        // Verify if a XMLHttpRequest is fired.
        // This can be done by checking the X-Requested-With
        // HTTP header.
        if (filterContext.HttpContext.Request.Headers["X-Requested-With"] != null
            && filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
        {
            ViewResult viewResult = filterContext.Result as ViewResult;
            if (viewResult != null)
            {
                viewResult.MasterName = MasterPage;
            }
        }
    }
}

This action filter checks for the precense of the X-Requested-With HTTP header, which is provided by jQuery when firing an asynchronous web request. When the X-Requested-With header is present, the view being rendered is instructed to use the empty master page instead of the original one.

One thing left though: the action filter should be applied to the details action method:

[jQueryPartial(MasterPage = "Empty")]
public ActionResult Details(int id)
{
    Employee employee =
        Employees.Where(e => e.Id == id).SingleOrDefault();

    ViewData["Title"] = "Details for " +
        employee.LastName + ", " + employee.FirstName;
    return View(employee);
}

When running the previous application, everything should render quite nicely now.

Working example

Want the sample code?

You can download the sample code here: jQueryPartialUpdates.zip (134.10 kb)

kick it on DotNetKicks.com


Categories: ASP.NET | C# | General | MVC | jQuery

Creating a generic Linq to SQL ModelBinder for the ASP.NET MVC framework

You are right! This is indeed my third post on ASP.NET MVC ModelBinders. The first one focussed on creating a ModelBinder from scratch in an older preview release, the second post did something similar trying to do some dirty ViewState-like stuff. Good news! There's more of this dirty stuff coming!

How about this action method, using a Person class which is a Linq to SQL entity type:

public ActionResult PersonDetails(Person id)
{
    if (id == null)
        return RedirectToAction("Index");

    return View(id);
}

This action method is called from a URL which looks like Home/PersonDetails/2. Nothing special about this? Read this whole post again, from the beginning! Yes, I said that the Person class is a Linq to SQL entity type, and yes, you are missing the DataContext here! The above method would normally look like this:

public ActionResult PersonDetails(int id)
{
    using (ApplicationDataContext context = new ApplicationDataContext())
    {
        Person person = context.Persons.Where(p => p.Id == id).SingleOrDefault();

        if (person == null)
            return RedirectToAction("Index");

        return View(person);
    }
}

Using the ASP.NET MVC ModelBinder infrastructure, I am actually able to bind action method parameters to real objects, based on simple query string parameters like, in this case, id. A custom ModelBinder maps this string id to a real Person instance from my Linq to SQL DataContext. Let me show you how I've created this ModelBinder.

Registering the LinqToSqlBinder<T>

As with any custom ModelBinder, the LinqToSqlBinder should be registered with the ModelBinder infrastructure:

protected void Application_Start()
{
    // ...

    LinqToSqlBinder<ApplicationDataContext>.Register(ModelBinders.Binders);
}

The above piece of code registers every entity type (or table, whatever you like to call it) in my Linq to Sql data contextwith a new LinqToSqlBinder<ApplicationDataContext> instance.

public static void Register(IDictionary<Type, IModelBinder> bindersDictionary)
{
    using (T context = new T())
    {
        foreach (var table in context.Mapping.GetTables())
        {
            ModelBinders.Binders.Add(table.RowType.Type, new LinqToSqlBinder<T>());
        }
    }
}

The LinqToSqlBinder<T> source code

The LinqToSqlBinder<T> will make use of a small utility class, TableDefinition, in which some information about the entity type's table will be stored. This class looks like the following:

private class TableDefinition
{
    public TableDefinition()
    {
        ColumnNames = new List<string>();
    }

    public string TableName;
    public string PrimaryKeyFieldName;
    public List<string> ColumnNames { get; set; }
}

My LinqToSqlBinder<T> overloads ASP.NET MVC's DefaultModelBinder class, of which I'll override the BindModel method:

public class LinqToSqlBinder<T> : DefaultModelBinder
{
    public override ModelBinderResult BindModel(ModelBindingContext bindingContext)
    {
        // ...
    }
}

First of all, the LinqToSqlBinder<T> has to determine if it can actually perform binding for the requested model type. In this case, this is determined using the metadata my Linq to SQL data context provides. If it does not support mapping the requested type, model binding is further processed by the base class.

// Check if bindingContext.ModelType can be delivered from T
MetaTable metaTable = context.Mapping.GetTable(bindingContext.ModelType);
if (metaTable == null)
{
    return base.BindModel(bindingContext);
}

Next task for the model binder: checking whether a value is provided. For example, if my action method expects a parameter named "id" and I provide a parameter "borat" (whatever...) in the request, the model binder should not accept the task given. If everything succeeds, I should have an identity value which I can use in a query later on.

// Get the object ID that is being passed in.
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == null)
{
    return base.BindModel(bindingContext);
}
string objectId = valueProviderResult.AttemptedValue;

Speaking of queries... Now is a good time to start filling my TableDefinition instance, on which I can generate a SQL query which will later retrieve the requested object. Filling the TableDefinition object is really an easy task when using the meta data Linq to SQL provides. Each member (or column) can be looped and queried for specific information such as type, name, primary key, ...

// Build table definition
TableDefinition tableDefinition = new TableDefinition();
tableDefinition.TableName = metaTable.TableName;

foreach (MetaDataMember dm in metaTable.RowType.DataMembers)
{
    if (dm.DbType != null)
    {
        tableDefinition.ColumnNames.Add(dm.MappedName);
    }
    if (dm.IsPrimaryKey)
    {
        tableDefinition.PrimaryKeyFieldName = dm.MappedName;
    }
}

With all this information in place, a SQL query can easily be built.

// Build query
StringBuilder queryBuffer = new StringBuilder();
queryBuffer.Append("SELECT ")
                .Append(string.Join(", ", tableDefinition.ColumnNames.ToArray()))
           .Append(" FROM ")
                .Append(tableDefinition.TableName)
           .Append(" WHERE ")
                .Append(tableDefinition.PrimaryKeyFieldName)
                .Append(" = \'").Append(objectId).Append("\'");

A nice looking query is generated using this code: SELECT id, name, email FROM dbo.person WHERE id = '2'. This query can now be executed on the Linq to SQL data context. The first result will be returned to the action method.

// Execute query
IEnumerable resultData = context.ExecuteQuery(bindingContext.ModelType,
    queryBuffer.ToString());

foreach (object result in resultData)
{
    return new ModelBinderResult(result);
}

Download the code

Feel free to download a working example based on this blog post: LinqModelBinderExample.zip (352.52 kb)

Note that this code may be vulnerable to SQL injection! This is not production code!

kick it on DotNetKicks.com


CarTrackr - Sample ASP.NET MVC application

CarTrackr - Sample ASP.NET MVC application Some people may have already noticed the link in my VISUG session blog post, but for those who didn't... I've released my sample application CarTrackr on CodePlex.

CarTrackr is a sample application for the ASP.NET MVC framework using the repository pattern and dependency injection using the Unity application block. It was written for various demos in presentations done by Maarten Balliauw.

CarTrackr is an online software application designed to help you understand and track your fuel usage and kilometers driven.

You will have a record on when you filled up on fuel, how many kilometers you got in a given tank, how much you spent and how much liters of fuel you are using per 100 kilometer.

CarTrackr will enable you to improve your fuel economy and save money as well as conserve fuel. Fuel economy and conservation is becoming an important way to control your finances with the current high price.

Go get the source code for CarTrackr on CodePlex! (note that it has been updated for ASP.NET MVC beta 1)

Technologies and techniques used

Here's a list of technologies and techniques used:

  • CarTrackr uses the Unity application block to provide dependency injection
  • The repository design pattern is used for building a flexible data layer
  • Controllers are instantiated by using a custom ASP.NET MVC ControllerBuilder, which uses Unity for dependency resolving
  • The testing project makes use of Moq to mock out parts of the ASP.NET runtime
  • Form validation is included on most forms, leveraging the ViewData.ModelState class
  • It is possible to sign in using OpenID, for which the ASP.NET MVC Membership starter kit was used
  • LinqToSQL is used as the persistence layer
  • CarTrackr uses my ASP.NET MVC sitemap provider
  • Configuration Section Designer was used to create a custom configuration section
  • Extension methods are created for including Silverlight charts (rendered with Visifire)
  • Web 2.0 logo creator was used to generate a classy logo

kick it on DotNetKicks.com


Introduction to ASP.NET MVC for VISUG - Presentation materials

VISUG Yesterday evening, I did a presentation on the ASP.NET MVC framework for VISUG (Visual Studio User Group Belgium). I really hope everyone got a good feel on what the ASP.NET MVC framework is all about and what it takes to build an ASP.NET MVC application. Thank you Pieter Gheysens for inviting me for this talk! And thank you audience for being interested for over an hour and a half!

A recorded version of this presentation will be available later, for the moment you'll have to do with the presentation materials. The download contains the slides, the Hello World application and the testing demo. The CarTrackr application can be found on CodePlex.

Downloads

Presentation materials: VISUG ASP.NET MVC materials.zip (5.63 mb)
CarTrackr sample application: http://www.codeplex.com/CarTrackr/

kick it on DotNetKicks.com


Categories: ASP.NET | C# | General | MVC | Presentations