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.

Xander.PasswordValidator – The config file

Earlier in this series I introduced the config file, but I didn’t say much about it other that show an example. In this post I’ll go in to more detail.

Defining the config section

To define the section:

<configSections>
<!-- Set up other config sections here—>
   <sectionGroup name="passwordValidation">
      <section name="rules" type="Xander.PasswordValidator.Config.PasswordValidationSection, Xander.PasswordValidator, Version=0.1.0.0, Culture=neutral, PublicKeyToken=fe72000dffcf195f" allowLocation="true" allowDefinition="Everywhere"/>
   </sectionGroup> </configSections>

This defines the configuration section will will appear later in the config file.

An example of the config section itself:

<!-- The configuration section that describes the configuration for the password validation -->
<passwordValidation>
   <rules minimumPasswordLength="6" needsNumber="false" needsLetter="false" needsSymbol="false">
     <wordListProcessOptions checkForNumberSuffix="true" checkForDoubledUpWord="true" checkForReversedWord="true" />
     <standardWordLists>
       <add value="FemaleNames"/>
       <add value="MaleNames"/>
       <add value="MostCommon500Passwords"/>
       <add value="Surnames"/>
     </standardWordLists>
     <customWordLists>
       <add file="WordLists/MyCustomWordList.txt" />
       <add file="WordLists/MyOtherCustomWordList.txt" />
     </customWordLists>
   </rules> </passwordValidation>

The rules

The rules section defines the actual rules by which the passwords will be validated.

<rules minimumPasswordLength="13" needsNumber="true" needsLetter="true" needsSymbol="true">
  • minimumPasswordLength: a positive integer that defines the minimum number of characters needed for a valid password. It is optional and if missing will default to 8.
  • needsNumber: Boolean that indicates whether the password needs a number in it. It is optional and if missing will default to true.
  • needsLetter: Boolean that indicates whether the password needs a letter in it. It is optional and if missing will default to true.
  • needsSymbol: Boolean that indicates whether the password needs a symbol in it. It is optional and if missing will default to false.

Rules can have a number of child elements also.

  • wordListProcessOptions: A set of options for how the word lists are processed
  • standardWordLists: A collection of built in word lists to use to check the password against.
  • customWordLists: A collection of custom word lists to use to check the password against.

The word list process options

By default, checking the password against the word lists only checks to see if the password is in a word list. These are additional options for checking against the word lists.

<wordListProcessOptions checkForNumberSuffix="true" checkForDoubledUpWord="true" checkForReversedWord="true" />
  • checkForNumberSuffix: Indicates whether the password should be checked to see if it is simply in the word list with an additional digit appended. This is optional, and by default is false.
  • checkForDoubledUpWord: Indicates whether the password should be checked to see if it is the same sequence repeated over again, and if it is to see if the first half is in the word list. This is optional and the default value is false.
  • checkForReversedWord: Indicates the a reversed form of the password should be checked to see if it in the word list. This is optional and the default value is false.

Standard word lists

This element is a container for a collection of standard word list items.

     <standardwordlists>
       <add value="FemaleNames" />
       <add value="MaleNames" />
       <add value="MostCommon500Passwords" />
       <add value="Surnames" />
     </standardwordlists>

The valid words list are:

Custom word lists

This element is a container for a collection of file paths to plain text files that contain custom word lists to check against. A word list file is simply a plain text file with one word per line.

     <customWordLists>
       <add file="WordLists/MyCustomWordList.txt" />
       <add file="WordLists/MyOtherCustomWordList.txt" />
     </customWordLists>

The paths are relative to the working directory of the application in which the password validator is operating. In an ASP.NET web application the paths should be prefixed with the ~ to ensure they are correctly mapped on the server relative to the root of the web application.

Umbraco installation woes

Recently, I created an Umbraco site on one machine and I wanted to move it to another. I say site, there was nothing in it. It was really a basic database, but I’d configured it with just an empty install before I zipped up the solution and all the files in it.

When I unzipped the files on to a new machine every time I tried to run the Umbraco install routine it would redirect me to a log on page even although the site had only a completely empty database to connect to (not a single table because the installation hadn’t run yet).

The reason is that on installation part of the web.config is updated.

