Saturday, February 25, 2012

ASP.NET MVC4 bundling in ASP.NET MVC3

One of the new wildly evangelized features of ASP.NET MVC4 is the built-in support for bundling and minification of scripts and stylesheets.

I don't see any reason why this new feature wouldn't work for ASP.NET MVC3 though. If you open the packages config of an ASP.NET MVC4 beta project, you will find that bundling support lives in the Microsoft.Web.Optimization package.
<package id="Microsoft.Web.Optimization" version="1.0.0-beta" />
So we should just be able to install this package for an ASP.NET MVC3 project. To install the package, run following command. Pay attention to the -Pre switch.
PM> Install-Package Microsoft.Web.Optimization -Pre
Attempting to resolve dependency 'Microsoft.Web.Infrastructure (= 1.0.0)'.
Successfully installed 'Microsoft.Web.Infrastructure 1.0.0.0'.
Successfully installed 'Microsoft.Web.Optimization 1.0.0-beta'.
Successfully added 'Microsoft.Web.Infrastructure 1.0.0.0' to Optimization.
Successfully added 'Microsoft.Web.Optimization 1.0.0-beta' to Optimization.
Adding bundles happens when the application starts, together with registering areas, adding global filters and registering routes.

The quickest way to enable bundling is by enabling the default bundles.
BundleTable.Bundles.EnableDefaultBundles();
This method will add two bundles to the bundle table: one bundle for the stylesheets in the Content folder and one bundle for the scripts in the Scripts folder. The default bundles try to take core scripts into account when ordering the scripts in the bundle. For example, jQuery will be included before any of its plug-ins are included.

To reference these bundles you can add following snippet to your view or layout file.
<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/css")" 
    rel="stylesheet" type="text/css" />
<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")">
</script>    
If you start your application now, and inspect the HTML, you will find two versioned links to a minified version of your CSS and JavaScript.


If you're trying this on a new project, you will probably have no problems. However, if you're trying this on an existing project, chances are that some things are not included how they should be.

To troubleshoot what's going wrong, you can inspect the results of the GetRegisteredBundles method.
var registeredBundles = BundleTable.Bundles.GetRegisteredBundles();
If you need more fine-grained control, you can remove the default bundles again and add your own bundles to the bundle table.

For example, this is how you can add a jQuery bundle.
var jQueryBundle = new Bundle("~/Scripts/jquery", new JsMinify());
jQueryBundle.AddDirectory("~/Scripts", "jquery*.js", searchSubdirectories: false, throwIfNotExist: true);

BundleTable.Bundles.Add(jQueryBundle);
<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/jQuery")">
</script>  
When you instantiate a new bundle, specify the relative path of the bundle and pick a bundle transformation. You can add a directory to the bundle, filtered by a search pattern. You can also tell the algorithm to search in the subdirectories or to throw an exception when the directory doesn't exist.

If you don't want to add a whole directory to the bundle, but just one or more files, you can use the AddFile method.

For example, this is a separate bundle for modernizr.
var modernizrBundle = new Bundle("~/Scripts/modernizr", new JsMinify());
modernizrBundle.AddFile("~/Scripts/modernizr-1.7.js", throwIfNotExist: true);

BundleTable.Bundles.Add(modernizrBundle);
Conclusion

It's relatively easy to take advantage of bundling in ASP.NET MVC3. Install the NuGet package, set up the bundle table, include the references in your view or layout page and you're done.

There are some more interesting things you can do using bundling. I just started experimenting with it, so I wouldn't be surprised if I will be writing a few more things on bundling in the coming weeks.

Monday, February 20, 2012

There's no place for monogamy in technology

In this post I would like to share some of my thoughts on a recent post by James Hague titled 'Don't Fall in Love With Your Technology'. If you haven't read that post yet, please do, it's so short that me summarizing it here would be silly.

I think there is nothing wrong with falling in love with your technology per se. If you want to have a fair relationship with your technology, you have to invest in her. Don't have a superficial relationship, take her home with you, spend some cosy Sunday afternoons together. Get to know her inside out. Know when it's fun to be with her, but also more importantly when it's not.

