5

I'm building an extensible application which will load additional assemblies at runtime through Assembly.LoadFile(). Those additional assemblies will contain things like WPF resource dictionaries (skins, etc.), plain resources (Resx), and/or plugin classes. The assembly can also contain no public classes, only resources or resource dictionaries.

I'm looking for a way to identify an assembly, things like a friendly name (like "Additional skins" or "Integrated browser"), the functional type of an assembly (SkinsLibrary, SkinsLibrary|PluginLibrary, etc) and additional info (like ConflictsWith(new [] {"SkinsLibrary", "BrowserPlugin").

So far I'm using a convention in naming assemblies (*.Skins.*.dll, etc.). In each assembly I have an empty, dummy class which is just a placeholder for custom class attributes which hold the actual (assembly-wide) information, but that feels like a hack. Is there some streamlined, standard way to handle this?

I'm developing the central loader system and other developers in my team will develop those additional assemblies, so I'd like to minimize the conventions and plumbing details.

Pranay Rana
  • 175,020
  • 35
  • 237
  • 263
Boris B.
  • 4,933
  • 1
  • 28
  • 59
  • Resource files, if properyly named (name.lang.resx) should be loaded automatically for current language, if you have "default" resource defined. Framework does that for you. Why do you need to load resources manually? – Goran Obradovic Jun 15 '11 at 08:28
  • Because those resource files are compiled into an assembly and there are many of them. For example I have assemblies Skin1 and Skin2, and the config indicates that the app uses Skin1, so the app should not automatically load both of them. However the app must be aware of existence of Skin2 so that the user may select it. – Boris B. Jun 15 '11 at 08:33
  • Is it possible to have skins in form of implementation of some interface (ISkin?), so you write your own implementation in one dll, and add necessary resources to that dll? For every skin you would have one assembly, and all resources that are necessary for that assembly that are referenced from it load automatically. You would only need to worry that two skins does not use resources with same name (easy to prevent by adding skinname to naming convention). – Goran Obradovic Jun 15 '11 at 08:37
  • But that's just the point, I'd like to minimize work (and possible runtime errors) for other developers. Making them provide a dummy implementation for a simple resource-dictionary assembly and referencing some app-specific DI framework is what I'd like to avoid. – Boris B. Jun 15 '11 at 08:44
  • If you want to minimize errors, then you should use interfaces. Actually you should use interfaces as a part of specification (or as a specification :)) whenever you need someone to make some functionality. And visual studio can provide automatic implementation of interfaces with placeholders so it is also minimization of work. If you don't want to use any DI framework, you may want to look at http://stackoverflow.com/questions/26733/getting-all-types-that-implement-an-interface-with-c-3-5 – Goran Obradovic Jun 15 '11 at 08:56
  • I see no way to define an interface for a WPF assembly that contains a resource dictionary only. Interface defines an *operational contract*, i.e. what actions a certain class must provide. In my case there is no contract, at least not in an operational sense. If there was such a thing as a resource-interface or any resource-inheritance which would define mandatory resource names and their types as a compilation requirement, then I'd gladly use it, but plain interfaces are simply not designed for that. I'm looking for a way to properly identify an assembly without examining all of it's types. – Boris B. Jun 15 '11 at 09:19

4 Answers4

3

EDIT: I've updated the answer with some more detailed information.

Here is an example how you might accomplish what you want to do.
Start by defining a enum for your different types of plugin types.

public enum AssemblyPluginType
{
    Skins,
    Browser
}

Add two attributes that will be used to describe the plugins (assembly plugin type and potential conflicts).

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class AssemblyPluginAttribute : Attribute
{
    private readonly AssemblyPluginType _type;

    public AssemblyPluginType PluginType
    {
        get { return _type; }
    }

    public AssemblyPluginAttribute(AssemblyPluginType type)
    {
        _type = type;
    }
}

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class AssemblyPluginConflictAttribute : Attribute
{
    private readonly AssemblyPluginType[] _conflicts;

    public AssemblyPluginType[] Conflicts
    {
        get { return _conflicts; }
    } 

    public AssemblyPluginConflictAttribute(params AssemblyPluginType[] conflicts)
    {
        _conflicts = conflicts;
    }
}

Now you can add these attributes to your assembly.

The following two lines can be added anywhere in the assembly as long as they're outside a namespace. I usually put assembly attributes in the AssemblyInfo.cs file that can be found in the Properties folder.

[assembly: AssemblyPluginAttribute(AssemblyPluginType.Browser)]
[assembly: AssemblyPluginConflictAttribute(AssemblyPluginType.Skins, AssemblyPluginType.Browser)]

Now you can use the following code to examine an assembly for specific attributes:

using System;
using System.Reflection;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Get the assembly we're going to check for attributes.
            // You will want to load the assemblies you want to check at runtime.
            Assembly assembly = typeof(Program).Assembly;

            // Get all assembly plugin attributes that the assembly contains.
            object[] attributes = assembly.GetCustomAttributes(typeof(AssemblyPluginAttribute), false);
            if (attributes.Length == 1)
            {
                // Cast the attribute and get the assembly plugin type from it.
                var attribute = attributes[0] as AssemblyPluginAttribute;
                AssemblyPluginType pluginType = attribute.PluginType;
            }
        }
    }
}
Patrik Svensson
  • 13,536
  • 8
  • 56
  • 77
  • Thank you, could you please just specify where should I put those `[assembly:...]` attributes, and how can I query an assembly for those attributes? – Boris B. Jun 15 '11 at 12:07
  • @Boris: I've updated the answer with some more information of where to declare the assembly attributes, and how to query the information from an assembly. – Patrik Svensson Jun 16 '11 at 16:12
2

I am partially getting information but

you can add Custom AssemblyInfo Attributes which you can see by visiting link..

Pranay Rana
  • 175,020
  • 35
  • 237
  • 263
1

You can use the built-in AssemblyMetadataAttribute class; it is available since .NET 4.5.

drowa
  • 682
  • 5
  • 13
0

For plugins, I have great experience with MvcTurbine (it can be used with other projects, not only mvc). If you use it in combination with Ninject and define interface for plugin, ie:

IPlugin{
    string Name {get;}
    someResultType PerformAction(someArgType arg);

}

and, in your plugin dll you register implementation of IPlugin by implementing IServiceRegistrator interface from MvcTurbine, then if you place dll with plugin in bin directory, your plugin implementation will be added to list that is passed into constructor of some class that uses DI and receives List, or you can resolve it from IOC container by hand. It is a lot cleaner than loading your assemblies by hand and inspecting them for interfaces/implementations etc...

If you are interested in this, please ask if anything is unclear and I will elaborate.

Goran Obradovic
  • 8,951
  • 9
  • 50
  • 79