IEventHandler

Download the sample code

The Event Bus Pattern and IEventhandler

When developing modular application services, there are times that you want to raise an event that other code can act upon. For example, consider having an IOrderManager that creates new orders. When a new order is created, some other code (perhaps even in a different module), like an OrderHandler, may want to act upon that event and start the shipping procedure. In our example, the IOrderManager would be a publisher of events, and the OrderHandler one of potentially many subscribers.

Two of the greatest advantages of such a setup is decoupling and extensibility. Orchard uses this approach almost everywhere: when a new user registers, when publishing, deleting, importing, exporting content items, when sending a message via the message service, when rendering shapes. The list goes on.

So how could we implement a pub / sub pattern in our own module? As it turns out, it's quite easy:

The Publisher

  1. Create an interface that derives from IEventHandler, e.g. IOrderEventHandler. Create one or more methods on this "event" that makes sense, e.g. OrderCreated, OrderCancelled, OrderShipped. Subscribers will need to implement this interface.
  2. Create a publisher class, e.g. OrderManager, that takes a constructor argument of type IOrderEventHandler.
  3. Create a method in your OrderManager class, e.g. CreateOrder and invoke the OrderCreated method on the IOrderEventHandler instance.

The Subscriber(s)

Next, we need an actual subscriber that implements IOrderEventHandler and handles Order events:

  1. Create a subscriber class, e.g. OrderHandler, that implements IOrderEventHandler.

 

A note on how the event bus works: The DefaultOrchardEventBus grabs all objects implementing IEventHandler and then chooses the appropriate ones to propagate the method call to by matching interface names. So if you call some method on any IOrderEventHandler, the event bus will call the same method on all other implementations of an interface called 'IOrderEventHandler' (even in different modules and namespaces!), as long as all will derive from IEventHandler. "

 

Let's see some working sample code, starting with a sample Order entity class:

Models/Order.cs:

namespace Orchard.Samples.EventBus.Models {
 
    // Represents an actual order
    public class Order {
        public int Id { getset; }
        public int CustomerId { getset; }
    }
}

The IOrderManager has just one method: CreateOrder:

Services/IOrderManager.cs:

using Orchard.Samples.EventBus.Models;
 
namespace Orchard.Samples.EventBus.Services {
    public interface IOrderManager : IDependency {
        Order CreateOrder(int customerId);
    }
}

 

The implementation is quite more interesting:

Services/OrderManager.cs:

using System.Collections.Generic;
using Orchard.Samples.EventBus.Events;
using Orchard.Samples.EventBus.Models;
 
namespace Orchard.Samples.EventBus.Services {
 
    // OrderManager is responsible for creating orders.
    // Note that we derive from Component, so that we have easy access to a Logger object.
    public class OrderManager : ComponentIOrderManager {
        private readonly IOrderEventHandler _orderEventHandler;
 
        public OrderManager(IOrderEventHandler orderEventHandler) {
 
            // Store the event handler for later use
            _orderEventHandler = orderEventHandler;
        }
 
        public Order CreateOrder(int customerId) {
 
            // Create the order
            var order = new Order();
 
            // Create an "event argument"
            var context = new OrderCreatedContext(order);
 
            // Invoke the event handler
            _orderEventHandler.OrderCreated(context);
 
            // Return the create order
            return order;
        }
    }
}

 

Note that OrderManager derives from Orchard.Component. Although that is not at all necessary, I found it to be a very handy base class. It's leightweight and provides us with a Logger object and the T delegate (although we're not using them here).

The second point of interest is the constructor: we are taking advantage of Dependency Injection by simply declaring that we want a an IOrderEventHandler implementation. Although the IoC container will pass us a mocked version of it, whenever we invoke a method on it, the event bus will invoke that method on all other objects that implement IEventHandler. Now this event bus pattern is quite a powerful concept: any users of our module will be able to create an implementation of IOrderEventHandler and extend the functionality of our module without having to touch our code. This is the Open Closed Principle in action.

The third point of interest is the use of a class called OrderCreatedContext. This is a simple class that we wrote ourselves with only a single property: Order. The class acts as an event argument if you will, and is a recommended pattern when designing APIs. The primary reason is extensibility: should we ever decide that we want to pass additional data to our subscribers, we don't have to introduce a breaking signature change. Consider for one moment that we were passing in the order directly to the OrderCreated method. Now, imagine we published our module to the world, and tons of IOrderEventhandler implementations exist. Next, for some reason, we received a new requirement to pass an extra argument, say, ShoppingCart. Now the IOrderEventHandler.OrderCreated signature would need to be changed, effectively breaking all other implementations when we publish our module. Now try explaining that to your mom. She's not going to be happy with you.

The IOrderEventHandler looks like this:

Handlers/IOrderEventHandler.cs:

using Orchard.Events;
 
namespace Orchard.Samples.EventBus.Events {
    public interface IOrderEventHandler : IEventHandler {
        void OrderCreated(OrderCreatedContext context);
        void OrderCancelled(OrderCancelledContext context);
        void OrderShipped(OrderShippedContext context);
    }
}

 

