Add a generic handler for Send and Publish methods of the MediatR library in asp .net core - c#

I use the CQS pattern in my asp.net core project. Let's start with an example to better explain what I want to achieve. I created a command:
public class EmptyCommand : INotification{}
The command handler:
public class EmptyCommandHandler : INotificationHandler<EmptyCommand>
{
public Task Handle(EmptyCommand notification, CancellationToken cancellationToken)
{
return Task.FromResult(string.Empty);
}
}
The query:
public class EmptyQuery : IRequest<string>{}
The query handler:
public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
{
return Task.FromResult(string.Empty);
}
}
and this is a simple example of how to run the command and query and invoke the Handle method from the EmptyCommandHandler and EmptyQueryHandler:
readonly IMediator _mediator;
public HomeController(IMediator mediator)
{
_mediator = mediator;
}
public async Task<IActionResult> Index()
{
await _mediator.Publish(new EmptyCommand());
var queryResult = await _mediator.Send(new EmptyQuery());
return View();
}
Please bear in mind that query can return other types not necessarily the string.
I would like to create some kind of a bridge class e.g. MediatorBoostrapper, which allows me to run some business logic(e.g. log command/query via Logger) every time the Publish method is invoked and then
invoke the public Task Handle(EmptyCommand notification,... method from the command handler. The solution must be generic, so this method would be invoked every time I run the Publish method. I also want to be able to do the same thing for the Send method.
I was thinking about the creation of the public class MediatorBoostrapper : IMediator
but not sure what should be a proper implementation of the class and if my idea is good.
Any ideas? Cheers
Edit
I want to have an example of how to use the Behaviors
to create a generic way to run some external method from the generic handler every time I Run the Send method for queries. I want to have a similar example for Publish method, which I use for sending commands.
I want to have an example of how to use Polymorphic dispatch
for the creation of the GenericCommandHandler and a GenericQueryHandler
I created a sample project on GitHub which can be found here
You can feel free to try to extend this project with your solution.

This time I want to answer the question starting from the end.
2.
TL;DR Polymorphic Dispatch cannot be used for the CQS
After some time of playing with the MediatR library, reading the comments under my Question and consultation with my friend, I found the Polymorphic Dispatch(PD) can be used to create a generic handler only in case of the Commands. The PD solution cannot be implemented for Queries. Based on the Documentation, the handlers are contravariant and not covariant. This means the PD works only in the case where the TResponse is a constant type. In case of the Queries, this is false and each Query handler can return a different result.
I also found this issue. I think it's interesting to know you can use the Polymorphic Dispatch only if your container supports it.
1. Behaviors is the one and only solution for CQS when using the MediatR.
Based on the comment under my question from #Steve and comment from jbogard I've found the way how to use Behaviors and IRequestHandler for the strict Command pattern. The full comment:
Just to summarize the changes, there are 2 main flavors of requests:
those that return a value, and those that do not. The ones that do not
now implement IRequest<T> where T : Unit. This was to unify requests
and handlers into one single type. The diverging types broke the
pipeline for many containers, the unification means you can use
pipelines for any kind of request.
It forced me to add the Unit type in all cases, so I've added some helper classes for you.
IRequestHandler<T> - implement this and you will return Task<Unit>.
AsyncRequestHandler<T> - inherit this and you will return Task.
RequestHandler<T> - inherit this and you will return nothing (void).
For requests that do return values:
IRequestHandler<T, U> - you will return Task<U>
RequestHandler<T, U> - you will return U
I got rid of the AsyncRequestHandler because it really wasn't doing anything after the consolidation, a redundant base class.
The example
a) The Commands management:
public class EmptyCommand : IRequest{...}
public class EmptyCommandHandler : RequestHandler<EmptyCommand>
{
protected override void Handle(EmptyCommand request){...}
}
b) The Queries management:
// can be any other type not necessarily `string`
public class EmptyQuery : IRequest<string>{...}
public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
{
return Task.FromResult("Sample response");
}
}
c) The sample LogginBehavior class:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var requestType = typeof(TRequest).Name;
var response = await next();
if (requestType.EndsWith("Command"))
{
_logger.LogInformation($"Command Request: {request}");
}
else if (requestType.EndsWith("Query"))
{
_logger.LogInformation($"Query Request: {request}");
_logger.LogInformation($"Query Response: {response}");
}
else
{
throw new Exception("The request is not the Command or Query type");
}
return response;
}
}
d) To register the LoggingBehavior add the command
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
to the body of the ConfigureServices method in the Startup.cs.
e) The example of how to run sample command and query:
await _mediator.Send(new EmptyCommand());
var result = await _mediator.Send(new EmptyQuery());

