Mediatr: How to register IRequestHandler<,> to multiple Implementations with type constraints.

Created on 7 May 2021  路  5Comments  路  Source: jbogard/MediatR

Hello I've been following the addition of @jbogard to .net 5.0 dependency injection and was wondering if it is all sorted out now.

I am trying to register a generic Handler for generic requests...

I am currently using asp.net core 3.1 and MediatR 8.1

//generic command
  public class AddNoteCommand<TEntity>  
        where TEntity : class, INoteEntity
    {
        public int ParentId { get; set; }
        public int CurrentUserProfileId { get; set; }
        public string NoteContent { get; set; }
    }

//generic handler
     public class AddNoteCommandHandler<TEntity> : IRequestHandler<AddNoteCommand<TEntity>>
        where TEntity : class, INoteEntity, new()

    {
        private readonly INoteRepository<TEntity> _repo;
        private readonly ITime _time;

        public AddNoteCommandHandler(INoteRepository<TEntity> repo, ITime time)
        {
            _repo = repo;
            _time = time;
        }
        public async Task<Unit> Handle(AddNoteCommand<TEntity> request, CancellationToken cancellationToken)
        {
            await _repo.Insert(new TEntity
            {
                CreatedByUserId = request.CurrentUserProfileId,
                CreatedDate = _time.Current,
                NoteContent = request.NoteContent,
                ParentId = request.ParentId                
            });

            await _repo.SaveChanges();

            return Unit.Value;
        }
    }

//another implementation 
//command
public class FetchAllNotesForParentQuery<TEntity> 
        where TEntity : class, INoteEntity
    {
        public int ParentId { get; set; }
    }

//handler
public class FetchAllNotesForParentQueryHandler<TEntity> : IRequestHandler<FetchAllNotesForParentQuery<TEntity>, List<TEntity>>
        where TEntity : class,  INoteEntity

    {
        private readonly INoteRepository<TEntity> _repo;

        public FetchAllNotesForParentQueryHandler(INoteRepository<TEntity> repo)
        {
            _repo = repo;
        }

        public Task<List<TEntity>> Handle(FetchAllNotesForParentQuery<TEntity> request, CancellationToken cancellationToken)
        {
            return _repo.FetchByParentId(request.ParentId);
        }
    }

//usage **MyNoteClass implements INoteEntity
await _mediator.Send(new AddNoteCommand<MyNoteClass>
 {
    //properties here... 
 });
 var notesList = await _mediator.Send(new FetchAllNotesForParentQuery<MyNoteClass> 
{
    ParentId = parentId
});

