Thursday, September 09, 2010

Last week on StackOverflow I answered a question, Make web.config transformations working locally and in a response to my answer the question asker asked me if I would be able to a question he posed earlier Advanced tasks using web.config transformation. Evidently he is really interested in config transformations! I don’t blame him, I’m really into them as well.

In his question he asks (summarizing) can we replace portions of attribute values instead of this entire attribute? So for instance you have the following in your web.config. Below is two sets of appSettings one from Dev and the other from Prod (taken from the original question).

<!-- DEV ENTRY -->
<appSettings>
 <add key="serviceName1_WebsService_Url" value="http://wsServiceName1.dev.domain.com/v1.2.3.4/entryPoint.asmx" />
 <add key="serviceName2_WebsService_Url" value="http://ma1-lab.lab1.domain.com/v1.2.3.4/entryPoint.asmx" />
</appSettings>

<!-- PROD ENTRY -->
<appSettings>
 <add key="serviceName1_WebsService_Url" value="http://wsServiceName1.prod.domain.com/v1.2.3.4/entryPoint.asmx" />
 <add key="serviceName2_WebsService_Url" value="http://ws.ServiceName2.domain.com/v1.2.3.4/entryPoint.asmx" />
</appSettings>

In the above we just want to replace dev with prod and ma1-lab.lab1.domain with ws.ServiceName2.domain. For those wondering currently we have the following transformations out of the box.

  • Replace – Replaces the entire element
  • Remove – Removes the entire element
  • RemoveAll – Removes all matching elements
  • Insert – Inserts an element
  • SetAttributes – Sets the value of the specified attributes
  • RemoveAttributes – Removes attributes
  • InsertAfter – Inserts an element after another
  • InsertBefore – Inserts an element before another

At the end of this article I’ve linked to another blog which has more info about these transformations. So it sounds like SetAttributes is almost what we want, but not quite what there. A little known fact is that you can create your own config transformations and use those. In fact all of the out of the box transformations follow the same patterns that custom transformations would. To solve this issue we need to create our own config transformation, AttributeRegexReplace. This transformation will take an attribute value and do a regular expression replace on its value. In order to create a new transformation you first reference the Microsoft.Web.Publishing.Tasks.dll which can be found in the %Program Files (x86)%MSBuild\Microsoft\VisualStudio\v10.0\Web folder. If you are working with a team it is best if you copy that assembly, place it in a shared folder in source control, and make the reference from that location. After you create the reference to that assembly you will need to create a class which extends the Transform class. The class diagram for this abstract class is shown below.

image

The only thing that you will need to implement is the Apply method. You don’t even need to fully understand all of the properties and methods just the portions that you are interested in. Here we will not cover all the details of this class, or other related classes which exist, but there will be future posts which will shed more light on this area.

In the sample class library that I created, I called the project CustomTransformType. Inside of that project I created the class AttributeRegexReplace. The entire contents of that class are shown below, we will go over the details after that.

namespace CustomTransformType
{
    using System;
    using System.Text.RegularExpressions;
    using System.Xml;
    using Microsoft.Web.Publishing.Tasks;

    public class AttributeRegexReplace : Transform
    {
        private string pattern;
        private string replacement;
        private string attributeName;

        protected string AttributeName
        {
            get
            {
                if (this.attributeName == null)
                {
                    this.attributeName = this.GetArgumentValue("Attribute");
                }
                return this.attributeName;
            }
        }
        protected string Pattern
        {
            get
            {
                if (this.pattern == null)
                {
                    this.pattern = this.GetArgumentValue("Pattern");
                }

                return pattern;
            }
        }

        protected string Replacement
        {
            get
            {
                if (this.replacement == null)
                {
                    this.replacement = this.GetArgumentValue("Replacement");
                }

                return replacement;
            }
        }

