June 2013 - Posts

Visual Studio 2013 Preview

Note: This is strictly my opinion and in no way should be conveyed as the opinion of anyone else.

Now that VS2013 Preview is available I can provide some feedback about it.  Then I can provide my opinion about whether it is a mandatory upgrade or not.

General Thoughts

Honestly, there are really no new features in VS2013 that are really critical.  VS2013 is a minor update, in my opinion, to support Win 8.1 and .NET 4.5.1.  There are no new language features or changes to even blog about.  The exception is with WinRT apps.  As such don’t expect a VS2010 to VS2012 jump in performance, behavior or features.

WinRT Apps

Almost all the enhancements made in VS2013 are around WinRT apps.  If you write WinRT apps then you’ll be targeting Win 8.1.  Therefore VS2013 is pretty much going to be mandatory.  There are lots of changes to support the enhancements Win 8.1 is going to provide.  Along the same front there are lots of enhancements made to JavaScript and C++ to support WinRT apps. 

Languages

C++

The C++ team continues to make improvements in this release.  If you are using C++ then you’ll be glad to hear that C++ is more compliant with the latest standard.  There are also some useful enhancements that you’ll want to check out.  If you don’t use C++ then none of this will matter to you.

JavaScript

Given that JavaScript is a core language in WinRT it is no surprise that the JS editor got more love in this release.  I haven’t verified but I’ve heard that Go to Definition works correctly now and that Intellisense is working much better.  If you do a lot of web development then this should be great.

Debugger

The debugger team has added some new features as well.  The one feature I’m really excited about is viewing the return value.  Since the early days of C++ it has been possible to see the return value of a function even if the calling program didn’t capture it.  .NET has never had this feature, until now.  I cannot count the times I wanted to see the return value of a function but wasn’t capturing the result.  Therefore I had to temporarily modify my code to capture the results and then make sure I undid the changes before checking in the code.  With VS2013 method return values show up in the Autos window even if the value isn’t being explicitly captured.  Awesome!!!

Edit and Continue

Are you sitting down?  One of the most requested features in VS history is EnC support for 64-bit managed code.  MS has always said that this feature required both CLR and Windows changes to support and the demand wasn’t there yet.  Well starting with VS2013 EnC with 64-bit managed code is supported.  EnC is not trivial to implement and I haven’t seen it in action yet so I cannot confirm whether it is as solid as 32-bit EnC but this is at least a step forward.

Managed Crash Dumps

One of the challenging aspects of managed code is debugging a crash dump file.  Managed memory isn’t like native memory so finding objects and piecing together chains can be hard.  With VS2013 you can load a managed crash dump into VS and VS will be able to view the dump as a managed process such that you can see managed memory objects.  I haven’t been able to play around with this feature to see if it provides any real benefits but it sounds promising.

Roaming Settings

Going along with the roaming profile concept of Windows, VS2013 is getting on the bandwagon.  One of the biggest issues with setting up a dev machine is getting your VS configured the way you want.  With roaming settings this becomes less of an issue.  Provided you hook VS up to your MS account (an option when you start VS the first time) then VS will pull down your VS profile.  This allows you to share VS settings across machines.  In this first release it isn’t clear what all will be pulled other than theme and possibly window layouts but still this feature has the potential to grow into a truly useful tool. 

One thing that caught me off guard is the fact that the roaming settings appear to be tied to your online TFS account rather than with your MS account.  This means you’ll have to set up a TFS account whether you want the rest of the services or not.  Even after setting all this up I didn’t see any options to configure settings for VS.  In reality you have to go to Tools\Options –> Environment\Synchronized Settings to specify whether roaming settings is enabled or not and what options you want to roam.  I like the fact that I have some choices I just wish it was easier to get to.  At this point it seems disconnected from my account link.

User Interface

General Changes

When Win8 was released the mandate from MS was for the products to comply with the new UI features.  VS did that and the backlash during beta was intense.  All caps menus and lack of color really angered a lot of people.  MS added some color back before final release but were pretty adamant that the new UI was better.  After release the anger still hasn’t fully subsided but MS has learned a valuable less.  One of the big changes coming in VS2013 is adding more color back into the UI.  This will make it far easier to identify things like selected items and to distinguish icons.  There is still work to do but I’m glad the VS team listened and are retreating on their original decisions.  There are also some other UX treatments being made such as altering lines to make things look nicer overall.  Compared to VS2012, VS2013 has a much friendlier UI.

