6

My needs

I would like our in house, standard product to fire different events when things happen. In global asax on different custom solutions, I would like to hookup on these events, when needed, and react.

Existing modules

I have been looking for an event aggregator for asp.net but I'm not really sure what to use. I have read about Prism, but it seems, that this targets WPF/Silverlight and not asp.net.

Then there is this guy, who seems to have ported the aggregator into his own version, independent of WPF: http://weblogs.asp.net/rashid/archive/2009/03/05/use-event-aggregator-to-make-your-application-more-extensible.aspx

Question

Have anyone experiences using an event aggregator for asp.net? This is for production use, so I prefer not to use some home-coded aggregator from a random guy on the net :)

Thankyou in advance.

EDIT 1: It seemed, that NServiceBus was a little overkill for the purpose. I created a single EventAggregator class that does the trick.

The class:

/// <summary>
/// A event aggregator.
/// </summary>
public class EventAggregator
{
/// <summary>The object to use when locking.</summary>
private readonly object _lock = new object();
/// <summary>Holder of registered event handlers</summary>
private readonly Dictionary<Type, List<object>> _handlers = new Dictionary<Type, List<object>>();
/// <summary>Registers the specified handler.</summary>
/// <typeparam name="T"></typeparam>
/// <param name="handler">The handler.</param>
public void Register<T>(EventHandler<T> handler) where T : EventArgs
{
    lock (_lock)
    {
        if (!_handlers.ContainsKey(typeof (T)))
            _handlers.Add(typeof (T), new List<object>());
        _handlers[typeof (T)].Add(handler);
    }
}
/// <summary>Publishes the specified event.</summary>
/// <typeparam name="T"></typeparam>
/// <param name="sender">The sender.</param>
/// <param name="eventToPublish">The event to publish.</param>
public void Publish<T>(object sender, T eventToPublish) where T : EventArgs
{
    lock (_lock)
    {
        if (!_handlers.ContainsKey(typeof (T)))
            return; // No listers for event
        foreach (EventHandler<T> handler in _handlers[typeof (T)])
            handler.Invoke(sender, eventToPublish);
    }
}
}

An event class:

public class EntityDeleted : EventArgs
{
}

Registering an event handler in global asax:

aggregator.Register<EntityDeleted>((s, e) => {
// Do stuff here
});

Raising an event:

aggregator.Publish(this, new EntityDeleted());

EDIT 2:

And here are my unit test for the ones interrested:

/// <summary>
/// Unit tests for EventAggregator
/// </summary>
[TestClass]
public class EventAggregatorTest
{
    /// <summary>Tests that no exceptions are thrown when calling an event with no handlers.</summary>
[TestMethod]
public void EmptyAggregatorTest()
{
    var aggregator = new EventAggregator();
    aggregator.Publish(this, new TestEventOne() { Property = "p1" });
}
/// <summary>Tests the aggregator using a single, registered handler.</summary>
[TestMethod]
public void SingleListenerTest()
{
    var aggregator = new EventAggregator();
    int calls = 0;
    aggregator.Register<TestEventOne>((sender, e) =>
    {
        Assert.AreEqual("p1", e.Property);
        calls ++;
    });
    Assert.AreEqual(0, calls);
    aggregator.Publish(this, new TestEventOne(){Property = "p1"});
    Assert.AreEqual(1, calls);
}

/// <summary>Tests the aggregator using multiple registered handlers.</summary>
[TestMethod]
public void MultipleListenersTest()
{
    var aggregator = new EventAggregator();
    int p1Calls = 0;
    int p2Calls = 0;
    aggregator.Register<TestEventOne>((sender, e) =>
    {
        Assert.AreEqual("p1", e.Property);
        p1Calls++;
    });
    aggregator.Register<TestEventOne>((sender, e) =>
    {
        Assert.AreEqual("p1", e.Property);
        p1Calls++;
    });
    aggregator.Register<TestEventTwo>((sender, e) =>
    {
        Assert.AreEqual("p2", e.Property);
        p2Calls++;
    });
    Assert.AreEqual(0, p1Calls);
    aggregator.Publish(this, new TestEventOne() { Property = "p1" });
    Assert.AreEqual(2, p1Calls);
    Assert.AreEqual(0, p2Calls);
    aggregator.Publish(this, new TestEventTwo() { Property = "p2" });
    Assert.AreEqual(1, p2Calls);
    Assert.AreEqual(2, p1Calls);
}
}

/// <summary>
/// Dummy test event 1
/// </summary>
public class TestEventOne : EventArgs
{
    public string Property { get; set; }
}
/// <summary>
/// Dummy test event 2
/// </summary>
public class TestEventTwo : EventArgs
{
    public string Property { get; set; }
}

EDIT 3:

Thanks to Steven Robbins for pointing out, that the aggregator was not thread safe, I added locking to the Publish and Register methods.

Stephan Møller
  • 1,247
  • 19
  • 39
  • I suppose I qualify as a "random guy on the net", but you can check out this project of mine: https://bitbucket.org/anton_gogolev/octalforty-mechanic/ . – Anton Gogolev Oct 21 '11 at 11:40
  • Are you looking for an event aggregator or a service bus? I.e. something for events within code (EA), or events within the business (SB)? – Kieren Johnstone Oct 21 '11 at 12:17
  • I guess I'm looking for events within the business, i.e. I want a notification whenever something has been updated, no matter who updated it, lets say user updates of product data. – Stephan Møller Oct 21 '11 at 13:31

2 Answers2

2

I had a similar requirement and for this I used NServiceBus its open source with big community and great documentation to get more info try this link

http://docs.particular.net/

Peter
  • 7,792
  • 9
  • 63
  • 94
Bobby
  • 1,594
  • 13
  • 20
1

If you want a simple (single cs file) drop in EA that gives you a bit more than your home rolled one (the one above doesn't appear to be thread safe, not sure if that's an issue for you) you can take a look at TinyMessenger.

Steven Robbins
  • 26,441
  • 7
  • 76
  • 90
  • Thanks for the comment. You are right, my example is not thread safe. I see that the TinyMessenger ensures thread safety using a lock on publishing and registering. I will add locks in the same cases in my example and update the main post with these. I kinda like the fact, that the class above is extremely simple and covers my needs. – Stephan Møller Oct 24 '11 at 11:00