This question deals with events (base class events and subclass events) and event handlers. I'm working on existing code, that doesn't seem to work the way the author expected it. I have difficulty understanding why it doesn't work though, so I want to understand what's going on before I try to fix the existing code.
I've found the following question, which may or may not suggest I need to make an additional event handler for the subtype events:
C#: Raising an inherited event
If making an additional event handler is indeed the solution, I would still like to learn why this is the case. This is my first question here, and I did really try to search for the answer/explanation to my question, but sincere apologies if it's still something I should've easily found. A stern "RTFM!" with a educational link would be fine with me at this point :)
We have 2 event classes, a base type and a subtype. The subtype event exists to deal with deletion events.
public class BaseTypeEvent
{
public Guid Id { get; set; }
public string Name { get; set; }
public BaseTypeEvent()
{ }
public BaseTypeEvent(SomeRandomThing item)
{
Id = item.Id;
Name = item.Name;
}
}
public class SubTypeEvent : BaseTypeEvent
{
public DateTimeOffset Deleted { get; set; }
public SubTypeEvent()
{
Deleted = DateTimeOffset.UtcNow;
}
}
A usage of these events that seems to be failing:
public class UsageClass
{
public UsageClass(IEventBusService eventBusService)
{
eventBusService.MyBaseTypeEvents += HandleMethod;
}
private void HandleMethod(BaseTypeEvent e)
{
if(e is SubTypeEvent)
{
//code that deals with deletion events
//execution never actually gets here
}
//code that deals with events that are not deletion events
}
}
The declaration of the events are in the IEventBusService and EventBusService:
public delegate void MyEventHandler(BaseTypeEvent e);
public interface IEventBusService
{
public event MyEventHandler MyBaseTypeEvents;
void PublishStuff(BaseTypeEvent e);
}
public class EventBusService : IEventBusService, IDisposable
{
public void Initialize()
{
//Bus is MassTransit
Bus.Initialize(sbc =>
{
sbc.Subscribe(subs => subs.Handler<BaseTypeEvent>(OnBaseTypeEvent));
}
}
private void OnBaseTypeEvent(BaseTypeEvent e)
{
if (MyBaseTypeEvents == null) return;
try
{
MyBaseTypeEvents(e);
}
catch (Exception e)
{
//some logging
}
}
public event MyEventHandler MyBaseTypeEvents;
public void PublishStuff(BaseTypeEvent e)
{
//some logging
//publish e to the event bus of our choice (MassTransit)
Bus.Instance.Publish(e);
}
}
And then finally the place where we send the deletion event (to try to delete an item of what I have cleverly named SomeRandomThing above):
eventBusService.PublishStuff(new SubTypeEvent
{
Id = id,
Deleted = DateTimeOffset.UtcNow
});
So the problem: after sending the deletion event with the last line of code above, the if-statement in the UsageClass that checks whether an incoming event is of type SubTypeEvent is never actually true. The type of e in HandleMethod of UsageClass is BaseTypeEvent.
Edit:
I've decided to get rid of the subtyping in this case. We now no longer have BaseTypeEvent and SubTypeEvent, but simply EventTypeA and EventTypeB. One deals with creates and updates, the other deals with deletes (for which we need significantly less information that the creates and updates anyway).
public delegate void MyEventAHandler(EventTypeA e);
public delegate void MyEventBHandler(EventTypeB e);
and
void PublishStuffForA(EventTypeA e);
void PublishStuffForB(EventTypeB e);
and so on.
I've made an extra subscription to MassTransit in the Initialize method of our EventbusService, and made extra handlers in the various UsageClasses that needed them:
sbc.Subscribe(subs => subs.Handler<EventTypeA>(OnEventTypeA));
sbc.Subscribe(subs => subs.Handler<EventTypeB>(OnEventTypeB));
and
public UsageClass(IEventBusService eventBusService)
{
eventBusService.MyEventTypeAEvents += HandleMethodForA;
eventBusService.MyEventTypeBEvents += HandleMethodForB;
}
and so on.
I now no longer have to check if an incoming event is of a certain type, I just handle to two types separately. Perhaps a cop out, but it works.
I'm hesitant to qualify this as the answer to my own question, as @Glubus' comments as well as @Travis' comments were what answered my question. Still thought this small edit write-up might be nice to let everyone know what I did as a solution :)
Edit 2:
Sources of information that were helpful:
Derived types are not published to consumers in MassTransit
MassTransit message mis-typing
MassTransit: Message contracts, polymorphism and dynamic proxy objects
https://groups.google.com/forum/#!searchin/masstransit-discuss/polymorphism/masstransit-discuss/q_M4erHQ7OI/FxaotfIzI7YJ
So I can tell you the short answer:
Using polymorphism in messaging contracts introduces coupling.
We believe, as MassTransit developers, that this is a bad idea. It's still possible, but not out of the box. You have to use binary serialization or a customer serializer. The default the serialization pipeline only populates a proxy of the type in the consumer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With