I have written an application that unit tests our hardware via a internet browser.
I have command classes in the assembly that are a wrapper around individual web browser actions such as ticking a checkbox, selecting from a dropdown box as such:
BasicConfigurationCommands
EventConfigurationCommands
StabilizationCommands
and a set of test classes, that use the command classes to perform scripted tests:
ConfigurationTests
StabilizationTests
These are then invoked via the GUI to run prescripted tests by our QA team. However, as the firmware is changed quite quickly between the releases it would be great if a developer could write an XML file that could invoke either the tests or the commands:
<?xml version="1.0" encoding="UTF-8" ?>
<testsuite>
<StabilizationTests>
<StressTest repetition="10" />
</StabilizationTests>
<BasicConfigurationCommands>
<SelectConfig number="2" />
<ChangeConfigProperties name="Weeeeee" timeOut="15000" delay="1000"/>
<ApplyConfig />
</BasicConfigurationCommands>
</testsuite>
I have been looking at the System.Reflection class and have seen examples using GetMethod and then Invoke. This requires me to create the class object at compile time and I would like to do all of this at runtime.
I would need to scan the whole assembly for the class name and then scan for the method within the class.
This seems a large solution, so any information pointing me (and future readers of this post) towards an answer would be great!
Thanks for reading,
Matt
To Find all classes in an assembly:
public Type FindClass(string name)
{
Assembly ass = null;
ass = Assembly.Load("System.My.Assembly"); // Load from the GAC
ass = Assembly.LoadFile(#"c:\System.My.Assembly.dll"); // Load from file
ass = Assembly.LoadFrom("System.My.Assembly"); // Load from GAC or File
foreach(Type t in ass.GetTypes())
{
if (t.Name == name)
return t;
}
return null;
}
In reality you should tag your class with an attribute, it makes them more discoverable.
To instantiate an instance of said class:
public T Instantiate<T>(Type typ, object[] arguments)
{
return (T)Activator.CreateInstance(typ, arguments, null);
}
Don't forget, you should probably wrap these with appropriate try/catch blocks
Then to find a method on a class
Type t = FindClass("MyType");
MethodInfo meth = t.GetMethod("TestSomething", typeof(string), typeof(int)); // finds public ??? TestSomething(string, int)
Just play around with the Object Browser in VStudio and learn the Reflection classes, there's lots you can do.
Related
Here's the deal: I want to create a C# console app. When run, this app will look in a particular folder for dll's with classes that implement a particular interface, and then run a method on those dll's.
I haven't done this before but from my reading that should be "easy enough" from an IoC/Ninject perspective. I think you can do something with kernel.Bind() to load assemblies of a certain interface in a certain directory. I think/hope I can figure that part out (if you know otherwise, please do tell!).
But here is my quandary.
Here is a visual to help first:
MainProgramFolder
-- MainProgram.exe
-- MainProgram.exe.config
-- LibraryFolder
----- Library1Folder
--------Library1.dll
--------Library1.dll.config
----- Library2Folder
--------Library2.dll
--------Library2.dll.config
The dll's that implement this interface are technically stand alone apps -- they are just libraries instead of exe's (or, rather, I'd like them to be for IoC purposes). I'd like for them to be run in their own context, with their own app.configs. So for example, MainProgram.exe would bind the ILibrary interface to classes inside Library1.dll and Library2.dll because they implement ILibrary. But inside Library1, it calls ConfigurationManager to get its settings. When I call Class.Method() for each of the bindings from MainProgram, how can I ensure they are referencing their own .config's and not MainProgram.exe.config? (Also, fwiw, these additional libraries will likely not be a part of the assembly or even namespace of the main programs -- we're basically providing a drop folder for an application to kind of "subscribe" to the main program's execution.)
IOW, I know you can attach an app.config to a class library but I wouldn't know how, after the bindings have been resolved from the IOC, to make those dll's "see" its own config rather than the main program's config.
All thoughts appreciated!
Thanks
Tom
First, to load and bind all of your classes you'll need ninject.extensions.conventions, and something like this:
var kernel = new StandardKernel();
/*add relevant loop/function here to make it recurse folders if need be*/
kernel.Bind(s => s.FromAssembliesMatching("Library*.dll")
.Select(type => type.IsClass && type.GetInterfaces().Contains(typeof(ILibrary)))
.BindSingleInterface()
.Configure(x=>x.InSingletonScope()));
To make each instance load its configuration as if it was the entry point you will need to run it in a new app domain. Your ILibrary implementation needs to inherit MarshalByRefObject and be Serializable so that it will run correctly in the alternate appdomain
[Serializable]
public class LibraryA :MarshalByRefObject, ILibrary
You can then add this activation strategy to your kernel that will cause it to swap out instances of ILibrary with an instance loaded in an alternate appdomain with your config file convention before they are returned.
public class AlternateAppDomainStrategy<T> : ActivationStrategy
{
public override void Activate(IContext context, InstanceReference reference)
{
if (reference.Instance.GetType().GetInterfaces().Contains(typeof(T)))
{
var type = reference.Instance.GetType();
var configFilePath = type.Assembly.GetName().Name + ".dll.config";
var file = new FileInfo(configFilePath);
if (file.Exists)
{
var setup = new AppDomainSetup() { ConfigurationFile = file.FullName, ApplicationBase = AppDomain.CurrentDomain.BaseDirectory };
var domain = AppDomain.CreateDomain(type.FullName, null, setup);
var instance = domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
reference.Instance = instance;
}
else
{
throw new FileNotFoundException("Missing config file", file.FullName);
}
}
}
}
And Add it to your kernel
kernel.Components.Add<IActivationStrategy, AlternateAppDomainStrategy<ILibrary>>();
From there you can simply instantiate your ILibrary instances and call methods on them. They will load in their own app domains with their own configs. It gets a lot more complicated if you need to pass things in/out of the instance either via methods or constructor, but from the sound if it you don't so this should be OK.
var libs = kernel.GetAll<ILibrary>();
foreach (var lib in libs)
{
lib.Method();
}
I'm currently trying to load and use the Gephi Toolkit from within a .Net 4 C# website.
I have a version of the toolkit jar file compiled against the IKVM virtual machine, which works as expected from a command line application using the following code:
var controller = (ProjectController)Lookup.getDefault().lookup(typeof(ProjectController));
controller.closeCurrentProject();
controller.newProject();
var project = controller.getCurrentProject();
var workspace = controller.getCurrentWorkspace();
The three instances are correctly instantiated in a form similar to org.gephi.project.impl.ProjectControllerImpl#8ddb93.
If however I run the exact same code, with the exact same using statements & references, the very first line loading the ProjectController instance returns null.
I have tried a couple of solutions
Firstly, I have tried ignoring the Lookup.getDefault().lookup(type) call, instead trying to create my own instances:
var controller = new ProjectControllerImpl();
controller.closeCurrentProject();
controller.newProject();
var project = controller.getCurrentProject();
var workspace = controller.getCurrentWorkspace();
This fails at the line controller.newProject();, I think because internally (using reflector) the same Lookup.getDefault().lookup(type) is used in a constructor, returns null and then throws an exception.
Secondly, from here: Lookup in Jython (and Gephi) I have tried to set the %CLASSPATH% to the location of both the toolkit JAR and DLL files.
Is there a reason why the Lookup.getDefault().lookup(type) would not work in a web environment? I'm not a Java developer, so I am a bit out of my depth with the Java side of this.
I would have thought it possible to create all of the instances myself, but haven't been able to find a way to do so.
I also cannot find a way of seeing why the ProjectController load returned null. No exception is thrown, and unless I'm being very dumb, there doesn't appear to be a method to see the result of the attempted load.
Update - Answer
Based on the answer from Jeroen Frijters, I resolved the issue like this:
public class Global : System.Web.HttpApplication
{
public Global()
{
var assembly = Assembly.LoadFrom(Path.Combine(root, "gephi-toolkit.dll"));
var acl = new AssemblyClassLoader(assembly);
java.lang.Thread.currentThread().setContextClassLoader(new MySystemClassLoader(acl));
}
}
internal class MySystemClassLoader : ClassLoader
{
public MySystemClassLoader(ClassLoader parent)
: base(new AppDomainAssemblyClassLoader(typeof(MySystemClassLoader).Assembly))
{ }
}
The code ikvm.runtime.Startup.addBootClassPathAssemby() didn't seem to work for me, but from the provided link, I was able to find a solution that seems to work in all instances.
This is a Java class loader issue. In a command line app your main executable functions as the system class loader and knows how to load assembly dependencies, but in a web process there is no main executable so that system class loader doesn't know how to load anything useful.
One of the solutions is to call ikvm.runtime.Startup.addBootClassPathAssemby() to add the relevant assemblies to the boot class loader.
For more on IKVM class loading issues see http://sourceforge.net/apps/mediawiki/ikvm/index.php?title=ClassLoader
Ok, here is the deal:
I want to load a user defined Assembly into my AppDomain, but I only want to do so if the specified Assembly matches some requirements. In my case, it must have, among other requirements, an Assembly level Attribute we could call MandatoryAssemblyAttribute.
There are two paths I can go as far as I see:
Load the Assembly into my current AppDomain and check if the Attribute is present. Easy but inconvenient as I'm stuck with the loaded Assembly even if it doesnt have the MandatoryAssemblyAttribute. Not good.
I can create a new AppDomain and load the Assembly from there and check if my good old MandatoryAddemblyAttribute is present. If it is, dump the created AppDomain and go ahead and load the Assembly into my CurrentAppDomain, if not, just dump the new AppDomain, tell the user, and have him try again.
Easier said than done. Searching the web, I've found a couple of examples on how to go about this, including this previous question posted in SO:Loading DLLs into a separate AppDomain
The problem I see with this solution is that you actually have to know a type (full name) in the Assembly you want to load to begin with. That is not a solution I like. The main point is trying to plug in an arbitrary Assembly that matches some requirements and through attributes discover what types to use. There is no knowledge beforehand of what types the Assembly will have. Of course I could make it a requirement that any Assembly meant to be used this way should implement some dummy class in order to offer an "entry point" for CreateInstanceFromAndUnwrap. I'd rather not.
Also, if I go ahead and do something along this line:
using (var frm = new OpenFileDialog())
{
frm.DefaultExt = "dll";
frm.Title = "Select dll...";
frm.Filter = "Model files (*.dll)|*.dll";
answer = frm.ShowDialog(this);
if (answer == DialogResult.OK)
{
domain = AppDomain.CreateDomain("Model", new Evidence(AppDomain.CurrentDomain.Evidence));
try
{
domain.CreateInstanceFrom(frm.FileName, "DummyNamespace.DummyObject");
modelIsValid = true;
}
catch (TypeLoadException)
{
...
}
finally
{
if (domain != null)
AppDomain.Unload(domain);
}
}
}
This will work fine, but if then I go ahead and do the following:
foreach (var ass in domain.GetAssemblies()) //Do not fret, I would run this before unloading the AppDomain
Console.WriteLine(ass.FullName);
I get a FileNotFoundException. Why?
Another path I could take is this one: How load DLL in separate AppDomain But I'm not getting any luck either. I'm getting a FileNotFoundException whenever I choose some random .NET Assembly and besides it defeats the purpose as I need to know the Assembly's name (not file name) in order to load it up which doesn't match my requirements.
Is there another way to do this barring MEF (I am not targeting .NET 3.5)? Or am I stuck with creating some dummy object in order to load the Assembly through CreateInstanceFromAndUnwrap? And if so, why can't I iterate through the loaded assemblies without getting a FileNotFoundException? What am I doing wrong?
Many thanks for any advice.
The problem I see with this solution is that you actually have to know
a type (full name) in the Assembly
That is not quite accurate. What you do need to know is a type name is some assembly, not necessarily the assembly you are trying to examine. You should create a MarshalByRef class in your assembly and then use CreateInstanceAndUnwrap to create an instance of it from your own assembly (not the one you are trying to examine). That class would then do the load (since it lives in the new appdomain) and examine and return a boolean result to the original appdomain.
Here is some code to get you going. These classes go in your own assembly (not the one you are trying to examine):
This first class is used to create the examination AppDomain and to create an instance of your MarshalByRefObject class (see bottom):
using System;
using System.Security.Policy;
internal static class AttributeValidationUtility
{
internal static bool ValidateAssembly(string pathToAssembly)
{
AppDomain appDomain = null;
try
{
appDomain = AppDomain.CreateDomain("ExaminationAppDomain", new Evidence(AppDomain.CurrentDomain.Evidence));
AttributeValidationMbro attributeValidationMbro = appDomain.CreateInstanceAndUnwrap(
typeof(AttributeValidationMbro).Assembly.FullName,
typeof(AttributeValidationMbro).FullName) as AttributeValidationMbro;
return attributeValidationMbro.ValidateAssembly(pathToAssembly);
}
finally
{
if (appDomain != null)
{
AppDomain.Unload(appDomain);
}
}
}
}
This is the MarshalByRefObject that will actually live in the new AppDomain and will do the actual examination of the assembly:
using System;
using System.Reflection;
public class AttributeValidationMbro : MarshalByRefObject
{
public override object InitializeLifetimeService()
{
// infinite lifetime
return null;
}
public bool ValidateAssembly(string pathToAssembly)
{
Assembly assemblyToExamine = Assembly.LoadFrom(pathToAssembly);
bool hasAttribute = false;
// TODO: examine the assemblyToExamine to see if it has the attribute
return hasAttribute;
}
}
This can be easily done by using a managed assembly reader, such as Mono.Cecil. You'd check whether the assembly is decorated with an attribute, without loading the assembly in the AppDomain, and actually, without messing with AppDomains at all. For instance, with Cecil:
bool HasMandatoryAttribute (string fileName)
{
return AssemblyDefinition.ReadAssembly (fileName)
.CustomAttributes
.Any (attribute => attribute.AttributType.Name == "MandatoryAttribute");
}
That's basically what are doing most of he plugins systems, as creating an AppDomain and tearing it down is quite expensive for the runtime.
I'm having a problem with XML deserialization that is baffling me.
I'm building an application that supports local customization of various services that it uses. I've implemented an abstract ServiceLocator class whose methods return various objects. Each custom installation is responsible for implementing a subclass of this and providing implementations of those methods. The meat of this class looks like this:
public abstract class ServiceLocator
{
public static void Initialize(string customFeaturesPath)
{
Assembly a = Assembly.LoadFrom(customFeaturesPath);
Type t = a.GetExportedTypes()
.AsEnumerable()
.Where(x => x.IsSubclassOf(typeof (ServiceLocator)))
.First();
Default = (ServiceLocator)a.CreateInstance(t.FullName);
}
public static ServiceLocator Default { get; private set; }
public abstract DefaultValuesContainer CreateDefaultValuesContainer();
}
This works just fine: I get the path to the custom features assembly from the application configuration file, the program calls Initialize, and then the application can call the various methods on ServiceLocator.Default and they return the appropriate custom implementations of the services.
One of these services is a DefaultValuesContainer. This is a simple object that exposes properties whose values need to be persisted in a user settings file. The idea is that I can serialize this object into a single user setting of type string. It makes for a user setting file that you wouldn't want to edit manually, but I'm cool with that.
Here's a concrete implementation of ServiceLocator.CreateDefaultValuesContainer:
protected override DefaultValuesContainer CreateDefaultValuesContainer(string serializedXml)
{
DefaultValuesContainer c = new ClientDefaultValuesContainer();
if (string.IsNullOrEmpty(serializedXml))
{
return c;
}
XmlSerializer x = new XmlSerializer(c.GetType());
return (DefaultValuesContainer) x.Deserialize(new StringReader(serializedXml));
}
Now here's the thing.
I've built unit tests for this using NUnit. When I run the tests in the test fixture class that exercises the client custom features, they work. When I run the entire test suite, the last line of the above method throws this exception:
System.InvalidOperationException : There is an error in XML document (0, 0).
----> System.IO.FileLoadException : Could not load file or assembly 'ClientCustomFeatures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))
----> System.ArgumentNullException : Value cannot be null.
Parameter name: path1
I'm kind of baffled as to why. The SetUp method still runs, and ServiceLocator.Default still returns an object of type ClientServiceLocator, which means that it has loaded the ClientCustomFeatures assembly. Indeed, the very method that's throwing the exception is in the assembly that I'm being told can't be loaded.
What is the XmlSerializer trying to do here? Why is it trying to load an assembly that's already loaded? What on earth does "Invalid pointer" mean? And above all, how should I be debugging something like this?
If your custom assembly does not know where to load the assembly containing the ClientCustomFeatures class, this will happen. This occurs when you've deployed your custom assembly to a location that is not in the path of your main assembly and your main assembly is not in the gac. So if your custom asseblies are loaded from sub directories of your main assembly this should go away. However, if they are located in arbitrary places, you'll have a problem because they need to load your main assembly as they need access to the ClientCustomFeatures type.
I've had problems with the assembly loader (Fusion?) when one assembly loads another assembly which itself has (non-GAC) references. YourDLL.XmlSerializers.dll might be one such assembly. Try turning off Visual Studio's option to automatically generate an XML serialization assembly (Project options) - this will remove the additional assembly (and hence the dependency on it).
Fusion Log Viewer
To help diagnose assembly loading problems like these, take a look at the Fusion Log Viewer (AKA fuslogvw.exe).
Fusion == the .NET component that locates and loads assemblies.
Try to replace the line:
XmlSerializer x = new XmlSerializer(c.GetType());
with:
XmlSerializer x = new XmlSerializer(c.GetType(), new Type[] { typeof(DefaultValuesContainer), typeof(ClientDefaultValuesContainer) });
I have been trying to get the following code to work(everything is defined in the same assembly) :
namespace SomeApp{
public class A : MarshalByRefObject
{
public byte[] GetSomeData() { // }
}
public class B : MarshalByRefObject
{
private A remoteObj;
public void SetA(A remoteObj)
{
this.remoteObj = remoteObj;
}
}
public class C
{
A someA = new A();
public void Init()
{
AppDomain domain = AppDomain.CreateDomain("ChildDomain");
string currentAssemblyPath = Assembly.GetExecutingAssembly().Location;
B remoteB = domain.domain.CreateInstanceFromAndUnwrap(currentAssemblyPath,"SomeApp.B") as B;
remoteB.SetA(someA); // this throws an ArgumentException "Object type cannot be converted to target type."
}
}
}
What I'm trying to do is pass a reference of an 'A' instance created in the first AppDomain to the child domain and have the child domain execute a method on the first domain. In some point on 'B' code I'm going to call 'remoteObj.GetSomeData()'. This has to be done because the 'byte[]' from 'GetSomeData' method must be 'calculated' on the first appdomain.
What should I do to avoid the exception, or what can I do to achieve the same result?
The actual root cause was your dll was getting loaded from different locations in the two different app domains. This causes .NET to think they are different assemblies which of course means the types are different (even though they have the same class name, namespace etc).
The reason Jeff's test failed when run through a unit test framework is because unit test frameworks generally create AppDomains with ShadowCopy set to "true". But your manually created AppDomain would default to ShadowCopy="false". This would cause the dlls to be loaded from different locations which leads to the nice "Object type cannot be converted to target type." error.
UPDATE: After further testing, it does seem to come down to the ApplicationBase being different between the two AppDomains. If they match, then the above scenario works. If they are different it doesn't (even though I've confirmed that the dll is loaded into both AppDomains from the same directory using windbg) Also, if I turn on ShadowCopy="true" in both of my AppDomains, then it fails with a different message: "System.InvalidCastException: Object must implement IConvertible".
UPDATE2: Further reading leads me to believe it is related to Load Contexts. When you use one of the "From" methods (Assembly.LoadFrom, or appDomain.CreateInstanceFromAndUnwrap), if the assembly is found in one of the normal load paths (the ApplicationBase or one of the probing paths) then is it loaded into the Default Load Context. If the assembly isn't found there, then it is loaded into the Load-From Context. So when both AppDomains have matching ApplicationBase's, then even though we use a "From" method, they are both loaded into their respective AppDomain's Default Load Context. But when the ApplicationBase's are different, then one AppDomain will have the assembly in its Default Load Context while the other has the assembly in it's Load-From Context.
I can duplicate the issue, and it seems to be related to TestDriven.net and/or xUnit.net. If I run C.Init() as a test method, I get the same error message. However, if I run C.Init() from a console application, I do not get the exception.
Are you seeing the same thing, running C.Init() from a unit test?
Edit: I'm also able to duplicate the issue using NUnit and TestDriven.net. I'm also able to duplicate the error using the NUnit runner instead of TestDriven.net. So the problem seems to be related to running this code through a testing framework, though I'm not sure why.
This is a comment to #RussellMcClure but as it is to complex for a comment I post this as an answer:
I am inside an ASP.NET application and turning off shadow-copy (which would also solve the problem) is not really an option, but I found the following solution:
AppDomainSetup adSetup = new AppDomainSetup();
if (AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles == "true")
{
var shadowCopyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (shadowCopyDir.Contains("assembly"))
shadowCopyDir = shadowCopyDir.Substring(0, shadowCopyDir.LastIndexOf("assembly"));
var privatePaths = new List<string>();
foreach (var dll in Directory.GetFiles(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, "*.dll"))
{
var shadowPath = Directory.GetFiles(shadowCopyDir, Path.GetFileName(dll), SearchOption.AllDirectories).FirstOrDefault();
if (!String.IsNullOrWhiteSpace(shadowPath))
privatePaths.Add(Path.GetDirectoryName(shadowPath));
}
adSetup.ApplicationBase = shadowCopyDir;
adSetup.PrivateBinPath = String.Join(";", privatePaths);
}
else
{
adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
adSetup.PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
}
This will use the shadow-copy directory of the main app-domain as the application-base and add all shadow-copied assemblies to the private path if shadow-copy is enabled.
If someone has a better way of doing this please tell me.