Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example of generic IRequestHandler #521

Closed
cirrusone opened this issue May 23, 2020 · 8 comments
Closed

Example of generic IRequestHandler #521

cirrusone opened this issue May 23, 2020 · 8 comments

Comments

@cirrusone
Copy link

Are there any examples to show how to make a generic IRequestHandler?

I can obviously add lots of interfaces such as

public class MainWindowViewModel : BindableBase
,IRequestHandler<MainViewModelPing1, string>
,IRequestHandler<MainViewModelPing2, string>
,IRequestHandler<MainViewModelPing3, SomeOtherClass>

but cannot find any examples of a generic handler to handle different requests and responses in the same handler. Is something like the following possible?

public class MainViewModelPing : IRequest<T>
    {
        public string Message { get; set; }
	public T Data { get; set; }
    }

which would handle something like the following

    public class MainViewModelPing : IRequest<string>
    {
        public string Message { get; set; }
    }

    public class MainViewModelPing : IRequest<string>
    {
        public string Message { get; set; }
	public SomeClass Data { get; set; }
    }

    public class MainViewModelPing : IRequest<SomeOtherClass>
    {
        public string Message { get; set; }
	public SomeClass Data { get; set; }
    }
@cirrusone
Copy link
Author

Is this a reasonable way to use Mediatr without any foreseeable issues?

App DI:

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    // MediatR DI dryloc
    var container = containerRegistry.GetContainer();
    container.RegisterDelegate<ServiceFactory>(r => r.Resolve);
    container.RegisterMany(new[] { typeof(IMediator).GetAssembly(), typeof(MainViewModelPing).GetAssembly() }, Registrator.Interfaces);

}

GenericRequest and GenericResponse:

public class MainViewModelPing : IRequest<PingResponse>
{
  public PingRequest Request { get; set; }
}

public abstract class PingRequest
{
}
public class GenericRequest<T> : PingRequest
{
  public string Message { get; set; }
  public T Data { get; set; }
}

public abstract class PingResponse
{
}
public class GenericResponse<T> : PingResponse
{
  public string Message { get; set; }
  public T Data { get; set; }
}

Generic Handler attached to MainViewModel:

public class MainWindowViewModel : BindableBase, IRequestHandler<MainViewModelPing, PingResponse>
{

        public Task<PingResponse> Handle(MainViewModelPing request, CancellationToken cancellationToken)
        {   
            // Test types and message to filter
            if(request.Request is GenericRequest<string> stringRequest)
            {
                if(stringRequest.Message.Equals("SomeVal"))
                {
                    // Perform action here
                }
            }
            else if (request.Request is GenericRequest<int> intRequest)
            {
                if(stringRequest.Message.Equals("SomeOtherVal"))
                {
                    // Perform action here
                }
            }
            
            // Example response
            var genericResponse = new GenericResponse<int> { Message = "It Works", Data = 42 };
            return Task.FromResult(genericResponse as PingResponse);
        }
}

Example request:

        private async void MediatrTest()
        {
            var genericRequest = new GenericRequest<string> { Message = "SomeVal", Data = "" };
            var response = await _mediator.Send(new MainViewModelPing() { Request = genericRequest });
        }

@no1melman
Copy link

I would say, making it more generic isn't an ideal use case.

The idea is that you have 1 request that maps to 1 handler because this follows SRP. It becomes easier to understand what the code is trying to achieve.

I would say that if you just want T to be stored in a database, handled the same way each time... I would probably just make it object Data {get;set;} and do some kind of reflection in the handler to get the object to "fit" into the database.

There maybe a design issue with what you're doing which has made this pattern emerge.

The pipeline handlers show how to "pass through" these requests and sort of do something with them.

@hanslai
Copy link

hanslai commented Jan 29, 2021

@cirrusone do you have this figured out. I am trying to do the same thing with asp.net core DI container, have not find the solution yet. Thanks

@cirrusone
Copy link
Author

I actually changed to Microsoft.Toolkit.Mvvm as the messenger suited my use-case better. It also allows me to register lots of message types without lots of interfaces. Since it targets .NET Standard 2.0, this means that it can be used anywhere from UWP apps, to Uno, Xamarin, Unity, ASP.NET, etc. Literally any framework supporting the .NET Standard 2.0 feature set. There are no platform specific dependencies at all. The whole package is entirely platform, runtime, and UI stack agnostic.

CommunityToolkit/WindowsCommunityToolkit#3428

CommunityToolkit/WindowsCommunityToolkit#3230

https://github.com/windows-toolkit/MVVM-Samples/blob/master/docs/mvvm/Messenger.md

@hanslai
Copy link

hanslai commented Jan 29, 2021

@cirrusone Thanks a lot, I will check it out also.

@zachpainter77
Copy link
Contributor

I have a similar issue.. I was able to resolve it by using AutoFac container. But wished I could have solved it using the default container.

Here is what I did...

Request...

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

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;
    }
}

Registration with AutoFac

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

If anybody knows of a way to do this in the default asp.net core DI container then please show me!

@zachpainter77
Copy link
Contributor

It is my understanding (and I tested this) that explicitly registering the GenericHandlerBase isn't necessary... If you are going to create concrete handlers for each generic type for that handler then MediatR will find the correct handler without issue..

Here is an example of this:
image
image

Here is the registration using Default DI Container:
image

This works just fine... Now imagine if you had multiple entities, and further more, imagine if you had multiple service dependencies for your handler. While even though you would not have to implement the handle method for each derived handler you still need to create a concrete class that closes the generic type and pass all dependencies to the base constructor... In my opinion that is very tedious to have to do that for every entity and every service dependency.. In my opinion it is much simpler to simply register the generic handler via Autofac like I showed above. But if not using a third party DI container then by all means create the concrete handlers... If I couldn't use a third party di container for whatever reason then I would do it this way..

Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants