Wednesday, December 11, 2013

Nuget package which supports different ASP.NET MVC versions

Problem

Let's suppose you are going to create a Nuget package which depends on ASP.NET MVC. You package will work with any MVC version but it should install only that assembly which is compatible with MVC version used in the target project.
Possible solution can be to create several Nuget packages - for each supported version of MVC.
However there are two essential drawbacks in this approach:
  1. When use decide to upgrade his/her project and use new version MVC - he/she will need to remove your package and install another its version.
  2. Imagine your package depends not only on MVC but on some other assembly as well (e.g. EntityFramework) and that assembly also has few different version. In this case the number different Nuget packages you need to create multiplies to the number of versions of second assembly.
    For example our package should support 3 different versions of MVC (3, 4 and 5) and two versions of EntityFramework. In this case the total number of packages we need to create will 3x2 = 6!
    You can only imagine how you will name all those packages. It will be something like "MyPackage for MVC 3 and EF 5" - quite messy as for my.

Solution

Fortunately we have found a solution when created a Nuget package for our EasyQuery library. This solution allows you to avoid creating several different Nuget packages for each supported MVC (or any other library) version and automatically install necessary assembly depending on MVC version used in the target project.
The solution is rather simple:
  1. You include into your Nuget package the assemblies for all supported versions of MVC (or any other library which you depend on). For example, in case of EasyQuery our package includes such assemblies as Korzh.EasyQuery.Mvc2, Korzh.EasyQuery.Mvc3 and so on.
  2. You include install.ps1 PowerShell script which runs after package installation. This script performs two tasks:
  • First it finds our which version of MVC use the target project based on list of project references.
  • Using that information it removes all unnecessary assemblies installed with your package and remain only that one which corresponds to MVC version used in the target project.
If you don't know how to add install.ps1 script into your Nuget package you can read this article for instructions.

Example

So here is an example of install.ps1 script:
param($rootPath, $toolsPath, $package, $project)

# Bail out if scaffolding is disabled (probably because you're running an incompatible version of NuGet)
if (-not (Get-Command Invoke-Scaffolder)) { return }

# Could use "Set-DefaultScaffolder" here if desired

#set default version of MVC to 5.0
$MvcVersion = "5.0.0.0"

#find out the version of MVC assembly in the target project
$project.Object.References | Where-Object { $_.Name -eq 'System.Web.Mvc' } | ForEach-Object { $MvcVersion = $_.Version}
Write-Host "MVC version: " $MvcVersion

#remove unnecessary Korzh.EasyQuery.MvcX assemblies from project references
if ($MvcVersion.StartsWith("2.0")) {
    $project.Object.References | Where-Object { ($_.Name.StartsWith("Korzh.EasyQuery.Mvc")) -and !($_.Name.StartsWith("Korzh.EasyQuery.Mvc2")) } | ForEach-Object { 
        $_.Remove()
    }
}
elseif ($MvcVersion.StartsWith("3.0")) {
    $project.Object.References | Where-Object { ($_.Name.StartsWith("Korzh.EasyQuery.Mvc")) -and !($_.Name.StartsWith("Korzh.EasyQuery.Mvc3")) } | ForEach-Object {
        $_.Remove()
    }
}
elseif ($MvcVersion.StartsWith("4.0")) {
    $project.Object.References | Where-Object { ($_.Name.StartsWith("Korzh.EasyQuery.Mvc")) -and !($_.Name.StartsWith("Korzh.EasyQuery.Mvc4")) } | ForEach-Object { 
        $_.Remove()
    }
}
elseif ($MvcVersion.StartsWith("5.0")) {
    $project.Object.References | Where-Object { ($_.Name.StartsWith("Korzh.EasyQuery.Mvc")) -and !($_.Name.StartsWith("Korzh.EasyQuery.Mvc5")) } | ForEach-Object { 
        $_.Remove()
    }
}