        protected string GetArgumentValue(string name)
        {
            // this extracts a value from the arguments provided
            if (string.IsNullOrWhiteSpace(name)) 
            { throw new ArgumentNullException("name"); }

            string result = null;
            if (this.Arguments != null && this.Arguments.Count > 0)
            {
                foreach (string arg in this.Arguments)
                {
                    if (!string.IsNullOrWhiteSpace(arg))
                    {
                        string trimmedArg = arg.Trim();
                        if (trimmedArg.ToUpperInvariant().StartsWith(name.ToUpperInvariant()))
                        {
                            int start = arg.IndexOf('\'');
                            int last = arg.LastIndexOf('\'');
                            if (start <= 0 || last <= 0 || last <= 0)
                            {
                                throw new ArgumentException("Expected two ['] characters");
                            }

                            string value = trimmedArg.Substring(start, last - start);
                            if (value != null)
                            {
                                // remove any leading or trailing '
                                value = value.Trim().TrimStart('\'').TrimStart('\'');
                            }
                            result = value;
                        }
                    }
                }
            }
            return result;
        }

        protected override void Apply()
        {
            foreach (XmlAttribute att in this.TargetNode.Attributes)
            {
                if (string.Compare(att.Name, this.AttributeName, StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    // get current value, perform the Regex
                    att.Value = Regex.Replace(att.Value, this.Pattern, this.Replacement);
                }
            }
        }
    }
}

In this class we have 3 properties; Pattern, Replacement, and AttributeName. All of these values will be provided via an argument in the config transformation. For example take a look at the element below which contains a transform attribute may look like the following.

<add key="two" value="two-replaced" 
         xdt:Transform="AttributeRegexReplace(Attribute='value', Pattern='here',Replacement='REPLACED')" 
         xdt:Locator="Match(key)"/>

In this example I declare that I am using AttributeRegexReplace and then specify the values for the attributes within the (). In the class above I have a method, GetArgumentValue, which is used to parse values from that argument string. When your transform is invoked the string inside of () is passed in as the ArgumentString value. If you are using a , as the argument separator, as I am, then you can use the Arguments list. Which will split up the arguments by the , character. Surprisingly in the 101 lines of code in the sample there are only a few interesting lines. Those are what’s contained inside the Apply method. Inside that method I search the TargetNode’s attributes (TargetNode is the node which was matched in the xml file being transformed) for an attribute with the same name as the one specified in the AttributeName property. Once I find it I just make a call to Regex.Replace to get the new value, and assign it. Pretty simple! Now lets see how can we use this.

Let’s say you have the following very simple web.config

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="one" value="one"/>
    <add key="two" value="partial-replace-here-end"/>
    <add key="three" value="three here"/>
  </appSettings>
</configuration>

If we want to be able to use our own transform then we will have to use the xdt:Import element. You can place that element inside the xml document anywhere immediately under the root element. This element will allow us to utilize our own transform class. It only has 3 possible attributes.

  • namespace – This is the namespace which the transform is contained in
  • path – This is the full path to the assembly
  • assembly – This is the assembly name which contains the transform

You can only use one of the two; path and assembly. Basically it boils down to how the assembly is loaded. If you use path the assembly will be loaded with Assembly.LoadFrom and if you chose to use assembly passing in the AssemblyName, for instance if the assembly in in the GAC, then it will be loaded using Assembly.Load.

I chose to use path, because I just placed the file inside of the MSBuild Extensions directory (%Program Files (x86)%MSBuild) in a folder named Custom. Then I created my config transform file to be the following.

<?xml version="1.0"?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
                        
  <xdt:Import path="C:\Program Files (x86)\MSBuild\Custom\CustomTransformType.dll"
              namespace="CustomTransformType" />

  <appSettings>
    <add key="one" value="one-replaced" xdt:Transform="Replace" xdt:Locator="Match(key)" />
    <add key="two" value="two-replaced" xdt:Transform="AttributeRegexReplace(Attribute='value', Pattern='here',Replacement='REPLACED')" xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

Then to run this I created an MSBuild file, PerformTransform.proj, which is shown below.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="TransformXml"
           AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

