Sunday, August 12, 2012

Event Handler in Orchard


Introduction:

The Orchard use the EventBus Pattern to handle the event. In briefly, the EventBus can let you define an EventHandler(It is just a interface with several methods), then if you want to listen to this EventHandler, all you need to do is to inherit this EventHandler interface. If you want to raise this EventHandler, all you need to do is declare the EventHandler as a parameter in the Event Caller constructure. This parameter will be injected automatically if you resolve it with the Autofac. This blog will focue on how your inherited EventHandler will be invoked and how the EventHandler been injected to your Event Class as parameter.



Inject IEventHandler To Event Caller

IEventHandler is the base interface for all the EventHandler. When Orchard web application startup, the OrchardStarter will be used to do most of the registration work.
public static IContainer CreateHostContainer(Action<ContainerBuilder> registrations) {
      var builder = new
ContainerBuilder();
      ...
      builder.RegisterModule(new
EventsModule());
      builder.RegisterType<
DefaultOrchardEventBus>().As<IEventBus>().SingleInstance();

}

internal class EventsModule : Module {
       protected override void Load(
ContainerBuilder builder) {
           builder.RegisterSource(new
EventsRegistrationSource());
           base.Load(builder);
       }
}


In the EventsModule, it Register the EventRegistrationSource to the container. Why using the RegistrationSource? Using RegistrationSource, we can change the registration resolve target for the registration service.
public class EventsRegistrationSource : IRegistrationSource {
public EventsRegistrationSource() {
           _proxyBuilder = new
DefaultProxyBuilder();
       }
       public
IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) {
          
    var serviceWithType = service as IServiceWithType;
           if (serviceWithType == null)
               yield break;

           var serviceType = serviceWithType.ServiceType;
           if (!serviceType.IsInterface || !typeof(
IEventHandler).IsAssignableFrom(serviceType) || serviceType == typeof(IEventHandler))
               yield break;
           var interfaceProxyType = _proxyBuilder.CreateInterfaceProxyTypeWithoutTarget(
               serviceType,
               new
Type[0],
               
ProxyGenerationOptions.Default);

            var rb = RegistrationBuilder
               .ForDelegate((ctx, parameters) => {
                   var interceptors = new
IInterceptor[] { new EventsInterceptor(ctx.Resolve<IEventBus>()) };
                   var args = new object[] { interceptors, null };
                   return
Activator.CreateInstance(interfaceProxyType, args);
               })
               .As(service);

           yield return rb.CreateRegistration();
       }
   }

The RegistrationsFor method will be called by the Autofac internally whenever a registration happened. Autofac will use the return value from RegistrationFor method as the resolve target for the registration type. Orchard use this method to change all the resolve target of IEventHandler to a dynamic created type.
It first check if the current registered type is IEventHandler and is not IEventHandler itself. If yes, then it will create a dynamic type using the Castle.DefaultProxyBuilder. Then it will create a IRegistrationBuilder using a delegate. The delegate will repponse to create the resolve target for the registered IEventHandler type. The resolve target will be the generated dynamic type and the IInterceptor will be  new EventsInterceptor(ctx.Resolve<IEventBus>()).  In another word, all the call to this dynamac type will be redirect to  new EventsInterceptor(ctx.Resolve<IEventBus>()).


Dynamic Type for IEventHandler

The EventInterceptor inherited from IInterceptor which has only one method: void Intercept(IInvocation invocation);Let’s look at the code:
       public EventsInterceptor(IEventBus eventBus) {
           _eventBus = eventBus;
       }

       public void Intercept(
IInvocation invocation) {
           var interfaceName = invocation.Method.DeclaringType.Name;
           var methodName = invocation.Method.Name;

           var data = invocation.Method.GetParameters()
               .Select((parameter, index) => new { parameter.Name, Value = invocation.Arguments[index] })
               .ToDictionary(kv => kv.Name, kv => kv.Value);

           var results = _eventBus.Notify(interfaceName +
"." + methodName, data);

           invocation.ReturnValue = Adjust(results, invocation.Method.ReturnType);
       }

The constructor required a instance of IEventBus which we will discuss later. In the Intercept, it will get the call method information and parameter data, then call IEventBus.Notify with the method name and the data. So when the Event Caller call method of IEventHandler, it eventually call IEventBus->Notify.

IEventBus

In the beginning of this blog, we can see the IEventBus is registered to DefaultOrcharEventBus. Let’s look at the constructor of DefaultOrchardEventBus:
public DefaultOrchardEventBus(Func<IEnumerable<IEventHandler>> eventHandlers, IExceptionPolicy exceptionPolicy) {
           _eventHandlers = eventHandlers;
           _exceptionPolicy = exceptionPolicy;
           T =
NullLocalizer.Instance;
       }
It required a Func<IEnumrable<IEventHanlder>>. According to the Autofac, this type of instance will be composed by Aufofac automatically and when you call this Func, it will return all the IEventHandler that alreadcy been registered into Autofac.  In Orchard, all the class that inherited from IDependency will be registered automatically. IEventHandler is inheried from IDependency. So all the class that inherited from IEventHandler will be registered. So this Func will actually return all the IEventHandler class that exits in the Orchard including your custom event class. Now let’s see what the Notify function doing:
       public IEnumerable Notify(string messageName, IDictionary<string, object> eventData) {
           // call ToArray to ensure evaluation has taken place
           return NotifyHandlers(messageName, eventData).ToArray();
       }

       private
IEnumerable<object> NotifyHandlers(string messageName, IDictionary<string, object> eventData) {
           string[] parameters = messageName.Split(
'.');
           if (parameters.Length != 2) {
               throw new
ArgumentException(T("{0} is not formatted correctly", messageName).Text);
           }
           string interfaceName = parameters[0];
           string methodName = parameters[1];

           var eventHandlers = _eventHandlers();
           foreach (var eventHandler in eventHandlers) {
               
IEnumerable returnValue;
               if (TryNotifyHandler(eventHandler, messageName, interfaceName, methodName, eventData, out returnValue)) {
                   if (returnValue != null) {
                       foreach (var value in returnValue) {
                           yield return value;
                       }
                   }
               }
           }
       }

       private bool TryNotifyHandler(IEventHandler eventHandler, string messageName, string interfaceName, string methodName, IDictionary<string, object> eventData, out IEnumerable returnValue) {
           try {
               return TryInvoke(eventHandler, interfaceName, methodName, eventData, out returnValue);
           }
           catch (
Exception exception) {
               if (!_exceptionPolicy.HandleException(this, exception)) {
                   throw;
               }

               returnValue = null;
               return false;
           }
       }

       private static bool TryInvoke(
IEventHandler eventHandler, string interfaceName, string methodName, IDictionary<string, object> arguments, out IEnumerable returnValue) {
           
Type type = eventHandler.GetType();
           foreach (var interfaceType in type.GetInterfaces()) {
               if (
String.Equals(interfaceType.Name, interfaceName, StringComparison.OrdinalIgnoreCase)) {
                   return TryInvokeMethod(eventHandler, interfaceType, methodName, arguments, out returnValue);
               }
           }
           returnValue = null;
           return false;
       }


The NotifyHandler will get the real interface name and the method name from the argument. Then for all the IEventHandler type in the system, if the type name is the same as interface name in the argument, it will try to invoke the method.
So whenever an Event Caller call the method of a particular IEventHander type method, the call will be invoked on all the type that inheried from this particular type.

1 comment:

Anonymous said...

PRAGMATIC138 | CasinoFi - CasinoFib ミスティーノ ミスティーノ 바카라사이트 바카라사이트 927WildCard Casino: Play at Scratch Cards and Win a Jackpot