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();
}
Related
I have a WPF project that should be able to run independently and can also run as a reference DLL.
I am trying to run the WPF project from another WPF project
Project1 needs to load Project2
Project1 needs to run Project2 as a whole from its App.xaml.cs since this is where DataContexts gets binded. Here is the App.xaml.cs of Project2:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
Dictionary<Type, Type> viewRegistry = new Dictionary<Type, Type>();
viewRegistry.Add(typeof(ImageUploaderViewModel), typeof(ImageUploaderView));
viewRegistry.Add(typeof(CodBoxViewModel), typeof(CodBoxView));
Type viewType = viewRegistry[typeof(ImageUploaderViewModel)];
MessageBox.Show("Hello World");
Window view = (Window)Activator.CreateInstance(viewType);
view.DataContext = new ImageUploaderViewModel(viewRegistry);
view.Show();
}
}
Here is where I try to call the Project 2
private void LoadProject2()
{
string assemblyName = string.Format("{0}\\Project2.dll", new
FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName);
Application app = LoadAssembly(assemblyName, "App");
app.Run();
}
private Application LoadAssembly(String assemblyName, String typeName)
{
try
{
Assembly assemblyInstance = Assembly.LoadFrom(assemblyName);
Application app;
foreach (Type t in assemblyInstance.GetTypes().Where(t => String.Equals(t.Name, typeName, StringComparison.OrdinalIgnoreCase)))
{
app = assemblyInstance.CreateInstance(t.FullName) as Application;
return app;
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(string.Format("Failed to load window from{0}{1}", assemblyName, ex.Message));
throw new Exception(string.Format("Failed to load external window{0}", assemblyName), ex);
}
}
With this I am able to get the Application but I get an error "Cannot create more than one System.Windows.Application instance in the same AppDomain.". I am not sure how to make this possible without having 2 Application instance. Thank you
If you want to start another app, you should compile the other app and start it in a new process using the Process.Start API.
You cannot run two WPF applications in the same process, and it doesn't make any sense to do so either.
If the DataContexts gets binded in App.xaml.cs, you should probably refactor your code into a separate class library that you can include from any application. Move the code from OnStartup into a decopupled method of class in a class library and simply call this method from the OnStartup method of the single running application.
How can I restart a ServiceStack self hosted Apphost? Setting my AppHost instance to null and disposing of it does not work correctly, it throws the following Exception:
System.ArgumentException: An entry with the same key already exists.
I need to be able to do this to reload settings and start the AppHost without restarting the Windows Service hosting the AppHost
EDIT:
Scott and Moo-Juice's suggestions to run the AppHost in a different AppDomain is the correct solution. In order to get past the Cross Domain calls to restart the AppHost, I created a second AppHost which runs in the Main AppDomain and calls the Restart method from Scott's solution. Enabling CORS on both AppHost instances allows for a simple $ajax call to restart the service and reload the page once the service is started and the request returns.
Use an AppDomain:
Moo-Juice's suggestion to use an AppDomain is correct. I have included a simple example of how to use an AppDomain to isolate ServiceStack, and allow it to be Started/Restarted/Stopped.
using System;
using ServiceStack;
using System.Runtime.Remoting;
namespace Test
{
public class ServiceStackConsoleHost : MarshalByRefObject
{
public static void Main()
{
Start();
}
static ObjectHandle Handle;
static AppDomain ServiceStackAppDomain;
public static void Start()
{
// Get the assembly of our host
var assemblyName = typeof(ServiceStackConsoleHost).Assembly.FullName;
// Create an AppDomain
ServiceStackAppDomain = AppDomain.CreateDomain("ServiceStackAppDomain");
// Load in our service assembly
ServiceStackAppDomain.Load(assemblyName);
// Create instance of our ServiceStack application
Handle = ServiceStackAppDomain.CreateInstance(assemblyName, "Test.ServiceStackConsoleHost");
// Show that the main application is in a separate AppDomain
Console.WriteLine("Main Application is running in AppDomain '{0}'", AppDomain.CurrentDomain.FriendlyName);
// Wait for input
Console.ReadLine();
// Restart the application
Restart();
}
public static void Stop()
{
if(ServiceStackAppDomain == null)
return;
// Notify ServiceStack that the AppDomain is going to be unloaded
var host = (ServiceStackConsoleHost)Handle.Unwrap();
host.Shutdown();
// Shutdown the ServiceStack application
AppDomain.Unload(ServiceStackAppDomain);
ServiceStackAppDomain = null;
}
public static void Restart()
{
Stop();
Console.WriteLine("Restarting ...");
Start();
}
readonly AppHost appHost;
public ServiceStackConsoleHost()
{
appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:8090/");
Console.WriteLine("ServiceStack is running in AppDomain '{0}'", AppDomain.CurrentDomain.FriendlyName);
}
public void Shutdown()
{
if(appHost != null)
{
Console.WriteLine("Shutting down ServiceStack host");
if(appHost.HasStarted)
appHost.Stop();
appHost.Dispose();
}
}
}
public class AppHost : AppSelfHostBase
{
public AppHost(): base("My ServiceStack Service", typeof(AppHost).Assembly)
{
}
public override void Configure(Funq.Container container)
{
}
}
}
Instructions for how to use an AppDomain can be found here.
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 have a wpf app I have registered as a URI Scheme by doing the following.
HKEY_CLASSES_ROOT
-->myappname
-->shell
-->open
-->command
(Default) = "c:\pathtomyapp\app.exe"
Fantastic! However, my application enforces that only one instance can run at a time. How can I detect that my app is already running and for example bring it to the foreground?
You can use a named mutex to detect that application is already running. Or, if you have a GUI app, you can inherit your form from VisualBasic's SingleInstance application , and it will do routhgly the same for you.
public class SingleInstanceController
: WindowsFormsApplicationBase
{
public SingleInstanceController()
{
// Set whether the application is single instance
this.IsSingleInstance = true;
this.StartupNextInstance += new
StartupNextInstanceEventHandler(this_StartupNextInstance);
}
void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
// Here you get the control when any other instance is
// invoked apart from the first one.
// You have args here in e.CommandLine.
// You custom code which should be run on other instances
}
protected override void OnCreateMainForm()
{
// Instantiate your main application form
this.MainForm = new Form1();
}
}
[STAThread]
static void Main(string[] args)
{
SingleInstanceController controller = new SingleInstanceController();
controller.Run(args);
}
It does not matter whenever you write your code in C#, as this class is avaliable as a part of .Net framework and for all languages.
And here is a wrapper for the WPF
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?