Friday, 16 July 2010

Patching files in the GAC, IIS, and the windowa\assembly\temp folder

We keep hitting the same odd problem on our dev server (a VM) when updating (patching) existing dll’s that are resident in the GAC…

The issue is that despite deleting the old file and then copying in the new file (typical patch), then re-starting the host, the change cannot be seen, or worse you get a “missing method” exception etc.

The cause of this appears to be associated with to the way Fusion works when an update is applied to a dll which is held by a long running process, say Biztalk or IIS…

Fusion appears to allow the old version of the GAC file to be removed (either because you’ve asked to remove it as a “step1” or because you simply dropping the new version in “over the top”) but to do so it must deal with the existing file, if it finds that something is still using the old version (a lock exists) it moves the old version to a “temp” folder, before moving the new version into the correct folder… It will try and clean up this temp folder later.


See here for more info



However this shouldn’t (in itself) cause us any problems, though it’s a good thing to be aware of…it’s supposed to work that way and it’s good.

Specifically our issues (and in the past we solved this sort of thing by “re-booting” the server) appear to be with the host which is using the file you’re trying to update, today on our dev server that has been IIS.

When you stop IIS it appears to leave some instances of w3wp (the worker host process, that runs the service/web site) running; using procexp (process explorer from sysinternals) you can see these host instances still have “file handles” pointed at the old version of the dll(s) in question, even though you can also see the new version(s) loaded.

Simply, the trick on our server has been to “iisreset /stop” twice… on the second time the w3wp processes die and the handles are released, when iis is started and a test made, the references to the old dll disappear and your code works.

I am guessing this could (and probably has for us previously) applied to Biztalk hosts. You can then clear the temp files yourself, or let fusion clear them out automatically later.

So in summary, you can “patch” files in the GAC but unless all old processes are unloaded (as you might expect) your old file will still be used, and worse a “hard” copy will exist somewhere on disk!

Temp folders:
c:\windows\assembly\tmp\
c:\windows\assembly\temp\
C:\WINDOWS\microsoft.net\Framework\v2.0.50727\Temporary ASP.NET Files\

** Note: The GAC files in question are strong named, but the new versions are differentiated by FILE VERSION ONLY, not assembly version. So the full name / qualified name of the dll is the same! **

Tuesday, 1 June 2010

TFS Branching

A good resource here

and in the "ranger" TFS branching guide here

SAK reference in VS projects

And here's why

Visual Studio project GUIDS

Some care needs to be taken when a large number of people are contributing to a solution... When a VS project is created a new (unique of course) GUID is assigned to the project, when the project is part of a solution the GUID is used to reference the project by VS.

Here comes the trouble... If VS sees inconsistency, i.e. it spots a project GUID within a solution which is cannot resolve
1. Because another project exists with the same guid (in which case VS will generate a new guid for one of these projects)
2. Because another user has the same project or the same solution but either the project or the project reference in the solution has a different GUID to the one you have and they checked-in! In this case (with automatic check-out configured) VS will correct the mistake and the user may not have been aware (or not bothered to check) when they checked-in their changes.

Either way great care is required, we've seen this when people create a new project by copying-and-pasting an existing project.

If it's not noticed, or not acted upon, it can cause a nasty circle of check-in, check-out, change, check-in amongst a large group or between teams. This causes a great deal of confusion with a lot of developers not familiar with the details.

The way to fix this is to make sure all projects within a solution have unique guid, and that a project referenced in the solution has the correct guid (note because the project can be changed by VS, you have to look carefully in history to get the correct guid, which other people may still be referencing).

This situation applies to scenarios where lot's of "project references" exist in the solution and where the solution is stored in source control.

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