Archive for August 2014

Bamboo Version Labelling

We’ve recently switched from CruiseControl.NET to Atlassian Bamboo for our CI. This was partly borne out of frustration with CC.NET’s XML-based configuration, but we also use Jira, Confluence and BitBucket, so the integration between these products and Bamboo had some appeal.

Naturally the conversion hasn’t been completely smooth. One thing that initially wasn’t particularly obvious was how to auto-increment a .NET project’s version number when building a deployment package, and how to then label the build in Bamboo with that version number.

Version Increment

The easiest way of adding build tasks to a Bamboo stage is to, well, add an MSBuild task. The clue’s in the name. There are plenty of pre-made build tasks out there. For incrementing the version number we’re going to use the MSBuild.ExtensionPack. This contains a wealth of build targets, including one called VersionNumber.

If all we want is to increment the version number, we just import the VersionNumber targets and set some attributes:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="SetAssemblyInfo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="..\packages\MSBuild.Extension.Pack.1.5.0\tools\net40\MSBuild.ExtensionPack.VersionNumber.targets"/>
  <Target Name="SetAssemblyInfo">
    <MSBuild.ExtensionPack.Framework.AssemblyInfo 
      AssemblyInfoFiles="..\..\MyProject.Interface.Web\Properties\AssemblyInfo.cs" 
      AssemblyBuildNumberType="DateString" 
      AssemblyBuildNumberFormat="MMdd" 
      AssemblyRevisionType="AutoIncrement"
      AssemblyRevisionFormat="00"
      AssemblyFileBuildNumberType="DateString"
      AssemblyFileBuildNumberFormat="MMdd"
      AssemblyFileRevisionType="AutoIncrement"
      AssemblyFileRevisionFormat="00" />
  </Target>
</Project>

The paths above assume I have placed the saved the above as [ROOT]\SolutionFiles\BuildTasks\IncrementAssemblyVersion.csproj, where [ROOT] is the root of the solution. The structure is unimportant. What matters is that the paths to MSBuild.ExtensionPack.VersionNumber.targets and your project’s AssemblyInfo.cs are correct.

The attributes I’ve set will produce version numbers in the following format:

Major.Minor.Date.Number

Where Date is the combination of zero-padded day of the month and month, eg 0208 if built on the 2nd of March. There is provision in VersionNumber.targets to format the version number using the year as well. I wouldn’t advise this as it will (dependent on the actual date) overflow the int which backs the version number.

The Number is a simple integer increment for each daily build. eg the first build for that day wil be 0, the next 1, and so on.

I like this as it gives some useful information about the build. It should be noted that for this to work you will need to set the masking in AssemblyInfo.cs appropriately. In this case, use “1.0.0.0”

To use this file in Bamboo, add a new MSBuild task to the build stage, and supply it with the path the project file. eg

SolutionFiles\buildtasks\IncrementAssemblyVersion.csproj

Labelling in Bamboo

It would also be nice to be able to see the version number for a build from Bamboo build list. This can be achieved using labels.

Now, before I proceed I have to hold up my hand and say that this feels like a bit of a kludge. However it was the only way I could see of achieving what I wanted, so I present it here in the hope that someone knows a better way and can tell me what it is.

In Bamboo, labels can be created by parsing the build log with regex. Yes, I know. I KNOW.

What I’ve done is add a build target which will write the current version number into the log file, so that it can be parsed as a label. I’ve reused the project file we created for the version increment for this, as it makes sense to write to the log immediately after setting it. The project file now contains:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="SetAssemblyInfo;RetrieveIdentities" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="..\packages\MSBuild.Extension.Pack.1.5.0\tools\net40\MSBuild.ExtensionPack.VersionNumber.targets"/>
  <Target Name="SetAssemblyInfo">
    <MSBuild.ExtensionPack.Framework.AssemblyInfo 
      AssemblyInfoFiles="..\..\MyProject.Interface.Web\Properties\AssemblyInfo.cs" 
      AssemblyBuildNumberType="DateString" 
      AssemblyBuildNumberFormat="MMdd" 
      AssemblyRevisionType="AutoIncrement"
      AssemblyRevisionFormat="00"
      AssemblyFileBuildNumberType="DateString"
      AssemblyFileBuildNumberFormat="MMdd"
      AssemblyFileRevisionType="AutoIncrement"
      AssemblyFileRevisionFormat="00" />
  </Target>

  <Target Name="RetrieveIdentities">
    <GetAssemblyIdentity AssemblyFiles="..\..\MyProject.Interface.Web\bin\MyProject.Interface.Web.dll">
      <Output TaskParameter="Assemblies" ItemName="AssemblyInfo"/>
    </GetAssemblyIdentity>
    <Message Text="MyProject.Interface.Web.dll has been set to Version_%(AssemblyInfo.Version)" />
  </Target>
</Project>

It’s pretty straightforward. The RetrieveIdentities target is called after SetAssemblyInfo. It uses the GetAssemblyIdentity target to get the current version number from the specified .dll. It then writes a message to the log containing the version number.

To make use of this message, go to the Miscellaneous tab for your job in Bamboo. At the bottom is a section title Pattern Match Labelling. This is where we enter a regex pattern to retrieve out version number from the logs. The following pattern will do this:

(Version_[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)

Finally we just need to tell Bamboo to use the first match as the build label. Do so by entering \1 in the Labels box.

Using local blocks to refine EPiServer’s on-page editing experience

The on-page editing interface introduced with CMS 7 is, on the whole, quite nice.* It allows fast, intuitive editing of properties in most situations. However on busier pages it can all get a bit out of hand. The editor bounding boxes start to overlap and the screen begins to resemble that old Amiga demo that draws rectangles on the screen at (as then) mind-blowing speeds. There are also some properties which don’t lend themselves to on-page editing in isolation. Links for example, typically use two properties in tandem for the text and the URL. Fortunately there’s a way to tidy things up, and that is to use local blocks

Local blocks allow you to group properties together so that they can be edited together. As an example, let’s say we have a promotional box on our home page. It shows a title and a link over a background image and has the following properties:

  • Title
  • Image
  • Link URL
  • Link Text

If these are defined in the home page model, then on-page editing becomes tricky. If it’s enabled for the image, its bounding box will cover the title and link making them uneditable. And even without that, there isn’t a direct way editing the Link Url. We could just let the editors use the all properties view, but that’s not very friendly.

Instead, we can create a local block called, imaginatively called PromoBlock, add the properties there, then add the local block to the home page. Now we can do this in the home page’s view:

<div class="promo" @Html.EditAttributes(x => x.CurrentBlock.Promo)>
  <div style="background: url(@Model.Promo.Image)>
    ... other promo markup ...
  </div>
</div>

Setting EditAttributes using the PromoBlock as a property will result in an edit box being drawn around the whole promo. Clicking on the promo will pop out the right-hand edit panel with all four of our PromoBlock properties there, ready to edit.

That isn’t all we can do though. We can also create a display template for the PromoBlock. To do this, create a new view in your project’s DisplayTemplates folder. This will typically be found at Views\Shared\DisplayTemplates. Give the View a model with a type of PromoBlock, and move all our PromoBlock markup from the home page’s view, like so:

@model PromoBlock

<div class="promo">
  <div style="background: url(@Model.Image)>
    ... other promo markup ...
  </div>
</div>

Now we can reduce the promo block code in the home page to this:

@Html.PropertyFor(x => x.CurrentPage.PromoBlock)

Which is rather neat. Another advantage of this is that should the promo block be used elsewhere, there’s no need to duplicate the markup. Just add a local PromoBlock property to the page in question and use PropertyFor with it.

*Let’s gloss over the pain and anguish of Dojo for now.

Conditionally hiding properties from editors in CMS 7

Completely hiding properties from editors is simple enough – just add the [ScaffoldColumn(false)] attribute to the property. However there are times when I want to show the property in some situations, and hide it in others. A typical scenario is sharing a local block between several page types. For example, let’s say we have a local block called PromoBlock. PromoBlock has two properties:

  • Title
  • Image

It is used on two page types, LandingPage and ContentPage.

However it has become vital that the PromoBlock on LandingPage has an optional video. We could add the property VideoUrl to PromoBlock, but then it would be presented to editors on the ContentPage, and we don’t want a video there.

One option is to simply make another block for this purpose. In most situations this is the route I’d advise, but it isn’t always the most appropriate. My example is deliberately simplified, and in reality PromoBlock could be used on many pages and have complex behaviour. It could also already be in production and used on many pages, making its replacement a tedious editing task.

Another option is to programmatically hide the VideoUrl property on the ContentPage. In CMS 6 this was easily achieved using the EPiServer.UI.Edit.EditPanel.LoadedPage event. This is no longer available in CMS 7, so we need a different approach. That approach is to use an EditorDescriptor:

[EditorDescriptorRegistration(TargetType = typeof(Url))]
public class HidePromoVideoUrl : EditorDescriptor
{
  public override void ModifyMetadata(
    ExtendedMetadata metadata,
    IEnumerable&lt;Attribute&gt; attributes)
  {
    base.ModifyMetadata(metadata, attributes);
    if (metadata.PropertyName == "VideoUrl" && metadata.Parent.ContainerType == typeof(ContentPage))
    {
      metadata.ShowForEdit = false;
    }
  }
}

The main points of interest here are:

  • EditorDescriptorRegistation attribute needs its TargetType setting to the type of the property we are modifying. In this case it is a Url.
  • metaData.Parent.ContainerType gives the type of the page containing the local block which in turn contains the property we are modifying. metaData.ContainerType would give the type of the block itself, in this case PromoBlock.
  • Once we’ve determined we’re in the right place, hiding the property is a simple matter of setting metadata.ShowForEdit to false.