Running an ASP.NET MVC application on a fresh IIS8 install

IIS has ever increasing amounts of security, you can’t publish a basic ASP.NET MVC website anymore and expect IIS to host it without some additional work. The default config settings that the MVC uses are locked down in IIS, so it issues an error when you try to navigate to your fresh site.

Initially you may get a screen that says something bland and non-descriptive, like “Internal Server Error” with no further information.

To get the more detailed error messages modify your web application’s web.config file and add the following line to the system.webServer section:

<httpErrors errorMode="Detailed" />

Now, you’ll get a more detailed error message. It will look something like this:

The key to the message is: This configuration section cannot be used at this path. This happens when the section is locked at a parent level. Locking is either by default (overrideModeDefault="Deny"), or set explicitly by a location tag with overrideMode="Deny" or the legacy allowOverride="false".

The “Config Source” section of the error message will highlight in red the part that is denied.

In order to allow the web.config to modify the the identified configuration element you need to find and modify the ApplicationHost.config file. It is located in C:\Windows\System32\inetsrv\config. You’ll need to be running as an Administrator level user in order to modify the file.

Find the section group the setting belongs to, e.g.

<sectionGroup name="system.webServer">

Then the section itself:

<section name="handlers" overrideModeDefault="Deny" />

And update overrideModeDefault to "Allow" in order to allow the web.config to override it.

When you refresh the page for the website the error will be gone (or replaced with an error for the next section that you are not permitted to override)

Injecting a Dependency into an IHttpModule with Unity

We’re starting a new project, and as part of that we want to get better at certain things. One is unit testing the things we didn’t last time around that were in hard-to-reach places. Pretty much most things that interact with ASP.NET have hard-to-reach places. Even ASP.NET MVC, which was supposed to be wonderful and much more unit testable that vanilla ASP.NET, has lots of places where this falls down completely. However, we’re gradually finding way to overcome these obstacles.

In this post, I’m going to concentrate on custom IHttpModule implementations.

We have a custom IHttpModule that requires the services of another class. It is already set up in our IoC container and we just want to inject it into the module we’re writing. However, modules are instantiated by ASP.NET before our IoC framework can get to it.

How I got around this was by creating an additional module (an InjectorModule) that wired up all the other modules that needed dependencies injected using Unity’s BuildUp method to inject the dependency into the existing object.

Setting up the HttpApplication

The application object stores the container and implements an interface that the InjectorModule can access the container through.

public interface IHttpUnityApplication
{
    IUnityContainer UnityContainer { get; } 
}

And the Application class in the global.asax.cs file looks like this:

public class MvcApplication : System.Web.HttpApplication, IHttpUnityApplication
{
    // This is static because it is normal for ASP.NET to create
    // several HttpApplication objects (pooling) but only the first
    // will run Application_Start(), which is where this is set.
    private static IUnityContainer _unityContainer;

    protected void Application_Start()
    {
        _unityContainer = UnityBootstrapper.Initialise();
	// Do other initialisation stuff here
    }

    // This implements the IHttpUnityApplication interface
    public IUnityContainer UnityContainer
    {
        get { return _unityContainer; }
    }
}

The UnityBootstrapper initialises the container for MVC, it is created by the Unity.Mvc4 NuGet package (there’s also a Unity.Mvc3 package too). You can read more about it here.

The InjectorModule

Next up the InjectorModule is created

public class InjectorModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        // Get the IoC container from the application class through
        // the common interace.
        var app = (IHttpUnityApplication) context;
        IUnityContainer container = app.UnityContainer;

        // Wire up each module that is registered with the IoC container
        foreach (var module in context.GetRegisteredModules(container))
            container.BuildUp(module.GetType(), module);
    }

    public void Dispose()
    {
    }
}

I’ve also been a wee bit sneaky and created an extension method on HttpApplication to work out which are the registered modules so that the code above is a bit nicer. That code is:

