.Net Framework

Precompiling ASP.NET MVC applications with Teamcity & Octopus

Notice the first time you open a page or view in your ASP.NET MVC application, it’s takes quite a bit longer, and subsequent loads are faster? This is because they are compiled on-demand by IIS the first time someone tries to access them – dynamically being turned into an alpha-numerically named DLL. There are quite a few problems with this process:

  • Some errors in your razor code won’t be made apparent until the view is compiled after being accessed for the first time. If you follow the principle of “crash early”, then you’ll agree the web server is much too late for this to happen!
  • Web servers are meant to serve web requests, not compile code. Compiling views comes with a performance overhead that may affect the performance of concurrent requests.
  • If a user is unlucky enough to be the first to access a view they will be met with a long load time, giving a poor impression that something may be wrong.

In this post I will show you how to setup true precompilation for your ASP.NET application. The goal is to package our entire web application, including views, into one or more DLL files. This comes with many benefits:

  • Any compilation errors in your razor code are found well before any code is deployed to a web server.
  • Compilation is done on your build server, allowing you to create a deployment package that requires no additional compiling on the web servers.
  • Users are no longer victim to long load times the first time a view is accessed.

I am assuming that you already have a build and deploy process setup using Teamcity and Octopus. I will be showing you the small tweaks necessary to that process to make precompilation work.

Setup a Publishing Profile

We’re going to leverage publishing profiles as a way of instructing MSBuild on how to compile our project.

  1. Start by right clicking your web project in Visual Studio and clicking Publish…
  2. You will be asked to select a publish target. Select Custom and enter a profile name when prompted
  3. Under publish method select File System
  4. Under target location enter $(ProjectDir)precompiled and click next
  5. Select the build configuration you want to apply, and under File Publish Options make sure both options to delete all existing files prior to publish and precompile during publishing are both checked
  6. Click the Configure button that is next to the precompile during publishing option. Details on all the options in this window are documented on MSDN. For now we will make sure the allow precompiled site to be updatable option is unchecked. Select the option to Merge all outputs to a single assembly and enter a name for the DLL file, for example MyWebProject.Precompiled
  7. Close out of the dialogs. You can push the publish button to test your profile. Once the compile is complete, you should be able to go into your project directory and see a new folder called precompiled. Inside of it you will find the bin folder where you will see some new compiled DLL’s that weren’t there before. Those are your precompiled views.

If you look in the Properties folder in your project you should have a new folder called PublishProfiles containing an xml file with the profile configuration. Here is a sample of what it may look like:

<?xml version="1.0" encoding="utf-8"?>
<!--
This file is used by the publish/package process of your Web project. You can customize the behavior of this process
by editing this MSBuild file. In order to learn more about this please visit http://go.microsoft.com/fwlink/?LinkID=208121. 
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <PropertyGroup>
 <WebPublishMethod>FileSystem</WebPublishMethod>
 <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
 <LastUsedPlatform>Any CPU</LastUsedPlatform>
 <SiteUrlToLaunchAfterPublish />
 <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
 <PrecompileBeforePublish>True</PrecompileBeforePublish>
 <EnableUpdateable>False</EnableUpdateable>
 <DebugSymbols>False</DebugSymbols>
 <WDPMergeOption>MergeAllOutputsToASingleAssembly</WDPMergeOption>
 <UseMerge>True</UseMerge>
 <SingleAssemblyName>MyWebProject.Precompiled</SingleAssemblyName>
 <ExcludeApp_Data>False</ExcludeApp_Data>
 <publishUrl>$(ProjectDir)precompiled</publishUrl>
 <DeleteExistingFiles>True</DeleteExistingFiles>
 </PropertyGroup>
</Project>

MSBuild Precompiling Views in Teamcity

Now that we have a publishing profile setup, the next step is to automate the precompilation step in Teamcity.

  1. Add a new MSBuild step to your current build configuration (you do have one setup already to compile your project, right?). We will want this to be one of the last steps in our configuration.
  2. Give it a name, point the build file path to your solution file, and set the command line parameters to the following:
/p:DeployOnBuild=true
/p:PublishProfile=<YourPublishProfileName>.pubxml
/p:VisualStudioVersion=14.0
/p:Configuration=Release
/p:AspnetMergePath="C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools"

And that’s it, Teamcity will invoke MSBuild using the publishing profile we created earlier, and generate the precompiled DLL’s.

If you are going to be deploying using Octopus, make sure the Run OctoPack option is checked in the build step.

Creating an Octopus Package