  <PropertyGroup>
    <TransformDest>web.tranzed.config</TransformDest>
  </PropertyGroup>
  
  <Target Name="Demo">
    <Delete Files="$(TransformDest)" />
    <TransformXml Source="web.config"
                  Transform="web.dev.config"
                  Destination="$(TransformDest)" />                  
  </Target>
  
</Project>

This file uses the TransformXml task as I outlined in a previous post Config transformations outside of web app builds. Once you execute the Demo target with the command msbuild PerformTransform.proj /t:Demo you will see the file web.tranzed.config with the following contents.

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="one" value="one-replaced"/>
    <add key="two" value="partial-replace-REPLACED-end"/>
    <add key="three" value="three here"/>
  </appSettings>
</configuration>

So you can see that the replacement did occur as we intended. Below you will find the download link for the samples as well as another blog entry for more info on the out of the box transformations.

Resources

Sayed Ibrahim Hashimi

Thursday, September 09, 2010 6:51:43 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Sunday, August 15, 2010

A while back I posted an entry on How to build a package including extra files or exclude files a reader posted a question to StackOverflow.com asking how to exclude files from the created package based on the configuration for the project. He asked me to take a look at it so I figured it would be a good blog post.

From the previous post we can see that the way to exclude files from packaging is by declaring an item as follows.

<ItemGroup>
  <ExcludeFromPackageFiles Include="Sample.Debug.xml">
    <FromTarget>Project</FromTarget>
  </ExcludeFromPackageFiles>
</ItemGroup>

So we need to extend this to only exclude files if the config is a certain value. Since MSBuild supports conditions on almost every element this is going to be a breeze. As an example I have created a sample web project with a scripts directory that has the following files.

image

In that folder there I there are two files which have ‘debug’ in the name of the file. We only want those to be included if the configuration is set to Debug, or another way of putting it is we want to exclude those files if the configuration is not Debug. So we need to create to add files to the ExcludeFromPackageFiles and guard it with the condition that the configuration is not debug. Here is that.

<Target Name="CustomExlucdeFiles" BeforeTargets="ExcludeFilesFromPackage">
  <ItemGroup Condition=" '$(Configuration)'!='Debug' ">
    <ExcludeFromPackageFiles Include="scripts\**\*debug*" />
  </ItemGroup>
  
