views:

176

answers:

4

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?

A: 

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).

Bradley Smith
A: 

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.

Jay
A: 

Try to replace the line:

XmlSerializer x = new XmlSerializer(c.GetType()); 

with:

XmlSerializer x = new XmlSerializer(c.GetType(), new Type[] { typeof(DefaultValuesContainer), typeof(ClientDefaultValuesContainer) }); 
Dinhduy
A: 

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.

Kell
Well, all the assemblies are in the same directory, so I don't think that's the case here.
Robert Rossney
Hi Robert, take a look at this site: http://msdn.microsoft.com/en-us/library/aa302290.aspx It has a number of excelent troubleshooting ideas that refer to the probelm that you are getting and reasons why it could be occuring. There is also a link to a tool that should help you troubleshoot this. AS we do not have access to all your code, we can't do this for you, but I wuold be very interesting to know what the outcome of this little adventure is :) You will want to look at the "Exceptions in the constructor" section
Kell
Just thought of something: NUnit runs in a temporary cache location and it may not have the appropriate privileges to load all assemblies and generate classes. But read the above URL, it has a lot more routes of investigation or you.
Kell