MassTransit: F# anonymous objects don't work (message type must be concrete and have members corresponding to the message interface)

Contact Details

No response

Version

8.x

On which operating system(s) are you experiencing the issue?

Windows

Using which broker(s) did you encounter the issue?

Azure Service Bus, In-Memory

What are the steps required to reproduce the issue?

Please see the attached repro solution:

Repro.zip

Code below for reference:

namespace Repro

open System
open System.Threading
open System.Threading.Tasks
open System.Text.Json
open MassTransit


type MyMessage =
  abstract member Data: int


type MyMessageImplWithoutMember(data) =

  interface MyMessage with
    member _.Data = data


type MyMessageImplWithMember(data) =
  member _.Data = data

  interface MyMessage with
    member _.Data = data


type MyMessageConsumer() =
  interface IConsumer<MyMessage> with
    member _.Consume(context) =
      Console.WriteLine($"Received: {JsonSerializer.Serialize(context.Message)}")
      Task.CompletedTask


module Test =

  let bus =
    Bus.Factory.CreateUsingInMemory(fun cfg ->
      cfg.ReceiveEndpoint(
        "queue",
        fun (epCfg: IInMemoryReceiveEndpointConfigurator) ->
          epCfg.Consumer(fun () -> MyMessageConsumer())
      ))
    
  bus.Start()


  let publishWithSerializedLog (msg: MyMessage) =
    Console.WriteLine($"Sending {msg.GetType().Name}: {JsonSerializer.Serialize(msg)}")
    bus.Publish<MyMessage>(msg).GetAwaiter().GetResult()


  publishWithSerializedLog
    // This is the F# anonymous object/interface syntax
    { new MyMessage with
        member _.Data = 100 }

  Thread.Sleep(1000)

  
  publishWithSerializedLog (MyMessageImplWithoutMember(200))

  Thread.Sleep(1000)

  
  publishWithSerializedLog (MyMessageImplWithMember(300))

  Thread.Sleep(1000)

MassTransit advocates instantiating messages using anonymous objects. Strangely, this does not work in F#. The only thing that works in F#, is sending a concrete message type that not only implements the interface, but also contains identically named regular members (F# interfaces are always explicitly implemented, as opposed to C#, where interfaces are normally implicitly implemented).

System.Text.Json can serialize all alternatives just fine, as shown in the output, so I’m not sure why this doesn’t work.

The weirdest thing is that if I debug and step through, and put a breakpoint at this line:

https://github.com/MassTransit/MassTransit/blob/3e1ebd4c1ba3847bf6c3bc611d1de0a3c78ae710/src/MassTransit/Serialization/SystemTextJsonMessageBody.cs#L50

Then look at what I got in the interactive window:

Object.ReferenceEquals(_context.Message, envelope.Message)
true

System.Text.Json.JsonSerializer.Serialize(_context.Message)
"{\"Data\":100}"

System.Text.Json.JsonSerializer.Serialize(envelope.Message)
"{}"

That just doesn’t make sense. It’s one and the same object, but the serialized output is different.

What is the expected behavior?

Sending clo@53: {"Data":100}
Received: {"Data":100}
Sending MyMessageImplWithoutMember: {"Data":200}
Received: {"Data":200}
Sending MyMessageImplWithMember: {"Data":300}
Received: {"Data":300}

What actually happened?

Sending clo@53: {"Data":100}
Received: {"Data":0}
Sending MyMessageImplWithoutMember: {"Data":200}
Received: {"Data":0}
Sending MyMessageImplWithMember: {"Data":300}
Received: {"Data":300}

Related log output, including any exceptions

No response

Link to repository that demonstrates/reproduces the issue

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 16 (16 by maintainers)

Most upvoted comments

I will leave this and move to using records instead. Thanks for all the help!