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;
}
}
Related
I have a library project (SamplePlugin) that is meant to be used as an MEF module and loaded at runtime. Recently, I decided to support internationalization. To that end, I created Properties/Resource.resx and respective Properties/Resource.xx-yy.resx for each supported locale. I then added string resources with locale specific translations to each resource file. However at runtime, I cannot seem to get the culture-correct resource. Here is the relavent code:
Loading the module:
var di = new DirectoryInfo("Plugins/");
var dlls = di.GetFileSystemInfos("*.dll", SearchOption.AllDirectories);
var catalog = new AggregateCatalog();
foreach (var fi in dlls)
{
var ac = new AssemblyCatalog(Assembly.LoadFile(fi.FullName));
catalog.Catalogs.Add(ac);
}
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
Method inside of MEF module (SamplePlugin):
string GetFrenchString()
{
var resourceManager = new ResourceManager(
"SamplePlugin.Properties.Resources",
System.Reflection.Assembly.GetExecutingAssembly());
var culture = new System.Globalization.CultureInfo("fr-FR");
return (string)resourceManager.GetObject("SampleText", culture);
}
File structure of the compiled application:
app.exe
Plugins/SamplePlugin/SamplePlugin.dll
Plugins/SamplePlugin/zh-CN/SamplePlugin.resources.dll
Plugins/SamplePlugin/de-DE/SamplePlugin.resources.dll
Plugins/SamplePlugin/fr-FR/SamplePlugin.resources.dll
...
When I run GetFrenchString() I get "Sample Text" (from resource.resx) instead of "exemple de texte" (from resource.fr-FR.resx). I actually found similar question but the poster answered his own question and provided very little detail in both his question and answer.
Additional details:
The access modifiers of all my resource files are "public"
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).
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
I'm working on a project i SharePoint 2010 where I have several under sites. each under site contain a list with news and I want to attach an Event Receiver to those lists.
The under sites and lists are created programmatically but I cannot attach the Event Receiver I have in my VS2010 Solution.
I've tried with this:
SPList list = new SPSite(siteURL).OpenWeb().Lists[listName];
SPEventReceiverDefinitionCollection eventReceivers = list.EventReceivers;
SPEventReceiverDefinition eventReceiver = eventReceivers.Add();
eventReceiver.Name = receiverName;
eventReceiver.Synchronization = SPEventReceiverSynchronization.Synchronous;
eventReceiver.Type = SPEventReceiverType.ItemAdded;
eventReceiver.SequenceNumber = sequenceNumber;
eventReceiver.Assembly = assemblyFullName;
eventReceiver.Class = assemblyClassName;
eventReceiver.Data = receiverData;
eventReceiver.Update();
But it does not work.
The error message is "Could not load file or assembly 'Projekt_Test1\, \, Version\=1.0.1777.23493\, Culture\=neutral\, PublicKeyToken\=49c7547d535382ab' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)"
Thanks for help.
I end up creating a List Extension method for this:
public static void EnsureEventReceiver(this SPList list,IEnumerable<SPEventReceiverType> receiverTypes, Type eventHander, SPEventReceiverSynchronization synchronization, int sequenceNumber)
{
foreach (SPEventReceiverType spEventReceiverType in receiverTypes)
{
string name = list.Title + spEventReceiverType.ToString();
if (list.EventReceivers.Cast<SPEventReceiverDefinition>().All(i => i.Name != name))
{
SPEventReceiverDefinition eventReceiver = list.EventReceivers.Add();
eventReceiver.Name = name;
eventReceiver.Type = spEventReceiverType;
eventReceiver.Assembly = eventHander.Assembly.FullName;
eventReceiver.Class = eventHander.FullName;
eventReceiver.SequenceNumber = sequenceNumber;
eventReceiver.Synchronization = synchronization;
eventReceiver.Update();
}
}
}
Caveats, Limitations of this method:
Only one event per List, since this is good enough for me, if you need more then you need to pass the name as a parameter
Event handler methods are in the same class
You can use it like this:
list.EnsureEventReceiver(
new[] { SPEventReceiverType.ItemAdded, SPEventReceiverType.ItemUpdated },
typeof(NewsItemsHandler),
SPEventReceiverSynchronization.Synchronous,
10000);
I have never succeeded with this version of eventReceivers.Add() you are using.
Here is a powershell framgent I'm using, it would be very similar in C#
$ev = $currentList.EventReceivers.Add([Microsoft.SharePoint.SPEventReceiverType]::ItemAdded, $assemblyName, $className);
Couple of things to look at:
Your assembly version is listed as 1.0.1777.23493. That looks like it is being auto-incremented. You will want to set a fixed assembly version or it will update with every build, breaking your code.
You're setting eventReceiver.Synchronization = SPEventReceiverSynchronization.Synchronous, but the ItemAdded is an asynchronous event.
Make sure that your event receiver assembly has been deployed to the GAC on the SharePoint server, and that you have recycled the SharePoint application pools in IIS before you run your code.
My projects are set up like this:
Project "Definition"
Project "Implementation"
Project "Consumer"
Project "Consumer" references both "Definition" and "Implementation", but does not statically reference any types in "Implementation".
When the application starts, Project "Consumer" calls a static method in "Definition", which needs to find types in "Implementation"
Is there a way I can force any referenced assembly to be loaded into the App Domain without knowing the path or name, and preferably without having to use a full-fledged IOC framework?
This seemed to do the trick:
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
var loadedPaths = loadedAssemblies.Select(a => a.Location).ToArray();
var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();
toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));
As Jon noted, the ideal solution would need to recurse into the dependencies for each of the loaded assemblies, but in my specific scenario I don't have to worry about it.
Update: The Managed Extensibility Framework (System.ComponentModel) included in .NET 4 has much better facilities for accomplishing things like this.
You can use Assembly.GetReferencedAssemblies to get an AssemblyName[], and then call Assembly.Load(AssemblyName) on each of them. You'll need to recurse, of course - but preferably keeping track of assemblies you've already loaded :)
just wanted to share a recursive example. I'm calling the LoadReferencedAssembly method in my startup routine like this:
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
this.LoadReferencedAssembly(assembly);
}
This is the recursive method:
private void LoadReferencedAssembly(Assembly assembly)
{
foreach (AssemblyName name in assembly.GetReferencedAssemblies())
{
if (!AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == name.FullName))
{
this.LoadReferencedAssembly(Assembly.Load(name));
}
}
}
If you use Fody.Costura, or any other assembly merging solution, the accepted answer will not work.
The following loads the Referenced Assemblies of any currently loaded Assembly. Recursion is left to you.
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
loadedAssemblies
.SelectMany(x => x.GetReferencedAssemblies())
.Distinct()
.Where(y => loadedAssemblies.Any((a) => a.FullName == y.FullName) == false)
.ToList()
.ForEach(x => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(x)));
Seeing as I had to load an assembly + dependencies from a specific path today I wrote this class to do it.
public static class AssemblyLoader
{
private static readonly ConcurrentDictionary<string, bool> AssemblyDirectories = new ConcurrentDictionary<string, bool>();
static AssemblyLoader()
{
AssemblyDirectories[GetExecutingAssemblyDirectory()] = true;
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
}
public static Assembly LoadWithDependencies(string assemblyPath)
{
AssemblyDirectories[Path.GetDirectoryName(assemblyPath)] = true;
return Assembly.LoadFile(assemblyPath);
}
private static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
string dependentAssemblyName = args.Name.Split(',')[0] + ".dll";
List<string> directoriesToScan = AssemblyDirectories.Keys.ToList();
foreach (string directoryToScan in directoriesToScan)
{
string dependentAssemblyPath = Path.Combine(directoryToScan, dependentAssemblyName);
if (File.Exists(dependentAssemblyPath))
return LoadWithDependencies(dependentAssemblyPath);
}
return null;
}
private static string GetExecutingAssemblyDirectory()
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
For getting referenced assembly by name you can use following method:
public static Assembly GetAssemblyByName(string name)
{
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == name);
if (asm == null)
asm = AppDomain.CurrentDomain.Load(name);
return asm;
}
Yet another version (based on Daniel Schaffer answer) is the case when you might not need to load all Assemblies, but a predefined number of them:
var assembliesToLoad = { "MY_SLN.PROJECT_1", "MY_SLN.PROJECT_2" };
// First trying to get all in above list, however this might not
// load all of them, because CLR will exclude the ones
// which are not used in the code
List<Assembly> dataAssembliesNames =
AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => AssembliesToLoad.Any(a => assembly.GetName().Name == a))
.ToList();
var loadedPaths = dataAssembliesNames.Select(a => a.Location).ToArray();
var compareConfig = StringComparison.InvariantCultureIgnoreCase;
var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
.Where(f =>
{
// filtering the ones which are in above list
var lastIndexOf = f.LastIndexOf("\\", compareConfig);
var dllIndex = f.LastIndexOf(".dll", compareConfig);
if (-1 == lastIndexOf || -1 == dllIndex)
{
return false;
}
return AssembliesToLoad.Any(aName => aName ==
f.Substring(lastIndexOf + 1, dllIndex - lastIndexOf - 1));
});
var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();
toLoad.ForEach(path => dataAssembliesNames.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));
if (dataAssembliesNames.Count() != AssembliesToLoad.Length)
{
throw new Exception("Not all assemblies were loaded into the project!");
}
If you have assemblies where no code is referenced at compile time, those assemblies will not be included as a reference to your other assembly, even if you have added the project or nuget package as a reference. This is regardless of Debug or Release build settings, code optimization, etc. In these cases, you must explicitly call Assembly.LoadFrom(dllFileName) to get the assembly loaded.
In my winforms application I give JavaScript (in a WebView2 control) the possibility to call various .NET things, for example methods of Microsoft.VisualBasic.Interaction in the assembly Microsoft.VisualBasic.dll (such as InputBox() etc).
But my application as such does not use that assembly, so the assembly is never loaded.
So to force the assembly to load, I ended up simply adding this in my Form1_Load:
if (DateTime.Now < new DateTime(1000, 1, 1, 0, 0, 0)) { // never happens
Microsoft.VisualBasic.Interaction.Beep();
// you can add more things here
}
The compiler thinks that the assembly might be needed, but in reality this never happens of course.
Not a very sophisticated solution, but quick and dirty.
I created my own based on #Jon Skeet answer with name prefix filtering to avoid loading unnecessary assemblies:
public static IEnumerable<Assembly> GetProjectAssemblies(string prefixName)
{
var assemblies = new HashSet<Assembly>
{
Assembly.GetEntryAssembly()
};
for (int i = 0; i < assemblies.Count; i++)
{
var assembly = assemblies.ElementAt(i);
var referencedProjectAssemblies = assembly.GetReferencedAssemblies()
.Where(assemblyName => assemblyName.FullName.StartsWith(prefixName))
.Select(assemblyName => Assembly.Load(assemblyName));
assemblies.UnionWith(referencedProjectAssemblies);
}
return assemblies;
}