using System;
using System.IO;
using System.Reflection;
using System.Text;
using MyApp.Logging;
namespace MyApp.SmsService.Common
{
public class MyAppAppDomain:MarshalByRefObject
{
private readonly AppDomainSetup domaininfo;
private readonly AppDomain appDomain;
public static string libDllPath;
public MyAppAppDomain(string appDomainName) //Constructor
{
//Setup the App Domain Parameters
domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
domaininfo.DisallowBindingRedirects = false;
domaininfo.DisallowCodeDownload = true;
domaininfo.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
// Create the application domain.
appDomain = AppDomain.CreateDomain(appDomainName, null, domaininfo);
//Set the dll path using Static class ref.
//Dependency resolution handler
appDomain.AssemblyResolve += LoadFromLibFolder; /*Exception*/
}
private static Assembly LoadFromLibFolder(object sender, ResolveEventArgs args)
{
if (libDllPath != null)
{
string assemblyPath = Path.Combine(libDllPath, new AssemblyName(args.Name).Name + ".dll");
if (File.Exists(assemblyPath) == false)
{
return null;
}
Assembly assembly = Assembly.LoadFrom(assemblyPath);
//Assembly dependancy resolved.
return assembly;
}
return null;
}
public Object getNewInstanceOf(string fullyQualifiedTypeName)
{
return appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, fullyQualifiedTypeName);
}
public Type getTypeOf(string fullyQualifiedTypeName)
{
return getNewInstanceOf(fullyQualifiedTypeName).GetType();
}
public void unloadDomain()
{
AppDomain.Unload(appDomain);
}
}
}
The above class is a wrapper that I want to create to setup and teardown an application domain. However, in my webservice I am getting a FileNotFoundException [Failed to load a dll xyz.dll] whenever I am instantiating the object of MyAppAppDomain.
Following is the ex:
MyAppAppDomain.libDllPath = appDllLibPath; //Some directory other than bin.
pluginDomain = new MyAppAppDomain("SmsServicePlugins"); //Throws FileNotFoundException.
When I debug, I see that the line that caused the exception is the one marked above as /exception/, inside the constructor of MyAppAppDomain.
What is going wrong?
EDIT:
I was going through other articles and I read that objects cannot be visible across domains. This can only happen when the object can be serialized in both domains (use of MarshalByRefObject) and then can be accessed through proxies.
Would be of great help if someone can point out the issues in the above code. In the meanwhile I am attempting to learn more on Marshalling and Proxy-ing.
First problem is you are trying to doing everything inside app domain wrapper, it is really two different components. The first component you need is your object that you want to create inside your new app domain which inherits from MarshalByRefObject (or marked as Serializable). The other lives inside your current AppDomain and is used to create the new AppDomain.
Have a look at the msdn example to see what I mean.
However your error is probably more than likely that your path is wrong to your DLL. Could you show the full stack trace to show what line is throwing the exception?
Related
I need to run some code in a new app domain. So I am trying to create an instance of my object in the new app domain... Here te code I am using:
public static class Program
{
private static ITemplateEngineProvider _templateEngineProvider;
static Program()
{
AppDomain ad = AppDomain.CreateDomain("New domain");
ObjectHandle handle = ad.CreateInstance(
assemblyName: typeof(RazorTemplateEngineProvider).Assembly.FullName,
typeName: "RazorTemplateEngineProvider"
//ignoreCase: false,
//bindingAttr: BindingFlags.CreateInstance,
//binder: null,
//args: new object[] { new string[] { templatePath, layoutPath } },
//culture: CultureInfo.InvariantCulture,
//activationAttributes: null
);
_templateEngineProvider = (RazorTemplateEngineProvider)handle.Unwrap();
}
}
RazorTemplateEngineProvider is a custom public class that has a public constructor. It has been implemented in a class library (MyCustomLib.dll) I referenced inside the azure function. The class implements an interfaces defined in another class library (IMyCustomLib.dll) referenced only by the previous class library, not by azure function.
At the moment there is no code inside the RazorTemplateEngineProvider class:
public class RazorTemplateEngineProvider : MarshalByRefObject, ITemplateEngineProvider
{
public RazorTemplateEngineProvider()
{ }
}
When I try to do ad.CreateInstance a FileNotFoundException has been thrown:
Could not load file or assembly 'MyCustomLib.dll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...' or one of its dependencies. The system cannot find the file specified.
But the file exists and it should be already correctly loaded... Infact if I run this 'query'
IEnumerable<string> loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !a.FullName.Contains("Version=0.0.0.0") && File.Exists(a.Location) && !a.Location.Contains("CompiledRazorTemplates.Dynamic") && a.FullName.Contains("My"))
.Select(f => f.FullName)
.ToArray();
I see both my dll. So, why do I get that error?
Thank you
UPDATE
I think problem is azure because I copied and pasted my code in a console application and thereit works.
UPDATE
I am watching the fusionlong and it seem it is trying to load the assembly from a "wrong" path: file:///C:/Users/csimb/AppData/Local/Azure.Functions.Cli/1.0.12/MyCustomLib.dll.. I expected the path was the bin folder...
I am creating a website which should be able to take in multiple modules and compose those modules to a main project. After programming the bootstrapper and custom View engine to make it able to find Views in the module.dll.
After compiling a test module for testing, I am getting a weird error where it says it cannot load System.Web.DataVisualization for some reason. I have also noticed that the Controller from the Module dll gets loaded properly, I can see it in the debug but this error keeps killing a thread and throws the error.
This is the code I have for Bootstrapper.cs that handles the loading/composing of the dlls.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
public class Bootstrapper
{
private static CompositionContainer CompositionContainer;
private static bool IsLoaded = false;
public static void Compose(List<string> pluginFolders)
{
if (IsLoaded) return;
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")));
foreach (var plugin in pluginFolders)
{
var directoryCatalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules", plugin));
catalog.Catalogs.Add(directoryCatalog);
}
CompositionContainer = new CompositionContainer(catalog);
CompositionContainer.ComposeParts();
IsLoaded = true;
}
public static T GetInstance<T>(string contractName = null)
{
var type = default(T);
if (CompositionContainer == null) return type;
if (!string.IsNullOrWhiteSpace(contractName))
type = CompositionContainer.GetExportedValue<T>(contractName);
else
type = CompositionContainer.GetExportedValue<T>();
return type;
}
}
And this is the error that gets thrown out when testing.
The solution for this was to unfortunately manually download the dll and add it to the reference, but the main edit was to Web.Config where I had to define the assembly to import System.Web.DataVisualization as such. <add assembly="System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
I am using Ninject.Extensions.Conventions to add bindings dynamically. The .dll names to load are stored in configuration. If the configuration is incorrect and the .dll can not be loaded it would be good to know about that. Currently any failure to load a .dll is not bubbled up. For example, if I try to load a potato there is no error I can catch:
foreach (var customModule in customModuleConfigs)
{
KeyValuePair<string, KVP> module = customModule;
_kernel.Bind(scanner => scanner
.From(module.Value.Value)
.SelectAllClasses().InheritedFrom<ICamModule>()
.BindAllInterfaces());
// I need to know this failed
_kernel.Bind(scanner => scanner
.From("potato")
.SelectAllClasses().InheritedFrom<ICamModule>()
.BindAllInterfaces());
}
Is there a way to know I have a bad configuration? In the IntelliTrace window I see an exception thrown but caught before it bubbles up.
You'll need to do the loading of the Assembly yourself, then you can control whether there is an exception thrown. Use the
From(params Assembly[] assemblies) overload.
Load the assembly by either using Assembly.LoadFrom() or Assembly.Load.
You could create a wrapper around the AllInterfacesBindingGenerator class and use this to count the generated bindings:
public class CountingInterfaceBindingGenerator : IBindingGenerator
{
private readonly IBindingGenerator innerBindingGenerator;
public CountingInterfaceBindingGenerator()
{
this.innerBindingGenerator =
new AllInterfacesBindingGenerator(new BindableTypeSelector(), new SingleConfigurationBindingCreator());
}
public int Count { get; private set; }
public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
{
this.Count++;
return this.innerBindingGenerator.CreateBindings(type, bindingRoot);
}
}
Usage:
var kernel = new StandardKernel();
var bindingGenerator = new CountingInterfaceBindingGenerator();
kernel.Bind(b =>
{
b.From("potato")
.SelectAllClasses()
.InheritedFrom<ICamModule>()
.BindWith(bindingGenerator);
});
if (bindingGenerator.Count == 0)
// whatever
This is probably longer than your current code, but it would allow further customization of the bindings that have been created.
I'm having issues with CreateInstanceAndUnwrap at the moment for some reason (was working prior).
My process is this:
I dynamically generate some code and it loads DLL's from a subdirectory via MEF. These applications then load different pieces (on demand) from those DLL's. I had to update my code to now include an AppDomainSetup that contains the path of the calling assembly.
I create the new AppDomain correctly -- no issues. When I try to run this code:
object runtime = domain.CreateInstanceAndUnwrap(
typeof(CrossDomainApplication).Assembly.FullName,
typeof(CrossDomainApplication).FullName);
I have massive problems -- the runtime (variable above) no longer can cast to CrossDomainApplication or ICrossDomainApplication.
The actual object looks like:
public class CrossDomainApplication : MarshalByRefObject, ICrossDomainApplication
And the interface looks like:
public interface ICrossDomainApplication
{
void Run(CrossDomainApplicationParameters parameters);
}
And the parameters look like:
[Serializable]
public class CrossDomainApplicationParameters : MarshalByRefObject
{
public object FactoryType { get; set; }
public Type ApplicationType { get; set; }
public string ModuleName { get; set; }
public object[] Parameters { get; set; }
}
The native type of runtime appears to be MarshalByRefObject -- and it doesn't like converting to anything else.
Any thoughts on what could be wrong?
EDIT: Here's the error I get when I run it as the following:
ICrossDomainApplication runtime = (ICrossDomainApplication)domain.CreateInstanceAndUnwrap(
typeof(CrossDomainApplication).Assembly.FullName,
typeof(CrossDomainApplication).FullName);
//Exception before reaching here
runtime.Run(parameters);
System.InvalidCastException: Unable to cast transparent proxy to type 'Infrastructure.ICrossDomainApplication'.
Here's what the domain looks like, as I create it:
AppDomain domain = AppDomain.CreateDomain(
Guid.NewGuid().ToString(),
null,
new AppDomainSetup()
{
ApplicationBase = GetPath(),
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
LoaderOptimization = LoaderOptimization.MultiDomainHost
});
and GetPath() looks like this:
private string GetPath()
{
Uri path = new Uri(Assembly.GetCallingAssembly().CodeBase);
if (path.IsFile)
{
path = new Uri(path, Path.GetDirectoryName(path.AbsolutePath));
}
return path.LocalPath.Replace("%20", " ");
}
In the desire to help some other poor, poor person out, I finally figured it out after trying SO MANY other combinations.
I had to change a few things... the first of which:
AppDomain domain = AppDomain.CreateDomain(
Guid.NewGuid().ToString(),
AppDomain.CurrentDomain.Evidence,
new AppDomainSetup()
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
LoaderOptimization = LoaderOptimization.MultiDomainHost,
PrivateBinPath = GetPrivateBin(AppDomain.CurrentDomain.SetupInformation.ApplicationBase)
});
PrivateBinPath is the real trick here that enabled everything else to (finally) start working. PrivateBinPath (read the documentation) ABSOLUTELY needs to be a relative path. If you have a subfolder called "assemblies" then the PrivateBinPath should be "assemblies". If you precede it with a \ or an absolute path, it will not work.
By doing this, it caused the AssemblyResolve event to finally fire. I did that with the following (before creating the new, child AppDomain):
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
and the ResolveAssembly method looks like this:
private Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in loadedAssemblies)
{
if (assembly.FullName == args.Name)
{
return assembly;
}
}
return null;
}
The method can be written much easier with Linq, but I kept it this way to try and make it somewhat obvious for myself what was being parsed and loaded for debugging purposes.
And, per always, make sure that you disconnect your event (if you're loading an AppDomain at a time):
AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly;
That fixed everything. Thank you to everyone who helped!
This is not an answer, just sample code to share
Hmm, could it be something else? This sample, not exactly 1:1 with what you described, works.
#region
using System;
using System.Reflection;
using System.Threading;
#endregion
internal class Program
{
#region Methods
private static void Main(string[] args)
{
// Get and display the friendly name of the default AppDomain.
string callingDomainName = Thread.GetDomain().FriendlyName;
Console.WriteLine(callingDomainName);
// Get and display the full name of the EXE assembly.
string exeAssembly = Assembly.GetEntryAssembly().FullName;
Console.WriteLine(exeAssembly);
// Construct and initialize settings for a second AppDomain.
var ads = new AppDomainSetup
{
ApplicationBase = Environment.CurrentDirectory,
DisallowBindingRedirects = false,
DisallowCodeDownload = true,
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
};
// Create the second AppDomain.
AppDomain ad2 = AppDomain.CreateDomain("AD #2", null, ads);
// Create an instance of MarshalbyRefType in the second AppDomain.
// A proxy to the object is returned.
var mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, typeof(MarshalByRefType).FullName);
// Call a method on the object via the proxy, passing the
// default AppDomain's friendly name in as a parameter.
mbrt.SomeMethod(new MarshalByRefParameter{ModuleName = callingDomainName});
// Unload the second AppDomain. This deletes its object and
// invalidates the proxy object.
AppDomain.Unload(ad2);
try
{
// Call the method again. Note that this time it fails
// because the second AppDomain was unloaded.
mbrt.SomeMethod(new MarshalByRefParameter { ModuleName = callingDomainName });
Console.WriteLine("Successful call.");
}
catch (AppDomainUnloadedException)
{
Console.WriteLine("Failed call; this is expected.");
}
}
#endregion
}
public interface IMarshalByRefTypeInterface
{
void SomeMethod(MarshalByRefParameter parameter);
}
public class MarshalByRefType : MarshalByRefObject, IMarshalByRefTypeInterface
{
// Call this method via a proxy.
#region Public Methods and Operators
public void SomeMethod(MarshalByRefParameter parameter)
{
// Get this AppDomain's settings and display some of them.
AppDomainSetup ads = AppDomain.CurrentDomain.SetupInformation;
Console.WriteLine("AppName={0}, AppBase={1}, ConfigFile={2}", ads.ApplicationName, ads.ApplicationBase, ads.ConfigurationFile);
// Display the name of the calling AppDomain and the name
// of the second domain.
// NOTE: The application's thread has transitioned between
// AppDomains.
Console.WriteLine("Calling from '{0}' to '{1}'.", parameter.ModuleName, Thread.GetDomain().FriendlyName);
}
#endregion
}
[Serializable]
public class MarshalByRefParameter : MarshalByRefObject
{
public string ModuleName { get; set; }
}
But then, I am just grabbing entry assembly, while you have one which gets dynamically compiled. What error and where do you actually get?
I am trying to run a WPF App from nunit. Since I only can run one App per AppDomain I instantiate a new AppDomain per acceptance test. When I do that, I run into serialization exceptions.
namespace Tests
{
[TestFixture, RequiresSTA, Serializable]
public class ApplicationTests
{
private MainWindow mainWindow;
private bool guiVisible;
private App app;
[TestCase("app domain name for instance of App")]
[TestCase("app domain name for another instance of App")]
public void ApplicationTest(string name)
{
AppDomain appDomain = AppDomain.CreateDomain(name);
//appDomain.ExecuteAssembly(#"C:\Users\bp\Documents\Visual Studio 2013\Projects\WpfApplication1\WpfApplication1\bin\Debug\WpfApplication1.exe");
CrossAppDomainDelegate action = () =>
{
app = new App();
app.InitializeComponent();
app.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => AppOnActivated(null, null)));
app.Run();
};
appDomain.DoCallBack(action);
}
private void AppOnActivated(object sender, EventArgs eventArgs)
{
if (!guiVisible)
{
mainWindow = (MainWindow)Application.Current.MainWindow;
mainWindow.ButtonViewModel = new ButtonViewModel();
mainWindow.ButtonViewModel.Name = "bla";
guiVisible = true;
}
app.Shutdown();
}
}
}
The exception I receive now:
System.Runtime.Serialization.SerializationException : Type is not
resolved for member 'Tests.ApplicationTests,Tests, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null'.
I made the test class [Serializable] which does not help either.
Help is very much appreciated. I just want to start my WPF application from an NUnit test so that I can write acceptance tests for my application. I keep running into different walls and eventually whatever path I choose seems to lead to a dead end...
Many thanks in advance,
Bas
AppDomains are software-isolated processes in .NET. This means you can't just reference objects belonging to one AppDomain from another. Objects can be either copied by value (serialization) or by reference using MarshalByRefObject. Since WPF's objects are neither of those, you can't move them around AppDomains.
For your your testing purposes, you could use a simpler approach: run everything within the new AppDomain, and use the SetData and GetData methods to transfer data to assert on.
[TestCase("app domain name for instance of App")]
[TestCase("app domain name for another instance of App")]
public void ApplicationTest(string name)
{
AppDomain appDomain = AppDomain.CreateDomain(name,
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);
appDomain.DoCallBack(StartApp);
Assert.IsTrue((bool)appDomain.GetData("GuiVisible"));
AppDomain.Unload(appDomain);
}
// using a static method instead of a lambda makes sure
// you haven't captured anything
private static void StartApp()
{
app = new App();
app.InitializeComponent();
app.Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
new Action(() => AppOnActivated()));
app.Run();
}
private static void AppOnActivated()
{
var mainWindow = (MainWindow)Application.Current.MainWindow;
mainWindow.ButtonViewModel = new ButtonViewModel();
mainWindow.ButtonViewModel.Name = "bla";
AppDomain.CurrentDomain.SetValue("GuiVisible") = true;
app.Shutdown();
}