Super simple, handy plugins for MediatR.
PM> Install-Package MediatR.Plugins
- Logging behavior
- Validation behavior (with FluentValidation)
- Caching behavior
The logging behavior will log:
- Error severity:
- If an exception was thrown in handler
- Debug severity:
- Pre processing:
Handling {typeof(TRequest)}, traceId: {GUID} {timestamp}
- Post processing:
Handled {typeof(TRequest)}, traceId: {GUID} {timestamp}
- Pre processing:
Log4netLoggingBehavior
obviously depends on Log4net, so an ILog
needs to be
configured in your IoC/DI.
Then simply register the Log4netLogginBehavior
in your IoC/DI container as well.
Structuremap example:
var container = new Container(cfg =>
{
...
For<ILog>().Use(Log4netInstance);
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(Log4netLoggingBehavior<,>));
});
SerilogLoggingBehavior
obviously depends on Serilog, so an ILogger
needs to be
configured in your IoC/DI.
Then simply register the SerilogLoggingBehavior
in your IoC/DI container as well.
Structuremap example:
var container = new Container(cfg =>
{
...
For<ILogger>().Use(SerilogInstance);
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(SerilogLoggingBehavior<,>));
});
No additional setup is required to use the validation behavior, even though it relies on
FluentValidation
. I choosed to design this as decoupled as posible from the users
IoC/DI framework as possible. So the only setup needed is to register the pipeline behavior:
var container = new Container(cfg =>
{
...
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(ValidationBehavior<,>));
});
A validatable request needs to implement IValidatableRequest
:
public class Request : IValidatableRequest<Result>
{
public Request(int value)
{
Value = value;
}
public int Value { get; set; }
public ValidationResult Validate() => new Validator().Validate(this);
}
public class Validator : AbstractValidator<Request>
{
public Validator()
{
RuleFor(x => x.Value)
.GreaterThan(10)
.LessThan(20);
}
}
The IValidatableRequest
is an IRequest
with a Validate()
method on top of it that is
used by the pipeline behavior to do the validation, without having to do IoC/DI framework
specific scanning to figure out which validator to use.
The reason why I felt the need of caching responses is that I was writing an application that needs to be extremely fast performing and that my handlers that I needed to cache was of pure I/O calculators. No external dependencies (e.g. lookups to database).
I did know for sure that given a set of parameters, I knew that the response would be exactly the same every time it was invoked. Hence caching is safe.
Before starting using a caching behavior of your request, make sure that you know exactly why you want to cache, what you want to cache and most importantly how to figure out unique caching keys. If caching keys are not generated properly, the caching behavior is a very dangerous feature to rely on.
That being said the caching behavior is safely implemented and you are fully responsible and in control of cache validation checks, which caching provider to use etc.
The caching behavior pipeline depends on an ICacheProvider
and an ICacheLogger
.
The caching itself is not done itself in the pipeline, but are left to the user to
implement using the ICacheProvider
. So whether you want to cache using a Dictionary
,
Redis, distributed cache, runtime cache or whatever - its up to you.
Same goes for the ICacheLogger, just implement the Debug
method to (preferrably) decorate
your favorite logging framework of choice.
var container = new Container(cfg =>
{
...
For<ICacheLogger>().Use(CacheLoggerInstance);
For<ICacheProvider>().Use(CacheProviderInstance);
cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(SerilogLoggingBehavior<,>));
});
The caching behavior pipeline will log with Debug
severity when:
- Retrieve from cache
- Adding to cache
A cachable request needs to be of type ICachableRequest
which is an IRequest
with a GetCacheKey()
method on top of it. This is where you should implement your
request-unique cache key generator.
public class CachablePing
{
public class Handler : IRequestHandler<Request, Result>
{
public Task<Result> Handle(Request request, CancellationToken cancellationToken)
{
return Task.FromResult(new Result
{
Value = Math.Pow(request.Value, 2),
ProcessedTicks = DateTime.UtcNow.Ticks
});
}
}
public class Request : ICachableRequest<Result>
{
public double Value { get; set; }
public string GetCacheKey()
{
return Value.ToString();
}
}
public class Result
{
public double Value { get; set; }
public long ProcessedTicks { get; set; }
}
}
MIT License
Copyright (c) 2017 Victor Alveflo
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.