Thursday 22 April 2010

MS Build, deployment of websites

I had a bit of trouble with this, finally coming accross this blog: blog.m.jedynak.pl

Two things to watch out for with _CopyWebApplication

1. You need to resolve the references by making an explicit call to "ResolveReferences"
2. You need to specify both the output / virtual directory path *and* the web project output path, if you don't (and as noted in the linked blog post) the references will only get copied one level deep!

However, once it's working, is well worth the effort and of course invaluable for that continuous integration!!!

<MSBuild 
Projects ="%(WebProject.path)"   
Properties ="OutDir=$(DestFolder)\%(WebProject.Identity)\BIN\;
WebProjectOutputDir=$(DestFolder)\%(WebProject.Identity)\"
Targets="ResolveReferences; 
        _CopyWebApplication" 
/>




Update: We have has some issues with this approach, principle is what appears to be a bug within the ResolveReferences target.

We always build a solution containing the web services to deploy and we do this first.

However (and I am not sure exactly what triggers this) the corecompile target can be called if the framework thinks an assembly is "out of date" and needs re-compliling. As I say this shouldn't happen for us given we build the solution up-front, but sometimes it does.

When this does happen (on a project by project basis) and if it happens where a project has either a direct or indirect reference to another project AND that other project has a binary/file ref to a MS assembly - in this example System.Web.Services then we hit some trouble.

The corecompile will call the compiler directly passing in a list of commands including all the dependancies, but in this case System.Web.Services is a second level dependancy (it's not directly referenced by the assembly being built, but is by a child assembly) and it doesn't get added causing an exception.

I am still not sure if there's a fix for this, but this problem was in context of some old vs2005 projects and I have a feeling this wouldn't be an issue with 2008 and 2010.

Anyway for now we are using a Folder.Copy target (given that we manage GAC dependancies and we pre build the solution) and this is fine for us, for now.

Thursday 8 April 2010

Testing biztalk maps where the xsl calls out to deployed components

It's quite common to write an xsl which obtains values from or makes use of existing .net lib functions

Example

<xsl:stylesheet version="1.0"
 xmlns:bjg="http://ns.com/myext"
                
    exclude-result-prefixes="bjg">

    <xsl:template match="/">

        <SomeOutput>
            <xsl:value-of select="bjg:SomeMethod()"/>
        </SomeOutput>
        
    </xsl:template>
</xsl:stylesheet>

Where the extension(s) are defined in a "mapper extension" xml file (pointed from the btm map file)

<Extensionobjects>

    <ExtensionObject
       Namespace="http://ns.com/myext"
       AssemblyName="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=123456abc123a123"
       ClassName="MyAssembly.MyClass" />

</ExtensionObjects>

However it's less straight forward to test the xsl outside of Biztalk.

The xslt-compiled-transform requires information relating to these assemblies along with the test xml instance.

The orginal map extensions file can be utilised to do this with the following code, which loads an xsl argument list to be applied to the xsl-compiled transform (possibly to be run from a test)

XmlDocument xmld = new XmlDocument();
            xmld.Load(mapperExtensionsFilePath);

            XmlNodeList ns = xmld.SelectNodes("/ExtensionObjects/ExtensionObject");

            // load each extension
            foreach (XmlNode n in ns)
            {
                // get attributes
                string assemblyName = n.SelectSingleNode("@AssemblyName").InnerText;
                string theNamespace = n.SelectSingleNode("@Namespace").InnerText;
                string className = n.SelectSingleNode("@ClassName").InnerText;

                // find type
                foreach (Type t in Assembly.Load(assemblyName).GetTypes())
                {
                    if (t.FullName.Equals(className) )
                    {
                        xslArgs.AddExtensionObject(theNamespace, Activator.CreateInstance(t));
                        break; 
                    }
                }
            } // get next extension