Notifications

There is now a notification icon next to the Quick Search control.  VS will now display notifications here rather than (or perhaps in addition to) toast notices on the Task Bar.  It is unclear yet whether extensions will be able to hook into this and whether extensions/updates will be displayed here as well.

Editor Changes

Outside the chrome stuff there are various tweaks and changes to allow more formatting of code.  Most of the changes aren’t compelling enough to warrant an upgrade but they are certainly nice to have.

One feature that is really nice is the new Code Indicator feature in the editor.  Above methods you will now see informational data such as the # of references to that method (think Find All References).  It is a link so if you click on it you’ll get to see the actual references.  Find All References is a heavily used feature in my world so this unto itself is worth its weight in gold. 

Code indicators also include other things like test results.  This is configurable from Tools\Options –> Text Editors\All Languages.  I was privileged to be able to play around with its predecessors in VS2012 and I can assure you know that when I lost that functionality on the last VS update I really missed it.  This feature is incredibly useful.

Source Control

VS2012 and TFS2012 have had partial Git support for a while.  It is officially integrated in VS2013.  If you are a fan of Git then it’ll be there for you.

Another nice change is related to multiple source control providers.  Some times you want to use an in house TFS server and other times you might want to use TFS Services.  Unfortunately this process isn’t streamlined.  The process of connecting to a local TFS server remains the same.  For TFS Services you’ll create a new TFS server connection as well.  But this triggers a login prompt to TFS Services even though I’m already hooked up via my MS account.  This same workflow also occurs when you are trying to edit your profile.  Hopefully this will be resolved before release.  Once you get past all that though you can now see each of your source control servers and their projects which makes it real easy to switch between them. 

Project Compatibility

When VS2012 came out one of the very big features was backwards compatibility with VS2010 SP1.  This is one of the reasons that VS2012 did so well.  It allowed for mixed development.  VS2013 makes this same guarantee but the link here indicates a slightly different story.  Some project types indicate VS2010 SP1 support and others don’t.  There are even duplicate entries for the same project type with different compatibility statements.  Hopefully MS will clear this up before release.  Right now it would be safe to assume VS2012 is the only compatible version.

Final Thoughts

The question boils down to whether VS2013 is a mandatory upgrade or not.  My initial thoughts were no unless you were doing WinRT work.  However given some critical new features like Code Indicators, 64-bit EnC and seeing return values of methods I’m now in the camp that VS2013 is going to be a good upgrade provided the upgrade price is reasonable.

One thing that has not been clarified yet is the upgrade cost from VS2012.  Will there be an upgrade price or will devs have to pay full price?  If there is no upgrade price then I don’t expect VS2013 to do well at all.  There simply isn’t any critical features that justify the high cost of VS.  Of course most devs either have MSDN or SA so the cost isn’t relevant but hobby developers or those who didn’t go the above route will be out of luck.  We’ll have to wait and see until MS publishes the price before we can decide.

Windows 8.1 Preview

Note: This is strictly my opinion and in no way should be conveyed as the opinion of anyone else.

Now that the Win 8.1 Preview is out I can give my personal opinion on it.  First we should discuss some of the new features and then whether it is a mandatory upgrade or not.  Note that I’m ignoring all the new features around corporate environments and Windows Store apps. 

Microsoft vs. Local Account

When Win8 released you had the option of a local vs. MS account.  An MS account is effectively your Windows Live account and allows Windows to hook up to all that information including your contacts, email, etc.  For a home user this can be really useful compared to a local account which is specific to your machine.  Since Win8 was the first release to support this it wasn’t that useful.  Now that Win8.1 is coming you can start to see some of the benefits.  For example my home machine is Win8 and I’m running Win8.1 in a VM.  Both are hooked up to my MS account.  (The preview only allows an MS account during installation but local account will be available upon release)  As I was playing around with some of the Win8.1 settings I noticed they were already configured the way I wanted (because it pulled them from my main machine).  Likewise I changed the background image in Win8.1 and my Win8 machine updated (eventually).  Some of this is pretty nice but I still haven’t bought into the roaming settings concept because not every setting I want on every machine.  I really wish this was configurable.  So, for now, if you want different settings between Win8/Win8.1 machines you should use different accounts.

