4

We are building an app for few customers, each has its own requirements along with the similar ones. We also want to keep all the code in the same app, not branch it, and the IFs is not good choice since it will be all over places.

I plan having the base classes for all. Then each customer will have its own class in which the override methods will do special logic.

How can we load the assemblies when compiling instead of doing this

public class BaseClass {
    public string getEventId()
}

public class ClassForJohn:BaseClass {
    [override]
    public string getEventId()
}

public class ClassForAdam:BaseClass {
    [override]
    public string getEventId()
}

void UglyBranchingLogicSomewhere() {
   BaseClass  eventOject;
   if("John"==ConfigurationManager.AppSettings["CustomerName"]){
        eventOject = new ClassForJohn();


   }else if("Adam"==ConfigurationManager.AppSettings["CustomerName"]){
        eventOject = new ClassForAdam();


   }else{
        eventOject = new BaseClass ();

   }
  eventId = eventOject.getEventId();
}
mycoffee
  • 41
  • 1
  • 1
  • 4
  • 2
    MEF seems like a good choice here. http://msdn.microsoft.com/en-us/library/dd460648.aspx – spender Feb 28 '13 at 18:29
  • @spender But not available pre-.NET 4, and I'm sure there are "less enterprisey" or "less this way" alternatives. –  Feb 28 '13 at 18:30
  • Have you considered using dependency injection? You could configure dependencies based on the given customer at application startup. – jrummell Feb 28 '13 at 18:31
  • @jrummell But which DI provider; and/or, have an example? DI and dynamically loading are two different things - at its core this question could be answered with Factory + Dynamic Loading. So, back to the start: how to Dynamically Load a Type from a satellite Assembly? –  Feb 28 '13 at 18:31
  • For how to load assembly/type and create instance: http://stackoverflow.com/questions/1803540/load-assembly-at-runtime-and-create-class-instance and http://stackoverflow.com/questions/1137781/c-sharp-correct-way-to-load-assembly-find-class-and-call-run-method - I've alluded to this being the "minimum required", although DI and composition frameworks *can* help overall design. I'd definitely recommend DI, at the least, but see these questions for direct answers. –  Feb 28 '13 at 18:37
  • Thank you you all. There are lot of good advice. – mycoffee Feb 28 '13 at 19:25

6 Answers6

6

This is how I load plug-ins (add-ins) into one of my projects:

const string PluginTypeName = "MyCompany.MyProject.Contracts.IMyPlugin";

/// <summary>Loads all plugins from a DLL file.</summary>
/// <param name="fileName">The filename of a DLL, e.g. "C:\Prog\MyApp\MyPlugIn.dll"</param>
/// <returns>A list of plugin objects.</returns>
/// <remarks>One DLL can contain several types which implement `IMyPlugin`.</remarks>
public List<IMyPlugin> LoadPluginsFromFile(string fileName)
{
    Assembly asm;
    IMyPlugin plugin;
    List<IMyPlugin> plugins;
    Type tInterface;

    plugins = new List<IMyPlugin>();
    asm = Assembly.LoadFrom(fileName);
    foreach (Type t in asm.GetExportedTypes()) {
        tInterface = t.GetInterface(PluginTypeName);
        if (tInterface != null && (t.Attributes & TypeAttributes.Abstract) !=
            TypeAttributes.Abstract) {

            plugin = (IMyPlugin)Activator.CreateInstance(t);
            plugins.Add(plugin);
        }
    }
    return plugins;
}

I assume that each plug-in implements IMyPlugin. You can define this interface any way you want. If you loop through all DLLs contained in a plug-ins folder and call this method, you can automatically load all the available plug-ins.