<add key="umbracoConfigurationStatus" value="4.8.0" />

And that indicates to Umbraco that the installation is complete. So, allow installation to proceed normally remove the value and the installation can start normally, like this:

<add key="umbracoConfigurationStatus" value="" />

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.

Installing a web site on a new server

Here are some blog posts that have been useful to me lately when I got caught out installing a website on a new server (I will eventually get that automated build and deploy process actually performing the deploy step successfully!!)

The configuration section ‘system.web.extensions’ cannot be read because it is missing a section declaration:

While installing a website on a new Windows Server I came across this error. In short, it was because the App Pool was set up as a .NET 2.0 application rather than a 4.0. The blog post explains what was going on and how to fix it.

[Resolved] Could not load file or assembly ‘XXXXX’ or one of its dependencies. An attempt was made to load a program with an incorrect format:

Although this didn’t help me in the end, it does suggest a solution. In my case, because of a third-party dependency that requires an x86 build, it couldn’t be used. In time that dependency will be removed, in the meantime the following was more helpful to me…

Could not load file or assembly ‘PresentationCore’ or one of its dependencies. An attempt was made to load a program with an incorrect format. : A solution:

This post did give me the pointer I needed to the setting that had to be changed to get the web site working.

Friendly error messages with Microsoft Report Viewer Control

For a project I’m working on I’ve got to display data that’s coming from SSRS (SQL Server Reporting Services) on a web page for our users. One of the feedback items from the first round of user testing was that the error messages from the control were not helpful or friendly.

For example, if a user types in a date in an incorrect format the Report Viewer Control would return an error message to the user like this:

The value provided for the report parameter 'pToDate' is not valid for its type. (rsReportParameterTypeMismatch)

The user doesn’t know that “pToDate” is. The nearest possibly alternative on the page reads “To Date” so they could possibly take a good guess, but why risk support calls over something like that? And the “(rsReportParameterTypeMismatch)” is likely to mean even less to a user. Why should they even have to see things like that?

So I set about trying to change that.

In fact the control gives you no way to alter the error messages that it shows. There is a ReportError event that you can subscribe to and indicate you’ve handled the error, but no where to provide a better error message.

With that in mind I thought that what I’d do is create a Label on the page to hold the error message and populate it if an error occurred.  However, I found that while I could get the message to display initially, I could not get it to go away once the user had corrected the error.

What I had was this:

ASPX:

<asp:Label runat="server" ID="ReportErrorMessage" Visible="false"
           CssClass="report-error-message"></asp:Label>
<rsweb:reportviewer runat="server" ID="TheReport"  Font-Names="Verdana"
                    Width="100%" Height="100%" Font-Size="8pt"
                    InteractiveDeviceInfos="(Collection)" ProcessingMode="Remote"
                    InteractivityPostBackMode="AlwaysSynchronous"
                    WaitMessageFont-Names="Verdana" WaitMessageFont-Size="14pt"
                    OnReportError="TheReport_ReportError"
                    OnReportRefresh="TheReport_ReportRefresh">
    <serverreport reportpath="/Path/To/The/Report"
                  reportserverurl="http://the-report-server.com/ReportServer" />
    </rsweb:reportviewer>

C#

protected void TheReport_ReportError(object sender, ReportErrorEventArgs e)
{
  if (e.Exception.Message.Contains("rsReportParameterTypeMismatch"))
    ReportErrorMessage.Text = BuildBadParameterMessage(e);
  else
    ReportErrorMessage.Text = BuildUnknownErrorMessage(e);

  ReportErrorMessage.Visible = true;
  e.Handled = true;
}

protected void TheReport_ReportRefresh(object sender, CancelEventArgs e)
{
  ReportErrorMessage.Visible = false;
  ReportErrorMessage.Text = string.Empty;
}

Somehow or another the initial message was being set, however the changes in TheReport_ReportRefresh were not being applied despite me verifying the code was being run.

I eventually realised that the report viewer control was not performing a full postback, but just a partial postback and that I needed to put the Label control inside an update panel. Like this:

<asp:UpdatePanel runat="server">
    <ContentTemplate>
        <asp:Label runat="server" ID="ReportErrorMessage" Visible="false"
                   CssClass="report-error-message"></asp:Label>
    </ContentTemplate>