Desktop

The first feature that is really nice is the ability to log directly into the desktop.  With Win8 you always logged into the Start screen.  For a tablet this is fine but for desktops the Start screen isn’t as useful.  In Win8.1 you can default to the desktop at login by going to Taskbar and Navigation Properties and then the Navigation tab and selecting the option to Go to the desktop

Honestly, now that I’ve been using Win8 for a while I don’t mind the Start screen as much because almost all my common programs are there.  If MS would just allow me to pin anything to the Start screen I would happy but there are some things that cannot be pinned there (shortcuts that look like URLs but aren’t).

While you’re on the Navigation tab you might also notice a new option called Replace Command Prompt with PowerShell.  This effectively makes PowerShell more accessible and is speeding up the end of the DOS prompt.

Start Button

This got a lot of publicity.  One of the biggest complaints about Win8 was that the Start button was removed.  Win8.1 adds it back.  This is sort of like a genie wish though, you need to be very careful about how you word it.  People didn’t actually miss the Start button.  They missed the Start Menu which appears when you click the Start button.  The Start button is back in W8.1 but the Start Menu isn’t.

What MS has done is move some common functionality into the fixed menu.  You still won’t have the Start Menu from Win7 and prior.  What is not clear at this time is whether the button is pulling shortcuts from some location that can be manipulated such that it will be possible to add items to the menu. 

The new button does provide some useful functionality though such as easy access to PowerShell, Run command and shutdown commands.  One of the things that came up during the Win8 beta was no easy way to shut down the machine.  MS assumed, I can only imagine, that you would never shut down your machine so they made it a 4 step process.  Now you can easily shut down or restart your computer from the Start button.  I still don’t think this is going to make people happy.  Somebody will probably provide a hack to solve this so we’ll just have to wait.

Start Screen

The Start screen has a few interesting new features.  The first one that will be noticeable is that you can name the column of icons you have.  This may be useful to help distinguish the various columns of icons you have.  Personally I have only a couple of columns so I can keep them apart but if you fill up your Start screen with lots of icons this could be useful.

Another feature is the ability to resize the icons.  Depending upon the icon you can make it small, medium or large.  It appears that Win Store apps can be resized to all 3 sizes but regular program icons can only be small or medium.  The API documents that tiles can be 4 different sizes but only 3 show up on the screen.  I personally like how the Windows Phone allows you to resize an icon just by touch, dragging the icon.  This might be possible on a touch screen in Win8.1 as well.

Somewhat related is that more than one Win Store app can be on the screen at the same time.  This has been mentioned in several previews and in the documentation but it isn’t intuitive on how to do it.  What you do is open a Win Store app and then move your mouse to the top of the window until it turns into the hand icon.  Then hold, left click and you can drag the window to either the left or right side of the screen.  If you drag the window to the left (or right) of the screen then you can remove it from the split screen view.  This places it effectively in the “toolbox” and allows you to move Win Store apps to and from split screen depending upon what you’re doing at the time.

In theory you can do this for each monitor so if you have 2 monitors you can have 4 apps running at once.  It’s sort of funny how the single window, single focus concept of Win8 is already going away and MS is moving back toward multiple windows at the same time again.

Under Construction

There are still lots of issues with Win8 that Win8.1 doesn’t resolve.  The management aspect is still scattered to the four winds.  The Control Panel remains the best place to go to do all the system management but Win8.1 still provides links all over the place to sub pieces.  For an IT person I’d recommend just sticking with Control Panel (which is one of the options on the Start button).

Homegroup still doesn’t seem to add any value to me and yet by default you’ll get auto-added to a Homegroup when you install.  This adds a needless layer of removal once the OS is installed.  Please MS, give me the option of joining a Homegroup when I install.

The OS continues to run software at startup that I neither asked for nor want.  SkyDrive and the touch keyboard start automatically.  Even worse is that I turned off the touch keyboard and it was still there the next time I logged in.  I turned it off again but it is still running in the background.  Please MS, stop adding more stuff to an already bloated OS.  Don’t make me have to turn things off via hacks!!

Worthy Upgrade

At this point it is time to evaluate whether Win8.1 is a mandatory upgrade or not.  The answer is, it depends.  The upgrade is free for Win8 users.  If you’re running Win8 then Win8.1 is a recommended upgrade if you want the newer features.  Let’s face it Win8 was more akin to Vista and Win7 so Win8.1 is more like Win7.  Anybody running Win8 should go ahead and apply the free update.

