morzel.net

.net, js, html, arduino, java... no rants or clickbaits.

Setting version of assemblies in ASP.NET MVC application with TeamCity build feature

INTRO

Last five posts (1, 2, 3, 4, 5) were all about fun stuff with Arduino. Now it’s time for something more mundane ;) In this post I will show you how to create TeamCity build that automatically sets version information in all assemblies produced by ASP.NET application. It's nothing new but I hope to give you some useful background info and note a few gotchas you may face...

Complete code of sample application is available in this GitHub repository.

TeamCity has a build feature called AssemblyInfo patcher that makes setting assembly version easy... This feature is usable on any type of .NET project because it works by updating AssemblyInfo files. Content of such files is used to create version information that .NET Framework uses for picking up correct version of referenced assemblies. Version data is also shown in Windows file properties... Here's a part of AssemblyInfo.cs file which is automatically added by Visual Studio when you create a new project:

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

It contains two attributes: AssemblyVersion and AssemblyFileVersion along with a comment that describes numbering pattern recommended by Microsoft. We will also use another attribute: AssemblyInformationalVersion which is not added by default. AssemblyVersion sets version number that is recognized by .NET for dependency resolution. AssemblyFileVersion is used for file version as seen by Windows and AssemblyInformationalVersion is meant more for human consumption as it can contain strings (we will make use of it for holding Git commit hash)... Detailed description of the meaning of these attributes is outside scope of this post but check this great SO answer if you want to know more.

 

SAMPLE APPLICATION

My test application was created in Visual Studio Community 2013 by using ASP.NET Web Application / MVC project template (C#/.NET 4.5). Two additional projects of Class Library type were added. Here’s how the full solution looks:

Visual Studio solution... Click to enlarge...

Home/Index.cshtml view generated by VS was modified to present version information pulled from three .NET assemblies that are produced by the solution (one is for main web app project and two other are for class libraries). Such div was added to the view:

<div class="row text-primary">
    <div class="col-md-12">
        <dl>
            <dt>Core assembly info:</dt>
            <dd>@ViewBag.CoreAssemblyInfo</dd>
            <dt>DataAccess assembly:</dt>
            <dd>@ViewBag.DataAccessAssemblyInfo</dd>
            <dt>Web assembly info:</dt>
            <dd>@ViewBag.WebAssemblyInfo</dd>
        </dl>
    </div>
</div>

You can see some Bootstrap classes there since nowadays Visual Studio templates use Bootstrap framework for styling...

This is how rendered view looks before TeamCity processes AssemblyInfo.cs files:

Version information in web app before TC build... Click to enlarge...

And here's how version info looks after version attributes are modified by build feature:

Version information in web app after TC build... Click to enlarge...

If you wonder how the view gets version info here's HomeController.Index action method: 

public ActionResult Index()
{
    ViewBag.CoreAssemblyInfo = SomeCoreClass.GetAssemblyInfo();
    ViewBag.DataAccessAssemblyInfo = SomeDataAccessClass.GetAssemblyInfo();

    Assembly assembly = Assembly.GetExecutingAssembly();
    string webAsseblyInfo = string.Format("Full Name = \"{0}\"; Informational Version = \"{1}\"",
                            assembly.FullName, FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion);
    ViewBag.WebAssemblyInfo = webAsseblyInfo;

    return View();
}

You can see how the most important assembly version number (the one used by .NET and designated by AssemblyVersion attribute) is a part of assembly's FullName. Informational version (the one that can have strings) is taken with the help of FileVersionInfo class. You can get the number form AssemblyFileVersion attribute too - just check all the interesting stuff that GetVersionInfo method returns... The same kind of code is used in GetAssemblyInfo methods in SomeCoreClass and SomeDataAccessClass.

Ok, so we have our test application - full code is here. Note: I’ve pushed all used Nuget packages to the repository – that takes some space in the repo and might be against recommended way of using Git but it makes TeamCity setup easier. If packages folder is not committed you can expect this type of error during build:

[Csc] App_Start\BundleConfig.cs(2, 18): error CS0234: The type or namespace name 'Optimization' does not exist in the namespace 'System.Web' (are you missing an assembly reference?)

To solve it you would have to restore Nuget packages during build (here’s some info on how to do it).

 

TEAMCITY CONFIGURATION

Now time for build server config! I assume that you have some working knowledge about setting TeamCity build for .NET application so I will discuss only the steps relevant to versioning. I’ve used TeamCity 9.1.3 but don't worry if you have a bit older TC (AssemblyInfo patcher feature exists for a while). I used TC to build code from Git repository checkout on my local drive...

Before setting up AssemblyInfo patcher, add two new build parameters: Minor and Major. These are meant to represent two initial segments of version number and should be set manually - it's your (technical/marketing?) decision whether to name your next version 1.9 or 2.0, right? 

Major and Minor build parameters... Click to enlarge...

Next step is to add AssemblyInfo patcher build feature:

AssemblyInfo patcher build feature... Click to enlarge settings...

And set its properties:

AssemblyInfo patcher settings... Click to enlarge...

I've decided to use such settings:

  • AssemblyVersion:   %Major%.%Minor%.%build.number%
  • AssemblyFileVersion:   %Major%.%Minor%.%build.number%
  • AssemblyInformationalVersion:   %Major%.%Minor%.%build.number%.%build.vcs.number%

You can see that our Major and Minor parameters are used. You can also see the use of TeamCity built-in parameter named build.number. Last attribute contains another TeamCity param: build.vcs.number. It gets version control revision id. I'm using Git so this is a long alphanumerical SHA-1 hash. It means that it cannot be used in setting AssemblyVersion attribute. If you try to do so you will get an error like this:

[Csc] Properties\AssemblyInfo.cs(35, 12): error CS0647: Error emitting 'System.Reflection.AssemblyVersionAttribute' attribute -- 'The version specified '2.1.13.536ea0163412325ab7962957ce1cec777799d587' is invalid'

If you try to use it for AssemblyFileVersion you can expect a warning: 

[Csc] CSC warning CS1607: Assembly generation -- The version '2.1.12.536ea0163412325ab7962957ce1cec777799d587' specified for the 'file version' is not in the normal 'major.minor.build.revision' format

But you can safely use it in AssemblyInformationalVersion as .NET doesn't care if you put letters there... Note: If you work with SVN instead of Git you are lucky because value returned for build.vcs.number is an integer and can be used in all three version-related attributes. If you really need to set revision in AssemblyVersion while using Git you might need to add a custom build step for creating integer id... Let's keep it simple here and leave the last part of version number intact (as 0)...

Once you have AssemblyInfo patcher feature configured and you run the build, you can expect such entries in the build log: 

[22:46:02]Step 1/1: Visual Studio (sln) (2s)
[22:46:02][Step 1/1] Update assembly versions: Scanning checkout directory for assembly information related files to update version to 2.1.14
[22:46:02][Update assembly versions] Updating assembly version in C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\AssemblyInfoTest\Properties\AssemblyInfo.cs
[22:46:02][Update assembly versions] Updating assembly version in C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\Core\Properties\AssemblyInfo.cs
[22:46:02][Update assembly versions] Updating assembly version in C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\DataAccess\Properties\AssemblyInfo.cs

If all went ok your log should also contain something like this:

[22:46:05]Reverting patched assembly versions
[22:46:05][Reverting patched assembly versions] Restoring C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\AssemblyInfoTest\Properties\AssemblyInfo.cs
[22:46:05][Reverting patched assembly versions] Restoring C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\Core\Properties\AssemblyInfo.cs
[22:46:05][Reverting patched assembly versions] Restoring C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\DataAccess\Properties\AssemblyInfo.cs

Don't worry, reverting takes place only in build agent work files. The build artifacts contain properly versioned assemblies. You've seen a proof of that rendered on HTML page, you can also check DLL files properties:

File properties with version information... Click to enlarge...

Properties window shows version set by AssemblyFileVersion and AssemblyInformationalVersion. I have Polish Windows so the properties are labeled Wersja pliku (it means File version) and Wersja produktu (it means Product version).

Keep in mind that AssemblyInfo patcher will not work if version attribute has non-standard format (or AssemblyInfo files are in unusual locations).

Let's say you have something like this (because you keep information about your product in static class constants):

[assembly: AssemblyVersion(ProductInfo.Version)]

You can expect such warning during build:

[Update assembly versions] Assembly info version was specified, but couldn't be patched in file C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\AssemblyInfoTest\Properties\AssemblyInfo.cs. Is necessary attribute missing?

 

SUMMARY

And that's all! We have a TeamCity build that sets version information in ASP.NET MVC application assemblies :) 

If somebody will be interested I can write a little supplement to this post in which I will describe how to add version info into zip package (artifact) and how to display it on Team City UI...

jQuery UI Autocomplete in MVC 5 - selecting nested entity

Imagine that you want to create edit view for Company entity which has two properties: Name (type string) and Boss (type Person). You want both properties to be editable. For Company.Name simple text input is enough but for Company.Boss you want to use jQuery UI Autocomplete widget*. This widget has to meet following requirements:

  • suggestions should appear when user starts typing person's last name or presses down arrow key;
  • identifier of person selected as boss should be sent to the server;
  • items in the list should provide additional information (first name and date of birth);
  • user has to select one of the suggested items (arbitrary text is not acceptable);
  • the boss property should be validated (with validation message and style set for appropriate input field).

Above requirements appear quite often in web applications. I've seen many over-complicated ways in which they were implemented. I want to show you how to do it quickly and cleanly... The assumption is that you have basic knowledge about jQuery UI Autocomplete and ASP.NET MVC. In this post I will show only the code which is related to autocomplete functionality but you can download full demo project here. It’s ASP.NET MVC 5/Entity Framework 6/jQuery UI 1.10.4 project created in Visual Studio 2013 Express for Web and tested in Chrome 34, FF 28 and IE 11 (in 11 and 8 mode). 

So here are our domain classes:

public class Company
{
    public int Id { get; set; } 

    [Required]
    public string Name { get; set; }

    [Required]
    public Person Boss { get; set; }
}
public class Person
{
    public int Id { get; set; }

    [Required]
    [DisplayName("First Name")]
    public string FirstName { get; set; }
    
    [Required]
    [DisplayName("Last Name")]
    public string LastName { get; set; }

    [Required]
    [DisplayName("Date of Birth")]
    public DateTime DateOfBirth { get; set; }

    public override string ToString()
    {
        return string.Format("{0}, {1} ({2})", LastName, FirstName, DateOfBirth.ToShortDateString());
    }
}

Nothing fancy there, few properties with standard attributes for validation and good looking display. Person class has ToString override – the text from this method will be used in autocomplete suggestions list.

Edit view for Company is based on this view model:

public class CompanyEditViewModel
{    
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public int BossId { get; set; }

    [Required(ErrorMessage="Please select the boss")]
    [DisplayName("Boss")]
    public string BossLastName { get; set; }
}

Notice that there are two properties for Boss related data.

Below is the part of edit view that is responsible for displaying input field with jQuery UI Autocomplete widget for Boss property:

<div class="form-group">
    @Html.LabelFor(model => model.BossLastName, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.TextBoxFor(Model => Model.BossLastName, new { @class = "autocomplete-with-hidden", data_url = Url.Action("GetListForAutocomplete", "Person") })
        @Html.HiddenFor(Model => Model.BossId)
        @Html.ValidationMessageFor(model => model.BossLastName)
    </div>
</div>

form-group and col-md-10 classes belong to Bootstrap framework which is used in MVC 5 web project template – don’t bother with them. BossLastName property is used for label, visible input field and validation message. There’s a hidden input field which stores the identifier of selected boss (Person entity). @Html.TextBoxFor helper which is responsible for rendering visible input field defines a class and a data attribute. autocomplete-with-hidden class marks inputs that should obtain the widget. data-url attribute value is used to inform about the address of action method that provides data for autocomplete. Using Url.Action is better than hardcoding such address in JavaScript file because helper takes into account routing rules which might change.

This is HTML markup that is produced by above Razor code:

<div class="form-group">
    <label class="control-label col-md-2" for="BossLastName">Boss</label>
    <div class="col-md-10">
        <span class="ui-helper-hidden-accessible" role="status" aria-live="polite"></span>
        <input name="BossLastName" class="autocomplete-with-hidden ui-autocomplete-input" id="BossLastName" type="text" value="Kowalski" 
         data-val-required="Please select the boss" data-val="true" data-url="/Person/GetListForAutocomplete" autocomplete="off">
        <input name="BossId" id="BossId" type="hidden" value="4" data-val-required="The BossId field is required." data-val-number="The field BossId must be a number." data-val="true">
        <span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="BossLastName"></span>
    </div>
</div>

This is JavaScript code responsible for installing jQuery UI Autocomplete widget:

$(function () {
    $('.autocomplete-with-hidden').autocomplete({
        minLength: 0,
        source: function (request, response) {
            var url = $(this.element).data('url');
   
            $.getJSON(url, { term: request.term }, function (data) {
                response(data);
            })
        },
        select: function (event, ui) {
            $(event.target).next('input[type=hidden]').val(ui.item.id);
        },
        change: function(event, ui) {
            if (!ui.item) {
                $(event.target).val('').next('input[type=hidden]').val('');
            }
        }
    });
})

Widget’s source option is set to a function. This function pulls data from the server by $.getJSON call. URL is extracted from data-url attribute. If you want to control caching or provide error handling you may want to switch to $.ajax function. The purpose of change event handler is to ensure that values for BossId and BossLastName are set only if user selected an item from suggestions list.

This is the action method that provides data for autocomplete:

public JsonResult GetListForAutocomplete(string term)
{               
    Person[] matching = string.IsNullOrWhiteSpace(term) ?
        db.Persons.ToArray() :
        db.Persons.Where(p => p.LastName.ToUpper().StartsWith(term.ToUpper())).ToArray();

    return Json(matching.Select(m => new { id = m.Id, value = m.LastName, label = m.ToString() }), JsonRequestBehavior.AllowGet);
}

value and label are standard properties expected by the widget. label determines the text which is shown in suggestion list, value designate what data is presented in the input filed on which the widget is installed. id is custom property for indicating which Person entity was selected. It is used in select event handler (notice the reference to ui.item.id): Selected ui.item.id is set as a value of hidden input field - this way it will be sent in HTTP request when user decides to save Company data.

Finally this is the controller method responsible for saving Company data:

public ActionResult Edit([Bind(Include="Id,Name,BossId,BossLastName")] CompanyEditViewModel companyEdit)
{
    if (ModelState.IsValid)
    {
        Company company = db.Companies.Find(companyEdit.Id);
        if (company == null)
        {
            return HttpNotFound();
        }

        company.Name = companyEdit.Name;

        Person boss = db.Persons.Find(companyEdit.BossId);
        company.Boss = boss;
        
        db.Entry(company).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(companyEdit);
}

Pretty standard stuff. If you've ever used Entity Framework above method should be clear to you. If it's not, don't worry. For the purpose of this post the important thing to notice is that we can use companyEdit.BossId because it was properly filled by model binder thanks to our hidden input field.

That's it, all requirements are met! Easy, huh? :)

* You may be wondering why I want to use jQuery UI widget in Visual Studio 2013 project which by default uses Twitter Bootstrap. It's true that Bootstrap has some widgets and plugins but after a bit of experimentation I've found that for some more complicated scenarios jQ UI does a better job. The set of controls is simply more mature...

Radio buttons for list items in MVC 4 – problem with id uniqueness

Let's suppose that we have some model that has a list property and we want to render some radio buttons for items of that list. Take the following basic setup as an example.

Main model class with list:

using System.Collections.Generic;

public class Team
{
    public string Name { get; set; }
    public List<Player> Players { get; set; }
}

List item class:

public class Player
{
    public string Name { get; set; }
    public string Level { get; set; }
}

There are three accepted values for player’s skill Level property: BEG (Beginner), INT (Intermediate) and ADV (Advanced) so we want three radio buttons (with labels) for each player in a team. Yup, normally we would rather use enum instead of a string for Level property, but let’s skip it here for the sake of simplicity…

Controller action method that returns sample data:

public ActionResult Index()
{
    var team = new Team() {
        Name = "Some Team",
        Players = new List<Player> {
               new Player() {Name = "Player A", Level="BEG"},
               new Player() {Name = "Player B", Level="INT"},
               new Player() {Name = "Player C", Level="ADV"}
        }
    };

    return View(team);
}

Here is our Index.cshtml view:

@model Team

<section>
    <h1>@Model.Name</h1>        

    @Html.EditorFor(model => model.Players)            
</section>

Notice that markup for Players is not created inside a loop. Instead, EditorTemplate is used. It’s a good practice since it makes code more clear and maintainable. Framework is smart enough to use code from template for each player on a list, because Team.Players property implements IEnumerable interface...

And here is Players.cshtml EditorTemplate:

@model Player
<div>
    <strong>@Model.Name:</strong>

    @Html.RadioButtonFor(model => model.Level, "BEG")
    @Html.LabelFor(model => model.Level, "Beginner")

    @Html.RadioButtonFor(model => model.Level, "INT")
    @Html.LabelFor(model => model.Level, "Intermediate")

    @Html.RadioButtonFor(model => model.Level, "ADV")
    @Html.LabelFor(model => model.Level, "Advanced")        
</div>

The code looks fine, nice strongly typed helpers that relay on lambda expressions (for compile-time checking and easier refactoring)... but there’s a catch: HTML markup that is generated by such code is actually seriously flawed. Check this snipped of web page source generated for the first player:

<div>
    <strong>Player A:</strong>  
 
    <input name="Players[0].Level" id="Players_0__Level" type="radio" checked="checked" value="BEG">
    <label for="Players_0__Level">Beginner</label>
 
    <input name="Players[0].Level" id="Players_0__Level" type="radio" value="INT">
    <label for="Players_0__Level">Intermediate</label>
 
    <input name="Players[0].Level" id="Players_0__Level" type="radio" value="ADV">
    <label for="Players_0__Level">Advanced</label>        
</div>

Players_0__Level id is used for three different radio buttons! Lack of uniqueness not only violates HTML specification and makes scripting hard but also causes label tags to not work properly (clicking on them doesn’t check their corresponding input element). 

Fortunately MVC framework contains TemplateInfo class that has GetFullHtmlFieldId method. This method returns id for DOM element. That id is constructed by appending name provided as method argument to an automatically determined prefix. This prefix takes into account nesting level and list item's index. Internally, GetFullHtmlFieldId uses TemplateInfo.HtmlFieldPrefix property and TagBuilder.CreateSanitizedId method so even if you pass some illegal characters to id suffix they will be replaced.

Here is modified EditorTemplate
@model Player
<div>
    <strong>@Model.Name:</strong>

    @{string rbBeginnerId = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("rbBeginner"); }
    @Html.RadioButtonFor(model => model.Level, "BEG", new { id = rbBeginnerId })
    @Html.LabelFor(model => model.Level, "Beginner",  new { @for = rbBeginnerId} )

    @{string rbIntermediateId = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("rbIntermediate"); }
    @Html.RadioButtonFor(model => model.Level, "INT", new { id = rbIntermediateId })
    @Html.LabelFor(model => model.Level, "Intermediate",  new { @for = rbIntermediateId })

    @{string rbAdvancedId = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("rbAdvanced"); }
    @Html.RadioButtonFor(model => model.Level, "ADV", new { id = rbAdvancedId })
    @Html.LabelFor(model => model.Level, "Advanced",  new { @for = rbAdvancedId })
</div>

Calls to ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId method let us obtain ids for radio buttons which are also used to set for attributes of labels. In MVC 3 there was no overload of LabelFor extension method that accepted htmlAttributes object. Luckily version 4 has it build-in.

Above code produces such markup:

<div>
    <strong>Player A:</strong>
 
    <input name="Players[0].Level" id="Players_0__rbBeginner" type="radio" checked="checked" value="BEG">
    <label for="Players_0__rbBeginner">Beginner</label>
 
    <input name="Players[0].Level" id="Players_0__rbIntermediate" type="radio" value="INT">
    <label for="Players_0__rbIntermediate">Intermediate</label>
 
    <input name="Players[0].Level" id="Players_0__rbAdvanced" type="radio" value="ADV">
    <label for="Players_0__rbAdvanced">Advanced</label>
</div>

Now inputs ids are unique and labels properly reference radio buttons via for attribute. Alright :) 

BTW: the weird name “radio buttons” for mutually exclusive option elements comes from buttons on radio receivers that were used to switch between stations (pushing one in automatically pushed the others out).