Archives for category: Tutorials

One of the many great features built in to Umbraco that I use on a daily basis, is it’s ability to cache the output of your Macros. This is great if you have a processor intensive Macro that doesn’t change too often, or if you have a high traffic site, and want to maximise on response times. The best thing about this, is just how easy it is to do.

Within the macro properties tab, simply set the cache duration and optionaly check one or more options on how you want to cache the Macro.

If no options are checked, the Macro will be cached for the cache period regardless. Checking the Cache by Page option, caches the Macros output on a page per page basis, and checking Cache Personlized caches the Macros output per Member. You can also combine the two options to cache on a per page and per Member basis.

For me though, there is one option missing, which is the ability to cache a Macro based upon a QueryString value. This is needed for things like paged lists, or where you have one page, that does multiple tasks based upon a QueryString parameter (You could get around this by using URL rewriting, but that’s not always an option). Recently, a few of us stumbled across a solution.

When you set a Macro to cache, Umbraco generates a key to cache that Macro by.

<umbraco:Macro Alias="TestMacro" runat="server"></umbraco:Macro>

In the example above, because we have the Cache by Page option set, the key is made up of the page id, and the Macro alias. One usefull thing to know about how this key is generated (and what will ultimatley make caching by QueryString possible) is that if we have any parameters defined on the Macro, the value of these will also be included.

<umbraco:Macro Alias="TestMacro" runat="server" TestParam="testparam"></umbraco:Macro>

In the example above, our Macro now has a parameter defined with its value set to “testparam“, which has also been included in the cache key. I know what you are thinking, “but this is a hardcoded value”, and you are right, but there is a way to set a Macro parameter to a QueryString value. This is done using the [@...] Macro parameter syntax.

<umbraco:Macro Alias="TestMacro" runat="server" TestParam="[@MyQueryStringKey]"></umbraco:Macro>

Now when we request our page, the TestParam property is set to the value of the MyQueryStringKey parameter, which in turn is then used as the cache key for the Macro.

Any change to the MyQueryStringKey will result in a new cache key, and will create a new cached instance of the Macro.

One final little tip. The cache key is actually generated based upon the attributes defined on the Macro server contol tag itself, regardless of whether these map to properties defined on the Macro in the Umbraco UI, so if you don’t actually need to know the value of the QueryString parameter, you can just define the attribute on the Macro server control tag, and not define it as an actual parameter for the Macro, and it will still work.

So there we have it, how to cache a Macro by QueryString parameter.

Whilst working on the Universal Media Picker, and more specifically, the various Providers for it, one thing that has been niggling away in the back of my mind is that of dependency conflicts.

Up untill now, we have been fairly fortunate that none of the Providers have utilized the same third-party libraries, but with many API’s using similar protocols, it’s only a matter of time before they do.

Utilizing the same library isn’t necessarily a problem, as long as they use the same version, but if each Provider did use a different version, then problems can arise if one relied upon a method that is not available in the other. What would be ideal is if each Provider could be completely self-contained, without the need to reference a third-party dependency at all, but how? Enter ILMerge.

So what is ILMerge? Well up untill a couple of days ago, I’d never heard of it myself, but it’s definitely one of those things I won’t be forgetting any time soon =)

This is how Microsoft describe it:

“ILMerge is a utility for merging multiple .NET assemblies into a single .NET assembly”

Bingo! So by using ILMerge, it becomes possible to merge the third-party libraries DLLs in with the Providers own DLL, meaning we will only have one DLL to deploy. But that’s not all, we can actually go one step further, and tell ILMerge to internalize all dependencies. What this does it converts all the third party classes to be declared as internal, meaning they can only be used from within the final DLL. Woo hoo! problem solved =)

Another great thing about ILMerge is that it has a command line interface, meaning it can be incorporated into an automated build process with ease. What follows is an updated version of my MSBuild script is originally blogged about in my Automating Umbraco Package Creation Using MSBuild post, but this time integrating the new ILMerge step to combine the various DLLs (Checkout the MergeDlls target for details).

<?xml version="1.0" encoding="utf-8" ?>
xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Package">

 <!--
 ****************************************
 * IMPORTS
 ****************************************
 -->
 <PropertyGroup>
 <MSBuildUmbracoTasksPath>$(MSBuildProjectDirectory)\..\Lib\MSBuildUmbracoTasks
 </PropertyGroup>

 <Import Project="$(MSBuildUmbracoTasksPath)\MSBuild.Umbraco.Tasks.Targets" />

 <!--
 ****************************************
 * PROPERTIES
 ****************************************
 -->
 <PropertyGroup>
 <PackageVersion>1.0
 <ProjectName>TheOutfield.UmbExt.UniversalMediaPicker.Providers.Dropbox
 </PropertyGroup>

 <PropertyGroup>
 <RootDir>$(MSBuildProjectDirectory)
 <BuildDir>$(RootDir)\Build
 <PackageDir>$(RootDir)\Package
 </PropertyGroup>

 <PropertyGroup>
 <ILMergeExe>$(MSBuildProjectDirectory)\..\Lib\ILMerge\ILMerge.exe
 </PropertyGroup>

 <!--
 ****************************************
 * TARGETS
 ****************************************
 -->

 <!-- CLEAN -->
 <Target Name="Clean">
 <RemoveDir Directories="$(BuildDir)" Condition="Exists('$(BuildDir)')" />
 <RemoveDir Directories="$(PackageDir)" Condition="Exists('$(PackageDir)')" />
 <MakeDir Directories="$(BuildDir)" />
 <MakeDir Directories="$(PackageDir)" />
 </Target>

 <!-- COMPILE -->
 <Target Name="Compile" DependsOnTargets="Clean">
 <MSBuild Projects="$(ProjectName).csproj" />
 </Target>

 <!-- MERGE <span class="hiddenSpellError" pre="MERGE ">DLLS</span> -->
 <Target Name="MergeDlls" DependsOnTargets="Compile">
 <ItemGroup>
 <Dlls Include="$(RootDir)\Bin\$(ProjectName).dll" />
 <Dlls Include="$(RootDir)\Bin\Newtonsoft.Json.dll" />
 <Dlls Include="$(RootDir)\Bin\RestSharp.dll" />
 <Dlls Include="$(RootDir)\Bin\Dropnet.dll" />
 </ItemGroup>
 ndebug /internalize /out:$(RootDir)\Bin\$(ProjectName).dll @(Dlls, ' ')" />
 </Target>

 <!-- <span class="hiddenSpellError" pre="">PREPAIRE</span> FILES -->
 <Target Name="PrepairFiles" DependsOnTargets="MergeDlls">
 <ItemGroup>
 <BinFile Include="$(RootDir)\Bin\$(ProjectName).dll" />
 <SmallIconFiles Include="$(RootDir)\Images\16x16\*.gif" />
 <LargeIconFiles Include="$(RootDir)\Images\16x16\*.gif" />
 <XsltFiles Include="$(RootDir)\Xslt\*.xslt" />
 <PackageFile Include="$(RootDir)\Package.xml" />
 </ItemGroup>
 SourceFiles="@(BinFile)" DestinationFolder="$(BuildDir)\bin" />
 SourceFiles="@(SmallIconFiles)" DestinationFolder="$(BuildDir)\umbraco\images\umbraco\dropbox\16x16" />
 SourceFiles="@(LargeIconFiles)" DestinationFolder="$(BuildDir)\umbraco\images\umbraco\dropbox\48x48" />
 SourceFiles="@(XsltFiles)" DestinationFolder="$(BuildDir)\xslt" />
 SourceFiles="@(PackageFile)" DestinationFolder="$(BuildDir)" />
 </Target>

 <!-- MANIFEST -->
 <Target Name="Manifest" DependsOnTargets="PrepairFiles">
 <ItemGroup>
 <ManifestFiles Include="$(BuildDir)\**\*" Exclude="$(BuildDir)\Package.xml" />
 </ItemGroup>
 <ReadLinesFromFile File="$(RootDir)\Readme.txt">
 Readme" />
 </ReadLinesFromFile>
 <ManifestUpdate ManifestFile="$(BuildDir)\Package.xml"
 WorkingDirectory="$(BuildDir)"
 PackageVersion="$(PackageVersion)"
 Readme="@(Readme->'%(Identity)', '%0a%0d')"
 Files="@(ManifestFiles)" />
 </Target>

 <!-- PACKAGE -->
 <Target Name="Package" DependsOnTargets="Manifest">
 <ItemGroup>
 <PackageFiles Include="$(BuildDir)\**\*.*" />
 </ItemGroup>
 ManifestFile="$(BuildDir)\Package.xml"
 WorkingDirectory="$(BuildDir)"
 OutputDirectory="$(PackageDir)"
 Files="@(PackageFiles)" />
 </Target>

</Project>

So far, I’ve tried this out with the Dropbox, Scribd and Picasa Providers, and they’ve worked a treat, so this is definitely something I’ll be recommending to theĀ  various Provider developers to start using.

Over the last year or so, I’ve created a number of packages, all of which have been created using the Umbraco UI. Whilst there is nothing wrong with doing it this way, I’ve been wanting to find a way to automate this for some time.

I started out by asking the awesome Umbraco community, which thanks to a tip-off by Sebastiaan Janseen, led me to the source code for Blog4Umbraco which has an example of creating a package using NAnt. This was a great start, but I was ideally looking for something using MSBuild, and I felt it could be streamlined a little. With that, I thought I’d have a go at porting it over, and making the improvements I thought it needed.

Prerequisites

As I’m using MSBuild, you’ll obviously need to have the .NET framework installed.

Package.xml

The first thing you’ll need to create is a base manifest file. If you’ve ever looked inside a package file, this will probably look fairly familiar to you. The manifest file contains the details of everything to be installed. Much of this can be created by hand, so the idea is to fill in the static bits, and use automation to fill in the bits that change more frequently. In the example demonstrated in this article, this will be the package version number, the readme info and the package files.

What follows is an example manifest file, as used in my Universal Media Picker package.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<umbPackage>
 <info>
 <package>
 <name>Universal Media Picker</name>
 <version>0.0</version>
 <license url="http://www.opensource.org/licenses/mit-license.php">MIT license</license>
 <url>http://our.umbraco.org/projects/backoffice-extensions/universal-media-picker</url>
 <requirements>
 <major>4</major>
 <minor>5</minor>
 <patch>0</patch>
 </requirements>
 </package>
 <author>
 <name>Matt Brailsford</name>
 <website>blog.mattbrailsford.com</website>
 </author>
 <readme><![CDATA[]]></readme>
 </info>

 <DocumentTypes />
 <Templates />
 <Stylesheets />
 <Macros />
 <DictionaryItems />
 <Languages />
 <DataTypes />

 <Actions>
 <Action runat="install" alias="addApplicationTree" silent="false" initialize="false" sortOrder="0" applicationAlias="media" treeAlias="UniversalMediaPicker" treeTitle="Media" iconOpened="folder_o.gif" iconClosed="folder.gif" assemblyName="TheOutfield.UmbExt.UniversalMediaPicker" treeHandlerType="UniversalMediaPickerAppTree" action="" />
 </Actions>

 <control>/umbraco/plugins/TheOutfield/UniversalMediaPicker/ProviderInstaller.ascx</control>

 <files />

</umbPackage>

Package.build.xml

