Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Resolving an Unreferenced Partial Type Name using Fusion

0.00/5 (No votes)
16 Sep 2020 1  
Resolve unreferenced type in .NET app using partial type name from GAC
In this article, you will learn how to resolve an unreferenced type in a .NET application using a partial type name from the GAC.

Background

I was recently tasked with writing a data extraction/reporting framework that would give client applications the ability to define a stored procedure to execute and have the results sent via email to interested parties. What made this project interesting was the requirement to allow a user to specify parameter values from a set of lookup tables, web service data and application derived data. Without any external references, the requirement to retrieve web service or application derived data required a mechanism to execute methods in an unreferenced assembly.

This article outlines my approach in allowing the framework code to execute methods in an unreferenced assembly via partial type-name resolution.

The Problem

The pre-population of lookup table data is very straight forward – select the Id and Value fields from a table – and was achieved through a simple ParameterLookup table. But when it got time to pre-populate the lookups with web service data or application specific data, we needed some mechanism to execute a method in the client application. Without having any references to the client application, we needed a mechanism to resolve and execute client application code. The approach we implemented was to include a LookupClass field in the ParameterLookup table and have the framework code resolve the type and execute the search method.

There are a number of methods in common use to allow .NET code to resolve and execute a method on an unreferenced type, although not all of them work for all environments. When the host process is a COM+ process, the Type.GetType() call can be used with a partially qualified type name (i.e., MyCompany.MyApplication.Search.Lookup.SomeLookup, MyCompany.MyApplication). But when the host process is IIS, either the fully qualified type name must be specified (Type Name, Assembly Name, Version, Culture, Public Key) or the assembly must be present in the /bin directory of the web package.

Not wanting to force client applications to update the ParameterLookup table each time a new release/build of the application was performed, we needed a mechanism to utilise only the Type and Assembly name parts.

Solution

Resolving types defined in an unreferenced assembly can be a right PITA if you don’t have the fully qualified type name (type name, assembly type, version, culture and public key).

For example, the fully qualified name for the type Microsoft.Build.BuildEngine.Engine type is Microsoft.Build.BuildEngine.Engine, Microsoft.Build.Engine, Culture=neutral, Version=4.0.0.0, PublicKeyToken=b03f5f7f11d50a3a (what a mouthful!). For relatively stable components, it could be possible to specify the fully qualified type name in a configuration table/file, but what do you do for components that are less stable (say, a component from a client application) where the version number changes for every build?

Type.GetType()

The Type.GetType() function can be used to load an assembly that is either referenced in the project or is in the assembly search path.

Type t = Type.GetType("MyCompany.MyApplication.Lookup.SomeLookup, MyCompany.MyApplication "); 

Since the assembly will not be referenced by the framework code, our only hope is that the assembly is in the assembly search path. Although this scenario is possible, it certainly is not guaranteed.

This solution must be discarded.

What about if we provide the fully qualified type name to the Type.GetType() function?

Type t = Type.GetType("MyCompany.MyApplication.Lookup.SomeLookup, 
  MyCompany.MyApplication, Culture=neutral, Version=1.0.0.3245, PublicKey= 1e9a8d893e3afa78");

This call certainly works, and a Type reference is successfully returned. All that is required is for this fully qualified type to be passed to the framework code - a relatively straightforward exercise. Alas, when the assembly is recompiled, a new version of the component is created with a different build number (assuming you are defaulting the build number), the call returns the original assembly; not the updated assembly. This is not ideal.

This solution must be discarded.

What I need is a method of resolving a type without having to specify the version number or relying on the assembly being in the assembly search path.

Assembly.LoadFrom()

The Assembly.LoadFrom() method looks promising as I can use this function to load an assembly regardless of its location and traverse through each of the types defined in that assembly.

internal static Type GetTypeFromAssembly(string assemblyName, string typeName)
{
    Assembly assembly = Assembly.LoadFrom(gacPath);
    Type[] types = assembly.GetTypes();
 
    foreach (Type type in types)
    {
        if (type.FullName == typeName))
            return type;
    }
    return null;
}

The condition if(type.FullName == typeName) is what gives us the ability of not having to specify the version, culture or public key properties of the fully qualified type. So now we can load our assembly and extract the appropriate type for use.

But this leads to a dependency between the code and the installation implementation. If a support operator decides to modify the application installation parameters, the software could be installed into an unknown location. We could, conceivably, demand that the software be installed into a particular location (Program Files for example), but this leads to 32bit/64bit issues (does Program Files mean “Program Files” or “Program Files (x86)”?).

But I think I’m almost there.

Fusion

If the assembly that holds the type we are after has been installed into the Global Assembly Cache, we can take advantage of the CLR assembly management system to load and find our assembly for us.

Using the Fusion.dll assembly (see this link for details), we can create a reference to the appropriate GAC (32bit or 64bit) and use the Assembly.LoadFrom() method to safely load our assembly.

[DllImport("fusion.dll")]
internal static extern int CreateAssemblyCache(out IAssemblyCache ppAsmCache, int reserved);
private static string GetAssemblyPath(string name)
{
    if (name == null) 
        throw new ArgumentNullException("name");
 
    string finalName = name;
    AssemblyInfo aInfo = new AssemblyInfo();
    aInfo.cchBuf = 1024; // should be fine...
    aInfo.currentAssemblyPath = new String('\0', aInfo.cchBuf);
 
    IAssemblyCache ac;
    int hr = CreateAssemblyCache(out ac, 0);
    if (hr >= 0)
    {
        hr = ac.QueryAssemblyInfo(0, finalName, ref aInfo);
        if (hr < 0)
            return null;
    }
 
    return aInfo.currentAssemblyPath;
}
public static Type ResolveType(string assemblyName, string typeName)
{
    string gacPath = GetAssemblyPath(assemblyName);
    return GetTypeFromAssembly(assemblyName, typeName);
}

Using just the name of the Type and the Assembly, the code will search the GAC directories appropriate to the Environment of the application (x86 / x64) and return a reference to the Type. With a reference to the correct type, a call to Activator.CreateInstance() will create a usable type to use.

Using the Code

The framework defines an interface that the type-to-be-resolved must implement ...

namespace FrameworkApp
{
    public interface ILookup
    {
        string Search();
    }
}

... whilst the class implements the interface.

public class SearchType1 : ILookup
{
    public string Search()
    {
        return string.Format("SearchType1.Search() from version {0}", 
                              Assembly.GetExecutingAssembly().GetName().Version);
    }
}

Now, we simply have to make a call to TypeResolver.ResolveType(), create an instance of the returned type (coerced into the interface of choice) and finally execute the method required.

Type type = TypeResolver.ResolveType("ClientApp.SearchType1,ClientApp");
ILookup lookup = Activator.CreateInstance(type) as ILookup;
lookup.Search();

Limitations

  • Assemblies must be registered in the GAC for this solution to work
  • Will not load incompatible .NET version assemblies
  • Will resolve to the highest version, not the most recently compiled version

Acknowledgements

History

  • 23rd August, 2013: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here