patrick31415 / protobuf-net

Automatically exported from code.google.com/p/protobuf-net
Other
0 stars 0 forks source link

Problems passing enums via WFC service when using ProtoEndpointBehavior #307

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
Hello,

I am attempt to use the ProtoEndpointBehavior to replace the DataContract 
serializer on a large project using WCF services.

e.g.  client.Endpoint.Behaviors.Add(new ProtoEndpointBehavior());

I have had some issues with exceptions being throws for IEnumerable<T> but have 
managed to get round this by changing to IList<T>

The main issue is with enums. I am getting an "Invalid wire-type" error 
whenever I pass enum in as a parameter to a service method. If I cast the enum 
to an int then everything works fine however this is not ideal.

This happens with enum defined as follows: 

    [DataContract]
    public enum Thingami
    {
        [EnumMember]
        This = 0,
        [EnumMember]
        That = 1,
        [EnumMember]
        TheOther = 2,
    }
or without the numbers or attributes

I wrote a little unit test to test serialising and then deserializing the enum 
directly with the serializer and this worked fine. 

I then changed the TestWcfClient and TestWcfServer projects in SVN as follows:

TestWcfDto.IService1.cs
-----------------------

using System.Runtime.Serialization;
using System.ServiceModel;

namespace TestWcfDto
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        Thingami GetEnumUsingDataContract(Thingami enumValue);

    }

    [DataContract]
    public enum Thingami
    {
        [EnumMember]
        This = 0,
        [EnumMember]
        That = 1,
        [EnumMember]
        TheOther = 2,
    }

}

TestWcfServer.Servce1svc.cs
---------------------------

using TestWcfDto;

namespace TestWcfServer
{
    public class Service1 : IService1
    {
        public Thingami GetEnumUsingDataContract(Thingami value)
        {
            Thingami output = (value == Thingami.This) ? Thingami.That : Thingami.TheOther;
            return output;
        }
    }
}

TestWcfClient.Program.cs
-------------------------

using System;
using ProtoBuf.ServiceModel;
using TestWcfClient.ServiceReference1;
using TestWcfDto;
namespace TestWcfClient
{
    class Program
    {
        static void Main()
        {

            using (var client = new Service1Client())
            {
                client.Endpoint.Behaviors.Add(new ProtoEndpointBehavior());
                var resp = client.GetEnumUsingDataContract(Thingami.That);
                Console.WriteLine(resp.ToString());

            }
        }
    }
}

The config files are unchanged. 

This code did not throw an exception however resp is "Thingami.This" not 
"Thingami.TheOther" as expected. The value received by the service function is 
also "Thingami.This". The serialisation seems to default the first item in the 
enum.  

I am using the latest version 510 of protobuf.net pulled from SVN in Visual 
Studio 2010 on Windows 7. 

Original issue reported on code.google.com by dsparkp...@gmail.com on 31 Jul 2012 at 7:42

GoogleCodeExporter commented 9 years ago
I've looked into this further today and found a possible fix for the issue.

Initially the TestWcfServer project did not have ProtoEndpointBehavior attached 
to the server (as this is commented out in the web.config). After putting this 
back and rerunning the test project, an "Invalid wire-type" exception is thrown 
as with my main project.

Debugging into the protobuf.net code I found that an assertion fails on this 
line in the Read method of TagDecorator.cs 

    Helpers.DebugAssert(fieldNumber == source.FieldNumber);

Here fieldNumber is 1 and source.FieldNumber is 0.

Tracing back through the call stack to ReadObject method of the 
XmlProtoSerializer I find the following code:

        if (isList)
                {
                    result = model.Deserialize(ms, null, type, null);
                }
                else
                {
                    using (ProtoReader protoReader = new ProtoReader(ms, model, null))
                    {
                        result = model.Deserialize(key, null, protoReader);
                    }
                }

which deserialises the stream differently for list types. 

If I add the following clause to the if statement so that enums are 
deserialised as lists then the issue seems to be fixed

        if (isList || type.IsEnum) 

Please look into this and check that this doesn't have any side effects. You 
can test the behaviour using the code sample the issue description above.

Thanks
Dave Sparks

Original comment by dsparkp...@gmail.com on 1 Aug 2012 at 2:21

GoogleCodeExporter commented 9 years ago
This is possibly related to issue 266

Original comment by dsparkp...@gmail.com on 1 Aug 2012 at 2:29

GoogleCodeExporter commented 9 years ago
 will need to investigate before concluding too much, but thanks for your description - I have a full repro now (the first passes, the second fails as you describe) - I will look as soon as I can:

        public enum Foo
        {
            A,
            B,
            C
        }
        [ProtoContract]
        public class FooWrapper
        {
            [ProtoMember(1)]
            public Foo Foo { get; set; }
        }
        [Test]
        public void TestRoundTripWrappedEnum()
        {
            var ser = new XmlProtoSerializer(RuntimeTypeModel.Default, typeof(FooWrapper));
            var ms = new MemoryStream();
            ser.WriteObject(ms, new FooWrapper { Foo = Foo.B });
            ms.Position = 0;
            var clone = (FooWrapper)ser.ReadObject(ms);

            Assert.AreEqual(Foo.B, clone.Foo);
        }
        [Test]
        public void TestRoundTripNakedEnum()
        {
            var ser = new XmlProtoSerializer(RuntimeTypeModel.Default, typeof (Foo));
            var ms = new MemoryStream();
            ser.WriteObject(ms, Foo.B);
            ms.Position = 0;
            var clone = (Foo)ser.ReadObject(ms);

            Assert.AreEqual(Foo.B, clone);

        }

Original comment by marc.gravell on 1 Aug 2012 at 6:42

GoogleCodeExporter commented 9 years ago
Hi,

The same problem happened to me today.

The quick fix of special-casing enums like in Comment 1 works but I don't 
really want to go in production with a hack on some code I don't fully 
understand.

Do you plan on looking into this soon or should I look deeper and try to solve 
it ?

For now I don't really understand the serialization process, specifically why 
TagDecorator is writing a type header in Write but expecting the type header to 
have already been readed in Read (Doesn't work in this case as the reader is 
still in a not-initialized state).

Thanks.

Original comment by virtualb...@gmail.com on 6 Nov 2012 at 8:55

GoogleCodeExporter commented 9 years ago
Hi,

same problem occurs, if you simply use a naked enum as return value like this:

[OperationContract, ProtoBehavior]
SimpleEnum GetNakedEnum();

(using r622)
Thanks.

Original comment by olischul...@gmail.com on 1 Feb 2013 at 8:10

GoogleCodeExporter commented 9 years ago
Hi again,

I think poster of #4 is correct. There is a missing call to ReadFieldHeaders() 
in TagDecorator.Read(). After I added the call it was working in my 
environment. Find attached the diff for TagDecorator.cs to r262. I also added 
the call to TagDecorator.EmitRead().

Regards,
Oliver.

Original comment by olischul...@gmail.com on 1 Feb 2013 at 2:16

Attachments:

GoogleCodeExporter commented 9 years ago
It would not normally be correct to query the field-header there. I'll take a 
look, but that sounds dubious at first glance.

Original comment by marc.gravell on 1 Feb 2013 at 2:45