0

I have a Streaming WCF service. It receives a stream of a serialized class called ContentObjectData. The bytes I receive in from the stream I have temporarily placed in an ArrayList as I don't know how big the Stream is and to be honest I don't know what to do with them anyway.

The ContentObjectData Class:

[Serializable]
    public class ContentObjectData
    {
     string Hash { get; set; }
     string Data { get; set; }
     string FileName { get; set; }
     string DisplayImage { get; set; }
    }

This is the Service's Method that receives the stream from the client.

[OperationContract]
        public void SendContentObject(Stream data)
        {
            ArrayList alBytes = new ArrayList();
            Console.WriteLine("Client connected");
            int count;
            byte[] buffer = new byte[4096];
            while ((count = data.Read(buffer, 0, buffer.Length)) > 0)
            {
                alBytes.AddRange(buffer);
            }
            long i = alBytes.Count;
            data.Close();
        }

At this moment in time I am sending an Image for testing using the following methods:

 private void btnLoadImage_Click(object sender, EventArgs e)
        {
            DialogResult dr = OFD.ShowDialog();
            if (dr == DialogResult.OK)
            {
                foreach (string filename in OFD.FileNames)
                {
                    try
                    {
                        ContentObject co = new ContentObject();

                        co.Data = LoadFile(filename);
                        co.Hash = Hashing.HashString(co.Data);
                        co.DisplayImage = co.Data;
                        co.FileName = co.Hash;
                        Stream stream = SerializeToStream(co);                        
                        SendContentObject(stream);
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }

                }
            }
        }

        private void SendContentObject(Stream stream)
        {
            NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, false);
            // TimeSpan.MaxValue is interpreted with a rounding error, so use 24 days instead
            binding.SendTimeout = TimeSpan.FromDays(24);
            binding.TransferMode = TransferMode.Streamed;
            ChannelFactory<RaptorStreamingHost> factory = new ChannelFactory<RaptorStreamingHost>(
                binding, new EndpointAddress("net.tcp://ccs-labs.com:804/"));
            RaptorStreamingHost service = factory.CreateChannel();
            service.SendContentObject(stream);
            ((IClientChannel)service).Close();
        }

        private string LoadFile(string filename)
        {
            return Hashing.BytesToString(File.ReadAllBytes(filename));
        }


        public static Stream SerializeToStream(object objectType)
        {
            MemoryStream stream = new MemoryStream();
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, objectType);
            stream.Position = 0L; // REMEMBER to reset stream or WCF will just send the stream from the end resulting in an empty stream!
            return (Stream)stream;
        }

I have this to DeSerialize but it doesn't make much sense to me:

public static object DeserializeFromStream(MemoryStream stream)
        {
            IFormatter formatter = new BinaryFormatter();
            stream.Seek(0, SeekOrigin.Begin);
            object objectType = formatter.Deserialize(stream);
            return objectType;
        }

How do I convert the ArrayList of Bytes (I guess DeSerialize them) into a New ContentObject?

Update One Ah So close! Ok in this method

public static ContentObjectData DeserializeFromStream(MemoryStream stream)
        {
            IFormatter formatter = new BinaryFormatter();
            stream.Seek(0, SeekOrigin.Begin);
            ContentObjectData objectType = (ContentObjectData)formatter.Deserialize(stream);
            return objectType;
        }

I have a problem. The Deserializer can not find the ContentObjectData because it is looking in the client's Namespace for ContentObjectData and not this Host's namespace.

Dave Gordon
  • 1,815
  • 4
  • 30
  • 52
  • what type is your object 'objectType' ( confusing name , it's not a type but an instance of some type). I'm betting it's your ArrayList . just try to cast it . var arrayList = (ArrayList)objectType; – eran otzap May 23 '15 at 22:15
  • The type is ContentObjectData – Dave Gordon May 23 '15 at 23:28
  • but you said you serialized an ArrayList , after desiralizing you get a ContentObjectData , R U SURE ? – eran otzap May 23 '15 at 23:44
  • No - I serialized the ContentObjectData and Sent it as a stream. On Receiving the stream - I saved the bytes into an arraylist – Dave Gordon May 24 '15 at 00:11

1 Answers1

1

Firstly, don't use an ArrayList to store your bytes. Since ArrayList is non-generic each individual byte will be boxed and a pointer to the byte saved in the array, which will use 5 (32 bit) or 9 (64 bit) times more memory than necessary.

Instead, you can copy the Stream to a local MemoryStream, then save the underlying byte [] array:

    public static void CopyStream(Stream input, Stream output)
    {
        byte[] buffer = new byte[32768];
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            output.Write(buffer, 0, read);
        }        
    }

    [OperationContract]
    public void SendContentObject(Stream data)
    {
        Console.WriteLine("Client connected");
        var memoryStream = new MemoryStream();
        using (data)
            CopyStream(data, memoryStream);
        byte [] alBytes = memoryStream.ToArray();
    }

Then later you can turn the byte [] array back to a MemoryStream for deserialization:

    public static object DeserializeFromStream(byte [] allBytes)
    {
        using (var stream = new MemoryStream(allBytes))
            return DeserializeFromStream(stream);
    }

    public static object DeserializeFromStream(MemoryStream stream)
    {
        IFormatter formatter = new BinaryFormatter();
        stream.Seek(0, SeekOrigin.Begin);
        object objectType = formatter.Deserialize(stream);
        return objectType;
    }

(Or, just keep the original MemoryStream and pass it around.)

Update

BinaryFormatter serializes full .Net type information (i.e. the fully qualified type name) to and from the serialization stream. If the receiving system doesn't have a type with exactly the same name, in exactly the same namespace, in exactly the same assembly, then deserialization fails.

If this is your situation, you have the following workarounds:

  1. Extract a shared DLL containing the type in question and link it into both client and server; problem solved.

  2. Write a SerializationBinder to map the type assemblies and names. See here and also here or here for instructions how to do it.

  3. Consider a different, more contract-oriented binary format. Bson is one option. protobuf-net is another, albeit one I have not used. More here.

Community
  • 1
  • 1
dbc
  • 104,963
  • 20
  • 228
  • 340