</asp:UpdatePanel>

Once I did that the message appeared and disappears correctly.

Opting out of Google Instant Browsing

I recently wrote about a new feature of Google Chrome called Instant Browsing. You can turn it on or off Basic tab of the  Options page in Chrome.

If you are a web site owner/administrator and are concerned about the impact it might have on your web server to have a deluge of requests going to your server that the end user is probably not really interested in, or having a number of requests going to your server that result in a 404 resource not found because the half formed URL in the “omnibox” does not actually resolve to a real page then you can opt out.

For folks running ASP.NET (both WebForms and MVC) I’ve created a simple HTTP Module that will opt your site out if it encounters requests from Chromes’ Instant Browsing.

You can download the Module here: Instant Browsing HTTP Module V1. And to activate it in your application you need to add the DLL file as a reference to your application and then add the bolded line to your web.config.

<configuration>
 <system.web>
  <httpModules>
   <add name="InstantBrowsingOptOut" type="InstantBrowsing.InstantBrowsingOptOut, InstantBrowsing"/>
  </httpModules>
 </system.web>
</configuration>

 

The Code

If you prefer, you can add the following source to your application and compile it yourself.

using System;
using System.Collections.Specialized;
using System.Web;

namespace InstantBrowsing
{
    public class InstantBrowsingOptOut : IHttpModule
    {
        public void Dispose()
        {
            // Nothing to dispose. Required by IHttpModule
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(BeginRequest);
        }

        void BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;

            string headerValue = GetPurposeHeaderValue(application.Request);
            if (HeaderValueDoesntExist(headerValue))
                return;

            if (PreviewMode(headerValue))
                Issue403Forbidden(application.Response);
        }

        private bool PreviewMode(string value)
        {
            return value.ToLowerInvariant().Contains("preview");
        }

        private bool HeaderValueDoesntExist(string value)
        {
            return string.IsNullOrEmpty(value);
        }

        private string GetPurposeHeaderValue(HttpRequest request)
        {
            NameValueCollection headers = request.Headers;
            return headers["X-Purpose"];
        }

        private void Issue403Forbidden(HttpResponse response)
        {
            response.Clear();
            response.StatusCode = 403;
            response.End();
        }
    }
}

And to activate it in your application you need to add the bolded line to your web.config.

<configuration>
 <system.web>
  <httpModules>
   <add name="InstantBrowsingOptOut" type="InstantBrowsing.InstantBrowsingOptOut, InstantBrowsing"/>
  </httpModules>
 </system.web>
</configuration>

Note that the second “InstantBrowsing” in the type attribute is the assembly, so if you’ve put it in an assembly with a different name you’ll need to change the type attribute to reflect that.

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.

Starting an ASP.NET MVC 3 application

In this post, I’m going to show the basics of starting an application with ASP.NET MVC 3. The demo application will be a simple calorie counter that takes in a number of values from the user that is then used to calculate the calorific intake. The original calculation can be found here here: How many calories should I be eating?

First, if you don’t have it already, you’ll need to download ASP.NET MVC 3. Remember to shut down Visual Studio 2010 for the installation. And if you don’t have it already, I’d also recommend downloading Visual Studio 2010 SP1 and upgrading to it.

Creating the project

In Visual Studio 2010, from the New Project Dialog, go to the Visual C#/Web templates section and select ASP.NET MVC 3 Web Application from the list in the middle. If you don’t see it, ensure that the drop down says .NET Framework 4.

For this project, the Name will be set to “HarrisBenedictCalculator” as that is the type of calculation that the application will be performing.

1 - Visual Studio 2010 New Project dialog

Once the appropriate details are entered the OK button takes you to a more specific dialog for ASP.NET MVC 3 projects.

Since I want to show everything rather than rely on existing templates where some of the ground work is already done, I’m selecting the Empty template. I’m also going to select the “Use HTML5 semantic markup” and set the view engine to Razor.

Because we are selecting the empty template, unit tests project creation is not available. We can do that later.

2 - New ASP.NET MVC 3 Project dialog

When the second dialog is OK’ed the project will be created. For all that I selected “Empty” Visual Studio has created an awful lot of files already.

