Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get assembly reference without reflection or a known type

In my current framework's design, I peruse all types of specific assemblies and do work based on the types found. The assemblies it searches through are determined by the application bootstrapper/initializer. The assemblies are compiled in, so they can be strongly referenced via: typeof(SomeTypeInTheAssembly).Assembly. This is nice because the bootstrapper code has a strong reference to a type in the assembly and there's no fumbling about with fully qualified names as inline strings which need to be manually kept up-to-date if the assembly qualified name changes. But I didn't like referencing some completely unrelated type in the assembly and being dependant on that type being there. (what if it moves to another assembly? What if we deprecate/delete the type? Change its namespace?) In the code, it looks a bit weird too:

FrameworkAssemblyReader.Read(typeof(SomeAssemblyNamespace.SubNamespace.GraphingCalculator).Assembly);

Now in my bootstrapping code, I have a direct dependency (although a pretty trivial dependency) on GraphingCalculator which has nothing to do with the bootstrapping stage (and as a GraphingCalculator, it certainly has nothing to do with obtaining assembly references). To avoid this, in each assembly I intend to use in this way, I added a class in their root:

namespace SomeAssemblyNamespace
{
    public static class AssemblyReference
    {
        public static System.Reflection.Assembly Get
        {
            get
            {
                return typeof(AssemblyReference).Assembly;
            }
        }
    }
}

Which isn't too bad because then my bootstrapping code looks like:

FrameworkAssemblyReader.Read(SomeAssembly.AssemblyReference.Get);

But now, I have about a dozen or so assemblies with this AssemblyReference class copy/pasted and I only expect it to continue to grow as applications are built ontop of the framework. So my question is, is there a nice way to avoid the AssemblyReference class duplication and still programmatically pass in assembly references to the base framework, yet avoid the fuzziness of pointing to some arbitrary/unrelated type in the target assembly? My googlings seem to tell me that there's no API or language feature that lets me strongly point to an assembly (like typeof for classes), but I'm hoping someone here has come up with a better solution than I have that avoids the class/code duplication. Also of note, the code is sometimes compiled against Silverlight so I know that can tend to limit some of the API/techniques. Thanks!

EDIT: Just to add another monkey wrench, based on answer by "the coon", I should mention that I don't always load the same assemblies. For example, I might have a "CarBuilding" assembly that's applicable for some applications but not all. It's up to the bootstrappers to tell me which ones they wish to use (along with any custom ones the developer is employing)

EDITx2: To answer Jon Skeet's question: Clients (or us internally) will create any projects/DLLs they wish to support their application. In turn, these projects will reference the internal base framework. Their projects may contain resources (3D files, images, text, localization, etc.) and plugin-esque classes (they implement scripts which we discover and instantiate/execute as needed during the lifetime of the application). Additionally, our system supplies optional modules (DLLs) which contain reusable plugins/content which clients (or us) can leverage when making specific applications. So you might see a project structure like:

Solution
    ->MyAppBootstrapper_Android
        ->MyAppGUI_Android
        ->MyAppFramework
        ->MyAppResources
        ->MyAppAdditionalResources_Android

    ->MyAppBootstrapper_Silverlight
        ->MyAppGUI_Silverlight
        ->MyAppFramework
        ->MyAppResources
        ->MyAppAdditionalResources_Silverlight
        ->BaseFrameworkGraphingModule

The bootstrapper projects wire up dependencies and provide which projects/dlls (assemblies) should be provided to the base framework for discovery. Note that in this case, "MyApp" shares its own mini-framework and shared resources across both Android and Silverlight builds, but both have their own references independent from one another. The Silverlight one takes advantage of the BaseFrameworkGraphingModule and they have their own resources specific to the platform.

So in the bootstrappers in the projects look something like (albeit simplified):

namespace MyAppBootstrapper_Android
{
    public class Bootstrapper
    {
        public void Setup()
        {
            FrameworkAssemblyReader.Read(MyAppGUI_Android.AssemblyReference.Get);
            FrameworkAssemblyReader.Read(MyAppFramework.AssemblyReference.Get);
            FrameworkAssemblyReader.Read(MyAppResources.AssemblyReference.Get);
            FrameworkAssemblyReader.Read(MyAppAdditionalResources_Android.AssemblyReference.Get);
        }
    }
}

namespace MyAppBootstrapper_Silverlight
{
    public class Bootstrapper
    {
        public void Setup()
        {
            FrameworkAssemblyReader.Read(MyAppGUI_Silverlight.AssemblyReference.Get);
            FrameworkAssemblyReader.Read(MyAppFramework.AssemblyReference.Get);
            FrameworkAssemblyReader.Read(MyAppResources.AssemblyReference.Get);
            FrameworkAssemblyReader.Read(MyAppAdditionalResources_Android.AssemblyReference.Get);
            FrameworkAssemblyReader.Read(BaseFrameworkGraphingModule.AssemblyReference.Get);
        }
    }
}

