0 Comments

I am currently working with a client that have an Azure API app that I would like to test for both performance and load. I was busy setting up Web performance and load testing using Visual Studio 2015 and Visual Studio Online, when I hit into strange assembly redirect issues. I eventually discovered the problem was due to the test runners inability to access the load tests app.config file. In this post I will explain how I got round this.

In order to setup the web performance test I first required to authenticate each test with the API Gateway which had Azure Active Directory Authentication in front of it. This meant I required to write a “request plug-in” for the web performance test that would get the “x-zumo-auth” token and append it to the request headers of any request going to the API. To do this I required a couple of packages from NuGet, which added the appropriate settings to my app.config. On running the tests I was hitting what looked like a classic dll file not found exception, however the binding redirects were correct, and so was “copy always” which means the dlls were getting copied to the correct test results output directory, but the test was still looking for an old version of a given dll.

I eventually discovered the app.config, that was included in the web and load test project, was not being used by the test runner. It turns out Visual Studio and VSO delegate the running of these test to “QTAgent_40.exe” which is located in the program files directory which in turn points to the test directory (not your bin folder) to pick up the dll containing the tests. All this means that when you do ConfigurationManager.AppSettings[configKey]; you are really reading the contents of QTAgent_40.exe app config, not your own. I came across this article from MS DevOps engineer Donovan Brown, which pointed me in the direction of my final solution below.

To solve the issue I read the assembly bindings in from my app config by parsing the xml file (no managed classes are available to access this area of the app.config) then I subscribed an event to listen out for any dlls that were not automatically being resolved, I then added a reference to my new static class and method at the top of my request plug in before calling the AD Authentication libraries. If the event fired I would go through the preloaded settings from my app.config and point the test to the correct version of the dll. Code below.

Cheers

Bryan


using System;
using System.Reflection;
using System.IO;
using System.Xml;

public static class AssemblyBinder
{
    private static XmlNodeList assemblyBindingFromAppContext;
    private static XmlNamespaceManager docNamepace;

    public static void Resolve()
    {
        LoadConfig();

        AppDomain.CurrentDomain.AssemblyResolve += delegate (object sender, ResolveEventArgs e)
        {
            var requestedName = new AssemblyName(e.Name);
    
            foreach (XmlNode assembly in assemblyBindingFromAppContext)
            {
                var selectSingleNode = assembly.SelectSingleNode("./bindings:assemblyIdentity/@name", docNamepace);
                if (selectSingleNode != null)
                {
                    var filename = selectSingleNode.Value;

                    if (requestedName.Name == filename)
                    {
                        // might want to add version number etc checks on the requestedName
                        try
                        {
                            return Assembly.LoadFrom($"{filename}.dll");
                        }
                        catch (Exception ex)
                        {                               
                            throw new FileNotFoundException($"Could not find {filename}. The error is {ex.Message}");
                        }
                    }
                }
            }
            return null;
        };
    }

    private static void LoadConfig()
    {
       var docname = Path.Combine(Environment.CurrentDirectory, $"{Assembly.GetExecutingAssembly().ManifestModule.Name}.config");

       var xmlDoc = new XmlDocument();
            xmlDoc.Load(docname);

        docNamepace = new XmlNamespaceManager(xmlDoc.NameTable);
        docNamepace.AddNamespace("bindings", "urn:schemas-microsoft-com:asm.v1");

        if (xmlDoc.DocumentElement != null)
        {
            assemblyBindingFromAppContext = xmlDoc.DocumentElement.SelectNodes("//bindings:dependentAssembly", docNamepace);
        }
    }
}