C# - Loading dlls and creating instances - c#

I have a situation and I need to know how to deal with it in the best way.
I have an application (MVC3) and I have couple of integrations for it. I have an interface "IntegrationInterface" and every integration implements it.
I want to load the dlls of the integrations, create a list of them, and run a loop that runs a method for every integration in the list.
For example - let's say I have integrations for facebook, myspace and twitter (for my appliction), and every time the user post a message in my application I want to post a message on his\her facebook, myspace and twitter.
I don't want that the code will know which integrations I have, so if tomorrow I'll create a new integration for google+, I'll just need to add a new DLL without changing the code of my application.
How can I do that?

First, you'll have to find all relevant dlls and classes:
loadedIntegrations.Clear();
if (!Directory.Exists(path))
return;
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] files = di.GetFiles("*.dll");
foreach (var file in files)
{
Assembly newAssembly = Assembly.LoadFile(file.FullName);
Type[] types = newAssembly.GetExportedTypes();
foreach (var type in types)
{
//If Type is a class and implements the IntegrationInterface interface
if (type.IsClass && (type.GetInterface(typeof(IntegrationInterface).FullName) != null))
loadedIntegrations.Add(type);
}
}
loadedIntegrations is of type List<Type>. Then you can instantiate each integration and call its methods:
foreach(var integrationType in loadedIntegrations)
{
var ctor = integrationType.GetConstructor(new Type[] { });
var integration = ctor.Invoke(new object[] { }) as IntegrationInterface;
//call methods on integration
}

I am doing something similar to what you described in an import utility that wrote. My issue was that I didn't want to load ALL the assemblies. I only wanted to load assemblies that contained types that were requested.
To accomplish this I've used the AppDomain.CurrentDomain.AssemblyResolve event handler.
This event handler is raised just before the AppDomain throw an exception notifying that an assembly is not found. I execute similar code to what Nico suggested in that handler and return the requested assembly.
NOTE: I have a sub-directory called 'Tasks' (think Import Tasks) where I store all of my assemblies I want to load at runtime.
Here is the code:
var tasks = GetTasks();
var basedir = AppDomain.CurrentDomain.BaseDirectory; // Get AppDomain Path
var tasksPath = Path.Combine(basedir, "Tasks"); // append 'Tasks' subdir
// NOTE: Cannot be factored, relies on 'tasksPath' variable (above).
AppDomain.CurrentDomain.AssemblyResolve += (s, e) => // defined 'AssemblyResolve' handler
{
var assemblyname = e.Name + ".dll"; // append DLL to assembly prefix
// *expected* assembly path
var assemblyPath = Path.Combine(tasksPath, assemblyname); // create full path to assembly
if (File.Exists(assemblyPath)) return Assembly.LoadFile(assemblyPath); // Load Assembly as file.
return null; // return Null if assembly was not found. (throws Exception)
};
foreach (var task in tasks.OrderBy(q => q.ExecutionOrder)) // enumerate Tasks by ExecutionOrder
{
Type importTaskType = Type.GetType(task.TaskType); // load task Type (may cause AssemblyResolve event to fire)
if (importTaskType == null)
{
log.Warning("Task Assembly not found");
continue;
}
ConstructorInfo ctorInfo = importTaskType.GetConstructor(Type.EmptyTypes); // get constructor info
IImportTask taskInstance = (IImportTask)ctorInfo.Invoke(new object[0]); // invoke constructor and cast as IImportTask
taskInstances.Add(taskInstance);
}
// rest of import logic omitted...

If u know MEF (Managed extensibility framework) this might help you I personally use it but have to say that using MEF with MVC is not trivial i think for more information please visit
http://msdn.microsoft.com/en-us/library/dd460648.aspx

Related

Caliburn Micro - View & viewmodel in separate DLL