The problem with falling in love with your technology is that a lot of us fall blindly in love. Please don't. Be unfaithful, do - at least - look at other technologies. Take them out on a crazy Friday night date. In the meanwhile you might discover that that new technology does a few things better than the current one and that she's a lot more fun.

I guarantee that what now seems like the love of your life, will one day be nothing more than a long forgotten memory which might bring a smirk on your face on remembering her, but there will be no regret in leaving her.

Sunday, February 19, 2012

ASP.NET MVC4 Installer is incompatible with Microsoft .NET Framework 4.5

I tried installing ASP.NET MVC4 beta today, but seconds into the installation the WebPI already halted the process.
ASP.NET MVC4 Installer is incompatible with Microsoft .NET Framework 4.5
Apparently, this was documented in the ASP.NET MVC4 installation notes.
This release is not compatible with the .NET Framework 4.5 Developer Preview. You must uninstall the .NET 4.5 Developer Preview before installing the ASP.NET MVC 4 Beta.
I guess you have to be forgiving if you want to play with the early bits.

Make sure to uninstall all the .NET Framework 4.5 related stuff though. I had some pain, wasting over an hour, after only partially removing the installation.


Update: Brad Wilson answers the question why they aren't compatible.
Update: Scott Guthrie announced that the next Beta release should be compatible.

Wednesday, February 8, 2012

Book review: Working with NHibernate 3.0

It's been a while since I wrote my last book review, mostly because I'm still trying to figure out when it adds value to write one. For this one it was pretty obvious, there are far too little reviews out there.

Being new to NHibernate, and NHibernate being known as having a steep learning curve, I thought it would be a good idea to do some reading. Searching for books on NHibernate 3.0 on Amazon only yielded three results: NHibernate 3 beginner's guide, NHibernate 3.0 cookbook and working with NHibernate 3.0. None of these books have a decent amount of reviews, so I had to pick judging by the cover and summary. I chose the last one.

The book Working with NHibernate 3.0 by Benjamin Delcamp Perkins contains six chapters, covered over 213 pages.

