Auto-Registration of Request Handlers

Serenity automatically registers request handlers from the type source — you don't have to add an AddSingleton/AddTransient line for each handler.

AddServiceHandlers

Everything starts with AddServiceHandlers(), called from Startup.ConfigureServices. It registers the core services and the handlers:

public static IServiceCollection AddServiceHandlers(this IServiceCollection collection,
    ITypeSource customHandlerTypeSource = null, Func<Type, Type, bool> customHandlerPredicate = null)
{
    collection.AddCaching();
    collection.AddEntities();
    collection.AddFeatureToggles();
    collection.AddTextRegistry();
    collection.AddServiceHandlerFactory();
    collection.AddServiceResolver();
    collection.AddCustomRequestHandlers(customHandlerTypeSource, customHandlerPredicate);
    collection.AddProxyRequestHandlers();

    collection.TryAddSingleton<IRequestContext, DefaultRequestContext>();
    return collection;
}

The relevant call for handler discovery is AddCustomRequestHandlers. It is called by AddServiceHandlers(), along with the other registrations shown above (AddCaching, AddEntities, AddFeatureToggles, AddTextRegistry, AddServiceBehaviors, AddServiceHandlerFactory, AddServiceResolver, AddUserProvider, and AddProxyRequestHandlers).

How AddCustomRequestHandlers Works

AddCustomRequestHandlers does the following:

  1. Scans the type source for all types implementing IRequestHandler (via GetTypesWithInterface(typeof(IRequestHandler))). This is why custom handlers must implement IRequestHandler — see Custom Request Handlers.
  2. Registers each concrete handler type as transient for itself.
  3. Registers each handler for its interfaces, as long as the interface derives from IRequestHandler. The base marker interfaces are skipped:
    • IRequestHandler, ISaveRequestHandler, IListRequestHandler, IRetrieveRequestHandler, IDeleteRequestHandler, IUndeleteRequestHandler and their *Processor counterparts;
    • the generic IRequestHandler<,,>, IRequestHandler<>, IRequestType<>, IResponseType<> interfaces (these are covered by AddProxyRequestHandlers).
  4. Handles multiple implementations: if more than one handler implements the same interface, one must be marked with DefaultHandler(true) — otherwise an InvalidProgramException is thrown asking you to pick a default.

Because handlers are registered as transient, each request gets a fresh handler instance.

The predicate parameter

AddCustomRequestHandlers (and AddServiceHandlers) accepts an optional predicate: (intf, impl) => bool. Returning false for a pair skips that registration. For example, to register handlers only for their interfaces (not for themselves):

services.AddServiceHandlers(customHandlerPredicate: (intf, impl) => intf != impl);

The default (no predicate) registers both the concrete handler types and their interfaces.

Proxy Request Handlers

AddProxyRequestHandlers registers transient proxies that let the DI container resolve the generic handler interfaces like ICreateHandler<TRow>, IUpdateHandler<TRow>, IDeleteHandler<TRow>, IListHandler<TRow>, IRetrieveHandler<TRow>, and IUndeleteHandler<TRow> on demand — even when the concrete handler is only registered for its own specific interface (e.g. ILanguageSaveHandler).

See Also