public static class HttpApplicationExtensions
{
    public static IEnumerable GetRegisteredModules(this HttpApplication context, IUnityContainer container)
    {
        var allModules = context.Modules.AllKeys.Select(k => context.Modules[k]);
        var registeredModules = allModules.Where(m => container.IsRegistered(m.GetType()));
        return registeredModules;
    }
}

Wiring it all up

The container must be told which modules have dependencies to inject and what properties to set. e.g.

container.RegisterType<MyCustomModule>(
    new InjectionProperty("TheDependentProperty"));

MyCustomModule is the class that implements the IHttpModule interface, and you need to supply an InjectionProperty for each of the properties through which the IoC containers will inject a dependency.

You can also decorate the properies with the [Dependency] attribute, but then you are just wiring in a dependency on the IoC container itself… which is not good.

Finally, this new module has to be wired up in the web.config file

  <system.webServer>
    <modules>
      <add name="InjectorModule"
           type="Web.IoC.Unity.InjectorModule" />
      <!-- Other modules go here, after the Injector Module -->
    </modules>

By putting the Injector module ahead of other modules in the web.config it means it gets a chance to run and inject the depedencies into other modules that have yet to be initialised.

Other considerations

The IHttpModule interface defines a method, Init(), that takes an HttpApplication as a parameter. Naturally, that’s difficult to mock out in a unit test.

What I did was to extract all the bits that I needed in the Init() method and pass them to another method to do the work. For example, HttpContext is easy to do because ASP.NET MVC provides an HttpContextWrapper and the method that is doing all the work just takes an HttpContextBase, which is easily mocked in a unit test.

public void Init(HttpApplication context)
{
   var wrapper = new HttpContextWrapper(context.Context);
   InitImpl(wrapper);
}
public void InitImpl(HttpContextBase httpContext)
{
    // Do stuff with the HttpContext via the abstract base class
}

Authenticating Across Virtual Directories

If you have an application set up in a way similar to the previous post, which is essentially a domain that contains a number of web application hosted in various virtual directories on the server.

In my previous example, the root of the domain contains the application that contains the account management (the sign in, password retrieval, account set up, etc.), however each of the applications in each virtual directory must know who is logged in.

Assuming you are using the .NET’s built in authentication mechanisms this is unlikely to work out of the box. There is some configuration that need to happen to allow each of the applications to sync up.

Setting up the web.config file

In MVC 4 Forms Authentication must be set up explicitly.

<system.web>
  <authentication mode="Forms">
  </authentication>
  <!-- Other config settings -->
</system.web>

To ensure that each application can decrypt the authentication ticket in the cookie, they all must share the same machine key as by default IIS will assign each application its own encryption and decryption keys for security.

<system.web>
  <machineKey decryptionKey="10FE3824EFDA35A7EE5E759651D2790747CEB6692467A57D" validationKey="E262707B8742B1772595A963EDF00BB0E32A7FACA7835EBE983A275A5307DEDBBB759B8B3D45CA44DA948A51E68B99195F9405780F8D80EE9C6AB46B9FEAB876" />
  <!-- Other config settings -->
</system.web>

Do not use the above key – it is only an example.

These two settings must be shared across each of the applications sitting in the one domain.

Generating a Machine Key

To generate a machine key:

  • Open “Internet Information Services (IIS) Manager” on your development machine.
  • Set up a dummy application so that it won’t affect anything else on the machine.
  • Open up the Machine Key feature in the ASP.NET section

    IIS Manager
    IIS Manager
  • (1) In the “Validation key” section uncheck “Automatically generate at runtime” and “Generate a unique key for each application”.

    Machine Key Configuration in the IIS Manager
    Machine Key Configuration in the IIS Manager
  • (2) In the “Decryption key” section uncheck “Automatically generate at runtime” and “Generate a unique key for each application”.
  • (3) Click “Generate Keys” (this will change the keys randomly each time it is pressed)
  • (4) Click “Apply”

The web.config for this web application will now contain the newly generated machine key in the system.web section. Copy the complete machineKey element to the applications that are linked together.

There is an “Explore” link on the site’s main page in IIS to open up Windows Exporer on the folder which contains the web site and the web.config file.

