Code - Type Resolution Problems

Friday, September 30, 2005

I developed a Wizard framework in C# which is laid out as follows:



The basic understanding is that there's a base form in Wizbase which is used to build wizard pages. Navigation through the wizard pages, and various other utility functions, is handled by WizardMain. I needed to be able to store an 'execution' of the wizard (user's selections on each page from beginning to end) and wanted to do so generically. Serialize the data maybe? That was my first thought. Here's the kicker: the data to be stored was in custom classes down inside the arbitrary wizard code. This is a problem because the framework is compiled without knowledge of 'arbitrary wizard' code, and thus does not have a reference to any of its classes. How then was I to serialize and deserialize Types of which I had no knowledge?

One idea was to move the code into the arbitrary wizard, which has direct references. I disliked that, as it left the work to the wizard programmer, as opposed to the framework programmer. So, I started doing tests using the BinaryFormatter. Guess what? It could serialize it to a file no problem. I assumed it used reflection to do so, and was not that surprised once I thought about it at little. So I did the deserialization code.


// deserialize the memory stream into the original object and return
BinaryFormatter binaryFormatter = new BinaryFormatter();
return binaryFormatter.Deserialize(memoryStream);


Guess what? No workie. There were numerous problems.

First, I learned the BinaryFormatter stored, along with the raw serialization, the version number, culture, etc. of the assembly from which the Type came. That meant every time I recompiled the project, that nifty little version number line in the AssemblyInfo.cs file changed the version number on me, and thus the serialized format couldn't be matched up to any classes in the running code.

Culprit 1: [assembly: AssemblyVersion("1.0.*")]

I changed the asterisk to a 1, which fixed the immediate problem, but I wasn't fond of the entire concept, so I switched to the slightly more forgiving (and hand editable) SoapFormatter. So far so good.

Next problem: Still wouldn't deserialize. File not found error. File not found? That's odd I thought. Turns out the serialized SOAP ASCII mush knew what class it was, and the CLR was trying to find the file from whence it came. Kinda neat actually. My test *.sln file had 3 projects being compiled into their own folders, so I added a post compile command line to copy the arbitrary wizard dll into the same folder as the wizardmain.exe. Guess what? It worked. Deserialization of an unreferenced type, made possible by the Type resolution of the CLR.

Om ... wait a sec. My layout, noted in the pretty Visio diagram above, reminded me that the arbitrary wizard file may not be in the same location as the WizardMain.exe. In fact, that's why the xml file exists: to tell the WizardMain where to get and load the arbitrary wizard file(s) from. Crappy. What now? The CLR can't resolve Types in assemblies in random locations on the target hard drive.

Some research lead me to understand that there's an event raised when the CLR can't resolve a type. I threw in an event hander for it:


// setup a handler for the case where the type to be resolved cannot be
// found by the CLR (ie: containing assembly is not in the current folder)

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(FileResolveEventHandler);

And the associated handler:

public Assembly FileResolveEventHandler(object sender, ResolveEventArgs args) {}

In my code that accepts an intial call from the arbitrary wizard, I just grab the assembly location:

_CallingAssembly = Assembly.GetCallingAssembly();

And inside the event handler just above I just inserted the following single line of code:

// we previously stored a reference to the calling assembly,
// and we know we are attempting to resolve a type in that same assembly
// (some wiz file) therefore, we merely pass that reference back to the
// appdomain via this handler, and we should be good to go

return _CallingAssembly;


And we have it. The WizardMain.Exe can de/serialize unknown types, regardless of location, allowing my framework consumers to store and retrieve anything they want (assuming it has the [Serializable] tag).

Cheers!
TPC

3 Comments:

Anonymous Anonymous said...

um,
*Owch*

what just happened.......

lol

H.

2:59 PM  
Anonymous Anonymous said...

You used the soap formatter in your binary formatter example, dude.

8:17 AM  
Blogger tpcmurray said...

Right you are. It seems I pasted in the code as it was in the end, vs. as it was during my development.

In the meantime, someone actually read that post ... crazy. :)

9:00 AM  

Post a Comment

<< Home