0

I have classes that I load dynamically (using mef), they are some sort of handlers (These work fine).

These handlers can receive (there's a method that receives a packet and returns one) packets, that all implement the same interface (let's say IPacket) and return an answer (an IPacket as well).

I receive these packets (through a tcp connection) and my program is not familiar with the specific class (although it is of a known interface - IPacket, but a different implementation).

So when I try to deserialize a packet (to hand it over to the handler), I get an exception.

  • I Serialize into bytes a deserialize back into an object (using Binary serializer) *

The only way I can access the packet implementations should be dynamic, as the dlls are stored in a folder I can access.

I thought that I could just use Assembly.LoadFrom in order to familiarize my program with the packets, because I don't even need to instatiate them, just deserialize (get an instance of the interface) and hand over to the handler, which in would then return an answer and I'd send it again.

But it didn't work..

I assume that I need to find a way to add a reference to these dlls during runtime and then my program will recognize them.. (I thought that maybe using the Export(typeof()..) on the pack classes would help, would it?)

The exception I am getting when trying to deserialize is that the class name is not found..

*I have edited the topic and I hope it is a little clearer, thank you =]


  • Edit:

I am not saying this is solvable with mef, I just though that it could be. It is definitely solvable with reflection. I have the folder that contains all the classes I want my program to recognize during runtime, I just need to make it "load" them during runtime, as if I'd added a reference to the dlls in that same folder.

So basically what I need to do is:

Load all the implementations of a certain interface (IPacket in this example) from a folder. I do not need to instantiate them, but only to receive them as variables without getting an exception that such type isn't in my project.


So I have found this snippet:

static constructor() {
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

}

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
    Assembly ayResult = null;
    string sShortAssemblyName = args.Name.Split(',')[0];
     Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
     foreach (Assembly ayAssembly in ayAssemblies) {
        if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0]) {
             ayResult = ayAssembly;
             break;
        }
     }
     return ayResult;
}

It seems to be close to what I am looking for but I don't actually understand this. Is there a way to midify this so that it would load only the dlls in a certain folder? Will my program then be familiar with the dlls?

Also, an explanation of the code would be much appreciated.

user2599803
  • 299
  • 1
  • 3
  • 7
  • 6
    What deserializer are you using? What exact exception are you getting? – default.kramer Oct 17 '13 at 17:24
  • 1
    I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Oct 17 '13 at 18:03
  • Can you explain a little more? You are exporting the type but it isn't being loaded by MEF or what is going on? – Mike Cheel Oct 17 '13 at 18:06
  • Just trying to understand what is going on here: You are serializing Type A on computer 1 to a packet, on Computer 2 you want to deserialize the packet containing the unknown type A. What do you want to achieve by using MEF here? – Mare Infinitus Oct 17 '13 at 18:46
  • The scenario seems to be clear. The application running on Computer B wants to deserialize a type A that it has never heard about. How should that work? Perhaps some sample code will help to understand how you are using MEF to solve that. – Mare Infinitus Oct 17 '13 at 18:52

2 Answers2

1

MEF will definitely help you, but not only it. You have to use the ISerializationSurrogate. Most of the explanations for the below you can find here.

So, given the following definition for the packet interface:

public interface IPacket
{
    string GetInfo();
}

You have the following implementations, residing in their own assembly:

[Export(typeof(IPacket))]
class FirstPacket : IPacket
{
    public FirstPacket()
    {
        Name = "Joe";
    }

    public string Name { get; set; }

    public string GetInfo()
    {
        return "Name: " + Name;
    }
}

[Export(typeof(IPacket))]
class SecondPacket : IPacket
{
    public SecondPacket()
    {
        Measurement = 42.42m;
    }

    public decimal Measurement { get; set; }

    public string GetInfo()
    {
        return "Measurement: " + Measurement;
    }
}

Now we will define another interface, something like:

public interface IPacketSurrogateProvider
{
    void AddSurrogate(SurrogateSelector toSelector);
}

And the matching implementations, in the same assembly where concrete packets are defined:

[Export(typeof(IPacketSurrogateProvider))]
class FirstPacketSurrogateProvider : IPacketSurrogateProvider, ISerializationSurrogate
{
    public void AddSurrogate(SurrogateSelector toSelector)
    {
        toSelector.AddSurrogate(typeof(FirstPacket), new StreamingContext(StreamingContextStates.All), this);
    }

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Name", ((FirstPacket)obj).Name);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        ((FirstPacket)obj).Name = info.GetString("Name");