Setting up a website that uses multiple projects

I’m looking at the possibility of restructuring some of our applications to unify them under one brand and one site. Currently our applications are on different sub-domains of our main domain and we’d like to bring all that under one roof so our application can be something like https://app.example.com and that’s it.

To that end I’m looking at setting up a central project (a portal, if you like) that the user enters and logs into and from there they can move off into the various applications depending on what they want to do. Each of the application would sit in a virtual directory off the main application.

Basic Setup

Each of the projects needs to have the project properties in the web tab synchronised so that they are in agreement with each other. I decided on a port number to use and duplicated that across each of the projects.

Root Project

To start with the root project (that’s the one that appears at the root of the domain) should be set to use IIS Express.

  • In the solution explorer right click the project and then select “Properties” from the menu, alternatively click the project then press Alt+Enter.
  • Once the properties appear go to the “Web” tab and scroll down to the “Server” section.
  • Ensure that “Use Local IIS Web Server” is selected
  • Check “Use IIS Express” if it isn’t already.
  • In the project URL choose a port number that you want to use across each of the projects. (You can leave the default for this project if you wish, but take a note of it for the others)
  • Press “Create Virtual Directory” to set up IIS Express.
Setting up the root application
Setting up the root application

Remember the port number that was used for the root project as it will be needed for the other projects.

Set up the first application

In the first application project put similar details in Project Properties.

The only difference is that the Project URL has a virtual directory added to it.

Setting up the first application
Setting up the first application

Set up the second application

This is similar to the first application, except that the Project URL has a different virtual directory added to it.

Setting up the second application
Setting up the second application

Xander.PasswordValidator – In a Web Application

In the last post I introduced Xander.PasswordValidator and showed the basics of how to configure it. In this post I’m going to show the PasswordValidationAttribute and how you can use it in your ASP.NET MVC application.

PasswordValidation attribute

At its simplest, all you need to do is to decorate a property in your model with the PasswordValidationAttribute, like this:

  public class SomeModel
  {
     [PasswordValidation]
     public string Password { get; set; }






   
    // Other stuff goes here
  }

That will validate the password based on the settings in the config file, which I discussed briefly in my previous post, and I’ll go into more detail later.

Registering the Password Validator

In order for the file paths to custom word lists to be resolved correctly in a web application you need to register the validator in the Application_Start() method in your web application’s HttpApplication derived class. (Or anywhere before first use).

For example, the Application_Start() method may look like this:

protected void Application_Start()
{
   PasswordValidatorRegistration.Register(); // Register password validator
   AreaRegistration.RegisterAllAreas();
   RegisterGlobalFilters(GlobalFilters.Filters);
   RegisterRoutes(RouteTable.Routes); }

Validating settings from code

As the settings can get quite complex they cannot be set directly in the attribute that you use to decorate the model. Instead they can be set elsewhere and referenced in the attribute.

The settings can be configured as normal then added to the PasswordValidationSettingsCache. For example:

var settings = new PasswordValidationSettings();
settings.NeedsNumber = true;
settings.NeedsSymbol = true;
settings.MinimumPasswordLength = 6;
settings.StandardWordLists.Add(StandardWordList.FemaleNames);
settings.StandardWordLists.Add(StandardWordList.MaleNames);
settings.StandardWordLists.Add(StandardWordList.Surnames);
settings.StandardWordLists.Add(StandardWordList.MostCommon500Passwords);
PasswordValidationSettingsCache.Add("StandardRules", settings);

This code would typically be placed in the Application_Start() method, after registering the password validator.

The important line is the last one. It adds the setting tot he cache with the name “StandardRules”. That can then be references in the attribute later. Like this:

public class MyModel
{
  [PasswordValidation("StandardRules")]
  public string Password { get; set; } 
}

The PasswordValidationAttribute references the entry in the cache, which is then retrieved to perform the validation.

IDisposable objects with StructureMap and ASP.NET MVC 4