MediatR supports dispatching notifications to generic handlers (polymorphic dispatch). For example:
public class GenericHandler<TNotification> : INotificationHandler<TNotification>
where TNotification : INotification
{
public Task Handle(TNotification notification, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
This handler will be invoked for every notification that is published through Publish(). The same is true for requests (queries/commands). You should also take a look at behaviors.
If you're using MediatR with ASP.NET Core I suggest you use the MediatR.Extensions.Microsoft.DependencyInjection library which takes care of wiring all the handlers together.

Related

Injecting Behaviors Before and After Method Execution to log input and output

I have many lines of code like:
private SomeOutputModel Method(SomeInputModel model)
{
_logger.LogDebug(JsonSerializer.Serialize(model));
SomeOutputModel result = DoSomething();
_logger.LogDebug(JsonSerializer.Serialize(result));
return result;
}
practically I need to log the input and output of some methods. I know I should do this in respect to AoP and I know there is some non-free library like PostSharp outside, but I need a hint to create a simple decorator pattern for my methods or delegates to give me this ability to invoke or execute these methods. Hence I would be able to inject any behavior among the functions, but I don't want to use reflection as much as possible because I assume it will make the code meaningless and comparatively slow to original one.
One approach for accomplishing this would be to use something like MediatR, which, to quote the author, is "a low-ambition library trying to solve a simple problem — decoupling the in-process sending of messages from handling messages."
That decoupling introduces opportunities to create handlers for specific requests types that can be wrapped with a pipeline behavior at a more generic level to cover cross-cutting concerns like logging.
You might create a handler like follows:
public class SomeOutputModelHandler : IRequestHandler<SomeInputModel, SomeOutputModel>
{
private ISomeInterfaceDependency _somethingDoer;
public ModelHandler(ISomeInterfaceDependency somethingDoer)
{
_somethingDoer = somethingDoer;
}
public async Task<SomeOutputModel> Handle(SomeInputModel request, CancellationToken cancellationToken)
{
var response = await _somethingDoer.DoSomething(request);
return response;
}
}
This would be invoked by passing the model to Mediatr like: var outputModel = await mediator.Send(myInputModel);
At this point, lifting directly from the MediatR wiki, you can register a generic pipeline behavior that covers all of your handler cases that logs everything before and after execution:
https://github.com/jbogard/MediatR/wiki/Behaviors#registering-pipeline-behaviors
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
var response = await next();
_logger.LogInformation($"Handled {typeof(TResponse).Name}");
return response;
}
}
You can see examples of this request/response behavior including pipelines in the MediatR samples at https://github.com/jbogard/MediatR/tree/master/samples/MediatR.Examples
Without using reflection, and with the caveat that the "decoration" has to be done on method call instead of on method definition, a simple and basic solution could be:
For each generic Func or Action signature you need, create a corresponding method that would perform the Logging.
Using Serilog (or equivalent), you wouldn't have to do serialization by yourself to log the input and output objects, by leveraging the Serilog # operator.
Serilog homepage explain briefly the # operator use, see: https://serilog.net/
Here is how it could be done:
public static class LogUtils
{
public static TResult LogDecorate<TSource, TResult>(Func<TSource, TResult> method, TSource input)
{
Log.Information("{#input1}", input1);
var result = method(input);
Log.Information("{#result}", result);
return result;
}
public static TResult LogDecorate<TSource1, TSource2, TResult>(Func<TSource1, TSource2, TResult> Method, TSource1 input1, TSource2 input2)
{
Log.Information("{#input1} - {#input2}", input1, input2);
var result = Method(input);
Log.Information("{#result}", result);
return result;
}
// Etc...
}
Use would be:
var result = LogUtils.LogDecorate(MyMethod, input);