Let's implement a sample subscriber called OrderEventHandler:

Handlers/OrderEventHandler.cs:

using Orchard.Logging;
using Orchard.Samples.EventBus.Events;
 
namespace Orchard.Samples.EventBus.Handlers {
    public class OrderEventHandler : ComponentIOrderEventHandler {
        public void OrderCreated(OrderCreatedContext context) {
            // Do something useful
            Logger.Log(LogLevel.Information, null"We received a new order! Order ID: {0}", context.Order.Id);
        }
 
        public void OrderCancelled(OrderCancelledContext context) {
            // Do something useful
            Logger.Log(LogLevel.Information, null"Bummer, order {0} just got cancelled.", context.Order.Id);
        }
 
        public void OrderShipped(OrderShippedContext context) {
            // Do something useful
            Logger.Log(LogLevel.Information, null"Great job, order {0} just got shipped!", context.Order.Id);
        }
    }
}

Although in this example we're simply logging the various stages of an order, we could go crazy and send out the actual order. Some orders may contain physical products that need to be handled and shipped. Others may contain digital products, which need to be delivered by email. You could go ahead and write different handlers for that. Easy. 

 

Special thanks to Piotr for explaining the details of how the Orchard event bus actually works, and also to Zgofmontreal for his tip on not having to inject an enumerable of IOrderEventHandler, but just an IOrderEventHandler.

12 comments

  • ... Posted by Piotr Szmyd Posted 04/17/2012 07:27 PM

    Nice one!

  • ... Posted by pubgrub Posted 04/17/2012 10:40 PM

    Great stuff, keep up the good work mate!

  • ... Posted by pszmyd Posted 04/18/2012 01:45 AM

    Just my 3 cents:)

    "(... ) Technically, you don't have to derive from IEventHandler. We could have derived IOrderEventHandler directly from IDependency. however, I'm sure there are good reasons to derivefrom IEventHandler instead. (...)"

    Actually, you have to and there are good reasons. If you don't do that, your event won't get propagated to other event handlers. The DefaultOrchardEventBus grabs all objects implementing IEventHandler first and then it chooses the appropriate ones to propagate the method call to by matching interface names.

    So if you call some method on any IOrderEventHandler, the event bus will call the same method on all other implementations of an interface called 'IOrderEventHandler' (even in different modules and namespaces!), as long as all will derive from IEventHandler.

  • ... Posted by Sipke Schoorstra Posted 04/18/2012 08:15 AM (Author)

    Ecellent, I didn't know! Will update the post accordingly :) Thanks!

  • ... Posted by pszmyd Posted 04/18/2012 02:03 PM

    You're welcome!:)

  • ... Posted by Offshore Development Posted 04/26/2012 07:07 AM [http://mindinventory.com/]

    Really,thanks because I'm searching on web this type of tutorial on web.

  • ... Posted by Software Development Company Posted 05/17/2012 12:57 PM [http://www.mindinventory.com/]

    This example are simple and useful.When I'm developing module for my project I've many issue come but here share nice example.

  • ... Posted by Zgofmontreal Posted 05/24/2012 05:35 PM
    <p> public OrderManager(IEnumerable&lt;iordereventhandler&gt; orderEventHandlers) {<br> <br> // Store the event handlers for later use<br> _orderEventHandlers = orderEventHandlers;<br> }<br>-------------------------<br>could be :<br>---------------<br> public OrderManager(IOrderEventHandler orderEventHandler) {<br> <br> // Store the event handler for later use<br> _orderEventHandler = orderEventHandler;<br> }<br>-------------------------<br>see <br><a href="http://orchard.codeplex.com/discussions/356856" rel="nofollow">http://orchard.codeplex.com/di...</a>&lt;/iordereventhandler&gt;</p>
  • ... Posted by Sipke Schoorstra Posted 05/24/2012 05:52 PM (Author)

    Excellent, I'll update the post. Thanks!

  • ... Posted by Sipke Schoorstra Posted 05/24/2012 06:11 PM (Author)

    If you give me a url to your homepage / blog I can hyperlink your name if you like.

  • ... Posted by Bill Cooper Posted 12/10/2014 07:58 PM [https://www.hybridcloud.ninja/]

    Given the example from the article - How would I write a test for OrderManager in which a given set of IOrderEventHandler would be handled in the same way as they would be in Orchard at runtime? In other words, instead of mocking IOrderEventHandler, I would like to setup my container and let the container resolve the dependence as it would at runtime.

    I am currently writing some tests around a module I have written, and I would like to have an integration test which would incorporate the event bus architecture for some dependencies.

  • ... Posted by Sipke Schoorstra Posted 12/14/2014 09:01 AM (Author)

    I imagine one would have to register the event bus service and its dependencies with the container. I wouldn't know offhand what else would be required, but it sounds like a fun thing to investigate. Hope you will share your findings!

Leave a comment