microsoft / service-fabric

Service Fabric is a distributed systems platform for packaging, deploying, and managing stateless and stateful distributed applications and containers at large scale.
https://docs.microsoft.com/en-us/azure/service-fabric/
MIT License
3.03k stars 401 forks source link

DataContract Serialization of DateTimeOffset #636

Open jhualpa opened 5 years ago

jhualpa commented 5 years ago

Hi, I have 2 actors, the first one sends the following model to the second. Both use service remoting 2.1.

[DataContract] [KnownType (typeof (DateTimeOffset))] public class TheModel { [DataMember] public DateTimeOffset TheDate { get; set; } ... }

I get a System.Runtime.Serialization.SerializationException with the following message: One or more errors occurred. Expecting element 'DateTimeOffset' from namespace 'http://schemas.datacontract.org/2004/07/System'.. Encountered 'Element' with name 'dateTime', namespace 'http://schemas.microsoft.com/2003/10/Serialization/'.

What I am missing or doing wrong? According to the docs, DateTimeOffset should be added to the KnownTypes of the DataContractSerializer.

suchiagicha commented 5 years ago

@jhualpa Which doc you are referring here? According to the docs, DateTimeOffset should be added to the KnownTypes of the DataContractSerializer.

jhualpa commented 5 years ago

@suchiagicha Doc: https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/data-contract-known-types Sure, that is why I do this [KnownType (typeof (DateTimeOffset))], can you provide a simple example of what out mean "DateTimeOffset should be added to the KnownTypes of the DataContractSerializer" in an actor context, I thought it only means to decorate the DataContract only.

suchiagicha commented 5 years ago

To add known types , you need to use custom serialization by extending existing serialization class and then adding knowntypes.

Here is 1 sample of how to add known types but this is for old serialization class. Make sure you instead use this one.

jhualpa commented 5 years ago

@suchiagicha Thanks, I have implemented it like this:

public class CustomSerializationProvider : WrappingServiceRemotingDataContractSerializationProvider
    {
        private readonly IEnumerable<Type> _myTypes;

        public CustomSerializationProvider()
        {
            _myTypes = new List<Type>
            {
                typeof(DateTimeOffset)
            };
        }
        protected override DataContractSerializer CreateRemotingRequestMessageBodyDataContractSerializer(
            Type remotingRequestType,
            IEnumerable<Type> knownTypes)
        {
            var newKnownTypes = knownTypes.Concat(_myTypes);
            return base.CreateRemotingRequestMessageBodyDataContractSerializer(remotingRequestType, newKnownTypes);
        }

        protected override DataContractSerializer CreateRemotingResponseMessageBodyDataContractSerializer(
            Type remotingResponseType,
            IEnumerable<Type> knownTypes)
        {
            var newKnownTypes = knownTypes.Concat(_myTypes);
            return base.CreateRemotingRequestMessageBodyDataContractSerializer(remotingResponseType, newKnownTypes);
        }
    }

The reminder of the code, I have used the reference project you provided to create the proxy and the listeners, the same error keeps emerging only when I deploy to the Service Fabric cluster on Azure, with the local emulator it works fine, but the previous solution also did locally. What else I may be missing ?

suchiagicha commented 5 years ago

@jhualpa Could you paste your code on how you are using this serializer in proxy and listener code?

jhualpa commented 5 years ago

@suchiagicha Sure, here is the proxy call from Sender actor which retrieves the entity with the DateTimeOffset member:

var proxyFactory = new ActorProxyFactory(c => new FabricTransportActorRemotingClientFactory(
                null,
                serializationProvider: new CustomSerializationProvider()));

var proxy = proxyFactory.CreateActorProxy<IReceiver>(actorId)

// now I call the actor interface that only accepts an entity with a DateTimeOffset member and returns a Task
proxy.Transfer(entity);

Here is the listener code from Receiver actor:

internal class ReceiverActorService : ActorService
    {
        public RececiverActorService(StatefulServiceContext context, ActorTypeInformation typeInfo, Func<ActorService, ActorId, ActorBase> factory)
            : base(context, typeInfo, factory)
        { }

        protected override Task OnOpenAsync(ReplicaOpenMode openMode, CancellationToken cancellationToken)
        {
            return Task.FromResult(true);
        }

        protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
        {
            return new[]
            {
                new ServiceReplicaListener(c =>
                {
                    return new FabricTransportActorServiceRemotingListener(this, new CustomSerializationProvider());
                })
            };
        }
suchiagicha commented 5 years ago

@jhualpa Did you change the actor registration(Program.cs) file to use new ReceiverActorService?

Could you try debugging client and service code and see for both customserializer is getting called?

jhualpa commented 5 years ago

@suchiagicha Of course I changed the actor registration to use the ReceiverActorService, when I debug locally with the emulator with 1 or 5 nodes CustomSerializer gets called, all fine in dev environment. Serialization happens, the problem is when I deploy to production, I get the SerializationException. Even I am able to debug successfully without the CustomSerializer and the listener, the problem is production, that is why this issue is so misleading.