I’ve recently discovered a bit of an issue with running an IoC container with ASP.NET MVC’s IDependencyResolver. If you have a controller that has dependencies on things that implement IDisposable then the dispose method was not being called.

Apparently, if the controller itself is disposable then MVC will clean that up and that can obviously clean up any dependencies that it created and are also disposable. However, if you are injecting the dependency then the controller should not really be disposing of those dependencies because it did not create them as it has no knowledge of the lifecycle of those objects – the owner (the object that created the dependency) is really responsible for disposing of its objects.

So, the responsible party for disposing of the is what ever created it. However, in MVC 4 the Service Locator has no way of disposing downstream objects that get created when instantiating the controller, it only deals with the controller directly, so if a downstream object that the controller depends on needs to be disposed then the IoC container has to manage that. Mike Hadlow has a much better explanation of what is going on here and his dealings with using, specifically, Castle Windsor and the IDepenencyResolver.

Since I’m using StructureMap, it does have a way of helping you clean up.

For example, in the Initialisation expression that the ObjectFactory.Initialize uses I’ve got a repository set up like this:

x.For<IRepository>().HttpContextScoped().Use<Repository>();

This creates a new Repository for each request that the MVC application receives. However, this on its own is not enough because it means that while each request gets a new repository, none of the resources of these repository objects are being cleaned up because it never releases them. Eventually those resources will run out, be they database connections, file handles, or what ever the repository needs to use.

You can put in your Global.asax.cs file a method called Application_EndRequest() which is called at the end of each request. Or, if you already have one you can simply add this line of code to it.

protected void Application_EndRequest()
{
  ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
}

Getting umbraco up and running in with MVC 4

In this post, I’ll look at getting Umbraco and MVC to play nice with each other in the same project.

Installing Umbraco 4.8

First off create a Web Application project in Visual Studio. For this example, I’m just going to create the project as “UmbMvc”.

Visual Studio 2010: New Project
Visual Studio 2010: New Project

Once Visual Studio has created the project, delete most of its content. We’re doing this because we don’t have fully empty projects. If you were doing this in VS 2012 you could have selected the Empty Web Application project instead.

The Solution explorer should look like this when your done:

Visual Studio 2010: Solution Explorer
Visual Studio 2010: Solution Explorer

Next up, Umbraco has to be installed. This can be done with NuGet. I used the Package Manager Console, which can be accessed from the Tools menu:

Visual Studio: NuGut Package Manager Console
Visual Studio: NuGut Package Manager Console

Then typed Install-Package UmbracoCms to install the package and its dependencies. The output looks like this:

PM> Install-Package UmbracoCms
'UmbracoCms.Core (= 4.8.0)' not installed. Attempting to retrieve dependency from source...
Done.
Successfully installed 'UmbracoCms.Core 4.8.0'.
Successfully installed 'UmbracoCms 4.8.0'.
Successfully added 'UmbracoCms.Core 4.8.0' to UmbMvc.
'web.config' already exists. Skipping...
Successfully added 'UmbracoCms 4.8.0' to UmbMvc.

Don’t worry about the message about web.config. It will write the necessary detail into the web.config file for you.

If you prefer to use the NuGet dialog, you can search for “UmbracoCms” and install the package from there. It will download and install the dependencies for you there too.

NuGet Package Manager Dialog
NuGet Package Manager Dialog

At this point you can run up Umbraco to configure it and set the databases up and so on. When you’ve finished this process you’ll arrive at the Umbraco administration area. At this point you want to stop the app from running in Visual Studio.

Wiring up MVC 4

Next up is to get MVC installed. For this I’m taking the advice on Aaron Powell’s blog, so go visit there for the detail. (Start at the section marked “Getting MVC installed”). I’ve added my own notes below for some differences I found between our experiences.

Installing ASP.NET at the time of writing installs MVC 4:

PM> install-package microsoft.aspnet.mvc
'Microsoft.AspNet.WebPages (= 2.0.20505.0)' not installed. Attempting to retrieve dependency from source...
Done.
'Microsoft.Web.Infrastructure (= 1.0.0.0)' not installed. Attempting to retrieve dependency from source...
Done.
'Microsoft.AspNet.Razor (= 2.0.20505.0)' not installed. Attempting to retrieve dependency from source...
Done.
Successfully installed 'Microsoft.Web.Infrastructure 1.0.0.0'.
Successfully installed 'Microsoft.AspNet.Razor 2.0.20505.0'.
Successfully installed 'Microsoft.AspNet.WebPages 2.0.20505.0'.
Successfully installed 'Microsoft.AspNet.Mvc 4.0.20505.0'.
Successfully added 'Microsoft.Web.Infrastructure 1.0.0.0' to UmbMvc.
Successfully added 'Microsoft.AspNet.Razor 2.0.20505.0' to UmbMvc.
Successfully added 'Microsoft.AspNet.WebPages 2.0.20505.0' to UmbMvc.
Successfully added 'Microsoft.AspNet.Mvc 4.0.20505.0' to UmbMvc.

Because of the way paths work in Umbraco, I was either going to have to reserve every controller and area name in the umbracoReservedPaths configuration element, or create a prefix. I decided it was probably best to create a prefix, that way I only have to modify the config once and everything else simply works after that. So, my RouteSetup class looked like this:

using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