So for each application built on top of the base framework, a bootstrapper is created that indicates which assemblies to include (and some other irrelevant wiring work). To answer the question specifically, the project has compile-time references to each assembly needed (this pulls double duty as it ensures that all DLLs are packaged together for Android/Silverlight deployment) as they are not dynamically downloaded to the user's smartphone or Silverlight app but packaged in the initial download. Your question as to how the Bootstrappers know which assemblies to use, well the developer knows and places necessary FrameworkAssemblyReader.Read calls. I hope this helps! Thanks for taking the time to look at it. I have a feeling I'm making too much out of nothing (or missing a far better solution/design altogether).

Finally, I neglected (stupidly) to mention that we also compile against Mono for Android, and in the near future, WPF. WinRT is a likely addition in the future but I'll be happy to cross that bridge when we come to it. Generally though, since each is compiled in their own way with their own platform features, I don't mind terribly if different platforms have a different way of pulling in the assembly references; though it would be nice if there was a unified syntax between the platforms.

EDITx3: Yeah, another one. I mentioned, but didn't clarify with an example that not all projects need or should be read by the base framework. For example, utility or business logic projects that have code that isn't relevant to the base framework but relevant to the client application. So from the above example:

Solution
    ->MyAppBootstrapper_Android
        ->MyAppGUI_Android
        ->MyAppFramework
        ->MyAppResources
        ->MyAppAdditionalResources_Android
        ->MyCompanySharedUtilities

    ->MyAppBootstrapper_Silverlight
        ->MyAppGUI_Silverlight
        ->MyAppFramework
        ->MyAppResources
        ->MyAppAdditionalResources_Silverlight
        ->BaseFrameworkGraphingModule
        ->MyCompanySharedUtilities

MyCompanySharedUtilities could be leveraged by the MyAppGUI_Silverlight, and MyAppFramework but the developer might not run it through FrameworkAssemblyReader.Read because it only contains, say, proprietary implementations of some math, or business rules.

like image 618
Chris Sinclair Avatar asked Jun 26 '12 18:06

Chris Sinclair


1 Answers

I'm not exactly sure how you expect your clients to use this code, so this suggestion may be irrelevant, but wouldn't it make more sense to do away with the Setup function (at least, hide it from the client) and use some kind of configuration file?

<ReferencedAssemblies>
    <ReferencedAssembly key="UnchangingKey" qualifiedName="SomeAssemblyName, version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef" />
</ReferencedAssemblies>

Load the assemblies from the config file. Using a key, you don't have to update the application if the fully qualified name changes, just update the config file (and leave the key as-is).

This is, basically, the approach Visual Studio uses with its project files. Here's an example from one of my VB projects:

<ItemGroup>
  <Reference Include="System.Core">
    <RequiredTargetFramework>3.5</RequiredTargetFramework>
  </Reference>
  <Reference Include="System.Drawing" />
  <Reference Include="System.Windows.Forms" />
  <Reference Include="System.Xml.Linq">
    <RequiredTargetFramework>3.5</RequiredTargetFramework>
  </Reference>
  <Reference Include="System.Data.DataSetExtensions">
    <RequiredTargetFramework>3.5</RequiredTargetFramework>
  </Reference>
  <Reference Include="UIAutomationProvider">
    <RequiredTargetFramework>3.0</RequiredTargetFramework>
  </Reference>
  <Reference Include="WindowsBase">
    <RequiredTargetFramework>3.0</RequiredTargetFramework>
  </Reference>
  <Reference Include="PresentationCore">
    <RequiredTargetFramework>3.0</RequiredTargetFramework>
  </Reference>
  <Reference Include="PresentationFramework">
    <RequiredTargetFramework>3.0</RequiredTargetFramework>
  </Reference>
  <Reference Include="System" />
  <Reference Include="System.Data" />
  <Reference Include="System.Xml" />
</ItemGroup>

EDIT:

Here's a thought... what if you used Reflection.Emit to generate an assembly from the XML config file? You could add a stub class to the generated assembly which your core project could use to create a hard reference to the generated assembly, and you could use Reflection to probe each reference assembly (in the XML file) to create a hard reference to any object in the referenced assemblies. You'd need to regenerate the assembly when one of the referenced assemblies changes, but it would require no code changes. You could even build the assembly generation code into your core project so that users would be able to update their projects as needed.

like image 179
JDB Avatar answered Sep 25 '22 18:09

JDB