Usually you would have at least three assemblies: One containing the interface definition, the main assembly referencing this interface assembly and at least one assembly implementing (and of course referencing) this interface.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 2
    Having used MEF and StructureMap, I am very comfortable with loading assemblies and extensions through reflection on my own. It is simply too easy. I'm not recommending MEF to anyone unless it is a huge issue to RYO assembly management framework. – IAbstract Feb 28 '13 at 19:39
  • In this scenario, what filename contains? A single .dll file? A folder name? – Marco Vasapollo Nov 16 '17 at 16:08
  • The full path of a DLL, e.g. `"C:\Program Files\MyApp\MyPlugIn.dll"`. – Olivier Jacot-Descombes Nov 16 '17 at 16:39
4

Does each customer get their own exe and config file, and there is a shared dll? Or is there a shared exe, and each customer has their own dll?

You can put the full type name in the configuration like this:

Shared.exe.config:

<appSettings>
  <add key="CustomerType" value="NamespaceForJohn.ClassForJohn, AssemblyForJohn"/>
</appSettings>

And put AssemblyForJohn.dll in the same folder as your Shared.exe.

Then you can load it dynamically in code like this:

Shared.exe:

var typeString = ConfigurationManager.AppSettings["CustomerType"];
var parts = typeString.Split(',');
var typeName = parts[0];
var assemblyName = parts[1];
var instance = (BaseClass)Activator.CreateInstance(assemblyName, typeName).Unwrap();
Steven Padfield
  • 644
  • 1
  • 5
  • 12
3

maybe this example will be helpful

public MyInterface GetNewType() { 
       Type type = Type.GetType( "MyClass", true ); 
       object newInstance = Activator.CreateInstance( type ); 
       return newInstance as MyInterface; 
    } 
Damir
  • 31
  • 2
1

Here's one way to handle it with DI using Unity.

IUnityContainer container = new UnityContainer();
string customerNamespace = ConfigurationManager.AppSettings["CustomerNamespace"];
container.RegisterType(typeof(ISomeInterface), 
                       Type.GetType(customerNamespace+".SomeImplementation"));


// ...

ISomeInterface instance = conainer.Resolve<ISomeInterface>();

Where each customer has their own implementation of ISomeInterface in a customer specific namespace.

jrummell
  • 42,637
  • 17
  • 112
  • 171
1

You can create an instance of an external type from an assembly this way:

object obj = Activator.CreateInstance( 
    "External.Assembly.Name", "External.Assembly.Name.TypeName");
BaseClass b = (BaseClass) obj;
b.getEventId();

You'd store the name of the assembly and type in your configuration file or some other appropriate place.

John Källén
  • 7,551
  • 31
  • 64
  • We are looking to implement the same type of situation where we have "standard" code contained in one assembly but then customize it for some customers by creating a customer-specific assembly containing a subclasses. The subclass will implement an interface from the standard assembly. We plan to store the assembly and type information in the database so support personnel can configure it for each customer. Then at run-time we can load the correct assembly (if specified in the database) and instantiate the correct subtype. – Mark Wagoner Dec 02 '14 at 15:36
0

I would use Unity, but as a Simple Factory.

Unity Framework: How to Instantiate two classes from the same Interface?

You could store your

I am using Unity.2.1.505.2 (just in case that makes a difference).

  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
  </configSections>


      <unity>
        <container>
          <register type="IVehicle" mapTo="Car" name="myCarKey" />
          <register type="IVehicle" mapTo="Truck" name="myTruckKey" />
        </container>
      </unity>

Here is the DotNet code.

UnityContainer container = new UnityContainer();

UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
                section.Configure(container);

string myKey = "John";  /* read from config file */ /* with this example, the value should be "myCarKey" or "myTruckKey"  */

IVehicle v1 = container.Resolve<IVehicle>(myKey); 

See:

http://msdn.microsoft.com/en-us/library/ff664762(v=pandp.50).aspx

and

http://www.sharpfellows.com/post/Unity-IoC-Container-.aspx

Community
  • 1
  • 1
granadaCoder
  • 26,328
  • 10
  • 113
  • 146