The first chapter very briefly explains what an ORM is, and then starts looking at configuring NHibernate. This means setting up the session factory and its configuration, creating entities and mapping them to the database (using XML and code), but also configuring log4net (NHibernate's logging framework), serializing startup and interceptors and events. These last two subjects felt a little misplaced in this chapter though.

In the first chapter the foundation for the Guitar store example application is laid. This example application is written in WPF, which bothered me a bit. Not because I dislike WPF, but because when I'm reading a book on a data access technology I really don't want it to be littered with fragments of XAML and code-behind. To make matters worse, the author suggests using a console application to test your queries. I think it would have been far more valuable to use real unit tests to prove the queries are correct, more like this. Another complaint about the example application that I read in another review is that there are no scripts available to set up the database, which might be discouraging if you want to follow along.

Chapter two, three and four cover the various NHibernate query API's: HQL (Hibernate Query Language), ICriteria and LINQ. Every chapter implements the same or at least similar queries. Concepts covered in these chapters are simple queries, complex queries, detached queries, futures and aggregates. I think these chapters succeed in giving a good overview of the ways you can use NHibernate to query data. Also the tips on how to use futures, various fetch modes, the stateless session and aggregates to improve performance will prove useful in the future.

The fifth chapter covers managing state and saving data. In this chapter, the author explains the various ways you can handle database concurrency, listing the advantages and disadvantages of each option. NHibernate caching is also explained, looking at first and second-level caching. Further in this chapter, you can find an example of a custom data type implementation. Finally we arrive at saving data with NHibernate. Next to the standard way of saving data, the author explains the use of Evict, Merge and Persist.

The last chapter, covering only 9 pages, shows how you should set up NHibernate in an MVC3 application.

Conclusion

Although this book doesn't do a great job showing you how to use NHibernate in the real world, it does do a decent job giving you a basic overview of NHibernate's capabilities. Reading this book when you're new to NHibernate will save you from a few costly common NHibernate pitfalls. I don't think I will be able to use this book for reference, but it should be easier now - knowing the correct terminology - to search in the NHibernate documentation online.

My rating: 3/5. Do you advise other books on NHibernate?

Monday, February 6, 2012

Testing DI bootstrappers

While your Dependency Injection bootstrappers - being responsible for gluing your application together - are a vital part of your application, they are seldom put under test. I don't see any reason why they shouldn't be though. The cost of these tests is negligible, definitely if you compare it to the cost of the often catastrophical outcome of bugs in your bootstrappers.

I encourage you to take a look at the commit history of your DI bootstrappers; I bet they change a lot. Wouldn't it be nice to have a set of tests that proves that the dependency container still behaves like you expect it to at runtime? Next to proving correctness, I think writing these tests also helps you discover various behaviours of your DI container, which is a valuable investment in itself.

Let me show you a few tests that I wrote to put my ASP.NET MVC Ninject bootstrapper under test.

I started by opening up the Ninject bootstrapper, making the CreateKernel method public.
public static IKernel CreateKernel()
{
    var kernel = new StandardKernel();
    
    kernel.Bind<IEntryService>().To<EntryService>();

    return kernel;
}
In the test class, I used the TestInitialize attribute to initialize a new instance of the kernel before every test. I'm not sure this is really necessary, but I want to avoid that my tests experience side-effects of a previous test.
[TestInitialize]
public void Setup()
{         
    _kernel = NinjectMVC3.CreateKernel();
}        
Needless to say, the first test should prove that an implementation of the IEntryService interface can be resolved. The behaviour I observed while playing with this case, is that Ninject throws a Ninject.ActivationException when the implementation can't be resolved.
Ninject.ActivationException: Error activating IEntryService
  No matching bindings are available, and the type is not self-bindable.
  Activation path:
Request for IEntryService

Suggestions:
  1. Ensure that you have defined a binding for IEntryService.
  2. If the binding was defined in a module, ensure that the module has been loaded into the kernel.
  3. Ensure you have not accidentally created more than one kernel.
  4. If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.
  5. If you are using automatic module loading, ensure the search path and filters are correct.
So to test whether an implementation can be resolved, I just make sure no exceptions are thrown on resolving the dependency.
[TestMethod]
public void Test_IEntryService_Can_Be_Resolved()
{
    AssertDoesNotThrowWhenResolved<IEntryService>();
}
The AssertDoesNotThrowWhenResolved method is a helper method which tries to resolve a dependency of T and asserts that no exceptions are thrown while doing so. The assertion is borrowed from Xunit (package available on Nuget).
private void AssertDoesNotThrowWhenResolved<T>() 
{
    Xunit.Assert.DoesNotThrow(() => _kernel.Get<T>());
}
A second useful test is testing the lifetime of the resolved implementation. For most of my dependencies, I expect a new instance every time they are resolved. This test looks like this.
[TestMethod]
public void Test_IEntryService_Is_New_Instance()
{
    AssertNewInstanceIsResolved<IEntryService>();
}
The AssertNewInstanceIsResolved method is another helper method which resolves two instances of T and asserts they are not the same.
private void AssertNewInstanceIsResolved<T>()
{
    var instance = _kernel.Get<T>();
    var secondInstance = _kernel.Get<T>();

    Assert.AreNotSame(instance, secondInstance);
}  
That's it. While these tests are very cheap to write, they do provide great value. I can imagine testing more complex bindings, like contextual bindings, taking a bit more time to set up, but the value of these tests increases proportionally.

Do you put your DI bootstrappers under test? If you don't, why not?

Saturday, February 4, 2012

Adding ELMAH to an AppHarbor application

For those who haven't heard of ELMAH yet, here is the project description.
ELMAH (Error Logging Modules and Handlers) is an application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.
While ELMAH is completely not AppHarbor specific, there do seem to be a fair amount of questions on the AppHarbor support forums on how to get ELMAH running. I just installed and configured ELMAH for one of my AppHarbor ASP.NET MVC3 applications, so I thought it would be nice to share and give something back to the AppHarbor support guys.

I think enabling ELMAH shouldn't take more than 10 minutes if you follow this tutorial.

Installation

The easiest way to add ELMAH to your web application is by installing the nuget package.
PM> Install-Package elmah
Attempting to resolve dependency 'elmah.corelibrary (= 1.2)'.
Successfully installed 'elmah 1.2.0.1'.
Successfully added 'elmah 1.2.0.1' to Docary.
Once this is done you will find a newly added Elmah reference and a changed web.config.

I'm not going to list all the changes to the web.config here. If you're interested in what the package does to your web.config, you should just run a git diff.

Error log storage

You have to tell ELMAH where it should store the logs. A list of error log implementations can be found on the project wiki.

I choose to use the in-memory error log for now. Add the elmah section and errorLog element to your web.config.
<elmah>    
    <errorLog type="Elmah.MemoryErrorLog, Elmah" size="250" />
</elmah>
Remote access

By default you can only see the logging on the local machine. To enable remote access, you have to add the security element to the elmah section.
<elmah>
    <security allowRemoteAccess="1" />
    <errorLog type="Elmah.MemoryErrorLog, Elmah" size="250" />
</elmah>
Security

Now that you're allowing remote access, you want to secure the logging page. I used ASP.NET authorization to achieve this.

Add the following element to your web.config, and make sure you change the authorization configuration to the relevant values.
<location path="elmah.axd">
    <system.web>
      <httpHandlers>
        <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
      </httpHandlers>
      <authorization>
        <allow roles="SuperUser"/>
        <deny users="*" />
      </authorization>
    </system.web>
    <system.webServer>
      <handlers>
        <add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
      </handlers>
    </system.webServer>
</location>
Finished

Push these changes, and you should be done. Navigate to elmah.axd in the root of your web application and you should see something like this.



It should be easier now to start playing with more advanced ELMAH options.

Wednesday, February 1, 2012

A solar storm anecdote

Last week, several news channels reported on the strongest solar storm since 2005. This news item reminded me of a peculiar support ticket we received one gray Monday morning a few years ago, when I was still writing software for fire departments.
*********************************************************************************
Ticket 7238
Subject: AVL broken
Status: New
Description
06:22 Vehicles stay mostly stationary on the map, even when we are
positive they are en route.
*********************************************************************************
Fire departments that have to cover a large area - and are wealthy enough - often use AVL (Automatic Vehicle Location) to track their vehicles and visualize them on a map. This is extremely valuable, because you always want to dispatch the vehicles with the smallest response time to a high priority intervention. Also being able to advise drivers of possible blockages and toxic gas clouds can save lives. To be able to track a vehicle, an AVL module is installed into each vehicle's cockpit. This module uses GPS to determine the location and sends the location data over GPRS to a central server.

On our end, we had a third party service listening for those location packets and translating them into a more understandable format. This service, not being mission critical, wasn't being monitored, so we had to look into the logs to see what was going on. Scrolling through megabytes of debug logs, we couldn't find anything suspicious.

While I was investigating this, a co-worker had come in and was reading through his mails while sipping on his morning coffee. After reading support ticket 7238, he nonchalantly said he knew what was going on with AVL and he would take over from there.
*********************************************************************************
Ticket 7238
Subject: AVL broken
Status: Pending
Description
08:45 I heard on the radio that there is a solar storm going on at
the moment, which affects sattelites. The AVL module might have
a hard time getting a GPS fix. This issue should solve itself over
time. We will keep an eye on this issue.
---------------------------------------------------------------------------------
06:22 Vehicles stay mostly stationary on the map, even when we are
positive they are en route.
*********************************************************************************
A few days passed and although the solar storm was over, we were still seeing signifcant packet loss. After spending a few hours working the ticket, in which we restarted the service, monitored network traffic on the machine and conctacted the telephony provider, we were getting a bit desperate. We were discussing other potential paths to investigate, when one of our more seasoned co-workers asked "You guys did try restarting the server, right?".

Good enough, after restarting the server, we were seeing no more packet loss and the vehicles started moving on the map again.


*********************************************************************************
Ticket 7238
Subject: AVL broken
Status: Resolved
Description
15:47 The solar storm must be over. The vehicle locations are
being updated in a timely fashion again.
--------------------------------------------------------------------------------
08:45 I heard on the radio that there is a solar storm going on at
the moment, which affects sattelites. The AVL module might have
a hard time getting a GPS fix. This issue should solve itself over
time. We will keep an eye on this issue.
--------------------------------------------------------------------------------
06:22 Vehicles stay mostly stationary on the map, even when we are
positive they are en route.
*********************************************************************************
Until today, we never talked about ticket 7238 again.