Mediatr IPipelineBehavior not triggered

I have implemented Mediatr in my .NET framework project and would like to use a IPipelineBehavior.
I have implemented and registered the container using the example from the project: https://github.com/jbogard/MediatR/blob/master/samples/MediatR.Examples.Unity/Program.cs
This is my Behavior
public class AuditPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILeaveAuditTrail _auditor;
public AuditPipelineBehavior(ILeaveAuditTrail auditor)
{
_auditor = auditor;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (AuditPrevented(request))
return await next();
var response = await next();
var auditLog = _auditor.CreateAuditLog(request, response);
//Dispatch audit
return response;
}
private static bool AuditPrevented<TInput>(TInput query)
{
return query.GetType().GetCustomAttributes(typeof(PreventAuditAttribute), true).Any();
}
}
And i register is like this together with my Mediatr
container.RegisterMediator(new HierarchicalLifetimeManager());
container.RegisterMediatorHandlers(applicationAssembly);
container.RegisterType(typeof(IPipelineBehavior<,>), typeof(AuditPipelineBehavior<,>));
container.RegisterType<ILeaveAuditTrail, DefaultAuditor>();
When i send the IRequest with Mediatr it is handled fine and i get the results but the AuditPipeline is not called.
If i remove the async keyword and just return next(); It works. But this way i cannot correctly intercept my response.
Also on the example Github the handle is implemented async: https://github.com/jbogard/MediatR/wiki/Behaviors
The issue was not related to the registration of Mediatror the PipelineBehavior.
When calling the mediator.Send()method it was not in an async method. .Resultwas used instead of await. Because of this the handling of the pipeline only worked when the asynckeyword was not present.
Make sure to never use the .Result to chain these async calls. Mark the controller method async and await the mediator.Send()

Call async method in AddTransient in Startup - Asp.Net Core

I have a service which is used to get some information and the method has a bunch of async calls in the chain.
public interface IFooService
{
Task<IFoo> GetFooAsync();
}
The concrete class,
public class FooService : IFooService
{
public async Task<IFoo> GetFooAsync()
{
// whole bunch of awaits on async calls and return IFoo at last
}
}
I register this service on StartUp,
services.AddTransient<IFooService, FooService>();
Several other services are injected with this service. One among them,
public class BarService : IBarService
{
private readonly IFooService _fooService;
public BarService(IFooService fooService)
{
_fooService = fooService;
}
public async Task<IBar> GetBarAsync()
{
var foo = await _fooService.GetFooAsync();
// additional calls & processing
var bar = SomeOtherMethod(foo);
return bar;
}
}
IFoo is integral to the application and used across several services. Most of my code is async just due to this one IFooService and the one method it has which returns IFoo.
Considering this use case, I would like to be able to just inject IFoo to all other services as opposed to injecting them with IFooService.
I gave this a shot,
services.AddTransient<IFoo>(provider =>
{
var fooService = provider.GetService<IFooService>();
var foo = fooService.GetFooAsync().GetAwaiter().GetResult();
return foo;
});
but it raises a red flag to me as I'm doing sync over async and I'm unsure if this will cause any issues like race conditions. Would startup be blocked by doing this. Im looking for a clean way to handle this, any recommendation for when we need something like this? Thank you for your help.
I guess what you want is an async constructor, which is not recommended.
here is more info about it and contains a solution using Lazy<T>.
but it raises a red flag to me as I'm doing sync over async
For this red flag, it is caused by that you call method GetFoo which is not defined in IFooService, it is not related with async or sync method.
Try the method which is defined
services.AddTransient<IFoo>(provider =>
{
var fooService = provider.GetService<IFooService>();
var foo = fooService.GetFooAsync().Result;
return foo;
});
2 Other options
Inject Func<Task> as the transient, the caller can await !
Task.Run( async () await abc).Wait(); // this deadlocks much less
due to creating a task and having less issues .