If you’re running Win7 then Win8.1 isn’t really a mandatory upgrade.  Most people didn’t upgrade either because Win7 was doing just fine or the Win8 UI was so different.  Nothing has really changed on that front so if you’re content with Win7 then stay with it.  Once you are ready to upgrade though you’ll be switching to Win8.1.  If you’re running anything prior to Win7 then you probably need to upgrade so Win8.1 makes the most sense.  Just be ready to learn the new UI.

Environmental Config Transforms, Part 2

Update 29 July 2013: Refer to this link for updated code supporting Visual Studio 2013 Preview.

In the previous article we updated the build process to support environmental config transforms in projects and to have the transforms run on any build.  In this article we are going to package up the implementation to make it very easy to add to any project.  There are a couple of things we want to wrap up.

  • Place the .targets file into a shared location
  • Add an import into the current project to the .targets file
  • Add environmental config transforms into the project
  • Remove the default config transforms generated by Visual Studio

Installing the Shared Targets File

The default location for storing shared .targets files is MSBuildExtensionsPath.  This property is set automatically during builds and will, by default, be pointing to C:\Program Files (x86)\MSBuild.  If you look under this directory you will see that the standard .targets file shipped by .NET are already there.  We need to place our .targets file under there as well but we should create a subdirectory for our file.  This prevents us from colliding with other files and provides a convenient place to store additional files if we need them.  For this article I’m going to call the directory P3Net.

For a single machine you can easily just create the directory and copy the file into it.  But for several machines a better approach is to provide an installation script.  I’ve attached a simple one called Install.ps1 to this article.  One issue that the installation has is that you have to be an administrator to write to the directory so either the script has to be run using an elevated account or it will need to be manually done using Windows Explorer. 

Irrelevant of how the file ultimately gets installed we now need to update the project file that we used last time to use the new path. 

<Import Project="$(MSBuildExtensionsPath32)\P3Net\P3Net.targets"/>

Reloading the project and rebuilding the solution should result in no errors.  In the future if we need to modify the .targets file then we’ll have to redeploy it.  We could, if we wanted, move this to a VS extension but the extension would need to be installed using administrator privileges. 

Creating the Item Template

Now that we have the .targets file in a shared location we can set about creating an item template to generate the config transforms for any project we want.  In previous articles we went over how to create item templates using T4 and how to deployment.  In this case we’ll be creating an item template but it won’t use T4.  We will however use the same deployment process that we used in the previous articles.  If you have not yet done so then download the version from the final article.  We will add the template to the solution so it is deployed like the others.

Before we can create the template we need to decide what config transforms we need for our environments and what they should contain by default.  To keep it simple we will stick with the environments we defined in the last article (Production, Test) and our transforms will simply contain the environment name.  For your environments you’ll want to set up any additional, standard transforms you’ll need.  It is important to note that the actual environment transform files isn’t relevant to the build as it builds all the transforms. 

Following the instructions for adding new item templates that were discussed in the template article we do the following to the ItemTemplates project.

  1. Create a new directory called EnvConfigs
  2. Add the environmental configs that we want into the folder.  Since we do not know whether this a web or Windows project rename the files to base.environment.config.  We’ll see later how to rename them.
  3. For each transform set the following properties:
    • Build Action = Content
    • Copy to Output = Do Not Copy
    • Include in VSIX = True
  4. Add the .vstemplate file to the project and update it accordingly.
  5. Set the following properties for the .vstemplate file
    • Build Action = Content
    • Copy to Output = Do Not Copy
    • Include in VSIX = True
    • (Optional) Category = My Templates

Here’s what my .vstemplate looks like.

<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" Type="Item" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:sdk="http://schemas.microsoft.com/developer/vstemplate-sdkextension/2010">
  <TemplateData>
    <Name>Environmental Configuration Transforms</Name>
    <Description>Template for generating basic environmental config transforms.</Description>
    <Icon Package="{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}" ID="4600" />
    <TemplateID>f7a423ac-7948-42a7-b9b9-cba719569106</TemplateID>
    <ProjectType>CSharp</ProjectType>
    <RequiredFrameworkVersion>4.0</RequiredFrameworkVersion>    
    <DefaultName>Environment</DefaultName>
  </TemplateData>
  <TemplateContent>
      <ProjectItem SubType="Code" ReplaceParameters="true" ItemType="None" TargetFileName="web.config\web.Test.config">base.Test.config</ProjectItem>
      <ProjectItem SubType="Code" ReplaceParameters="true" ItemType="None" TargetFileName="web.config\web.Production.config">base.Production.config</ProjectItem>
  </TemplateContent>