        return obj;
    }
}

[Export(typeof(IPacketSurrogateProvider))]
class SecondPacketSurrogateProvider : IPacketSurrogateProvider, ISerializationSurrogate
{
    public void AddSurrogate(SurrogateSelector toSelector)
    {
        toSelector.AddSurrogate(typeof(SecondPacket), new StreamingContext(StreamingContextStates.All), this);
    }

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Measurement", ((SecondPacket)obj).Measurement);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        ((SecondPacket)obj).Measurement = info.GetDecimal("Measurement");

        return obj;
    }
}

And now, in an assembly which does have reference to the one with the interfaces, but not to the one with the implementations, and having the same deployment folder as both of the above:

public static void Test()
{
    var container = new CompositionContainer(new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)));

    var packets = container.GetExportedValues<IPacket>().ToArray();
    var packetSurrogateProviders = container.GetExportedValues<IPacketSurrogateProvider>();

    var surrogateSelector = new SurrogateSelector();
    foreach (var provider in packetSurrogateProviders)
    {
        provider.AddSurrogate(surrogateSelector);
    }

    var deserializedPackets = new IPacket[] { };
    using (var stream = new MemoryStream())
    {
        var formatter = new BinaryFormatter {SurrogateSelector = surrogateSelector};

        formatter.Serialize(stream, packets);

        stream.Position = 0;

        deserializedPackets = (IPacket[])formatter.Deserialize(stream);
    }

    foreach (var packet in deserializedPackets)
    {
        Console.WriteLine("Packet info: {0}", packet.GetInfo());
    }
}

Which produces:

Packet info: Name: Joe

Packet info: Measurement: 42.42

Community
  • 1
  • 1
galenus
  • 2,087
  • 16
  • 24
  • Thank you for your answer, but I have some questions. – user2599803 Oct 18 '13 at 08:40
  • Thank you for your answer, but I have some questions. Why do I need that surrogate class? I could use mef to load all IPackets into a list of IPacket without it and I'm pretty sure that my program would then recognize them.. I just thought that since I don't actually need them in a list, I would just let my program recognize them somehow during runtime.. It seems to me that it should be much simpler, I have the path and can Load the assembly, it shouldn't require so much effort.. – user2599803 Oct 18 '13 at 08:47
  • You have to understand that exporting/loading the packets through MEF in my example is just for the sake of convenience. I could also serialize the packets from inside their own assembly and then deserialize on the receiving side, without adding reference. Your problem is not to recognize the type, because, as I understand, it's enough that you have the IPacket interface. Your problem is making the BinaryFormatter recognize the type, and that's what the surrogates are for. Another question is why don't you use WCF for communication? – galenus Oct 18 '13 at 12:16
  • I was instructed not to use wcf. And I think that the problem is that I can't recognize the type. That's what the exception says - it can't find the type, it is not familiar with that implementation of the IPacket class and that's why it can't return me that class to be held in a variable. That's the reason I tried to use Assembly.Load - I though it was equal to referencing the dll, but in runtime.. – user2599803 Oct 19 '13 at 09:43
  • Then try the code I provided, but without the surrogates, just packet exports. MEF loads the assembly, as you would expect, not sure it will be enough in your case, though. – galenus Oct 19 '13 at 11:10
  • So if I add Export on a class and import it on another (not into an object) it would recognize the dll? I have tried using mef in order to load all the exported packets into a list. Even after they're in a list, i get an exception that the the dll from wich the packet class has been taken cannot be found.... What am I missing? should I import them into the class (put the importmany above the class instead of the property?) – user2599803 Oct 19 '13 at 17:02
  • That’s what I meant, even after you've loaded the assembly, it's not enough for deserialization. You have to somehow let the BinaryFormatter know about the type. And surrogates are the way. Maybe I miss something and there's another approach... Have you tried the solution proposed by default.kramer? – galenus Oct 19 '13 at 18:35
0

Take a look at this example, in particular, the BindToType method. I think you could check if the assembly is loaded. If it is not, then load it using reflection (or MEF if you wish). Then simply return base.BindToType and it should work. (Unless the assembly versions are different between the two machines, then you may need to find the type yourself.)

Community
  • 1
  • 1
default.kramer
  • 5,943
  • 2
  • 32
  • 50