What’s already in this “empty” project?

3 - What's in the empty ASP.NET MVC 3 project

The project contains a set of predefined folders, some of which are already populated with files.

  • Content: Contains static content files such as CSS, graphics and javascript files.
  • Scripts: Contains a profusion of javascript files.
  • Views:  This contains some files that act like Master files for the Razor engine, an error page Razor template and a web.config.

The top level folder also contains a global.asax file which defines a set of default routes and filters, a packages.config file which is used by NuGet and a set of web.config files.

If you attempt to run the application as is then it will compile, but you go directly to a error message that says “The resource cannot be found” because there are not controllers as yet, so the routing engine cannot find a route for the default resource.

4 - Resource Not Found

For the moment, we are going to have a simple static HTML page for the error. To that end the web.config will have the following added to it:

<customErrors mode="RemoteOnly" defaultRedirect="Error.htm" />

We will see error messages, but once deployed the end users won’t. If you want to learn more about error handling see this other post on custom error handling in ASP.NET MVC 3.

Creating the initial view and controller

First up we are going to create the model of the view, the ViewModel if you like. This will contains all the variables needed to generate the request and receive data back in the response.

public class HarrisBenedictViewModel
{
    public HarrisBenedictViewModel()
    {
        LifestyleRates = new List<KeyValuePair>();
    }

    public bool IsMale { get; set; }
    public double Weight { get; set; }
    public double Height { get; set; }
    public int Age { get; set; }
    public double BasalMetabolicRate { get; set; }
    public IList<KeyValuePair<string, int>> LifestyleRates { get; set; }
}

Incidentally, I always initialise lists so that I don’t have to do a null check. Normally most code will loop over the list, an empty list will loop the same number of times as a list that isn’t there.

Now, we are ready to create the controller. When you right click on the Controllers folder in the project structure, you get an “Controller…” option in the "Add” sub-menu.

5 - Add Controller

For this, I’m going to create a controller called Main and leave it empty. That gives us a class called MainController that is derived from Controller.

The controller is going to have two actions (this is a very simple application), one called CalculatorInput which will simply return a view for accepting the values, and the other called CalculatorResult which will display the results of the calculation. Both views use the view model we created earlier.

The CalculatorInput method looks like this:

public ActionResult CalculatorInput()
{
    HarrisBenedictViewModel viewModel = new HarrisBenedictViewModel();
    return View(viewModel);
}

The empty view model will be populated by the user. If we want to pre-populate values on the view then we can do so by setting the appropriate values in the view model.

ASP.NET MVC uses naming conventions to find things. So, by default, the view will be in a folder named after the controller (in this case “Main”) and the view will be named after the controller action (in this case “CalculatorInput”).

To create the View, create the appropriate folder in the Views folder (if it doesn’t already exist) and then right click the folder you’ve just created and select Add->View…

A dialog appears that looks like this:

6 - Add View

@model HarrisBenedictCalculator.Models.HarrisBenedictViewModel

@{
    ViewBag.Title = "Harris Benedict Calculator";
}

<h2>Harris Benedict Calculator</h2>

@using(Html.BeginForm("CalculatorResult", "Main", FormMethod.Post)){
    <fieldset>
        <legend>Information about you</legend>

        <div class="editor-label">Are you male?</div>
        <div class="editor-field">@Html.CheckBox("IsMale", Model.IsMale)</div>


        <div class="editor-label">Weight (in kilos)</div>
        <div class="editor-field"><input name="Weight" value="@Model.Weight" /></div>

        <div class="editor-label">Height (in centimetres)</div>
        <div class="editor-field"><input name="Height" value="@Model.Height" /></div>

        <div class="editor-label">Age (in years)</div>
        <div class="editor-field"><input name="Age" value="@Model.Age" /></div>

        <div class="submit"><input type="submit" value="Calculate!" /></div>
    </fieldset>
}

The view sets up the form for getting the user inputs.

The Html.BeginForm defines where the form will be sent to once it is complete and how it will be sent. In this case, the form will be sent by and HTTP POST to the CalculatorResult method on the MainController class. I’ll talk more about what that does in the next section.