Executing MediatR PreProcessor Only for Specific Interface Types (Commands)

[NOTE: This is a "replacement" question. The first one was based on my main project's code so I've redone the question with code from a single-purpose project that illustrates the principle more cleanly. The question remains the same, just better presented.]
The Scenario
I'm trying to setup a command pre-processor on a CQRS request pipeline using MediatR pipeline behaviors and Autofac for request routing. My goal is for the pre-processor to run only for commands (ICommand<>) as opposed to all requests (IRequest<>), which will result in the pre-processor executing for commands, queries and events.
The Issue
I can get my GenericPreProcessor or any other pre-processor to run fine for all types of requests, but any method I've used to try to "filter" the injection either returns an error or simply doesn't execute the desired pre-processor.
My working-for-all-requests pipeline configuration in Autofac looks like this:
// Pipeline pre/post processors
builder
.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>))
.As(typeof(IPipelineBehavior<,>));
builder
.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>))
.As(typeof(IPipelineBehavior<,>));
// Works as desired: Fires generic pre-processor for ALL requests, both cmd and query
builder
.RegisterGeneric(typeof(GenericRequestPreProcessor<>))
.As(typeof(IRequestPreProcessor<>));
// Works for all requests, but I need a way to limit it to commands
builder
.RegisterGeneric(typeof(MyCommandPreProcessor<>))
.As(typeof(IRequestPreProcessor<>));
Conceptually I'm trying to do something like any of these, which fail:
builder
.RegisterGeneric(typeof(MyCommandPreProcessor<>)) // Note generic
.As(typeof(IRequestPreProcessor<ICommand<>>));
// Intellisense error "Unexpected use of an unbound generic"
builder
.RegisterType(typeof(MyCommandPreProcessor)) // Note non-generic
.As(typeof(IRequestPreProcessor<ICommand<>>));
// Intellisense error "Unexpected use of an unbound generic"
builder
.RegisterType(typeof(MyCommandPreProcessor)) // Note non-generic
.As(typeof(IRequestPreProcessor<ICommand<CommonResult>>));
// No errors, but MyCommandPreProcessor not firing
I'm trying a couple of different configurations for MyCommandPreProcessor, a generic and a non-generic but am stumped with either:
public class MyCommandPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
- OR -
public class MyCommandPreProcessor : IRequestPreProcessor<IRequest<ICommonResponse>>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
My Question
Any ideas on how I can register a pre-processor that will be restricted to only fire for IRequest<> types that are closed types of ICommand<>?
Supporting Materials
Project on GitHub
The entire minimal sample project can be viewed or cloned at https://github.com/jhoiby/MediatRPreProcessorTest
Autofac MediatR Config
A working config, with a single GenericRequestPreProcessor for all requests.
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
var mediatrOpenTypes = new[]
{
typeof(IRequestHandler<,>),
typeof(IRequestHandler<>),
typeof(INotificationHandler<>)
};
foreach (var mediatrOpenType in mediatrOpenTypes)
{
// Register all command handler in the same assembly as WriteLogMessageCommandHandler
builder
.RegisterAssemblyTypes(typeof(MyCommandHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
// Register all QueryHandlers in the same assembly as GetExternalLoginQueryHandler
builder
.RegisterAssemblyTypes(typeof(MyQueryHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
// Pipeline pre/post processors
builder.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(GenericRequestPreProcessor<>)).As(typeof(IRequestPreProcessor<>));
// builder.RegisterGeneric(typeof(GenericRequestPostProcessor<,>)).As(typeof(IRequestPostProcessor<,>));
// builder.RegisterGeneric(typeof(GenericPipelineBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
});
MyCommandPreProcessor Class
I'm experimenting with both of these, generic and non-generic:
public class MyCommandPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
- AND -
public class MyCommandPreProcessor : IRequestPreProcessor<IRequest<ICommonResponse>>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
Inheritance Structures
// Requests
IMediatR.IRequest<TResponse>
<- IMessage<TResponse>
<- ICommand<TResponse>
<- concrete MyCommand : ICommand<CommonResponse>
<- IQuery<TResponse>
<- concrete MyQuery : IQuery<CommonResponse>
// Request Handlers
IMediatR.IRequestHandler<in TRequest,TResponse>
<- IMessageHandler<in TRequest,TResponse>
<- ICommandHandler<in TRequest,TResponse>
<- concrete MyCommandHandler : ICommandHandler<MyCommand,CommonResponse>
<- IQueryHandler<In TRequest,TResponse>
<- concrete MyQueryHandler : IQueryHandler<MyQuery,CommonResponse>
// CommonResponse - A POCO that returns result info
ICommonResponse
<- concrete CommonResponse
Commands
public interface IMessage<TResponse> : MediatR.IRequest<TResponse>
{
}
public interface ICommand<TResponse> : IMessage<TResponse>
{
}
public class MyCommand : ICommand<CommonResponse>
{
}
Command Handlers
public interface IMessageHandler<in TRequest, TResponse>
: MediatR.IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
}
public interface ICommandHandler<in TRequest, TResponse>
: IMessageHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
}
public class MyCommandHandler : ICommandHandler<MyCommand, CommonResponse>
{
public async Task<CommonResponse> Handle(
MyCommand request,
CancellationToken cancellationToken)
{
Debug.WriteLine(" ***** Command handler executing *****");
return
new CommonResponse(
succeeded: true,
data: "Command execution completed successfully.");
}
}
PreProcessor Injection Target (in the MediatR Pipeline Code)
The constructor that receives the injected IRequestPreProcessor<> is:
public RequestPreProcessorBehavior(IEnumerable<IRequestPreProcessor<TRequest>> preProcessors)
{
...
}
It can be seen on Github on line 17 of the file at:
https://github.com/jbogard/MediatR/blob/master/src/MediatR/Pipeline/RequestPreProcessorBehavior.cs
Thank you!
I have the same exact scenario as you and I believe the issue is stemming from RequestPreProcessorBehavior<TRequest, TResponse> not passing all types down to IRequestPreProcessor<TRequest>.
You either:
No constraints: check the type of request in MyCommandPreProcessor<TRequest> in every IRequestPreProcessor:
public Task Process(TRequest request, CancellationToken cancellationToken)
{
var isCommand = typeof(TRequest).GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommand<>));
if (isCommand)
{
// Magic
}
}
Create your own pre-processing behavior that exposes TRequest of IPipelineBehavior<TRequest, TResponse>:
public interface IRequestPreProcessor<in TRequest, TResponse> : IRequestPreProcessor<TRequest>
where TRequest : IRequest<TResponse>
{
}
public class MyRequestPreProcessorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IRequestPreProcessor<TRequest, TResponse>> _preProcessors;
public RequestPreProcessorBehavior(IEnumerable<IRequestPreProcessor<TRequest, TResponse>> preProcessors)
{
_preProcessors = preProcessors;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
foreach (var processor in _preProcessors)
{
await processor.Process(request, cancellationToken).ConfigureAwait(false);
}
return await next().ConfigureAwait(false);
}
}
With option 2, you would add constraints to any class that implemented IRequestPreProcessor<TRequest, TResponse> for command / query specific pre-processors.
Just add the interface on command/query class and add the preprocessor for that interface.
Please go through the below example
public interface IUser{}
public class AddUserCommand: IUser, IRequest<UserModel>{....
public class UpdateUserCommand: IUser, IRequest<UserModel>{....
public class GetUserQuery: IRequest<UserModel>{....
remember: here in above classes GetUserQuery class don't have IUser interface.
Now lets create the pre-processor class which execute only for command/query which marked with IUser interface
public class UserCommandQueryPrepProcessor<T>: IRequestPreProcessor<T> where T: IUser
{.....
public Task Process(T request, CancellationToken token)
{ //enter your interface specific logic here
}
}
Note: above UserCommandQueryPrepProcessor class is a generic class with where IUser which executes only for class 'AddUserCommand' and 'UpdateUserCommand' inheriting IUser interface.

Use AsyncLocal to store request information?

We are starting with ASP.NET Core 2. We need a way for each element that is involved in a request to write a message to a message handler.
Some limitations:
We won't use HttpContext.Items (HttpContext is not available in the class that we are using inside the Controller, and we don't like to forward the whole context there).
We tried to use it without dependency injection because if we have multiple different services, we will have too many parameters in the constructors.
Must also work with async/await.
We tried an approach using AsyncLocal<T>.
For that we created a class:
public class NotificationExecutionContext
{
private static readonly AsyncLocal<NotificationHandler> NotificationHandler =
new AsyncLocal<NotificationHandler>();
public static NotificationHandler Instance =>
NotificationHandler.Value ?? (NotificationHandler.Value = new NotificationHandler());
}
There will be a NotificationHandler created, which should live per-request. The NotificationHandler is a simple class where you can add/get messages to/from a collection:
public class NotificationHandler : INotificationHandler
{
public List<NotificationBase> Notifications { get; } = new List<NotificationBase>();
public void AddNotification(NotificationBase notification)
{
Notifications.Add(notification);
}
public void AddNotificationRange(List<NotificationBase> notifications)
{
Notifications.AddRange(notifications);
}
}
With this solution, I can easily get the NotificationHandler for this context and add a notification.
NotificationExecutionContext.Instance.AddNotification(new NotificationBase(){..})
Inside a middleware, we are waiting on the Response.OnStarting() event and then we take all messages from the NotificationHandler and add them the response header:
public async Task Invoke(HttpContext context)
{
var e = NotificationExecutionContext.Instance; // Required so that notification handler will be created in this context
context.Response.OnStarting((state) =>
{
List<NotificationBase> notifications = NotificationExecutionContext.Instance.Notifications;
if (notifications.Count > 0)
{
string messageString = JsonConvert.SerializeObject(notifications, Formatting.None);
context.Response.Headers.Add("NotificationHeader", messageString);
}
return Task.FromResult(0);
}, null);
await Next(context);
}
This code works, but are there pitfalls that we do not know? Or are there better solutions?
You should not use static singletons like that. Having static dependencies like that inside your code defeats the whole purpose of dependency injection. You should just embrace dependency injection here, which would make this super simple:
/* in Startup.ConfigureServices */
// register the notification handler as a scoped dependency, this automatically makes the
// instance shared per request but not outside of it
services.AddScoped<INotificationHandler, NotificationHandler>();
/* in Startup.Configure */
// register your custom middleware
app.Use<NotificationHandlerMiddleware>();
public class NotificationHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly NotificationHandler _notificationHandler;
public NotificationHandlerMiddleware(RequestDelegate next, INotificationHandler notificationHandler)
{
_next = next;
_notificationHandler = notificationHandler;
}
public void Invoke(HttpContext context)
{
// do whatever with _notificationHandler
await _next(context);
}
}
And that’s all. No need to introduce statics, but using full dependency injection making your code completely testable and all dependencies clear.
We tried to use it without dependency injection because if we have multiple different services we will have to many parameters in the constructors.
Too many constructor parameters is a clear sign for a violation of the single responsibility principle. If you find your services take many dependencies, you should consider splitting it up. You may also want to consider refactoring to facade services.

Categories