  <Message Text="Configuration: $(Configuration)" />
  <Message Text="ExcludeFromPackageFiles: @(ExcludeFromPackageFiles)" Importance="high" />
</Target>

You can see the item group defined above which does what we want. Please note that I put this inside of a target, CustomExcludeFiles, I will discuss why in a bit but let’s stay on topic now. So this is pretty straight forward when the item group is evaluated all files under scripts which have debug in the file name will be excluded if the configuration is not set to Debug. Let’s see if it works, I will build the deployment package once in both debug & release then examine the contents of the Package folder.

image

So we can see that the files were excluded from the Release package. Now back to why I declared the item group in a target instead of directly in the project file itself. I noticed that if I declare that item in the project file there are some visual issues with the representation in the Solution Explorer. To be specific the files show up as dups, see image below.

image

I have reported this to the right people, but for now this is a harmless issue with an easy workaround.

Sayed Ibrahim Hashimi

Sunday, August 15, 2010 7:56:50 PM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Wednesday, August 04, 2010

Recently there was a question on an internal mailing list asking the question can you tell the difference between

<ItemGroup>
  <Content Include="Sample-DefinedEmpty.sdf">
    <SubPath></SubPath>
  </Content>
</ItemGroup>

and

<ItemGroup>
  <Content Include="Sample-NotDefined.sdf">
  </Content>
</ItemGroup>

You cannot detect this out of the box, but you can by creating a custom task. Better would be to create an inline task so that you don’t have to deal with the headache of maintaining a .dll for something like this, unless you are using it on many different project files. Basically the task that we will create will need two parameters; the Item itself (single value) and MetadataName (metadata name to check for). It will have one output parameter, MetadataDefined, which we can check to see if the metadata value was defined or not.

This is a pretty easy task to create because we just look at the MetadataNames property on the ITaskItem interface. The task as well as a sample target is shown below.

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  
  <UsingTask TaskFactory="CodeTaskFactory"
             TaskName="MetadataExists"
             AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <MetadataName Required="true"/>
      <Item ParameterType="Microsoft.Build.Framework.ITaskItem"/>
      <MetadataDefined ParameterType="System.Boolean" Output="true" />      
    </ParameterGroup>
    <Task>
      <Code>
        <![CDATA[
            this.MetadataDefined = false;
            if (this.Item != null)
            {
                foreach (string name in this.Item.MetadataNames)
                {
                    if (string.Compare(this.MetadataName, name, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        this.MetadataDefined = true;
                        break;
                    }
                }
            }          
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="CheckForMetadata">

<ItemGroup>
  <Content Include="Sample-DefinedEmpty.sdf">
    <SubPath></SubPath>
  </Content>
  <Content Include="Sample-NotDefined.sdf">
  </Content>
</ItemGroup>

    <Message Text="Starting - Content"/>

    <!-- Create an Item which has exactly 1 value to pass to the task -->
    <ItemGroup>
      <_Content Remove="@(_Content)"/>
      <_Content Include="@(Content)" Condition=" '%(Content.Identity)' == 'Sample-DefinedEmpty.sdf' "/>
    </ItemGroup>
    <MetadataExists MetadataName="SubPath" Item="@(_Content)">
      <Output PropertyName="existsResult" TaskParameter="MetadataDefined"/>
    </MetadataExists>

    <Message Text="existsResult: $(existsResult)" />


    <Message Text="Starting - Content2"/>
    <!-- Create an Item which has exactly 1 value to pass to the task -->
    <ItemGroup>
      <_Content Remove="@(_Content)"/>
      <_Content Include="@(Content)" Condition=" '%(Content.Identity)' == 'Sample-NotDefined.sdf' "/>
    </ItemGroup>
    <MetadataExists MetadataName="SubPath" Item="@(_Content)">
      <Output PropertyName="existsResult" TaskParameter="MetadataDefined"/>
    </MetadataExists>

    <Message Text="existsResult: $(existsResult)" />
  </Target>
</Project>

Here you can take a look at the MetadataExists task and its usage. The only thing that really needs to be pointed out here is that since this task accepts a single item value we will have to take the item group Content and pick from it a specific value which is passed to the task. That is what I am doing when I create the temp item _Content. If you execute the CheckForMetadata target with the command msbuild CheckMetadata01.proj /t:CheckForMetadata the result will be what is shown below.

CheckMetadata

So from the output you can see that we were able to tell the difference!

BTW, if you were wondering if you can do the same with properties, the answer is no.

Sayed Ibrahim Hashimi

Wednesday, August 04, 2010 7:17:20 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Saturday, July 31, 2010

A while back I talked about the new feature available in MSBuild 4 Inline Tasks in.

In the previous examples I have use C# as my language of choice. C# is not your only choice you can also use VB.Net but you can also use JavaScript! When MSBuild creates the class for the inline task it uses CodeDom to do so, and JavaScript is one of its supported languages. Perhaps its actually called JScript. In any case take a look at the project file below which shows this in action.

<Project ToolsVersion="4.0" DefaultTargets="PrintValues" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <UsingTask
    TaskName="Jsex01"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]"/>
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="javascript">
        <![CDATA[
          function printMessageFor(item : Microsoft.Build.Framework.ITaskItem) {
            Log.LogMessage(item.ItemSpec + "fullpath: " + item.GetMetadata("FullPath"));
          }
          
          for(var i = 0; i<Files.length; i++) {
            printMessageFor(Files[0]);
          }
          
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <ItemGroup>
    <Source Include="one.cs"/>
    <Source Include="two.cs"/>
    <Source Include="three.cs"/>
    <Source Include="four.cs"/>
  </ItemGroup>
  
  <Target Name="PrintValues">
    <Jsex01 Files="@(Source)" />
  </Target>

</Project>

Here you can see that I created a new inline task called, Jsex01 and its written in Javascript. Then inside of the PrintValues target this task is called. If you execute the PrintValues target the result will be what you see below.

image

So if you prefer JavaScript to C# or VB.Net then you should try this out!

Sayed Ibrahim Hashimi

Saturday, July 31, 2010 5:22:28 AM (GMT Daylight Time, UTC+01:00)  #    Comments [1]  | 
Friday, July 23, 2010

Do you have a great idea for the next version of Visual Studio and you want to get your voice heard? Now is your chance! Go to twitter and post a message stating your request with the hash tag #vswish and we will take a look at it. We are listening to that channel so please do submit your feedback.

Sayed Ibrahim Hashimi

Friday, July 23, 2010 8:24:45 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2]  | 
Thursday, June 10, 2010

I’m happy to say that I was offered, and accepted, a position on the Web Platform and Tools team with Microsoft. I will be a Program Manager on that team. In a nutshell I will be responsible for helping to make development easier for web developers using .NET and Visual Studio. Its a very interesting role and I’m gonna be pretty challenged in the next few months to adapt to my new role. The Web Platform and Tools team has a bunch of tools that they are responsible for, and one of the is the Web Deployment Tool. I think that this will be one of the areas that I will be working in, so if you have any ideas feel free to drop me a line here or privately at sayed –DOT—hashimi [AT] gmail –DOT—com. I will start in this role in about 2 weeks.

Sayed Ibrahim Hashimi

Thursday, June 10, 2010 4:50:30 AM (GMT Daylight Time, UTC+01:00)  #    Comments [2]  | 

Have you ever executed a .cmd file (or .bat) file and received a message that looks like the following at the top of your script (including image for compatibility) image ? This was really annoying to me until I find out what was happening. I noticed that Visual Studio (2010 at least, but I think previous version as well but not sure) was changing the encoding of my .cmd files to be UTF-8! For example consider this simple script (sample.cmd) that I created with Notepad.

echo 'hello world'

When I execute this .cmd file from the command line the results are as shown below.

image

What I did then was to simply create a copy of that file and placed that in a file named sample-vs.cmd and then edited the script using Visual Studio 2010 to have the contents below.

echo 'hello world from Visual Studio'

When I execute that .cmd file to my surprise the results shown below are displayed.

image

I was definitely not expecting that (OK yeah I was because I created that script for this blog post, but just go with it). I then opened the file in notepad and it looked normal. So I edited it it notpad, saved it and the result was still the same. The first line was not being processed correctly, it seemed. As I was editing the file I noticed the encoding of the files were different. The encoding for the sample.cmd file was set to ANSI and for sample-vs.cmd UTF-8. See for yourself from the screen shots below.

image 

image

So I switched the setting for the sample-vs.cmd file to ANSI, executed the script and all was good!

I’m not sure why Visual Studio is changing the encoding for these files, but it looks like a bug to me. I have logged a bug with Microsoft at https://connect.microsoft.com/VisualStudio/feedback/details/566322/vs-2010-changs-encoding-of-cmd-file-to-utf-8. The bug may not be visible yet, but hopefully it will become public so that you can vote on it if you have been bitten by this bug.

Sayed Ibrahim Hashimi

Thursday, June 10, 2010 4:24:01 AM (GMT Daylight Time, UTC+01:00)  #    Comments [4]  | 
Monday, June 07, 2010

If you are doing any kind of web development and you are not familiar with the Web Platform Installer(WPI) then you need to take a look at it. I just installed WordPress on IIS 7 with just a few clicks and  filled in a few text boxes. When you install WordPress there are some prerequisites like mySql and php. The WPI was smart enough to realize that I had neither installed, downloaded those, installed them and configured them. I was prompted for some info for those tools of course. I’ve also installed a few other apps using the WPI like, MSDeploy and dasBlog and I didn’t have any issues what so ever.

When using the WPI there are two main categories that can be installed, Web Platform and Web Applications. The Web Platform category includes items like frameworks (i.e. ASP.NET, PHP), Database (i.e. mySql) and other high level shared components. The Web Applications includes various web applications. Some others that I didn’t list previously include; DotNetNuke, nopCommerce, and umbarco just to name a few. I’m not sure how many apps are available but it looks like at least 50.

If you are an app creator and would like to share your app then you can visit the WPI Developer page for a starting point.

Monday, June 07, 2010 4:17:01 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Sunday, May 09, 2010

The past few days I was unable to Edit and Continue in Visual Studio 2010 Ultimate from my home machine, which is running Windows 7 64bit. I knew there were some issues with this on 64bit, I figured one of my projects was not targeting x86 so I just ignored it. Then today I was really getting annoyed at seeing the dialog.

edit-and-continue

So I started playing around with the configuration settings made sure that everything was in order and still no dice.

After that I performed a repair of Visual Studio, and then reset all the settings under Tools>Import and Export Settings and that still didn’t solve the problem. Then I remembered that I change my IntelliTrace settings the other day so I went to disable it and to my surprise I saw the dialog box shown below.

edit-and-continue2

When I changed the IntelliTrace setting to collect call information I didn’t notice the warning stating “Edit and continue is disabled when collecting Call Information”! So I changed the setting and everything was good. Microsoft should add this to the Edit and Continue dialog box not available, I’ll ping a few people I know about that. FYI I created this question on Stackoverflow before I resolved it myself.

Sayed Ibrahim Hashimi

Sunday, May 09, 2010 12:19:38 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 
Saturday, May 08, 2010

Recently I had the chance to work on a WPF app and it was behaving very strangely when I built and ran the application. For instance I tried to debug the application from Visual Studio and I received an error staging “Visual Studio cannot start debugging because the debug target ‘PATH HERE’ is missing. Please build the project and retry, or set the OutputPath and AssemblyName properties appropriately to point at the correct location for the target assembly.

vs-build-error-01

So I checked the properties, every thing looked good. I even built the project from the command line and it built fine. Anywayz at some point after that I was able to debug the app. But then I noticed that the UI was not getting updated with my changes. So I started digging a bit deeper. I noticed that when I ran a Clean from Visual Studio the output files for that particular project.

I then rebuilt the solution in Visual Studio. From the Output window I selected all the text,copied it, and then pasted that into an editor. After looking at the message

------ Skipped Clean: Project: R…ion: Debug Any CPU ------
Project not selected to build for this solution configuration
------ Skipped Clean: Project: R…Admin.Wpf, Configuration: Debug x86 ------
Project not selected to build for this solution configuration
------ Clean started: Project: Test…er, Configuration: Debug Any CPU ------
Build started 5/8/2010 1:10:02 AM.

I immediately knew that there was an incorrect value in the build configuration manager. You can find the configuration manager from the toolbar.

image

Here is what I found.

image

In this dialog there are two project set not to build for Debug builds; the DB project and the Wpf project. The DB project is OK to not build on Debug if there are a small % of DB changes versus code changes. If that is the case the devs can just build the DB project manually when the change it. In the case of the Wpf project, it should be set to build and the fact that it wasn’t was causing all the issues. So I checked the build check box, clicked the Close button and it was all good. You should also make sure that all other configuration as setup correctly. You don’t want to get bitten by this again.

Sayed Ibrahim Hashimi

Saturday, May 08, 2010 6:33:26 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0]  | 

Theme design by Jelle Druyts