I am trying to create a MediatR pipeline which calls the SaveChangesAsync on my DbContext after each command. I am trying to make this work in a generic way, so that I can add the pipeline with DI, and it will work for all my commands. By default, I want to save it, but I want to have the option in my command to set a boolean, and if it is set to false, I do not want to call the SaveChangesAsync on my DbContext. I have basically come to a working solution, but in my DI registration, I have to specify which DbContext I am using, which I want to prevent.
I have written the following code:
SaveCommandBehavior:
public class SaveCommandBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IRequestHandler<TRequest, TResponse> _requestHandler;
public SaveCommandBehavior(IRequestHandler<TRequest, TResponse> requestHandler)
{
_requestHandler = requestHandler;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var response = await next();
var isDbCommandHandler = IsAssignableToGenericType(_requestHandler.GetType(), typeof(DbCommandHandler<>));
if (isDbCommandHandler &&
_requestHandler is DbCommandHandler<MyDbContext> commandHandler &&
request is DbCommand { SaveChanges: true } or DbCommand<TResponse> { SaveChanges: true })
{
await commandHandler.DbContext.SaveChangesAsync(cancellationToken);
}
return response;
}
public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
var interfaceTypes = givenType.GetInterfaces();
foreach (var it in interfaceTypes)
{
if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
return true;
}
if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
return true;
Type baseType = givenType.BaseType;
if (baseType == null) return false;
return IsAssignableToGenericType(baseType, genericType);
}
}
Registration:
public static IServiceCollection AddSaveCommandPipelineLocal(this IServiceCollection services)
{
return services.AddTransient(typeof(IPipelineBehavior<,>), typeof(SaveCommandBehavior<,>));
}
Abstract classes that I use in my commands:
public abstract class DbCommand: ICommand
{
public bool SaveChanges { get; set; } = true;
}
public abstract class DbCommand<TResponse> : ICommand<TResponse>
{
public bool SaveChanges { get; set; } = true;
}
public abstract class DbCommandHandler<TDbContext> where TDbContext : DbContext
{
public TDbContext DbContext;
protected DbCommandHandler(TDbContext dbContext)
{
DbContext = dbContext;
}
}
Example command and handler:
public sealed class MyCommand: DbCommand
{
public Guid Parameter1 { get; set; }
}
public class MyCommandHandler: DbCommandHandler<MyDbContext>, ICommandHandler<MyCommand>
{
public MyCommandHandler(MyDbContext dbContext)
: base(dbContext)
{
}
public async Task<Unit> Handle(MyCommand request, CancellationToken cancellationToken)
{
// Command logic making changes to EF, without calling save.
}
}
While this code would work, I have to use
_requestHandler is DbCommandHandler commandHandler
which I do not want: I already know at that point that I am dealing with a _requestHandler which should have a DbContext of base type 'DbContext', which should be enough information to call 'SaveChangesAsync' on it. However, I can not figure out how to get that type, without having to specify which DbContext I am dealing with.
I am able to figure out whether or not it is a valid type with a DbContext of any type, using the isDbCommandHandler variable, but I can then not utilise that to cast this to a usable variable of which I can use the DbContext.
I feel like I am overlooking something simple. I feel like I should add a parameter TContext to SaveCommandBehavior where TContext : DbContext, so I can use that in the cast, but then I have an extra generic parameter in my SaveCommandBehavior which would make my DI fail, since the PipelineBehavior only takes 2 parameters
A proposed alternative more than a direct answer.
What if you don't get the DbContext form the handler, but you inject it in the pipeline?
You can still check for the abstract class / interface for the bool just to know if you need to save, but you can get rid of the dependency between the handler and the dbcontext.
public class SaveCommandBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IRequestHandler<TRequest, TResponse> _requestHandler;
private readonly MyDbContext _myDbContext;
public SaveCommandBehavior(
MyDbContext myDbContext,
IRequestHandler<TRequest, TResponse> requestHandler)
{
_requestHandler = requestHandler;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var response = await next();
var isDbCommandHandler = IsAssignableToGenericType(_requestHandler.GetType(), typeof(DbCommandHandler<>));
if (isDbCommandHandler &&
request is DbCommand { SaveChanges: true } or DbCommand<TResponse> { SaveChanges: true })
{
await _myDbContext.SaveChangesAsync(cancellationToken);
}
return response;
}
...
}
The only problem that I see with this is that right now you are using only one dbcontext which is most likely something is the case for most projects.
In case you want to use multiple DBcontexts you would have to inject a factory instead of MyDbContext.
That factory would have to get the DbContext With the service provider like
serviceProvider.GetRequierdService<MyDbContext>();
interface IDbContextFactory{
DbContext GetDBContextForType(Type type);
}
class DbContextFactory : IDBContextFactory{
DbContext GetDBContextForType(Type type){
//Some custom logic here
}
}
Related
My controller action to update user status in mvc:
public class UserController : AdminController
{
public async Task UpdateUserStatus(int id, int status)
{
await UpdateTheStatus<UserService, User>(id, status);
}
}
I have a base controller to update the user status
public abstract class AdminController : ControllerBase
{
public async Task UpdateTheStatus<TService, T>(int id, int status)
where TService : StatusService<T>, new()
{
await new TService().UpdateStatus(id, status);
}
}
My UserService.cs have constructor
public class UserService : StatusService<User>
{
public UserService(MyContext context) : base(context)
{
}
....
}
Base class StatusService.cs
public abstract class StatusService<T>
{
protected MyContext ctx = null;
public StatusService(MyContext context)
{
ctx = context;
}
public async virtual Task UpdateStatus(int id, int status)
{
...
await ctx.SaveChangesAsync();
}
}
Then my code have the error:
'UserService' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TService' in the generic type or method
How do I pass the constructor with parameter as generic type?
Your fundamental issue is that you need an instance of MyContext at the point where you want to create the TService, as long as you are using a UserService object.
One way to solve this is to pass in a Func<TService> to the UpdateStatus() method instead of specifying the new restraint:
public async Task UpdateStatus<TService, T>(int id, int status, Func<TService> serviceCreator)
where TService : StatusService<T>
{
await serviceCreator().UpdateStatus(id, status);
}
Then the implementation of UpdateUserStatus() would look something like this:
public async Task UpdateUserStatus(int id, int status)
{
var context = new MyContext(); // However you obtain this.
await UpdateStatus<UserService, User>(id, status, () => new UserService(context));
}
The issue you then face is how to obtain the MyContext instance that you need for creating the UserService object. You can't avoid the need for the MyContext instance because the UserService requires it when you create it.
Here's a runnable example on DotNetFiddle.
You can take a further step of injecting a delegate with which to create the MyContext object:
public async Task UpdateUserStatus(int id, int status, Func<MyContext> contextProvider)
{
await UpdateStatus<UserService, User>(id, status, () => new UserService(contextProvider()));
}
Now we've pushed the MyContext creation to the outer level. The outer level must still be able to obtain or create a MyContext, of course.
Here's a runnable example with those changes on DotNetFiddle.
Looks like you're set up to use DI in your project, so you should use it.
In your Program.cs, register your classes that implement your base class (which could be better as an interface), eg:
builder.Services.AddTransient<StatusService<User>, UserService>();
Now instead of having a separate task to update the status, just pass your expected service into your constructor via DI which will automatically include the context:
public class WhatIsYourClassCalled
{
private readonly StatusService<User> _statusService;
public WhatIsYourClassCalled(StatusService<User> statusService)
{
_statusService = statusService;
}
public async Task UpdateUserStatus(int id, int status)
{
await _statusService.UpdateStatus(id, status);
}
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
readonly private IProductWriteRepository _productWriteRepository;
readonly private IProductReadRepository _productReadRepository;
public ProductsController(IProductWriteRepository productWriteRepository, IProductReadRepository productReadRepository)
{
_productWriteRepository = productWriteRepository;
_productReadRepository = productReadRepository;
}
[HttpGet]
public async void Get()
{
await _productWriteRepository.AddRangeAsync(new()
{
new() {Id=Guid.NewGuid(),Name="Test",Price = 300,Stock = 10,CreatedDate = DateTime.Now,},
new() {Id=Guid.NewGuid(),Name="Test 2",Price = 350,Stock = 20,CreatedDate = DateTime.Now,}
});
await _productWriteRepository.SaveAsync();
}
}
IProductWriteRepository:
public interface IProductWriteRepository : IWriteRepository<eCommerceAPI.Domain.Entities.Product>
{
}
IProductReadRepository:
public interface IProductReadRepository : IReadRepository<eCommerceAPI.Domain.Entities.Product>
{
}
IReadRepository;
public interface IReadRepository<T> : IRepository<T> where T: BaseEntity
{
IQueryable<T> GetAll();
IQueryable<T> GetWhere(Expression<Func<T,bool>> method);
Task<T> GetSingleAsync(Expression<Func<T, bool>> method);
Task<T> GetByIdAsync(string id);
}
IWriteRepository:
public interface IWriteRepository<T> : IRepository<T> where T : BaseEntity
{
Task<bool> AddAsync(T model);
Task<bool> AddRangeAsync(List<T> model);
bool Remove(T datas);
bool RemoveRange(List<T> datas);
Task<bool> RemoveAsync(string id);
bool Update(T model);
Task<int> SaveAsync();
}
Cannot access a disposed context instance. A common cause of this
error is disposing a context instance that was resolved from
dependency injection and then later trying to use the same context
instance elsewhere in your application. This may occur if you are
calling 'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances.
public async void Get()
Your controller action is async, but returns void. So it will continue to run in the background, after the request scope has been disposed.
You need your routing endpoint to wait for the action to complete;
public async Task Get()
If you really want this request to run in the background, without forcing the client to wait, you should investigate some other solution.
I am playing around with CQRS and the MediatR library, trying to learn some of the best practices.
One problem I have is code duplication in Command/Query handlers. I would like to know what is the best way to share logic between handlers.
Example:
I have an abstract Entity class that defines an ID property. All entities inherit from that class.
public abstract class Entity
{
public long Id { get; private set; }
protected Entity(long id)
{
Id = id;
}
...
}
Then for every entity, I want to create a GetById query. One of those queries looks like so:
public class GetUserByIdQuery : IRequest<UserDto>
{
public long UserId { get; set; }
public class Handler : IRequestHandler<GetUserByIdQuery, UserDto>
{
private readonly IRepository<User> repository;
private readonly IMapper mapper;
public Handler(IUnitOfWork unitOfWork, IMapper mapper)
{
repository = unitOfWork.GetRepository<User>();
this.mapper = mapper;
}
public async Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = await repository.FindAsync(request.UserId, null, cancellationToken);
if (user is null)
{
throw new EntityNotFoundException();
}
return mapper.Map<UserDto>(user);
}
}
}
The problem is that this class looks exactly the same for all the entities. Without CQRS I would probably have something like this:
public class EntityFinder<TEntity, TDto> where TEntity : Entity
{
private readonly IRepository<TEntity> repository;
private readonly IMapper mapper;
public EntityFinder(IUnitOfWork unitOfWork, IMapper mapper)
{
repository = unitOfWork.GetRepository<TEntity>();
this.mapper = mapper;
}
public async Task<TDto> GetByIdAsync(long id)
{
var entity = await repository.FindAsync(id);
if (entity is null)
{
throw new EntityNotFoundException();
}
return mapper.Map<TDto>(entity);
}
}
I tried doing something similar with a generic query and handler but MediatR had trouble finding the handler (even when I tried registering it manually to the DI container).
What is the best way to avoid such duplication?
Can you try below code. This way, you reuse the loading code, and at the same time, provide an end point to handle the request.
public class EntityFinder<TEntity, TDto> where TEntity : Entity
{ ... // Same as your code }
public class GetUserByIdQuery : IRequest<UserDto>
{
public long UserId { get; set; }
public class Handler : IRequestHandler<GetUserByIdQuery, UserDto>, EntityFinder<User, UserDto>
{
public Handler(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
{ }
public async Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
=> await base.GetByIdAsync(request.UserId);
}
}
[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.
Looking at using the new Mediatr 3.0 feature pipeline behaviors for authentication/authorization.
Would you normally auth based on the message or the handler? reason I'm asking is that I'd auth on the handler (same as controller in MVC) but behaviors don't appear to have knowledge about the handler so I'm not sure this is possible/suitable.
I could add an IAuthorisationRequired marker interface to each message, but if the message is a notification/event and has multiple handlers then maybe some should run but not others. Really does feel better checking auth on the handler code that does the actual work.
Would love to be able to put a [Authorize] attribute on a handler and user a behaviour to check it (I currently do exactly this but with a base class instead of a behaviour).
public class AuthenticationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
//Can't access handler class here, so how do I know the action requires authentication/authorization?
return next();
}
}
[Authorize]
public class ChangePasswordRequestHandler : IAsyncRequestHandler<ChangePassword, ReponseType>
{
protected override async Task<ReponseType> Handle(AsyncRequestBase<ChangePassword> message)
{
//change users password here
}
}
You're right, the RequestDelegateHandler<TResponse> doesn't expose what handler will run next, and this is intentional. If you think about it, pipelines in MediatR 2.x used decorators, and while the decorator had access to the instance of the decoratee, I would advise against doing auth based on it. The reason is that if you need your authorization decorator to decorate one specific instance of a handler - the one decorated with specific attributes - then they're coupled, which defeats the purpose of decorators where you should be able to put them on top of each other independently.
That's why I would advise basing authorization on the message, at least in most cases. You could have an extensible design where to each message are associated several authorization rules, and a behavior evaluates all of them.
public interface IAuthorizationRule<TRequest>
{
Task Evaluate(TRequest message);
}
public class AuthorizationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IAuthorizationRule<TRequest>[] _rules;
public AuthorizationBehavior(IAuthorizationRule<TRequest>[] rules)
{
_rules = rules;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
// catch it or let it bubble up depending on your strategy
await Task.WaitAll(_rules.Select(x => x.Evaluate(request)));
return next();
}
}
For the specific case you mention where, for a notification, some handlers might run while others shouldn't, you can always use authorization behaviors that target that specific message and apply them selectively to the handlers that need them. I guess my point is you'll have to do a bit of crafting yourself when you hit those specific scenarios.
I had the same requirement for a project and implemented a specific pipeline where I could inject (if required) a AuthorisationHandler for a specific request. This means I just need to add a new AuthorisationHandler for each new command that I created, and then it will be called before the request to process the actual command.
The pipeline:
public class Pipeline<TRequest, TResponse> : IAsyncRequestHandler<TRequest, TResponse> where TRequest : IAsyncRequest<TResponse>
{
private readonly IAuthorisationHandler<TRequest, TResponse>[] _authorisationHandlers;
private readonly IAsyncRequestHandler<TRequest, TResponse> _inner;
private readonly IPostRequestHandler<TRequest, TResponse>[] _postHandlers;
public Pipeline(IAuthorisationHandler<TRequest, TResponse>[] authorisationHandlers, IAsyncRequestHandler<TRequest, TResponse> inner, IPostRequestHandler<TRequest, TResponse>[] postHandlers)
{
_authorisationHandlers = authorisationHandlers;
_inner = inner;
_postHandlers = postHandlers;
}
public async Task<TResponse> Handle(TRequest message)
{
foreach (var authorisationHandler in _authorisationHandlers)
{
var result = (ICommandResult)await authorisationHandler.Handle(message);
if (result.IsFailure)
{
return (TResponse)result;
}
}
var response = await _inner.Handle(message);
foreach (var postHandler in _postHandlers)
{
postHandler.Handle(message, response);
}
return response;
}
}
The Authorsiation Handler:
public class DeleteTodoAuthorisationHandler : IAuthorisationHandler<DeleteTodoCommand, ICommandResult>
{
private IMediator _mediator;
private IAuthorizationService _authorisationService;
private IHttpContextAccessor _httpContextAccessor;
public DeleteTodoAuthorisationHandler(IMediator mediator, IAuthorizationService authorisationService, IHttpContextAccessor httpContextAccessor)
{
_mediator = mediator;
_authorisationService = authorisationService;
_httpContextAccessor = httpContextAccessor;
}
public async Task<ICommandResult> Handle(DeleteTodoCommand request)
{
if (await _authorisationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, "DeleteTodo"))
{
return new SuccessResult();
}
var message = "You do not have permission to delete a todo";
_mediator.Publish(new AuthorisationFailure(message));
return new FailureResult(message);
}
}
My AuthorisationHandler implemements IAuthorisationHandler which looks like this:
public interface IAuthorisationHandler<in TRequest, TResponse> where TRequest : IAsyncRequest<TResponse>
{
Task<TResponse> Handle(TRequest request);
}
It then hangs together using the DecorateAllWith (part of structuremap)
cfg.For(typeof(IAsyncRequestHandler<,>)).DecorateAllWith(typeof(Pipeline<,>));
Not sure you should do this for 3.x as this now has a new pipeline interface
IPipelineBehavior<TRequest, TResponse>
Not used it yet but I think it will simplify the implementation and mean you can stop using the decorator pattern DecorateAllWith.
You could do this in the same way I use Fluent Validation.
I created the following behaviour:
namespace MediatR.Extensions.FluentValidation
{
public class ValidationPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IValidator<TRequest>[] _validators;
public ValidationPipelineBehavior(IValidator<TRequest>[] validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext(request);
var failures =
_validators.Select(v => v.Validate(context)).SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Any())
{
throw new ValidationException(failures);
}
return await next();
}
}
}
Create a AbstractValidator
public classs SaveCommand: IRequest<int>
{
public string FirstName { get; set; }
public string Surname { get; set; }
}
public class SaveCommandValidator : AbstractValidator<SaveCommand>
{
public SaveCommandValidator()
{
RuleFor(x => x.FirstName).Length(0, 200);
RuleFor(x => x.Surname).NotEmpty().Length(0, 200);
}
}
So you could create a Authorization<T> class where you could add your custom authorization code per request and have injected into a AuthorizationPipelineBehavior<TRequest, TResponse> class.