[assembly: PreApplicationStartMethod(typeof(UmbMvc.App_Start.RouteSetup), "Setup")]
namespace UmbMvc.App_Start
{
  public class RouteSetup
  {
     public static void Setup()
     {
       RouteTable.Routes.MapRoute(
         "Default", // Route name
         "x/{controller}/{action}/{id}", // URL with parameters
         new {controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
         );
     }
  }
}

Note the “x” at the start of the URL part of the route.

My umbracoReservedPaths config element now looks like this:

<!-- Remember to add into the umbracoReservedPaths every route that MVC wants to take. 
     It may be better to create a prefix so you only have to do this the once.-->
<add key="umbracoReservedPaths" value="~/umbraco,~/install/,~/x" />

Fixing up the web.config file

From Aaron’s blog post, I still couldn’t quite get it to work. I got an error message that read: Compiler Error Message: CS0234: The type or namespace name ‘Helpers’ does not exist in the namespace ‘System.Web’ (are you missing an assembly reference?)

I found that I needed to add the following to the <assemblies> section of the web.config file:

<add assembly="System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

That helped, but I was still getting error an error message. This time it was Compiler Error Message: CS0234: The type or namespace name ‘Ajax’ does not exist in the namespace ‘System.Web.Mvc’ (are you missing an assembly reference?)

I found that I also needed to set the reference to System.Web.Mvc to “Copy Local”

Visual Studio 2010: System.Web.Mvc Copy Local
Visual Studio 2010: System.Web.Mvc Copy Local

Then when I ran the application and went to the URL http://localhost:60445/x/Home, I got a page back that said: Hello I’m a razor view.

This is finally what I expected.

Fixing up the project type.

One last thing, Aaron also mentions that you’ll get various errors in the views in Visual Studio because the project type was a Web Application not an MVC Web Appliction project. Although it doesn’t stop the application from running correctly, it is very disconcerting to see. He doesn’t give a solution to that. To solve this, you need to add a GUID in the csproj file.

To do this, you need to right click on the web project and click “Unload Project”, when it has unloaded, right-click again and click “Edit xxx.csproj”.

Look for the element named “ProjectTypeGuids” and add in the guid: {E3E379DF-F4C6-4180-9B81-6769533ABE47}. The whole line should now read:

<ProjectTypeGuids>{E3E379DF-F4C6-4180-9B81-6769533ABE47};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

Save the file, then right-click the project in the solution and click “Reload project”. It will prompt you to close the text version of the project file, and then the project will be loaded back. Now you should not have any issues with the views finding false errors, such as not being able to resolve ViewBag.

Tip of the day: Expire a cookie, don’t remove it

I recently found a bug in my code that I couldn’t fathom initially until I walked through the HTTP headers in firebug. In short, you cannot simply remove a cookie by calling Remove(cookieName) on the HttpCookieCollection. That will have no effect. You have to expire the cookie in order for it to be removed.

In other words, you need code like this:

HttpCookie cookie = new HttpCookie("MyCookie");
cookie.Expires = DateTime.UtcNow.AddYears(-1);
Response.Cookies.Add(cookie);

When you create a cookie, the response from the server will contain an HTTP Header called Set-Cookie that contains the value of the cookie.

For example, if we create a cookie like this:

HttpCookie cookie = new HttpCookie("MyCookie");
cookie.Value = "The Value of the cookie";
Response.Cookies.Add(cookie);

Then the Response will contain this:

Set-Cookie    MyCookie=The Value of the cookie; path=/

Each subsequent request to the server will contain the cookie, like this:

Cookie        MyCookie=The Value of the cookie

The responses from the server do not contain the cookie unless the server is updating the value of the cookie.

When the cookie is to be removed forcefully, the server must update the cookie with a new expiry, like this:

HttpCookie cookie = new HttpCookie("MyCookie");
cookie.Expires = DateTime.UtcNow.AddYears(-1);
Response.Cookies.Add(cookie);

The response will then have this header:

Set-Cookie    MyCookie=; expires=Mon, 20-Sep-2010 21:32:53 GMT; path=/

And in subsequent requests the cookie won’t be present any more as the browser will have removed it.

Parallel Tasks and the HttpContext

A few days ago I spotted a question on StackOverflow by someone trying to use a parallel loop in an ASP.NET application. It may have been an ASP.NET MVC application (I don’t recall) but the issue is the same.

This person had some code in a parallel task that was using the HttpContext object. I would be hesitant to use that object in the first instance as I don’t know how thread safe it is. I suspect that since it holds a lot of information about the state of a request/response that it would be quite dangerous to access an instance in many threads.

His main issue what that he was getting a null back from HttpContext.Current inside the parallel tasks.

ASP.NET is already multithreaded. It abstracts most of that away so that when you are writing against it you only really ever see the request you are currently dealing with. Many other requests are happening around you, but the framework does its best to shield you from that so that you can write code cleanly. It is also its downfall in some cases.

If you don’t realise what the framework is doing for you then you could very easily fall into a number of traps when you get to the edges of that abstraction. So, when someone uses HttpContext.Current inside parallel tasks not realising that there must already by multiple requests being handled, and therefore there must be multiple simultaneous HttpContext objects floating around masquerading as the Current context. It can become very difficult to track down bugs if you know what the constraints of what Current means in this… erm… context.

Ultimately, HttpContext.Current is only available on the thread that you started with in ASP.NET. If you create new threads then it is no longer available unless you explicitly set it yourself.

ASP.NET MVC – Pretty URLs

As the little Harris Benedict Calculator application stands, the only way to get some sort of answer out of it is to fill in the form. What if you wanted to pass someone a URL that takes them directly to the answer?

We could just create a controller action that takes the viewModel and have the MVC framework populate it from the URL. For example:

public ActionResult Result(HarrisBenedictViewModel viewModel)
{
    return Calculate(viewModel);
}

However, this would make the URL require a query string like this: http://localhost:42225/Main/Result?IsMale=True&Weight=75&Height=173&Age=37

This could be better.

Mapping a route for nicer URLs

What we can do is create a route to make the mapping nicer to look at than query string parameters. If we add it in before the default route it will be processed first if there is a match. So, the new RegisterRoutes method in the global.asax.cs file now looks like this:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute("Permalink",
        "Gender/{Gender}/Weight/{Weight}/Height/{Height}/Age/{Age}",
        new { controller = "Main", action = "CalculateResult"}
        );

    routes.MapRoute(
        "Default",
        "{controller}/{action}",
        new { controller = "Main", action = "Calculate" }
    );
}

As you can see the new “Permalink” route defines a pattern that replaces parameters with properties from the view model. The controller’s action method takes in the view model that will have been populated with the values from the URL.

The controller’s action method is specified in the route as CalculateResult, and that method looks like this:

