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:
- Scans the type source for all types implementing IRequestHandler (via
GetTypesWithInterface(typeof(IRequestHandler))). This is why custom handlers must implementIRequestHandler— see Custom Request Handlers. - Registers each concrete handler type as transient for itself.
- 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,IUndeleteRequestHandlerand their*Processorcounterparts;- the generic
IRequestHandler<,,>,IRequestHandler<>,IRequestType<>,IResponseType<>interfaces (these are covered by AddProxyRequestHandlers).
- Handles multiple implementations: if more than one handler implements the same interface, one must be marked with DefaultHandler(true) — otherwise an
InvalidProgramExceptionis 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).