</VSTemplate>

There are a couple of interesting points about this file.  Notice the Icon element.  Since VS already ships with icons for config transforms I’m using the same values for my icon.  This gives the user a consistent icon for both versions.  The DefaultName element is not a full file name and won’t actually be used anyway.

The most interesting part of this is the template contents.  There is a project item for each environment transform.  The target file name is a partial path starting with the web.config file.  This causes VS to insert the transforms as subfiles under the base file.  This mimics the behavior that you see in VS today.

At this point we have a working item template but there are still a couple of issues.  The first issue is that the template is keyed to a web project.  If we try to use it on a Windows project it will be using the wrong config.  We need to fix that but we cannot using the existing .vstemplate file.  Instead we need to modify the project item entry to use a template property that we can replace when the template runs.  Here’s the updated project item elements.

<ProjectItem SubType="Code" ReplaceParameters="true" ItemType="None" TargetFileName="$BaseConfigFileName$.config\$BaseConfigFileName$.Test.config">base.Test.config</ProjectItem>
<ProjectItem SubType="Code" ReplaceParameters="true" ItemType="None" TargetFileName="$BaseConfigFileName$.config\$BaseConfigFileName$.Production.config">base.Production.config</ProjectItem>

Now all we need to do is replace the template property with the actual config file name when it is inserted.  But to do that we need to use a template wizard.

Creating a Template Wizard

Most templates do not need any code to support them but some do.  For those that need custom code you have to write a template wizard.  The documentation makes lots of restrictions on template wizards but based upon the current behavior of VS and building on the existing template deployment architecture we can simply create a new template wizard project as part of our template project and deploy it along with the item templates.

  1. Create a new class library called P3Net.MyTemplateWizards.
  2. Add references to the following assemblies
    • EnvDTE (set Embed Interop Types to false)
    • Microsoft.Build
    • Microsoft.VisualStudio.TemplateWizardInterface
    • System.Windows.Forms
    • TemplateLib (contains some extension methods)
  3. Add a new class called EnvironmentConfigsWizard that implements IWizard.

The code for the wizard is too long to post so I’ll only mention the RunStartedCore method.  This method is called when the template runs.  It will be responsible for doing the heavily lifting.  Here’s the code.

private void RunStartedCore ( EnvDTE.DTE dte, 
                Dictionary<string, string> replacementsDictionary, 
                WizardRunKind runKind, 
                object[] customParams )
{
    //Get the current project                
    var project = dte.GetCurrentProject();

    //Get the configuration file
    var configFile = GetConfigurationFile(project);
    if (configFile == null)
        ReportErrorAndCancel("No configuration file could be found.");

    //Set the template parameters
    replacementsDictionary.Add("$IsWebProject$", configFile.ProjectType == ProjectType.Web ? "1" : "0");
    replacementsDictionary.Add("$BaseConfigFileName$", configFile.BaseConfigFileName);
}

The method first finds the config file in the project.  Based upon the config file name it knows whether this is a web or Windows project.  It then updates the template property accordingly so that the .vstemplate file will generate the correct target information.

Now that the template wizard is defined we need to update the .vstemplate file to reference the wizard.  Add the following to the end of the .vstemplate file.

<WizardExtension>
    <Assembly>P3Net.MyTemplateWizards, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</Assembly>
    <FullClassName>P3Net.MyTemplateWizards.EnvironmentConfigsWizard</FullClassName>
</WizardExtension>

The above elements identify the assembly to load and the class within the assembly that will be used to support adding the template to the project.  Now we need to hook it up to the deployment project.

Adding Wizard to Setup

To add the template wizard library to the setup we do the following.

  1. Open the .vsixmanifest file in the designer
  2. Go to the Assets tab and click New
  3. Select Assembly as the type
  4. Select A project in the current solution as the source
  5. Select the project
  6. Click OK

The template wizard will now be deployed as part of the extension.  We have just a couple of more issues to resolve before we are done.