public ActionResult CalculateResult(HarrisBenedictViewModel viewModel, string gender)
{
    viewModel.IsMale = gender.ToLowerInvariant() == "male";

    return Calculate(viewModel);
}

There is actually on surprise here. Since we wanted a nice clean URL, the IsMale value is replaced with something more friendly looking. Since this isn’t in the view model, a new parameter on the action method is added to capture the value coming in from the URL. It is then processed so that the viewModel object is updated with the value that is needed.

Finally, the action calls another action, one that was created when we added our server side validation. This validates the view model and either returns the result, or if the view model is invalid directs the user to input the correct data. If the user has to fill in any data then the form will contain the values as provided in the URL and the validation messages will point out which bits need corrected.

Making a permalink on the results view

Now that we have everything set up to be able to pass around a URL with all the form values preset, it would be great to give people a way to get that link. So, on the CalculatorResults.cshtml file we are going to make that link using the HTML helper method ActionLink.

The snippet from the view looks like this:

<p>@Html.ActionLink("Calculate another?", "Calculate", "Main");
@Html.ActionLink("Permalink", "CalculateResult", new {
    Weight = Model.Weight,
    Height = Model.Height,
    Age = Model.Age,
    Gender = Model.IsMale ? "Male" : "Female" })</p>

The first action link is what was there before. The second is for the new permalink.

The first parameter is the text the user sees. The second parameter is the action method to use. When the routes are examined the first match is used, so the first route that can be determined to use the CalculateResult action method is the one we set up earlier. The third parameter is an anonymous type that provides the values to inject into the URL template provided in the RegisterRoutes method in the global.asax.cs file.

Now the user can get a permalink with nice URLs like this: http://localhost:42225/Gender/Male/Weight/75/Height/173/Age/37

Tidying things up a bit

Although we’ve got what we want, we are not going to leave things here. There is a little bit of tidying up to do first.

The action method is a little clunky. It essentially has to marshal values between the view model and the URL. The view model is a model of the view and the URL is just as valid a view as the HTML. If we can move that marshalling into the view model itself things would look better.

The HarrisBenedictViewModel class gets a new property that acts as a friendlier route to setting the IsMale property. The new property, called Gender, looks like this:

public string Gender
{
    get
    {
        return IsMale ? "Male" : "Female";
    }
    set
    {
        IsMale = (value.ToLowerInvariant() == "male");
    }
}

As a result, the action method on the controller no longer needs the extra parameter nor does it need the code to interpret that extra parameter. It now looks like this:

public ActionResult CalculateResult(HarrisBenedictViewModel viewModel)
{
    return Calculate(viewModel);
}

Finally, because the view model now contains a way to get the gender out (as well as in) the code in the cshtml file to generate the permalink can be cleaned up too. It now looks like this:

@Html.ActionLink("Permalink", "CalculateResult", new {
    Weight = Model.Weight,
    Height = Model.Height,
    Age = Model.Age,
    Gender = Model.Gender })

But why not…?

All the code in the cshtml file is doing is passing only bits of the view model to the ActionLink method, what’s wrong with just passing the whole view model. Surely it will just discard the bits it doesn’t need.

The problem is that it doesn’t know which bits it will need. If it finds properties on the view model that aren’t already in the URL template defined on the route, it will just add them as query string parameters, which makes the URL look like this:http://localhost:42225/Gender/Male/Weight/80/Height/173/Age/35?IsMale=True&BasalMetabolicRate=0&LifestyleRates=System.Collections.Generic.List%601%5BSystem.Collections.Generic.KeyValuePair%602%5BSystem.String%2CSystem.Int32%5D%5D

That list on the view model looks really ugly in the URL (and it doesn’t actually mean anything useful either).

Summary

In this post we made a new route to provide pretty URLs in order to access the resource that we needed in a permanent fashion without having to fill in the form or craft an HTTP Post. Then we tidied up the code a little in order to keep things a little cleaner.

You can download the sample code if you want to have a further play with it.

Other posts in this series