Search code examples
.netdataset.net-remoting

Sending DataSets from .net 4 to .net 3.5 via remoting


I have a service that uses .NET 3.5 and exposes some methods via .NET remoting. Some of those methods expect a DataSet as an argument. When the client is also running .NET 3.5 everything is fine. However, when the client is running .NET 4 I get the following remote exception (on the server):

Could not load file or assembly 'System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or one of its dependencies. The system cannot find the file specified.

So apparently there are two version of DataSets - version 2 and version 4. I have tried creating a separate .NET 3.5 project where the DataSet is created and sent to the server. But when it is loaded in the .NET 4 runtime along with other .NET 4 assemblies, it still uses DataSet version 4.

The weirdest thing is with a different service also running in .net 3.5 I can send version 4 DataSets with no problems. I have not been able to figure out what is different there.

Any insight or solutions would be greatly appreciated.

Update: Microsoft seems to be aware of the problem: See here. Pretty sad though if such a hack is really required to communicate between different .NET versions...


Solution

  • The below code implements a custom BinaryFormatterSink that can be used on the client instead of the BinaryClientFormatterSink. It is based on the microsoft example at MSDN

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Messaging;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Text;
    
    namespace bla
    {
        class VersionConversionClientSinkProvider : IClientChannelSinkProvider
        {
            public IClientChannelSink CreateSink(IChannelSender channel, string url, object remoteChannelData)
            {
                if (Next != null )
                {
                    var nextSink = Next.CreateSink(channel, url, remoteChannelData);
                    if (nextSink != null)
                    {
                        return new VersionConversionClientSink(nextSink);  
                    }
                }
                return null;
            }
    
            public IClientChannelSinkProvider Next { get; set; }
        }
    
        class VersionConversionClientSink : IClientChannelSink, IMessageSink
        {
            public VersionConversionClientSink(IClientChannelSink channel)
            {
                _next = channel;
            }
    
            readonly IClientChannelSink _next;
    
            public IDictionary Properties
            {
                get { return NextChannelSink.Properties; }
            }
    
            public void ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream)
            {
                throw new NotSupportedException();
            }
    
            private void SerializeMessage(IMessage msg, out ITransportHeaders headers, out Stream stream)
            {
                var binaryFormatter = new BinaryFormatter
                    {
                        Binder = new VersionConversionSerializationBinder()
                    };
    
                stream = new MemoryStream();
                binaryFormatter.Serialize(stream, msg);
                stream.Position = 0;
    
                headers = new TransportHeaders();
            }
    
            private IMessage DeserializeMessage(Stream stream)
            {
                var binaryFormatter = new BinaryFormatter
                    {
                        AssemblyFormat = FormatterAssemblyStyle.Simple
                    };
    
                var msg = (IMessage) binaryFormatter.Deserialize(stream);
                stream.Close();
                return msg;
            }
    
            public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg, ITransportHeaders headers, Stream stream)
            {
                throw new NotSupportedException();
            }
    
            public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, object state, ITransportHeaders headers, Stream stream)
            {
                sinkStack.AsyncProcessResponse(headers, stream);
            }
    
            public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
            {
                return _next.GetRequestStream(msg, headers);
            }
    
            public IClientChannelSink NextChannelSink
            {
                get { return _next; }
            }
    
            public IMessage SyncProcessMessage(IMessage msg)
            {
                IMethodCallMessage mcm = msg as IMethodCallMessage;
                try
                {
                    ITransportHeaders headers;
                    Stream stream;
                    SerializeMessage(msg, out headers, out stream);
                    ITransportHeaders responseHeaders;
                    Stream responseStream;
                    NextChannelSink.ProcessMessage(msg, headers, stream, out responseHeaders, out responseStream);
                    if (responseHeaders == null)
                        throw new ArgumentNullException("returnHeaders");
                    else
                        return DeserializeMessage(responseStream);
                }
                catch (Exception ex)
                {
                    return new ReturnMessage(ex, mcm);
                }
            }
    
            public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
            {
                var mcm = (IMethodCallMessage)msg;
                try
                {
                    ITransportHeaders headers;
                    Stream stream;
                    SerializeMessage(msg, out headers, out stream);
                    var channelSinkStack = new ClientChannelSinkStack(replySink);
                    channelSinkStack.Push(this, msg);
                    NextChannelSink.AsyncProcessRequest(channelSinkStack, msg, headers, stream);
                }
                catch (Exception ex)
                {
                    IMessage msg1 = new ReturnMessage(ex, mcm);
                    if (replySink != null)
                        replySink.SyncProcessMessage(msg1);
                }
                return null;
            }
    
            public IMessageSink NextSink
            {
                get { throw new NotSupportedException(); }
            }
        }
    
        class VersionConversionSerializationBinder : SerializationBinder
        {
            string[] frameworkPublicKeyTokens = new string[] {
                    "B7-7A-5C-56-19-34-E0-89",
                    "B0-3F-5F-7F-11-D5-0A-3A",
                    "31-BF-38-56-AD-36-4E-35",
                    "89-84-5D-CD-80-80-CC-91"
                };
    
            bool IsFrameworkAssembly(Assembly assembly)
            {
                foreach (string frameworkToken in frameworkPublicKeyTokens)
                {
                    if (frameworkToken == BitConverter.ToString(assembly.GetName().GetPublicKeyToken()))
                    {
                        return true;
                    }
                }
                return false;
            }
    
            public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
            {
                // To handle arrays
                if (serializedType.IsArray)
                {
                    string elementTypeName;
                    Type elementType = serializedType.GetElementType();
                    BindToName(elementType, out assemblyName, out elementTypeName);
                    StringBuilder typeNameBuilder = new StringBuilder(elementTypeName);
                    typeNameBuilder.Append("[");
                    int arrayRank = serializedType.GetArrayRank();
                    for (int i = 1; i < arrayRank; i++)
                    {
                        typeNameBuilder.Append(",");
                    }
                    if (arrayRank == 1 && serializedType == elementType.MakeArrayType(1))
                    {
                        typeNameBuilder.Append("*");
                    }
                    typeNameBuilder.Append("]");
                    typeName = typeNameBuilder.ToString();
                }
                // To handle generic types
                else if (serializedType.IsGenericType && !serializedType.IsGenericTypeDefinition)
                {
                    string definitionTypeName;
                    Type[] genericParameters = serializedType.GetGenericArguments();
                    BindToName(serializedType.GetGenericTypeDefinition(), out assemblyName, out definitionTypeName);
                    StringBuilder typeNameBuilder = new StringBuilder(definitionTypeName);
                    typeNameBuilder.Append("[");
                    for (int i = 0; i < genericParameters.Length; i++)
                    {
                        if (i > 0)
                        {
                            typeNameBuilder.Append(",");
                        }
                        string parameterTypeName, parameterAssemblyName;
                        BindToName(genericParameters[i], out parameterAssemblyName, out parameterTypeName);
                        typeNameBuilder.AppendFormat("[{0}, {1}]", parameterTypeName, parameterAssemblyName);
                    }
                    typeNameBuilder.Append("]");
                    typeName = typeNameBuilder.ToString();
                }
                // To handle the rest of types
                else
                {
                    assemblyName = serializedType.Assembly.FullName;
                    if (IsFrameworkAssembly(serializedType.Assembly))
                    {
                        assemblyName = assemblyName.Replace("Version=4.0.0.0", "Version=2.0.0.0");
                    }
                    typeName = serializedType.FullName;
                }
            }
    
            public override Type BindToType(string assemblyName, string typeName)
            {
                return null;
            }
        }
    }