Removing the Default Config Transforms

The next issue we need to resolve is the removal of the default transforms that are most likely in the project.  This is a simple matter of removing the files from the project so we update the RunStartedCore method from earlier to find and remove any existing transforms before we add our new ones.

//Remove the default config transforms if they exist            
RemoveProjectItem(configFile.ConfigurationItem, configFile.BaseConfigFileName + ".Debug.config");
RemoveProjectItem(configFile.ConfigurationItem, configFile.BaseConfigFileName + ".Release.config");

The above code only removes the default transforms.  If you wanted to remove them all you would need to update the code.

Importing the Targets File

The final issue to solve is getting the shared .targets file into the project.  We don’t want to manually have to do that so we will modify the template method to check for the import of the .targets file.  If it hasn’t been imported yet then the template will add it.  Here’s the code.

private void EnsureStandardTargetIsImported ( EnvDTE.Project project )
{
    var buildProject = ProjectCollection.GlobalProjectCollection.GetLoadedProjects(project.FullName).First();

    //Check for the import
    var hasImport = (from i in buildProject.Xml.Imports
                     where String.Compare(Path.GetFileName(i.Project), SharedTargetsFileName, true) == 0
                     select i).Any();
    if (hasImport)
        return;

    //Make sure it exists first
    var extensionsPath = buildProject.GetPropertyValue("MSBuildExtensionsPath32");
    var targetsPath = Path.Combine(SharedTargetsFilePath, SharedTargetsFileName);
    var fullPath = Path.Combine(extensionsPath, targetsPath);
    if (!File.Exists(fullPath))
        ReportErrorAndCancel("The standard .targets file could not be located.");

    //Add it             
    buildProject.Xml.AddImport(@"$(MSBuildExtensionsPath32)\" + targetsPath);
}

The above method gets the imports from the project and looks for the shared .targets file.  If it isn’t found then an import is added to it otherwise nothing happens.  A call to this method is placed in the template method shown earlier.

Conclusion

We are done.  We have a new item template that generates the environmental transforms that we need and cleans up the existing version that VS uses.  Whenever the project is built all the environmental transforms are generated and stored so we can easily build once and deploy to any of our environments.

Additionally we have added a template wizard to our T4 template project that we can use as a basis for more advanced templates in the future.  Finally we created a shared .targets file that we can use to add new build tasks to any project. 

Environmental Config Transforms, Part 1

Configuration file transforms have been around for quite a while and most companies use them to generate different config files for each environment but Visual Studio does not really support the functionality most companies need.  In this article I’m going to talk about why the existing Visual Studio implementation is flawed and how to fix it so that you can have true environmental transforms that are actually useful.

For purposes of this article we will assume a simple web application (although it applies to any application) that will be deployed to 2 different environments – test and production.  We will also assume that developers debug the application on their local machine and therefore should not have to do any special tasks to debug the application locally.  As such we’ll have a 3th, default, environment – debug.

Per Configuration Transforms

One very big issue with the existing VS implementation of transforms is that it is tied to the configuration (Debug, Release) of the project being built.  For example if you’re building a project in debug mode then only the debug transform is run.  There are several problems with this approach.  The first problem is that VS is tying environmental settings to build configurations.  I don’t know about you but since my earliest years in the business I have heard (and followed) the philosophy that each (potentially releasable) build has to be tested.  Therefore if we have 3 environments we would need to have 3 different configurations (one for each environment) and a separate transform for each.  Furthermore we’d have to build the application 3 times even though just the transform should change.  When you start getting into large applications with many different projects it becomes clear that managing configuration per environment just doesn’t add up.  A single build should be deployable to any environment along with its transformed config file. 

Another issue with this approach is verification of transforms.  If I make a change to the root config file or any transform I’d like to ideally build and verify all the transforms still work.  I shouldn’t have to build my application for each environment to verify one change.

Clearly the VS approach of tying environmental transforms to build configurations just does not make sense.  Unfortunately that is the default.  Fortunately the rest of this article will show you how to create environmental transforms that avoid these issues.  Specifically this article will allow the following:

  • Have 1 transform for each environment (not configuration)
  • Validate all transforms at build time
  • Provide the final, transformed configs for each environment to simplify deployment
  • Make it very easy to add environmental transforms to new projects

For this article I’ve created a simple ASP.NET application that simply displays the environment name.  It was generated using VS 2012 and then stripped of everything except the default page with a label for the environment name.

Per Environment Transforms

To get started we need to agree upon an approach.  Following the default VS approach we will create a separate config file for each environment called web.environment.config.  Ideally these transforms will be stored under the config file.  Within each environmental transform will be the standard transforms for that environment.  For example in development we will likely leave debugging information turned on but in the production environment we will turn off all debugging. 

Assuming you have an application open you would simply add a new config transform for each environment (test, production).  Since the transform syntax can be a little different the easiest approach is to simply copy and paste the existing web.debug.config transform.  Since we do not want per configuration transforms then the default transforms can be removed.  Here’s what you should have in the final project.

  • Web.config
    • Web.Production.Config
    • Web.Test.Config

For clarification I’ll add a simple app setting entry that contains the environmental name so it is easier to identify which transform was used.

<appSettings>
    <add key="Name" value="Debug" />
</appSettings>

And the corresponding Test transform

<appSettings>
    <add key="Name" value="Test" xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
</appSettings>

Running the application at this point would print out “Debug” because VS cannot find a transform for the configuration.  More importantly without a manual process we do not have the environmental transforms to test. 

Configuration File Names

Before moving on it is important to note that there are two different types of config files we have to deal with.  Web-based projects use a web.config file.  The filename does not change between design and run time.  Web config files are simple to deal with.  On the other hand Windows-based projects use app.config at design time but the file is copied and renamed to myapp.exe.config where myapp is the name of the executable.  To support the transformation of either type of file we have to write extra code to select the correct base name and transform the file to the correct final name.  This will add some extra code later on but does not overly complicate the process any.

Building the Transforms

Whenever we do a build we want the transforms to run, irrelevant of configuration.  This provides both the ability to validate any change to the config or the transforms at build time and to allow us to have the fully transformed configs so that we can deploy the build and its config to any environment, without the need for another build.

To trigger something at build time you are going to have to use MS Build.  A project file is really nothing more than a set of MS Build tasks that run.  To do something during a build you need only call a pre-defined task, or write your own.  If the task is involved or is going to be used in many projects then it is best to store it separately in a targets file.  .NET ships with lots of them.  For building the transforms we will store the appropriate task in a targets file that can be easily reused in any number of project files.

Creating the Targets File

A description of how targets files and MS Build work is beyond the scope of this article.  You can refer to the download file for the full file.  I am just going to highlight the process of building the transforms.  Here is the base flow:

  • Get the source file to be transformed
  • Get the list of transform files to be applied
  • For each transform file apply it to the source file to get the output file
  • Save the output file

All this work will require a custom task (TransformXmlFiles).  MS Build supports different approaches to building tasks but I’m opting for an inline task to simplify deployment.  As such the task code is part of the file and contained within a UsingTask.

First we need to set up some parameters for the transformation task.  MS Build tasks are parameter-based so we define the following:

  • SourceFile – The config file that will be transformed (web.config or app.config)
  • TransformFiles – The list of files to process
  • OutputDirectory – The directory where the final transformed files will be stored.
  • TargetFile – The name of the final transform file
  • ProjectName – The project name
  • ToolsDirectory – The path to the VS installation

To trigger a task the project file must either explicitly call the task or the task must hook into the build process.  Since I want the .targets file to be as non-intrusive as possible in the project file I have opted to automatically run the transformation task after a build.  Using this approach I can completely configure the task within the .targets file and project files need only import the file to get the behavior.  Here’s the relevant section.

<!-- Because of the MS Build "bug" VSToolsPath is needed but it isn't always available so handle that case now -->
<PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>


<!-- Get the config transform files -->
<ItemGroup>
    <WebConfigTransformFiles Include="web.*.config" Exclude="web.config" />
    <AppConfigTransformFiles Include="app.*.config" Exclude="app.config" />
</ItemGroup>
    
    <!-- Runs after a successful build -->
<Target Name="TransformConfigurationFiles" AfterTargets="AfterBuild">
    <TransformXmlFiles TransformFiles="@(WebConfigTransformFiles)" SourceFile="web.config" TargetFile="web.config" 
                            OutputDirectory="$([System.IO.Path]::Combine($(OutDir), 'Configs'))" ProjectName="$(MSBuildProjectName)" 
                            ToolsDirectory="$(VSToolsPath)" />

    <TransformXmlFiles TransformFiles="@(AppConfigTransformFiles)" SourceFile="app.config" TargetFile="$(TargetFileName).config"
                            OutputDirectory="$([System.IO.Path]::Combine($(OutDir), 'Configs'))" ProjectName="$(MSBuildProjectName)"
                            ToolsDirectory="$(VSToolsPath)" />
</Target>  

Notice that the transform task is called twice, once for each type of config.  This is simpler (in a task) than trying to determine which type of config to transform.  Notice also that in each case the parameters are set accordingly to ensure the final transform is generated.  The targets file is set based upon whether we are transforming a web or app config. 

The output directory is set to the project’s output directory with a subdirectory of Configs.  Additionally the project name is determined by the project property.  This results in a hierarchy where all the configs are stored as subfolders based upon the project and environment name.  This allows a single solution to have multiple projects with multiple environmental transforms and each one goes into a separate directory for ease of deployment.

MS Build “Bug”

All that remains is the actual logic for transforming the config file. 

if (TransformFiles != null && TransformFiles.Length > 0)
{
    //The reference assembly path is only used for compilation so force the assembly to load so it is available when we need it
    var assemblyWebPublishing = Assembly.LoadFrom(Path.Combine(ToolsDirectory, @"Web\Microsoft.Web.Publishing.Tasks.dll"));

    dynamic transform = assemblyWebPublishing.CreateInstance("Microsoft.Web.Publishing.Tasks.TransformXml");
    //var transform = new TransformXml();

    transform.BuildEngine = this.BuildEngine;
    transform.HostObject = this.HostObject;

    foreach (var inputFile in TransformFiles)
    {
        //Get the env name
        var fileParts = Path.GetFileNameWithoutExtension(inputFile.ItemSpec).Split('.');
        var envName = fileParts.LastOrDefault();

        //Build output directory as base output directory plus environment plus project (if supplied)                        
        var outDir = Path.Combine(OutputDirectory, envName);
        if (!String.IsNullOrEmpty(ProjectName))
            outDir = Path.Combine(outDir, ProjectName);

        //Build the output path
        var outFile = Path.Combine(outDir, TargetFile);

        //Make sure the directory exists
        if (!Directory.Exists(outDir))
            Directory.CreateDirectory(outDir);

        //Transform the config                
        transform.Destination = outFile;
        transform.Source = SourceFile;
        transform.Transform = inputFile.ItemSpec;
        transform.Execute();
    };
};

The actual transformation is triggered by calling the same code that VS itself uses.  That functionality resides in a web publishing assembly shipped with VS.  Note: If you use TFS Build with a project using this .targets file then either VS needs to be installed on the build agent or the assembly needs to be copied to a location that MSBuild will use.

Unto itself this is trivial except for a “bug” in MSBuild.  MSBuild allows you to specify assembly references for inline tasks and it will honor the path during compilation.  Unfortunately when the task is then run the assembly will not be found because the runtime path is not being configured.  I reported this bug at Connect but whatever engineer was assigned to the ticket completely missed the point of the bug and marked the bug as by design.  Connect can be a nightmare to use because of the lackluster support that MS sometimes gives it.  Rather than fighting that battle I simply worked around the issue by loading the assembly manually.  Note the use of dynamic to work around the compiler issues.

Using the .targets file

That completes the .targets file.  Now all we need to do is put it someplace the build can find it and then add an import into the project file.  The standard location for shared .targets files is MSBuildExtensions32Path which is created when .NET is installed and contains most of the standard .targets file.  To keep things simple I’ll just reference the .targets file that is in the solution directly.  For real solutions the .targets file should be stored in the standard location.

Now just add an import into the project file (I prefer at the bottom).

<Import Project="$(SolutionDir)Targets\P3Net.targets"/>

Rebuild and in the output directory should be the Configs directory with the transformed files in subdirectories.  To verify the validation is occurring you can add a bad transform in one of the transform files and you should get a compilation warning.

Next Time

We now have the ability to generate environmental transforms during a VS build for any type of project.  We’ve solved the problem we started with but we can go further.  The current solution, while slick, is a little much to do for every new project.  Next time we’ll wrap this up in a deployable package and add a template to automate the setup of this stuff so we can add template and run on any new projects.