I've been trying this for a while and i have some issues. I have a project which dynamically loads 1 or more DLLs and I can't get the view binding to work.
I've overridden the SelectAssemblies method as such:
protected override IEnumerable<Assembly> SelectAssemblies()
{
string[] AppFolders = Directory.GetDirectories(Config.AppsFolder);
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(Assembly.GetExecutingAssembly());
foreach (string f in AppFolders)
{
Assembly ass = Directory.GetFiles(f, "*.dll", SearchOption.AllDirectories).Select(Assembly.LoadFrom).SingleOrDefault();
if (ass != null)
{
assemblies.Add(ass);
}
}
Apps = assemblies;
return assemblies;
}
This works as intended, i then have a method which runs on a button click which does:
public void OpenApp(string appName)
{
//AppName should be the same as the dll.
string assName = string.Format("TabletApp.{0}", appName);
Assembly ass = AppBootstrapper.Apps.SingleOrDefault(x => x.GetAssemblyName() == assName);
if (ass != null)
{
dynamic vm = ass.CreateInstance(string.Format("TabletApp.{0}.ViewModels.{0}ViewModel", appName));
IoC.Get<IWindowManager>().ShowDialog(vm);
}
}
This finds the viewmodel fine, however i get the error "unable to find contract for 'ExampleView'" when i load ExampleViewModel. I've also had to add [Export(typeof(view)] for each view in the base assembly since I've made this changes. It appears that Caliburn micro has stopped initialising views automatically.
Anyone know what i've done wrong?
So it turns out i was doing nothing wrong, Along the way I've updated my caliburn.micro to 3.0.2. As it turns out a small change they made became a major breaking update. I wont go into it fully here other than to point out its the GetInstance in the bootstrapper that needs to be changed.
protected override object GetInstance(Type service, string key)
{
// Skip trying to instantiate views since MEF will throw an exception
if (typeof(UIElement).IsAssignableFrom(service))
return null;
var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(service) : key;
var exports = container.GetExportedValues<object>(contract);
if (exports.Any())
return exports.First();
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
Please review the following link for more detailed information.
https://github.com/Caliburn-Micro/Caliburn.Micro/pull/339

Reflection Reading DLL and adding that to a list of type Interface

I have a List of type ITemp. ITemp is a interface.
List<ITemp> temp = new List<ITemp>();
Now what I have to do is to read all the dlls from Specific file Location and add that into this temp list
so what I am doing is this :
List<Assembly> allAssemblies = new List<Assembly>();
string path = Path.GetDirectoryName("C:\\TemplatesDLL\\");
foreach (string dll in Directory.GetFiles(path, "*.dll"))
{
allAssemblies.Add(Assembly.LoadFile(dll));
}
foreach (Assembly assembly in allAssemblies)
{
var DLL = Assembly.LoadFile(assembly.Location.ToString());
foreach (var t in DLL.GetTypes())
{
Activator.CreateInstance(t);
var constructors = t.GetConstructors();
temp.Add(t.GetConstructors()); // here I have to add all the dll that implements ITemp interfaces
}
}
adding the interface something like this if its in the same project
temp.Add(new TestTemp());
here TestTemp is a C# file in the same project. Now I move this TestTemp in to a DLL file. And Read it from the same application and add into the list.
Thanks in Advance.
I got it.
List<Assembly> allAssemblies = new List<Assembly>();
string path = Path.GetDirectoryName("C:\\TemplatesDLL\\");
List<ITemp> temp = new List<ITemp>();
foreach (string dll in Directory.GetFiles(path, "*.dll"))
{
allAssemblies.Add(Assembly.LoadFile(dll));
}
foreach (Assembly assembly in allAssemblies)
{
var DLL = Assembly.LoadFile(assembly.Location.ToString());
Type[] types = DLL.GetTypes();
if (typeof(ITemp).IsAssignableFrom(types[0]))
{
temp.Add(Activator.CreateInstance(types[0]) as ITemp); //adding all the instance into the list that is what I was looking for.
}
}
I actually needed to do this recently. This example only gets the first type, but you could easily modify it to return a list and iterate the list.
var dll = Assembly.LoadFile(#"C:\SimplePlugin.dll");
Type pluginType = dll.GetExportedTypes()
.Where(t => typeof(SimplePluginContracts.ISomePlugin).IsAssignableFrom(t)
&& !t.IsInterface).First();
SimplePluginContracts.ISomePlugin plugin =
(SimplePluginContracts.ISomePlugin)Activator.CreateInstance(pluginType);
This question covers variations on this, and the top answer has a good discussion of nuances of this technique that my simple example doesn't handle:
Getting all types that implement an interface
You might consider also filtering out non-public classes, in case your DLL has some classes not intended to be loaded which also implement the interface.

Load interface from remote assembly

I am currently developing client-server application.
It should load interfaces from modules and show them inside of its own window.
But sometimes I need to plugin remote module.
Can I run form from module (with all actions working) without loading module file on disk?
Thank you.
Yes, you can load an assembly sent from a remote computer (I will not discuss here security implications of this, I'd - at least - check for a signature):
var data = new WebClient.DownloadData(url); // For example...
var assembly = Assembly.Load(data);
In C++/CLI (it's not clear in your question what's language you're using):
array<Byte>^ data = (gcnew WebClient())->DownloadData(url);
Assembly^ assembly = Assembly::Load(data);
Now you have assembly and you can load something from it, for example (just for illustration):
var plugins = assembly.GetExportedTypes()
.Where(x => typeof(IYourContract).IsAssignableFrom(x) && !x.IsAbstract)
.Select(x => (IYourContract)Activator.CreateInstance(x));
Please note that this is a very naive implementation because each instance will be different (if you load same plugin multiple times) and it's also expansive in terms of resources (primary memory). You should keep an assembly cache:
private static Dictionary<string, Assembly> _cachedAssemblies =
new Dictionary<string, Assembly>();
public static Assembly LoadRemoteAssembly(string url)
{
lock (_cachedAssemblies)
{
if (_cachedAssemblies.ContainsKey(url))
return _cachedAssemblies[url];
var data = new WebClient.DownloadData(url); // For example...
var assembly = Assembly.Load(data);
_cachedAssemblies.Add(url, assembly);
return assembly;
}
}

Unloading AppDomain

Is there a way to unload parent AppDomain?
I am trying to load a different version of an assembly in my new AppDomain, but it keeps loading the version from the parent domain. When I am loading the assembly in the new AppDomain I am showing the correct path.
Or maybe there is another way I can do that?
Thanks in advance.
EDIT
AppDomain MailChimpDomain = AppDomain.CreateDomain("MailChimpDomain");
string path = AppDomain.CurrentDomain.BaseDirectory + "ServiceStack_V3\\ServiceStack.Text.dll";
MailChimpDomain.Load(AssemblyName.GetAssemblyName(path));
EDIT2
Code 2:
var MailDom = AppDomain.CreateDomain("MailChimpDomain");
MailDom.AssemblyLoad += MailDom_AssemblyLoad;
MailDom.AssemblyResolve += new ResolveEventHandler(MailDom_AssemblyResolve);
MailDom.DoCallBack(() =>
{
string name = #"ServiceStack.Text.dll";
var assembly = AppDomain.CurrentDomain.Load(name);
string name2 = #"MailChimp.dll";
var assembly2 = AppDomain.CurrentDomain.Load(name2);
//mailChimp object with API key found in mailChimp profile
MailChimp.MailChimpManager mc = new MailChimp.MailChimpManager("111111111111222f984b9b1288ddf6f0-us1");
//After this line there are both versions of ServiceStack.Text Assembly
MailChimp.Helper.EmailParameter em = new MailChimp.Helper.EmailParameter();
em.Email = strEmailTo;
//Creating email parameters
string CampaignName = "Digest for " + strEmailTo + " " + DateTime.Now.ToShortDateString();
MailChimp.Campaigns.CampaignCreateOptions opt = new MailChimp.Campaigns.CampaignCreateOptions();
opt.ListId = "l338dh";
opt.Subject = strSubject;
opt.FromEmail = strEmailFrom;
opt.FromName = strNameFrom;
opt.Title = CampaignName;
//creating email content
MailChimp.Campaigns.CampaignCreateContent content = new MailChimp.Campaigns.CampaignCreateContent();
content.HTML = strEmailContent;
//Creating new email and sending it
MailChimp.Campaigns.CampaignFilter par = null;
MailChimp.Campaigns.CampaignSegmentOptions SegOpt = null;
MailChimp.Campaigns.CampaignTypeOptions typeOpt = null;
mc.CreateCampaign("regular", opt, content, SegOpt, typeOpt);
MailChimp.Campaigns.CampaignListResult camp2 = mc.GetCampaigns(par, 0, 5, "create_time", "DESC");
foreach (var item in camp2.Data)
{
if (item.Title == CampaignName)
{
mc.SendCampaign(item.Id);
break;
}
}
});
static Assembly MailDom_AssemblyResolve(object sender, ResolveEventArgs args)
{
byte[] rawAssembly = File.ReadAllBytes(Path.Combine(path, args.Name));
return Assembly.Load(rawAssembly);
}
What your code actually does is it loads assembly to your parent domain. If you want to load assembly into child domain you have to do it from inside child domain. This is kind of chicken-egg problem, because the parent assembly (which loads child assembly into child domain) has to be loaded into your child domain as well in order to be executed.
Assuming simple example that you have console application and assembly called MyAssembly.dll it can be done like this:
static void Main(string[] args) {
var domain = AppDomain.CreateDomain("MailChimpDomain");
domain.AssemblyResolve +=new ResolveEventHandler(domain_AssemblyResolve);
domain.DoCallBack(() => {
string path = #"MyAssembly.dll";
var assembly = AppDomain.CurrentDomain.Load(path);
// to do something with the assembly
var type = assembly.GetType("MailChimp.MailChimpManager");
var ctor = type.GetConstructor(new[] { typeof(string) });
var mc = ctor.Invoke(new object[] { "111111111111222f984b9b1288ddf6f0" });
});
}
static Assembly domain_AssemblyResolve(object sender, ResolveEventArgs args) {
byte[] rawAssembly = File.ReadAllBytes(Path.Combine(#"c:\MyAssemblyPath", args.Name));
return Assembly.Load(rawAssembly);
}
In this case child domain has same root directory for resolving assemblies as parent domain (and thus it can execute the code which loads "MyAssembly.dll").
If the code working with reflection is longer than this, you may consider using bootstrapper instead.
I.E. you create new library called MyBootstrapper.dll, you'll reference directly version of ServiceStack.Text.dll and MailChimp.dll you like from MyBootstrapper.dll, and you'll create bootstrap class - lets call it Bootstrapper that will be static and will have single public static method called Run that will do dirty work.
Then inside DoCallBack() method you'll call this bootstrapper instead.
string path = #"MyBootstrapper.dll";
var assembly = AppDomain.CurrentDomain.Load(path);
// to do something with the assembly
var type = assembly.GetType("MyBootstrapper.Bootstrapper");
var method = type.GetMethod("Run", BindingFlags.Static);
method.Invoke(null, null);
// or if the Run method has one parameter of "string" type
var method = type.GetMethod("Run", BindingFlags.Static, Type.DefaultBinder, new[] { typeof(string) }, null);
method.Invoke(null, new object[] { "Parameter to run" });
No, you may not unload the default AppDomain or any assemblies loaded in the default AppDomain.
What you can do, however, is load both versions of the ServiceStack assembly in two child domains. You should be able to unload either of them. However, using types from these domains may prove more difficult than usual. You'll have to do it via remoting.
Given the overhead imposed by this, you should consider using only one version of that assembly (even if this means adapting part of your app, the one that runs in the default domain).

How do i get a list of classes in .NET

I tried Assembly.ReflectionOnlyLoadFrom(#"path\System.Core.dll") and ReflectionOnlyLoad but i got exceptions and errors. How do i properly get all the namespaces/classes in an assembly?
For example i got this exception.
Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
To load an assembly and then get a list of all types:
Assembly assembly = Assembly.ReflectionOnlyLoadFrom("System.Core.dll");
Type[] types = assembly.GetTypes();
Unfortunately this will throw an exception if any of the types exposed cannot be loaded, and sometimes this load failure cannot be avoided. In this case however the thrown exception contains a list of all types that were successfully loaded and so we can just do this:
Assembly assembly = Assembly.ReflectionOnlyLoadFrom("System.Core.dll");
Type[] types;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types;
}
This will give you a list of all types, including interfaces, structs, enums etc... (If you want just the classes then you can filter that list).
If you can reference System.Core then
List<string> namespaces = new List<string>();
var refs = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
foreach (var rf in refs) {
if (rf.Name == "System.Core")
{
var ass = Assembly.Load(rf);
foreach (var tp in ass.GetTypes())
{
if (!namespaces.Contains(tp.Namespace))
{
namespaces.Add(tp.Namespace);
Console.WriteLine(tp.Namespace);
}
}
}
}
If you cannot, you will need to attach to the AssemblyResolve event of the CurrentDomain and load all assemblies of types that System.Core.dll uses when loading the dll.
Here is your answer to your question.
I do not need to copy & paste it here for you, it might be greener to save space rather that copying code from the other thread. :-)

Categories

Resources