Boris B. Boris B. - 1 month ago 20
C# Question

.Net 4 - Include custom information in assembly

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.

Answer

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;
            }
        }
    }
}