rebus-org / Rebus

:bus: Simple and lean service bus implementation for .NET
https://mookid.dk/category/rebus
Other
2.32k stars 363 forks source link

Issue with Rebus #1054

Closed ssbehera-pmap closed 2 years ago

ssbehera-pmap commented 2 years ago

@mookid8000

recently we upgraded to dotnet 6 Rebus : 7.0.0-rc4 , Rebus.RabbitMq : 7.4.2

getting issue with rebus

---> System.FormatException: Could not get .NET type named 'abcd.Api.Core.PubSub.Message, core-pubsub' at Rebus.Serialization.Json.JsonSerializer.GetTypeOrNull(TransportMessage transportMessage) at Rebus.Serialization.Json.JsonSerializer.GetMessage(TransportMessage transportMessage, Encoding bodyEncoding) at Rebus.Serialization.Json.JsonSerializer.Deserialize(TransportMessage transportMessage) at Rebus.Compression.UnzippingSerializerDecorator.Deserialize(TransportMessage transportMessage) at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func1 next) at Rebus.DataBus.ClaimCheck.HydrateIncomingMessageStep.Process(IncomingStepContext context, Func1 next) at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func1 next) at Rebus.Retry.FailFast.FailFastStep.Process(IncomingStepContext context, Func1 next) at Rebus.Retry.Simple.SimpleRetryStrategyStep.DispatchWithTrackerIdentifier(Func`1 next, String identifierToTrackMessageBy, ITransactionContext transactionContext, String messageId, String secondLevelMessageId) --- End of inner exception stack trace ---

we have below serialisation settings

var settings = new JsonSerializerSettings { }; settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; settings.MissingMemberHandling = MissingMemberHandling.Ignore; settings.TypeNameHandling = TypeNameHandling.Auto; settings.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple;

mookid8000 commented 2 years ago

The error

---> System.FormatException: Could not get .NET type named 'abcd.Api.Core.PubSub.Message, core-pubsub'
at Rebus.Serialization.Json.JsonSerializer.GetTypeOrNull(TransportMessage transportMessage)
at Rebus.Serialization.Json.JsonSerializer.GetMessage(TransportMessage transportMessage, Encoding bodyEncoding)
at Rebus.Serialization.Json.JsonSerializer.Deserialize(TransportMessage transportMessage)
(...)

is the message deserializer complaining that it wasn't able to find the type abcd.Api.Core.PubSub.Message, core-pubsub.

You should check that your program has access to the "core-pubsub" assembly, and that the namespace "abcd.Api.Core.PubSub" does in fact contain a class named "Message".

Could you check that and tell me what you find? 🙂

ssbehera-pmap commented 2 years ago

@mookid8000
The assembly and namespace is different during serialization and deserialization . The solution / project is completely different for serialization and deserialization . Question is why it's looking for that type , how to avoid it.

solution A sends messages to rabbitmq using rebus . solution B subscribing to the specific channel & receives the message , using rebus .

while in subscriber we have similar class , but not exactly the same name space , however in subscriber side message handler we have used dynamic

var subscriber = new BuiltinHandlerActivator();
            subscriber.Handle<dynamic>(async msg => { await ProcessIncomingMessage(msg, config); });

            var clientProperties = new Dictionary<string, string>() { { "connection_name", config.QueueName } };
            var settings = new JsonSerializerSettings { };
            settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            settings.MissingMemberHandling = MissingMemberHandling.Ignore;
            settings.TypeNameHandling = TypeNameHandling.Auto; 
            settings.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple;   //<-- this should not look for same type .

            Configure.With(subscriber)
                    .Logging(l => l.ColoredConsole())
                    .Transport(t => t.UseRabbitMq(config.ConnectionString, config.QueueName)
                    .AddClientProperties(clientProperties)) 
                    .Serialization(s => s.UseNewtonsoftJson(settings))
                    .Start();

            await subscriber.Bus.Advanced.Topics.Subscribe(config.Channel);
            _subscribers.Add(subscriber); 

however this is working fine with rebus 4.2.1

mookid8000 commented 2 years ago

The assembly and namespace is different during serialization and deserialization (...) however this is working fine with rebus 4.2.1

I don't understand why this would have worked with Rebus 4.2.1, or with any version of Rebus ever, actually.

This thing is: When a .NET program receives a message with some JSON json and the rbs2-msg-type header set to "abcd.Api.Core.PubSub.Message, core-pubsub" and the next step is to

var messageBodyObject = JsonConvert.Deserialize(json, type);

it will be necessary for Rebus to somehow translate the string "abcd.Api.Core.PubSub.Message, core-pubsub" into type of type Type (😅).

When the header points to a specific assembly, namespace and class name, there's really no option besides having that type available to the receiving .NET program (that is, unless Rebus knows beforehand what type to look up from the type name...)

If you do not want to share DLLs between process (which is a totally valid concern), Rebus provides the option of customizing type names (i.e. mapping them to agreed-upon type names), so that senders/receives agree on what to call specific types!

You can do that out of the box by going

Configure.With(new BuiltinHandlerActivator())
    .(...)
    .Serialization(c => c.UseCustomMessageTypeNames().AddWithCustomName<Message>("message"))
    .Start()

to configure the type name "message" for the type Message. This has to be done both in the sending and the receiving end, using the same agreed-upon type name. They're free to use different class names though, as the message class name will not affect the serialized format.

You can see a working example here: https://github.com/rebus-org/Rebus/blob/master/Rebus.Tests/Examples/ShowHowToAvoidSharingMessageDllsAcrossProcesses.cs

I hope that makes it clearer how serialization works in Rebus and how you can solve your issue 🙂

ssbehera-pmap commented 2 years ago

@mookid8000 we still have the issue with following implementation

The class Message contains the same members in sender and receiver .

 .Serialization(s => s.UseCustomMessageTypeNames().AddWithCustomName<Message>("abcd.Api.Core.PubSub.Message").AllowFallbackToDefaultConvention()) 
                    .Start();

error : Rebus.Exceptions.MessageCouldNotBeDispatchedToAnyHandlersException: Message with ID 377f72b7-380f-48e4-aec8-919f0d5395e0 and type abcd.Api.Core.PubSub.Message, core-pubsub could not be dispatched to any handlers (and will not be retried under the default fail-fast settings) at Rebus.Pipeline.Receive.DispatchIncomingMessageStep.Process(IncomingStepContext context, Func1 next) at Rebus.Sagas.LoadSagaDataStep.Process(IncomingStepContext context, Func1 next) at Rebus.Pipeline.Receive.DeserializeIncomingMessageStep.Process(IncomingStepContext context, Func1 next) at Rebus.Pipeline.Receive.HandleRoutingSlipsStep.Process(IncomingStepContext context, Func1 next) at Rebus.DataBus.ClaimCheck.HydrateIncomingMessageStep.Process(IncomingStepContext context, Func1 next) at Rebus.Pipeline.Receive.HandleDeferredMessagesStep.Process(IncomingStepContext context, Func1 next) at Rebus.Pipeline.Receive.ActivateHandlersStep.Process(IncomingStepContext context, Func1 next) at Rebus.Retry.FailFast.FailFastStep.Process(IncomingStepContext context, Func1 next) at Rebus.Retry.Simple.SimpleRetryStrategyStep.DispatchWithTrackerIdentifier(Func`1 next, String identifierToTrackMessageBy, ITransactionContext transactionContext, String messageId, String secondLevelMessageId)

mookid8000 commented 2 years ago

This is another error, which means that the message was properly serialized – so you have now fixed the System.FormatException you got in the first place. 🙂

The error you are getting now, MessageCouldNotBeDispatchedToAnyHandlersException, means that the Rebus instance that received the message could not find a handler for the message.

I now see that you're trying to register a handler for dynamic

subscriber.Handle<dynamic>(async msg => { await ProcessIncomingMessage(msg, config); });

which apparently doesn't work (I've never tried it). What you can do though is to register a handler for object, and then cast the object to dynamic when passing it on:

subscriber.Handle<object>(async msg => { await ProcessIncomingMessage((dynamic)msg, config); });
ssbehera-pmap commented 2 years ago

@mookid8000 we tried with subscriber.Handle(async msg => { await ProcessIncomingMessage((dynamic)msg, config); }); still same issue type abcd.Api.Core.PubSub.Message, core-pubsub could not be dispatched to any handlers (and will not be retried under the default fail-fast settings)

mookid8000 commented 2 years ago

I just coded this example: https://github.com/rebus-org/Rebus/blob/master/Rebus.Tests/Examples/DispatchMessageAsDynamic.cs

Could you check it out and see if there's anything being done differently when comparing my code to your code?