The last step is to take our precompiled application and package it up for octopus to deploy. The first thing we need to do is create a .nuspec file in our project, make sure it has a build action property of Content. This will tell OctoPack how and what to package in our project. Name the .nuspec file the same as your web project and enter the following:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
 <metadata>
  <id>MyWebProject</id>
  <title>MyWebProject</title>
  <version>0.0.0.0</version>
  <authors>Me</authors>
  <description>The MyWebProject deployment package</description>
  <releaseNotes></releaseNotes>
 </metadata>
 <files>
  <file src="precompiled\**\*.*" target=""/>
  <file src="Web.*.config" target=""/>
 </files>
</package>

Basically we’re telling OctoPack some basic information about our project, and to include everything in the precompiled folder into our package. We are also asking Octopack to include any extra config transformations, this is optional but necessary if you wish to perform config transformation during your Octopus deploy process.

That should be it. Now when TeamCity runs, it will tell MSBuild to precompile all your views into one or more DLL’s using the publishing profile you created. Once that is done it will invoke OctoPack which will look at the nuspec file in your project and create an Octopus package containing the contents of the precompiled folder. You can then push that package to your Octopus server where it can then be deployed to your web servers.

Compile Time View Validation in ASP.NET MVC

Open up your favorite .cshtml file, put the mouse cursor in the middle of some razor code, and have a cat walk across your keyboard. If you don’t have a cat nearby, rolling your face on your keyboard will also suffice. You should start seeing things highlighted and underlined in red. Now go ahead and build your project.

Build Succeeded – Really?

Unlike all the other code in your project, your view files are not compiled when you hit the Build button in your IDE. Instead, they are compiled on-demand by IIS the first time someone tries to access them – dynamically being turned into an alpha-numerically named DLL. The problem is that any errors you have in your views won’t be made apparent until IIS tries to compile them, at which point the user who requested the view would see an error page. So how do you protect yourself from this happening?

Pre-compilation To The Rescue

Pre-compiling Razor views is possible, there are projects out there that will allow you to turn your views into DLL files before they even touch an IIS server. However doing so in this case would be overkill, we just want to know if there are obvious errors in our views.

To let you find those compile-time bugs there’s a flag you can set in your .csproj file.

<MvcBuildViews>true</MvcBuildViews>

This will cause your views to be test compiled when your project is built. Why do I emphasize test compiled? Because they aren’t compiled in the traditional sense that you end up with resulting DLL files, they will still need to be dynamically compiled by IIS later on. It’s just a test to see if when they are compiled by IIS, if any errors will be thrown.

You will find that this setting is false by default, and there’s a good reason for that – view compilation takes time. In a large enough project it could take enough time to seriously annoy a developer who is used to those quick compiles. A medium sized project of around 70 views has the compile time grow by 36 seconds when this feature is enabled.

But there’s a compromise, instead of having your views test compile during every build, we can set it to only test compile when performing a release build. If you look in your .csproj file, you will find a PropertyGroup block for each build configuration in your project. Find your release build configuration and add the MvcBuildViews property. In this example my build configuration is simply called Release.

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <MvcBuildViews>true</MvcBuildViews>
    ...
</PropertyGroup>

This way the debug builds you do on your machine will run fast, while the builds that run on your build server will take a bit longer, while validating that all your views will compile. If a view can’t be compiled, the build fails, and the code will never be deployed to an IIS server.

Using Assembly.GetCallingAssembly() inside custom HTML helpers in ASP.NET MVC

Suppose you need to get a reference to the assembly that originated the call to a custom HTML helper,  you have probably tried calling Assembly.GetCallingAssembly() within your helper method to achieve this. Instead, it will return an assembly name that you didn’t expect, perhaps something like: App_Web_views.home.index.cshtml.26149570.q0lhhvru. This can happen in several situations, for instance placing your custom HTML helpers in a different class library, or embedding your Razor views in seperate .dlls.

You probably know that by default, ASP.NET web pages (.aspx), user controls (.ascx), and MVC Razor views (.cshtml and .vbhtml) are compiled dynamically on the server by the ASP.NET compiler (although it is possible to pre-compile them). What some don’t realize is that Razor views are compiled as separate assemblies by the ASP.NET runtime. Those assemblies are dynamic, hence the cryptic assembly naming.

For example, you may have code in your index.cshtml file that calls your custom helper:

@Html.GetMyAssemblyName()

And your custom HTML helper:

public static string GetMyAssemblyName(this HtmlHelper htmlHelper)
{
	// returns the name of the dynamically generated dll that 
	// the razor was compiled into
	return Assembly.GetCallingAssembly().GetName().Name;
}

When the Razor code, within its dynamically generated dll, calls the helper method, it will end up returning the name of the dynamically generated dll. So how could you get the name of the project assembly that originally contained the .cshtml Razor view file?

One solution involves digging through the ViewContext to get the Controller that is associated with the view. Unlike views, code files in a web application project are compiled into a single assembly. Once you know the name of the Controller, you can search through the app domain for the assembly that contains it. Here is the modified HTML helper that does this:

public static string GetMyAssemblyName(this HtmlHelper htmlHelper)
{
	var controllerType = htmlHelper.ViewContext.Controller.GetType()
	var callingAssembly = Assembly.GetAssembly(controllerType);

	if(callingAssembly != null)
		return callingAssembly.GetName().Name;

	return null;
}

Not all views have a controller associated with them, for instance, layouts. In this case another way of getting the originating controller would be through calling:

htmlHelper.ViewContext.RouteData.Values["controller"]

And then retrieving the controller type through reflection.

Dynamically Generating Lambda Expressions at Runtime From Properties Obtained Through Reflection on Generic Types

Lately I’ve been having to export some of my data entities into CSV files, and I’ve been using the CSVHelper nuget package to achieve this. As is common, property names don’t translate well into readable column headers, so you have to provide some kind of property to string mapping.

This is how CSVHelper handles it:

namespace MyApplication.CSVMapping
{
	public class MyModelCsvMap : CsvClassMap
	{
		public override void CreateMap()
		{
			Map(m => m.Id).Name("Model Id");
			Map(m => m.Description).Name("Model Description");
			Map(m => m.StartDate).Name("Start Date");
			Map(m => m.EndDate).Name("End Date");
			Map(m => m.RunDate).Name("Run Date");
		}
	}
}

Nothing too fancy, just passing my model type into the derived class, and going through each class member, setting the Name property.

However, as is also common, I may also have a form tied to this model and I want to use the built in DataAnnotations to set the form labels for each field, like so:

namespace MyApplication.Models
{
	public partial class MyModel
	{
		[DisplayName("Model ID")]
		public int Id { get; set; }
		[DisplayName("Model Description")]
		public string Description { get; set; }
		[DisplayName("Start Date")]
		public DateTime StartDate { get; set; }
		[DisplayName("End Date")]
		public DateTime EndDate { get; set; }
		[DisplayName("Run Date")]
		public DateTime RunDate { get; set; }
	}
}

Noticing some redundancy here? Could I perhaps have CSVHelper get the property column header names from the DisplayName Attribute in the model rather than having to create a seperate CsvClassMap? That way I wouldn’t have to repeat my property to string mappings.

For this I will have to create a generic version of the CsvClassMap class, which takes in my entity type. From there I can get all the properties in that type, and start iterating through them. For each property, I check if it has a DisplayName attribute, and if it does, get what the value is. The tricky part is passing in the property into CSVHelper’s map method which expects a Expression<Func<TEntity, object>>. Here’s the complete code:

using System;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using CsvHelper.Configuration;

namespace MyApplication.Common
{
	public class BaseCsvMap : CsvClassMap where TEntity : class
	{
		public override void CreateMap()
		{
			PropertyInfo[] props = typeof(TEntity).GetProperties();
			foreach (PropertyInfo prop in props)
			{
				var displayAttribute = prop.GetCustomAttributes(false).FirstOrDefault(a => a.GetType() == typeof(DisplayNameAttribute)) as DisplayNameAttribute;
				if (displayAttribute != null)
				{
					var parameterExpression = Expression.Parameter(typeof(TEntity), "x");
					var memberExpression = Expression.PropertyOrField(parameterExpression, prop.Name);
					var memberExpressionConversion = Expression.Convert(memberExpression, typeof(object));
					var lambda = Expression.Lambda<Func<TEntity, object>>(memberExpressionConversion, parameterExpression);
					Map(lambda).Name(displayAttribute.DisplayName);
				}
			}
		}
	}
}

That should be fairly self explanatory. The only strange “gotcha” is having to call Expression.Convert() before constructing the lambda expression. This is because the expression explicitly expects “object” as it’s type, and your entity likely contains typed members ie. strings, ints, decimals, etc.

You can also modify the above class to working with any custom attributes that you may have defined, just remember to pass true into the GetCustomAttributes() method.

Move a ClickOnce Deployment to Another Server or Location

The goal of this post is to demonstrate how to move a clickonce deployment to another location, be it another server or another folder, without having to publish the package again.

You will need a tool called MageUI.exe which is available for download in the .NET SDK. Once installed you can find it in “Program Files\Microsoft SDKs\Windows\v6.0A\bin\MageUI.exe”.

The first step is to simply copy over the clickonce deployment folder to your new location. Next, open up MageUI.exe and select the Open command from the file menu. Search through your clickonce deployment folder for all instances of *.application files and open all of them. This is important as you will need to make changes to all *.application files for this to work.

For each *.application file, under deployment options, you will need to edit the start location to reflect on the new location you are moving your deployment to. Once you have done this for all files select the save all command from the file menu. You will be prompted to sign your package. One option for signing your package is to generate a .PFX file and point to that, providing the password that you specified when creating it. The other option is to point to a certificate that has already been stored on your machine.

Once saved, test the new deployment location by running the setup file.