NET PubSub; Using Attributes for Configuration

By Chris Scott

Presentation on github

Repository: LEM-Concept-Starter

Created using reveal.js

The Problem

What happens in LEM?

It starts with a user completing a step...

Then what?

  1. Assigned mentor is notified
  2. Parent learning activity possibly earned
  3. Pending steps to review count is increased for mentor
  4. ???
  5. Profit

ALL of these require modifying the ProcessingEngine code to do additional activities

Too many different people have worked on this, there is not enough documentation for this to be maintainable

As PubSub

Complete badge:


//user completes step
MessageHub.Hub.Publish(new BadgeCompletedEvent
{
    Step = completedStep,
    User = user
});
						

Handle it!


//email manager
MessageHub.Hub.Subscribe<BadgeCompletedEvent>(e =>
{
    Email.send(e.User.Mentor, string.Format("{0} completed {1}", e.User.Name, e.Step.Name));
});
					

//notification manager
MessageHub.Hub.Subscribe<BadgeCompletedEvent>(e =>
{
    db.Notifications.Add(new Notification(e.User.Mentor, e.User, e.Step));
});
						

Doomsday Scenario

This is great and all, but right now all it gets us is a lot of work rewriting our existing system.

What if...

  1. Dan, Chris and Chris quit
  2. Marty's ideas run wild
  3. LEM gets native iOS and Android apps
  4. "We NEED push notifications. That's the big advantage"

PubSub to the rescue!


//push notification manager
MessageHub.Hub.Subscribe<BadgeCompletedEvent>(e =>
{
    applePush.NewNotification(/* ... */).Send();
    androidPush.NewNotification(/* ... */).Send();
});
						

Adding this does not require touching the core processing framework, AT ALL

Decoupled, but Not Really

This is a great way to decouple these two tools.

But how do we actually register the PushNotificationManager to listen for events?

It ruins the "not touching core code" if we have to manually call all of these different RegisterForEvents methods

Custom Attributes to the Rescue!


[AttributeUsage(AttributeTargets.Class)]
public class ManagerAttribute : Attribute
{
}
						

This is a custom attribute, that will be written as [Manager] that gets added at the class level

Add to our Managers


public interface IManager
{
    void Startup();
}
						

All of our managers will implement this interface, which gives us a handle on starting them up.


[Manager]
public class EmailManager : IManager
{
    public void Startup()
    {
        MessageHub.Hub.Subscribe<SampleBadgeCompleted>(e =>
        {
        });
		
		//etc
    }
}
						

Auto-register on Application_Start


foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    foreach (var type in assembly.GetTypes())
    {
        if (type.GetCustomAttributes(typeof(ManagerAttribute), true).Length > 0)
        {
            //do this as a blind cast. if it fails the app should hard crash, anyway.
            var manager = (IManager) Activator.CreateInstance(type);
            manager.Startup();
        }
    }
}
						

This gets all of the types in the system with the [Manager] attribute

Then calls .Startup()

Adding new managers now no longer requires ANY modification of the core classes.

Questions?