//asp.net core DI registration
services
    .AddTransient(typeof(IRequestHandler<,>), typeof(AddNoteCommandHandler<,>))
    .AddTransient(typeof(IRequestHandler<,>), typeof(FetchAllNotesForParentQueryHandler<,>);

Will this work somehow?

I've tried this and it seems to try and get the last implementation I register at runtime and fails letting me know that the type parameter violates the constraints of the implementation it tries to use.

It always seems to try and use the last implementation that I register.

Will this be solved by upgrading to .net 5.0? or maybe mediator 9+ ?

Any help would be appreciated. Thank you!

All 5 comments

I actually got this working with AutoFac as a container.

public static ContainerBuilder AddGenericHandlers(this ContainerBuilder builder)
        {
            builder.RegisterSource(new ContravariantRegistrationSource());
            builder.RegisterGeneric(typeof(FetchDropdownItemByEnumQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(FetchAllNotesForParentQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(FetchAllNotesWithTypeForParentQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(FetchAllSelectListItemsQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(AddNoteCommandHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(AddNoteWithTypeCommandHandler<>)).AsImplementedInterfaces();
            return builder;
        }

Is there currently a way to do this with the default .Net 5.0 DI container?

This issue can be closed, I guess I'm just looking for a concrete answer if this is possible with the default container.

Thanks.

@zachpainter77, @jbogard I'd like to know if it is possible (and how) too!
Thanks!

@OculiViridi

One way I've found to achieve it is by creating concrete handlers that are derived from the generic ones.

The downside to this is that you still have to write the definition of each handler. Also, you would still need to repeat the constructors for each one of them as well. This can be quite tedious, especially if you have more than one type parameter, more implementations and more dependencies to inject.

But at least this way you wouldn't have to rewrite the handler implementation.

I don't think there is any way of getting around this at the moment besides using a third party DI container. I think this is because the default .NET DI container does not support generic variance. So, until they do I'm just going to use Autofac which works quite well along side the default container.

For example:

//Three implementations of INoteEntity
public class NoteType1 : INoteEntity { }
public class NoteType2 : INoteEntity { }
public class NoteType3 : INoteEntity { }

//Three concreate handlers. :(
public NoteType1Handler : FetchAllNotesForParentQuery<NoteType1>
{
    //constructor still have to rewrite for each implementation
    public NoteType1Handler(INoteRepository<NoteType1> repo) : base(repo) {}
}
public NoteType2Handler : FetchAllNotesForParentQuery<NoteType2>
{
    //constructor still have to rewrite for each implementation
    public NoteType2Handler(INoteRepository<NoteType2> repo) : base(repo) {}
}
public NoteType3Handler : FetchAllNotesForParentQuery<NoteType3>
{
    //constructor still have to rewrite for each implementation
    public NoteType3Handler(INoteRepository<NoteType3> repo) : base(repo) {}
}

public class FetchAllNotesForParentQuery<TEntity> 
    where TEntity : class, INoteEntity
{
    public int ParentId { get; set; }
}

public class FetchAllNotesForParentQueryHandler<TEntity> : IRequestHandler<FetchAllNotesForParentQuery<TEntity>, List<TEntity>>
    where TEntity : class,  INoteEntity
{
    private readonly INoteRepository<TEntity> _repo;
    public FetchAllNotesForParentQueryHandler(INoteRepository<TEntity> repo)
    {
        _repo = repo;
    }

    public Task<List<TEntity>> Handle(FetchAllNotesForParentQuery<TEntity> request, CancellationToken cancellationToken)
    {
        return _repo.FetchByParentId(request.ParentId);
    }
}

@zachpainter77

public class Command<T> : IDistributedCommand, IRequest<bool> where T : class, IDistributedCommand
{
    public T Data { get; }
    public OutBoxStatus Status { get; }

    public Command(T data, OutBoxStatus status)
    {
        Data = data;
        Status = status;
    }
}

services.AddTransient<IRequestHandler<Outbox.Command<CreateInvoiceRequest.CreateInvoiceRequestCommand>, bool>, Outbox.CommandHandler<CreateInvoiceRequest.CreateInvoiceRequestCommand>>();
            services.AddTransient<IRequestHandler<Outbox.Command<CreateInvoiceRequest.UpdateInvoiceRequestCommand>, bool>, Outbox.CommandHandler<CreateInvoiceRequest.UpdateInvoiceRequestCommand>>();

This worked for me to register multiple generic request handlers. But need to do the implicitly. Is anyone having any idea to register the DI in a generic way?

@abuzaforfagun

This sample is registered without having to explicitly register the handlers by using the default AddMediatr extension method. Since they are concrete handlers derived from a closed generic base class, Mediatr is able to find the correct handler without any explicit service registration.

@OculiViridi

One way I've found to achieve it is by creating concrete handlers that are derived from the generic ones.

The downside to this is that you still have to write the definition of each handler. Also, you would still need to repeat the constructors for each one of them as well. This can be quite tedious, especially if you have more than one type parameter, more implementations and more dependencies to inject.

But at least this way you wouldn't have to rewrite the handler implementation.

I don't think there is any way of getting around this at the moment besides using a third party DI container. I think this is because the default .NET DI container does not support generic variance. So, until they do I'm just going to use Autofac which works quite well along side the default container.

For example:

//Three implementations of INoteEntity
public class NoteType1 : INoteEntity { }
public class NoteType2 : INoteEntity { }
public class NoteType3 : INoteEntity { }

//Three concreate handlers. :(
public NoteType1Handler : FetchAllNotesForParentQuery<NoteType1>
{
    //constructor still have to rewrite for each implementation
    public NoteType1Handler(INoteRepository<NoteType1> repo) : base(repo) {}
}
public NoteType2Handler : FetchAllNotesForParentQuery<NoteType2>
{
    //constructor still have to rewrite for each implementation
    public NoteType2Handler(INoteRepository<NoteType2> repo) : base(repo) {}
}
public NoteType3Handler : FetchAllNotesForParentQuery<NoteType3>
{
    //constructor still have to rewrite for each implementation
    public NoteType3Handler(INoteRepository<NoteType3> repo) : base(repo) {}
}

public class FetchAllNotesForParentQuery<TEntity> 
    where TEntity : class, INoteEntity
{
    public int ParentId { get; set; }
}

public class FetchAllNotesForParentQueryHandler<TEntity> : IRequestHandler<FetchAllNotesForParentQuery<TEntity>, List<TEntity>>
    where TEntity : class,  INoteEntity
{
    private readonly INoteRepository<TEntity> _repo;
    public FetchAllNotesForParentQueryHandler(INoteRepository<TEntity> repo)
    {
        _repo = repo;
    }

    public Task<List<TEntity>> Handle(FetchAllNotesForParentQuery<TEntity> request, CancellationToken cancellationToken)
    {
        return _repo.FetchByParentId(request.ParentId);
    }
}

The best way in my opinion is to not worry about any of that... Just simply create your generic commands / handlers and then register them using autofac.

I actually got this working with AutoFac as a container.

public static ContainerBuilder AddGenericHandlers(this ContainerBuilder builder)
        {
            builder.RegisterSource(new ContravariantRegistrationSource());
            builder.RegisterGeneric(typeof(FetchDropdownItemByEnumQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(FetchAllNotesForParentQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(FetchAllNotesWithTypeForParentQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(FetchAllSelectListItemsQueryHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(AddNoteCommandHandler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(AddNoteWithTypeCommandHandler<>)).AsImplementedInterfaces();
            return builder;
        }

Is there currently a way to do this with the default .Net 5.0 DI container?

This issue can be closed, I guess I'm just looking for a concrete answer if this is possible with the default container.

Thanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bugproof picture bugproof  路  6Comments

TheRubble picture TheRubble  路  5Comments

mdmoura picture mdmoura  路  4Comments

FuncLun picture FuncLun  路  5Comments

etechnologiesng picture etechnologiesng  路  3Comments