Assembly Location Changes in Runtime - c#

I'm running coded ui automation and defined a method attribute called [ExternalDataSource()] to read a document (csv, xml...) and parse the data into some dictionaries. I'll copy it here so you can have a better insight:
[System.AttributeUsage(System.AttributeTargets.Method)]
public class ExternalDataSource : System.Attribute
{
public ExternalDataSource(string filename)
{
DirectoryInfo di = new DirectoryInfo(Assembly.GetExecutingAssembly().Location);
string file = Path.Combine(Path.GetDirectoryName(di.FullName), filename);
try
{
code
}
catch (Exception)
{
throw new UITestException("Cannot load data source document");
}
}
}
In it I try to access Assembly.GetExecutingAssembly().Location to get a file that is copied to the TestResult/Out folder. I assigned this attribute to only one TestMethod() in the whole application and while debugging, I found out that the application enters the attribute's c'tor twice. Both times the Location is different. Once it's from the bin/Debug folder, the other time it's from the TestResults/Out folder. Two questions:
Why does the debugger enter that attribute twice if I call it only once in my application?
Why does the location of the same assembly change?

Well it seems nobody had an answer, but while debugging a run from the command line using mstest.exe with the vs2012 JIT Debugger i found out a strange thing:
When putting a System.Diagnostics.Debugger.Break() in the class where this attribute is the jitter was called from MSTest.exe but when this breakpoint was in the testmethod decorated with this attribute, QTAgent32.exe was called. I had implemented a singleton class to handle my parameters, and while it was populated in ExternalDataSource in this attribute by MSTest, when entering QTAgent32 (the test) it was empty.
The solution that worked for me was just to initialize that Singleton with the data on [TestInitialize()].
Hope this helps somebody.

Related

Unknown exception at declaration and initalisation of a Class

The main Problem is completely different, please skip to the Edit
I have an exception of an unknown type which doesn't even get thrown properly. Following Code provides the Context:
MMDataAccess.InitDemoDB();
MMDataAccess.InitInternalDB();
MMDataAccess.InitMaintDB();
try
{
SQLiteToDBLib sqltdbl = new SQLiteToDBLib();
sqltdbl.WriteToSQLite();
}
catch(Exception ex)
{
string message = ex.Message;
}
These are the very first lines of my first Activity in my app. The first 3 lines belong to my very own implementation of an in-memory database and are behaving nicely. The problem rises with the next two lines inside the try-block. The declaration and initalistation of the sqltdbl variable never happens. The constructor of SQLiteToDBLib looks like this:
public SQLiteToDBLib()
{
msc = new MSConnection();
}
The MSConnection class doesn't even have a constructor (except for the default one of course).
As you can see i've tried to catch any exceptions, but without success. everything i can figure out is, that a exception is thrown because of the debugger going into the catch section while ignoring everything that has to do with "ex". Without breakpoints everything seems fine. Just without the call to WriteToSQLite which should create a .sqlite file on the external Memory.
What can I do to resolve this error? Is there anything i can catch except the default Exception?
Edit:
After some testing with commented code something interresting happened. I could step into commented code. Well not exactly the commented code, but the code that was there before my changes. Visual Studio somehow shows me the things, that are changed in the file, but is compiling the old code. Up to now i tried to rebuild, clean and build the project in various combinations, unload and reload the project, Restart Visual Studio and restart Windows. Nothing has changed so far. I Will now proceed to create a new .cs File With the exact same Code. I'm working with VS 2013 Community
add static constructor to your SQLiteToDBLib class and perform all static objects initialization in it:
static SQLiteToDBLib()
{
// initialize static members here
}
If this doesn't give you a clue, try enabling CLRE exceptions-break in visual-studio:
DEBUG
Exceptions
Check the 'Common Language Runtime Exceptions' option (under the 'Thrown' column)
Press OK
Restart your app and try again

Assembly.GetEntryAssembly() in NUnit

I am using NUnit to test one functionality where I need to load XML file to object. The XML file is in location of the Console Application.
I have Following method where configuration will be read :
public string GetConfiguration(TempFlexProcessor processor)
{
var exePath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
var configPath = Path.Combine(Path.GetFullPath(exePath), "configuration");
var configFile = string.Format(#"{0}.xml", processor.GetType().Name);
}
Now in my NUnit Test I have test method where I test GetConfiguration :
[Test]
public void TempFlexProcessorExecuteTest()
{
#region Given
#endregion
#region When
var tempFlexProcessor = new TempFlexProcessor();
var actual = tempFlexProcessor.GetConfiguration(tempFlexProcessor);
#endregion
Assert.AreEqual("path of the file", actual);
}
But System.Reflection.Assembly.GetEntryAssembly() is null, please help.
I used AppDomain.CurrentDomain.BaseDirectory instead of System.Reflection.Assembly.GetEntryAssembly().Location
I suspect the problem is that NUnit is running your tests in a different AppDomain, but without using ExecuteAssembly. From the documentation for Assembly.GetEntryAssembly:
Gets the process executable in the default application domain. In other application domains, this is the first executable that was executed by AppDomain.ExecuteAssembly.
It's not clear which assembly you really want to get - even if this did return something "appropriate" for NUnit, that's likely to be the NUnit executable, which would be well away from any configuration directories you happen to have.
Basically, I think that you should at least provide an alternative way of specifying the configuration directory - and you might want to reconsider whether using GetEntryAssembly is a good idea anyway. (Aside from anything else, it's slightly odd that you're calling GetConfiguration on a processor and passing in another processor... that may be suitable for your design, but it's at least somewhat unusual, given that in your test case you're passing in a reference to the same object.)

Instantiating a .NET class with a reference to a dll causes FileNotFoundException

I've added as reference PDFSharp classes for a project. When I tried to use a method in a class that includes PDFSharp libraries I get a FileNotFoundException that states I'm missing the referenced (PDFSharp) file (while I can assure you the dll file is there).
So I've tried adding and readding and changing the dll version, but still without success. So I switched to PDFClown, a similar purpose library, but the error is the same: FileNotFoundException at runtime.
The exception is thrown even before the method is executed.
Calling method (where the exception is thrown):
//method call: here the debugger stops at the breakpoint
ReportUtils.CreateFile(tempDirectoryPath ecc);
Called method:
using org.pdfclown.documents;
using org.pdfclown.documents.contents.composition;
using org.pdfclown.documents.contents.fonts;
using clownFiles = org.pdfclown.files;
class ReportUtils
{
public static void CreateFile(/*parameters*/)
{
//the debugger never enter here: the exception is thrown before
string filename = Settings.ReportFileName + String.Format("{0:yyyyMMddHHmmssffff}", DateTime.Now);
//ecc
}
//...
Why is this exception showing up? Am I doing something wrong adding the references?
I could reproduce your issue by setting the option Copy Local on my library to false. Please, make sure this boolean is set to true..

Is it possible to use Gephi compiled with IKVM in a website?

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

How do I pass references as method parameters across AppDomains?

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.

Categories