The form consists of a number of inputs which, by convention, have the same name as properties on the Model. If the model is pre-populated then the initial values will be used to populate the values in each of the input elements.

The CheckBox is a special case. Because of the way HTML works, if the checkbox is unchecked then nothing is returned. The MVC application then does not know if the checkbox was not ticked, or if the checkbox simply didn’t exist at all. This may be an important distinction. Therefore an Html helper method is available that outputs the checkbox and an hidden field to go with it.

At this point we can run the application and get the initial view being displayed to us:

7 - Rendering first view

Submitting the answers

As I mentioned above, the Html.BeginForm helper method tells ASP.NET MVC what controller and method to return the result to when the user presses the submit button. So, we have to create a method to process that on the specified controller (in this case Main)

The Main.CalculatorResult method looks like this:

[HttpPost]
public ActionResult CalculatorResult(HarrisBenedictViewModel viewModel)
{
    double bmr = 0; // Base Metabolic Rate
    if (viewModel.IsMale)
    {
        bmr =
            66 +
            (13.7 * viewModel.Weight) +
            (5 * viewModel.Height) -
            (6.76 * viewModel.Age);
    }
    else
    {
        bmr =
            655 +
            (9.6 * viewModel.Weight) +
            (1.8 * viewModel.Height) -
            (4.7 * viewModel.Age);
    }

    viewModel.LifestyleRates.Clear();
    viewModel.LifestyleRates.Add(
        new KeyValuePair<string, int>("Sedentry", (int)(bmr * 1.2)));
    viewModel.LifestyleRates.Add(
        new KeyValuePair<string, int>("Lightly Active", (int)(bmr * 1.375)));
    viewModel.LifestyleRates.Add(
        new KeyValuePair<string, int>("Moderately Active", (int)(bmr * 1.55)));
    viewModel.LifestyleRates.Add(
        new KeyValuePair<string, int>("Very Active", (int)(bmr * 1.725)));
    viewModel.LifestyleRates.Add(
        new KeyValuePair<string, int>("Extra Active", (int)(bmr * 1.9)));

    return View(viewModel);
}

The above performs the calculation. The code may look a bit long, but the calculation is relatively simple. In a full business application this code would be separated out elsewhere.

The method is decorated with the HttpPost attribute which tells MVC that the method may only be called in response to an HTTP POST verb. The method also takes the view model class as a parameter. MVC will may the form inputs to the view model class as best it can. You can also specify a list of more primitive types (like int, string, double, etc.) that map to the input elements on the form.

The method itself performs the calculation then updates some items in the view model with the results of the calculation. The View is then returned with the view model.

The convention is that, unless specified otherwise, the view returned will be named after the controller method in a folder named after the controller class. So the view is in the ~ViewsMain folder in a file called CalculatorResult.cshtml

@model HarrisBenedictCalculator.Models.HarrisBenedictViewModel

@{
    ViewBag.Title = "Harris Benedict Calculator Result";
}

<h2>Result</h2>

<p>For a <em>@(Model.IsMale ? "man" : "woman")</em> aged <em>@Model.Age</em> years old,
weighing <em>@Model.Weight.ToString("0.0")</em> kg, and <em>@Model.Height</em> cm tall
should be taking in the following calories per day:
</p>

<div class="result">
@foreach (var lifestyle in Model.LifestyleRates)
{
    <div class="result-line">
        <span class="result-label">@lifestyle.Key</span>@lifestyle.Value Calories
    </div>
}
</div>

<p>@Html.ActionLink("Calculate another?", "CalculatorInput", "Main")</p>

This view extracts the data from the view model and renders it to the browser.

So, the answer I get looks like this:

8 - Final Result

The Razor syntax is quite easy to follow for the most part. Anything starting with an @ sign indicates the start of some C# code. The rendering engine is clever enough to detect HTML code and revert back when needed.

At the bottom of the page the HTML helper method ActionLink generates the URL to take the user back round to the start of the process again in case they want to calculate another set of calorie intakes.

Summary

In this post I’ve demonstrated some very basic initial steps to get going with ASP.NET MVC 3.

You can also download the sample code in order to have a play yourself.

Follow

Get every new post delivered to your Inbox.

Join 25 other followers