pardahlman / RawRabbit

A modern .NET framework for communication over RabbitMq
MIT License
747 stars 144 forks source link

Serializing published message behaviour changed #199

Closed jayrulez closed 7 years ago

jayrulez commented 7 years ago

I believe this issue may be as a result of my updated environment (dotnet 1.1.0).

I had a message which had an object type property.

    public class FcmMessage : IMessage
    {
        public Dictionary<string, string> RegistrationIds { get; set; }
        public string Type { get; set; }
        public object Data { get; set; }
    }

This used to work properly until the upgrade.

Now the consumer cannot run because internally rawrabbit cannot deserialize the Data property.

The data property assembly name will be serialized as a part of the message pushed to the queue.

{"$id":"3","$type":"Caricoin.Core.Messages.FcmMessage, Caricoin.Core.Messages","registrationIds":{"$id":"4","$type":"System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[System.String, System.Private.CoreLib]], System.Private.CoreLib","fake":"Android"},"type":"TestMessage","data":{"$id":"5","$type":"<>f__AnonymousType1`1[[System.Int32, System.Private.CoreLib]], Caricoin.API","value":123}}

The consumer cannot deserialize it because it does not know about the assembly of the publisher.

"$id":"5","$type":"<>f__AnonymousType1`1[[System.Int32, System.Private.CoreLib]], Caricoin.API"
pardahlman commented 7 years ago

Hello, @jayrulez!

Thanks for reporting this, and for the thorough troubleshooting. We've been discussing message serialization over at the Slack channel, and it looks like we are going to change the TypeNameHandling setting from TypeNameHandling.All to TypeNameHandling.Auto. I believe that change will mitigate your problem.

If you want to try it yourself today, you could create a custom ISerializer like this

    public class CustomSerializer : RawRabbit.Serialization.JsonSerializer
    {
        public CustomSerializer() : base(new Newtonsoft.Json.JsonSerializer
        {
            TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
            Formatting = Formatting.None,
            CheckAdditionalContent = true,
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            ObjectCreationHandling = ObjectCreationHandling.Auto,
            DefaultValueHandling = DefaultValueHandling.Ignore,
            TypeNameHandling = TypeNameHandling.Auto,
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
            MissingMemberHandling = MissingMemberHandling.Ignore,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            NullValueHandling = NullValueHandling.Ignore
        }) { }
    }

And the register it in the client

builder.RegisterRawRabbit(new RawRabbitOptions
{
    DependencyInjection = ioc => ioc.AddSingleton<ISerializer, CustomSerializer>()
});

Let me know if you try this solution, and if it helps!

jayrulez commented 7 years ago

is this for 1.10.3 or 2.0? I am on 1.10.3 and unable to update now because we have a release coming up soon.

I tried this

            services
                .AddRawRabbit<AdvancedMessageContext>(config => {
                    config.SetBasePath(_rootPath)
                        .AddJsonFile("rawrabbit.json");
                }, container => {
                    container.AddSingleton(LoggingFactory.ApplicationLogger);
                    container.AddSingleton<IConfigurationEvaluator, AttributeConfigEvaluator>();
                    container.AddSingleton<IMessageSerializer, TypeSerializer>();
                });

but getting the same behaviour.

pardahlman commented 7 years ago

Ah - my bad. For 1.10.3, you should be able to register the Newtonsoft.Json.JsonSerializer in you container func to update the settings

jayrulez commented 7 years ago
{"$id":"6","registrationIds":{"$id":"7","fcm_registration_id":"Android"},"type":"TestMessage","data":{"$id":"8","$type":"<>f__AnonymousType1`1[[System.String, System.Private.CoreLib]], Caricoin.API","name":"Robert"},"serializedData":"{\"name\":\"Robert\"}"}

The type of the message is not serialized but the assembly of the anonymous type is.

So the issue is still there:

services
                .AddRawRabbit<AdvancedMessageContext>(config =>
                {
                    config.SetBasePath(_rootPath)
                        .AddJsonFile("rawrabbit.json");
                }, container =>
                {
                    container.AddSingleton(LoggingFactory.ApplicationLogger);
                    container.AddSingleton<IConfigurationEvaluator, AttributeConfigEvaluator>();
                    container.AddTransient<IMessageSerializer, TypeSerializer>();
                    container.AddTransient(c => new JsonSerializer
                    {
                        TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
                        Formatting = Formatting.None,
                        CheckAdditionalContent = true,
                        ContractResolver = new CamelCasePropertyNamesContractResolver(),
                        ObjectCreationHandling = ObjectCreationHandling.Auto,
                        DefaultValueHandling = DefaultValueHandling.Ignore,
                        TypeNameHandling = TypeNameHandling.Auto,
                        ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                        MissingMemberHandling = MissingMemberHandling.Ignore,
                        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                        NullValueHandling = NullValueHandling.Ignore

                    });
                });
pardahlman commented 7 years ago

I took the liberty to format the json payload.

    {
        "$id": "6",
        "registrationIds": {
            "$id": "7",
            "fcm_registration_id": "Android"
        },
        "type": "TestMessage",
        "data": {
            "$id": "8",
            "$type": "<>f__AnonymousType1`1[[System.String, System.Private.CoreLib]], Caricoin.API",
            "name": "Robert"
        },
        "serializedData": "{\"name\":\"Robert\"}"
    }

Looking closer at it, I'm wondering how you declare data. Do you perhaps do something like

new FcmMessage
{
    Data = new
    {
        name = "Robert"
    }
}

If that's the case, the json is actually as expected (even though I don't agree: https://github.com/JamesNK/Newtonsoft.Json/issues/1012). To resolve this, you should use declared types (rather than anonymous ones).

jayrulez commented 7 years ago

Yes, that's exactly what's happening.

Thanks for the pointer.