Using Package Restore in Umbraco Projects
If you often base your Umbraco projects on the UmbracoCms Nuget package, and you’d rather not include the umbraco files in your CVS repository, then this blogpost is for you.
What’s the problem
When creating an Umbraco project in Visual Studio, the fastest way to get the website up an running is to start the project by installing the UmbracoCms nuget package provided by the good lads at UmbracoHQ (mostly Sebastiaan Janssen and Morten Christensen I think).
This package will setup up the project in perfect working order, so that you’re able to just press F5 and run the Umbraco install from your Visual Studio and you might think that all is well in Umbracoland after that.
When your project is up and running, the first thing you’d want to do is commit the files to your CVS of choice, in order to start sharing the code for your project with your fellow web-developers. After all doing Umbraco sites is much more fun, when you get to do it with other brilliant developers - right?
So you whip out the commit dialog of your CVS and right there you notice there’s some folders in your website that aren’t visible in Visual Studio. For instance you’ll see a folder named umbraco and umbraco_client, and as a competent Umbraco developer you know that these folders are essential in order to get your website running. This means that if you do not include these folders in your repository, and then share this repository with other developers they will not be able to run the website without coping these folder manually from somewhere first.
In case you’re wondering where these folders came from: They were copied from the \packages\UmbracoCms.versionnumber\UmbracoFiles folder found in your solution root during the install of the UmbracoCms package, by a script called install.ps1 int the tools folder placed the same place as the UmbracoFiles folder.
Problem is; this script only runs when you install the package, not when your copy the project by cloning the repository to another location.
But that’s no problem you think, I have plenty of diskspace where I keep my repositories, so I’ll just include the files and be done with that. If that’s the case you might as well stop reading right now.
If, on the other hand, you’d rather not include these files, maybe because you’d rather not waste disk space, or because doing so actually messes with the your repository based TODO item list, then read on.
How to solve this problem in a better way
In order to solve this problem in a better way, you need to employ a combination of a piece of functionality found in the NuGet package manager called Package restore and a MSBuild target file you can get here.
Enable NuGet package restore
What NuGet package restore does for you is, it automatically fetches the NuGet packages from the repository when you build a project, if they’re not already in the file system. This means you will not even have to commit the package files to you repository which will save you even more diskspace and messing with your repo-based todo list. You only need to commit the file named packages.config placed in the root of each project in your Visual Studion solution to make it work.
You will also need to make sure that you and your fellow developers have enabled the NuGet package restore functionality. Go to Tools - Options
, expand the Package Manager
item select General
and check the two checboxes below Package Restore
When this is done you have a setup where cloning the repository to another location and doing a build will automatically download all the packages you’ve added to you project. Which also means that after a build, there will be a folder named UmbracoFiles
with all the files you need to copy to your project in order to get Umbraco running in \packages\UmbracoCms.versionnumber\
in your solution.
Making sure the Umbraco files are in the project
Okay; so we got the files ready, and we just need to copy these files from the package folder to our project so they’re there when we need to run the project. You could do this by adding a couple of DOS copy commands to the Pre-build event command line
in the Buld Events
part of your projects properties. But my preferred method when solving this kind of problems is currently using MSBuild Targets.
Let’s start by creating af target that’ll copy the files before the build runs.
<Target Name="CopyUmbracoFilesBeforeBuild" BeforeTargets="BeforeBuild">
<PropertyGroup>
<UmbFilesRoot>
$(MSBuildProjectDirectory)\..\Packages\UmbracoCms.6.1.6\UmbracoFiles
</UmbFilesRoot>
</PropertyGroup>
<ItemGroup>
<UmbracoFiles Include="$(UmbFilesRoot)\UmbracoFiles\**\*"
Exclude="$(UmbFilesRoot)\**\*.config" />
</ItemGroup>
<Message Text="Copying UmbracoFiles from: $(UmbFilesRoot)\UmbracoFiles" Importance="high" />
<Copy SourceFiles="@(UmbracoFiles)"
DestinationFiles="@(UmbracoFiles->'$(MSBuildProjectDirectory)\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true" />
</Target>
What you see above is some code, first grabbing a reference to the placement of the UmbracoFiles in the package in the PropertyGroup
tag,. Then setting up a set of files in the ItemGroup
tag, I exclude the config files already in there (mainly web.config) since I usually add these to my Visual Studio project and commit them to the repo, in order to edit them according to my liking. And lastly copying the files to the project using a Copy
task
You can add this target to the end of your web-applications .csproj file just before closing project-tag, and commit the changes to your repository, and you should be good to go … until you decide to upgrade the Umbraco version in the Web application.
Making this solution more future proof
You might have noticed that the UmbFilesRoot property is pointing at a folder that has the UmbracoVersion yo installed in it’s path. This means that if you decied to update the Umbraco installation using the NuGet package manager, you will end up copying the wrong files, if you don’t change your target accordingly. It’s possible to do something about that beforehand.
What we need to do is write the target so that the UmbFilesRoot always points to the folder with he highest version number, which is a bit harder than it sounds.
When you upgrade a NuGet package in a Visual Studio Project, the package manager will place the files for the upgraded package in a new folder below the packages
folder. The package folder are always name by concatenating the package name with the version number separated by a comma. The folder of the old version of the upgraded package is kept in place.
This means that it is not trivial to create a property that point to the package folder using a wildcard, since using wildcards will include files from all UmbracoCms package versions in the package folder.
What we need to find is a way to create a property that holds the path to the package with the highest version number, which as far as I can tell is not possible to do with the normal path selection tools in MSBuild.
C# and the .NET framework to the rescue
Off course it’s possible to create your own MSBuild Tasks in a class library of your own, but since what we need to do here is pretty simple, I think a custom build task in a dll is a bit over the top.
Instead I’m going to declare the task in my project file using inline C# like this
<UsingTask TaskName="GetLastDir"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<PathFilter ParameterType="System.String" Required="true" />
<Location ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.IO" />
<Using Namespace="System.Linq" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var dirs = Directory.GetDirectories(Path, PathFilter);
Location = dirs.Last();
]]>
</Code>
</Task>
</UsingTask>
The UsingTask
element allows us to create a parameterized task, which can be used to create a property with a value. As you can see in the ParameterGroup
tag this task is going to take two string parameters, which is used in the code to find all directories in a path that matches the pathfilter provided. Next the code uses LINQ to get the last of the found directories, which should be the latest version, since the GetDirectories
methods sorts the found directories by name by default.
Usage of the created task looks like this:
<GetLastDir Path="$(MSBuildProjectDirectory)\..\Packages\"
PathFilter="UmbracoCms.6*">
<Output PropertyName="UmbFilesRoot" TaskParameter="Location" />
</GetLastDir>
This will create a property that points to the path of the UmbracoCms v6 package with the highest version number in the packages folder.
Putting it all together
To make all this nice and reusable, you can bundle all this up in a separate .targets file, that can just be referenced in the .csproj file instead of copying the same code into all your project files.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="GetLastDir"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<PathFilter ParameterType="System.String" Required="true" />
<Location ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.IO" />
<Using Namespace="System.Linq" />
<Code Type="Fragment" Language="cs">
<![CDATA[
var dirs = Directory.GetDirectories(Path, PathFilter);
Location = dirs.Last();
]]>
</Code>
</Task>
</UsingTask>
<Target Name="CopyUmbracoFilesBeforeBuild" BeforeTargets="BeforeBuild">
<GetLastDir Path="$(MSBuildProjectDirectory)\..\Packages\" PathFilter="UmbracoCms.6*">
<Output PropertyName="UmbFilesRoot" TaskParameter="Location" />
</GetLastDir>
<ItemGroup>
<UmbracoFiles Include="$(UmbFilesRoot)\UmbracoFiles\**\*"
Exclude="$(UmbFilesRoot)\**\*.config" />
</ItemGroup>
<Message Text="Copying UmbracoFiles from: $(UmbFilesRoot)\UmbracoFiles"
Importance="high" />
<Copy SourceFiles="@(UmbracoFiles)"
DestinationFiles="@(UmbracoFiles->'$(MSBuildProjectDirectory)\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true" />
</Target>
</Project>
If you keep the above content in a separate file with a .targets
file extension, you can include it in any .csproj
file like this:
<Import Project="..\CopyUmbracoCms.targets"
Condition="Exists('..\CopyUmbracoCms.targets')" />
Just remember to insert the import statement at the end of your .csproj
file, at the same place you’d insert a Target tag.
And that should be it. If you want, you can download the targets file here and use it in your own projects.