The next thing you’ll need is the build script itself. I’m not going to go into the syntax of it, as there are already plenty of examples of this online, but to outline the example that follows, we first import the custom MSBuild tasks and define a number of properties, and then go on to fire a series of targets in the following order.

  • Clean: Cleanup any previous builds
  • Compile: Compile the Visual Studio project
  • PrepairFiles: Copy the files to package up into a temporary folder, arranging them into their final folder struture
  • Manifest: Update the manifest file with the version number, readme information and file references
  • Package: Generate a package file

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Package">

 <!--
 ****************************************
 * IMPORTS
 ****************************************
 -->
 <Import Project="$(MSBuildProjectDirectory)\Lib\MSBuildUmbracoTasks\MSBuild.Umbraco.Tasks.Targets" />

 <!--
 ****************************************
 * PROPERTIES
 ****************************************
 -->
 <PropertyGroup>
 <PackageVersion>1.0</PackageVersion>
 </PropertyGroup>

 <PropertyGroup>
 <RootDir>$(MSBuildProjectDirectory)</RootDir>
 <BuildDir>$(RootDir)\Build</BuildDir>
 <PackageDir>$(RootDir)\Package</PackageDir>
 </PropertyGroup>

 <!--
 ****************************************
 * TARGETS
 ****************************************
 -->

 <!-- CLEAN -->
 <Target Name="Clean">
 <RemoveDir Directories="$(BuildDir)" Condition="Exists('$(BuildDir)')" />
 <RemoveDir Directories="$(PackageDir)" Condition="Exists('$(PackageDir)')" />
 <MakeDir Directories="$(BuildDir)" />
 <MakeDir Directories="$(PackageDir)" />
 </Target>

 <!-- COMPILE -->
 <Target Name="Compile" DependsOnTargets="Clean">
 <MSBuild Projects="TheOutfield.UmbExt.UniversalMediaPicker.csproj" />
 </Target>

 <!-- PREPAIRE FILES -->
 <Target Name="PrepairFiles" DependsOnTargets="Compile">
 <ItemGroup>
 <BinFiles Include="$(RootDir)\Bin\TheOutfield.UmbExt.UniversalMediaPicker.dll" />
 <BinFiles Include="$(RootDir)\Bin\TheOutfield.UmbExt.UniversalMediaPicker.pdb" />
 <PluginFiles Include="$(RootDir)\Images\*.*" />
 <PluginFiles Include="$(RootDir)\Dialogs\*.aspx" />
 <PluginFiles Include="$(RootDir)\Controls\*.ascx" />
 <PackageFile Include="$(RootDir)\Package.xml" />
 </ItemGroup>
 <Copy SourceFiles="@(BinFiles)" DestinationFolder="$(BuildDir)\bin" />
 <Copy SourceFiles="@(PluginFiles)" DestinationFolder="$(BuildDir)\umbraco\plugins\TheOutfield\UniversalMediaPicker" />
 <Copy SourceFiles="@(PackageFile)" DestinationFolder="$(BuildDir)" />
 </Target>

 <!-- MANIFEST -->
 <Target Name="Manifest" DependsOnTargets="PrepairFiles">
 <ItemGroup>
 <ManifestFiles Include="$(BuildDir)\**\*" Exclude="$(BuildDir)\Package.xml" />
 </ItemGroup>
 <ReadLinesFromFile File="$(RootDir)\Readme.txt">
 <Output TaskParameter="Lines" ItemName="Readme" />
 </ReadLinesFromFile>
 <ManifestUpdate ManifestFile="$(BuildDir)\Package.xml"
 WorkingDirectory="$(BuildDir)"
 PackageVersion="$(PackageVersion)"
 Readme="@(Readme->'%(Identity)', '%0a%0d')"
 Files="@(ManifestFiles)" />
 </Target>

 <!-- PACKAGE -->
 <Target Name="Package" DependsOnTargets="Manifest">
 <ItemGroup>
 <PackageFiles Include="$(BuildDir)\**\*.*" />
 </ItemGroup>
 <Package ManifestFile="$(BuildDir)\Package.xml"
 WorkingDirectory="$(BuildDir)"
 OutputDirectory="$(PackageDir)"
 Files="@(PackageFiles)" />
 </Target>

</Project>

Package.build.cmd

Now that we have the build script defined, you’ll want an easy way to fire it off to generate the package file. The simplest way to do this, is to just create a simple DOS command file as follows:

C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe Package.build.xml

Once this is set up, any time you want to create a new package file, all you need to do is double-click the .cmd file, and bobs your uncle.

The next step would be to integrate it with a build automation server like Cruise Control or Team City, but it definitely feels like a step in the right direction.

Download

You can download the main files here, but if you’d like to see an example of it in a project, you may want to checkout the source for my Universal Media Picker package.

Follow

Get every new post delivered to your Inbox.