web-dev-qa-db-ja.com

ASP.NETコアのMediatRライブラリのSendメソッドとPublishメソッドの汎用ハンドラーを追加します

Asp.netコアプロジェクトでCQSパターンを使用しています。私が達成したいことをより良く説明するための例から始めましょう。コマンドを作成しました:

public class EmptyCommand : INotification{}

コマンドハンドラ:

public class EmptyCommandHandler : INotificationHandler<EmptyCommand>
{
    public Task Handle(EmptyCommand notification, CancellationToken cancellationToken)
    {
        return Task.FromResult(string.Empty);
    }
}

クエリ:

public class EmptyQuery : IRequest<string>{}

クエリハンドラ:

public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
    public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
    {
        return Task.FromResult(string.Empty);
    }
}

これは、コマンドとクエリを実行し、EmptyCommandHandlerとEmptyQueryHandlerからHandleメソッドを呼び出す方法の簡単な例です。

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

クエリは、必ずしもstringではない他のタイプを返す可能性があることに注意してください。ある種のブリッジクラスを作成したいと思います。 MediatorBoostrapper。これにより、Publishメソッドが呼び出されるたびにビジネスロジック(Loggerを介したログコマンド/クエリなど)を実行し、public Task Handle(EmptyCommand notification,...コマンドハンドラのメソッド。ソリューションは汎用である必要があるため、このメソッドはPublishメソッドを実行するたびに呼び出されます。 Sendメソッドにも同じことができるようにしたいです。

私はpublic class MediatorBoostrapper : IMediatorしかし、クラスの適切な実装と、私のアイデアが良いかどうかはわかりません。何か案は?乾杯

編集

  1. Behaviors を使用して、クエリに対してSendメソッドを実行するたびに、汎用ハンドラーから外部メソッドを実行する汎用的な方法を作成する方法の例が必要です。コマンドを送信するために使用するPublishメソッドの同様の例が必要です。

  2. GenericCommandHandlerとGenericQueryHandlerの作成に Polymorphic dispatch を使用する方法の例を示したい

GitHubでサンプルプロジェクトを作成しました。このプロジェクトは here ソリューションでこのプロジェクトを拡張してみてください。

15
GoldenAge

今回は最後から質問に答えたいと思います。

2。

TL; DRポリモーフィックディスパッチはCQSに使用できません

MediatRライブラリでしばらく遊んだ後、質問の下でコメントを読んで友人と相談した後、Polymorphic Dispatch(PD)を使用して汎用ハンドラーを作成できるのはコマンド。 PDソリューションはクエリに実装できません。 Documentation に基づいて、ハンドラはcontravariant共変ではありません。これは、PDが機能するonlyTResponseは定数型です。クエリの場合、これはfalseであり、各クエリハンドラは異なる結果を返すことができます。

また、この問題を見つけました。コンテナがサポートしている場合にのみ多態性ディスパッチを使用できることを知っているのは興味深いと思います。

1。Behaviors は、MediatRを使用する場合のCQSの唯一のソリューションです。 #Steveからの私の質問とjbogardからの のコメントの下にあるコメントに基づいて 厳格なCommandパターンでBehaviorsとIRequestHandlerを使用する方法を見つけました。完全なコメント:

変更を要約すると、リクエストには2つの主な種類があります。値を返すものと返さないものです。現在IRequest<T>を実装していないものは、T : Unitです。これは、リクエストとハンドラを単一のタイプに統合することでした。分岐タイプにより、多くのコンテナのパイプラインが壊れたため、統一により、あらゆる種類のリクエストにパイプラインを使用できます。

すべての場合にユニットタイプを追加する必要があったため、いくつかのヘルパークラスを追加しました。

  • IRequestHandler<T>-これを実装すると、Task<Unit>が返されます。
  • AsyncRequestHandler<T>-これを継承すると、Taskが返されます。
  • RequestHandler<T>-これを継承すると、何も返されません(void)

値を返すリクエストの場合:

  • IRequestHandler<T, U>-Task<U>を返します
  • RequestHandler<T, U>-Uを返します

AsyncRequestHandlerを削除したのは、統合後に冗長な基本クラスである実際には何も実行していなかったためです。

a)コマンド管理:

public class EmptyCommand : IRequest{...}

public class EmptyCommandHandler : RequestHandler<EmptyCommand>
{
    protected override void Handle(EmptyCommand request){...}
}

b)クエリ管理:

// 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)サンプルLogginBehaviorクラス:

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)LoggingBehaviorを登録するには、コマンドを追加します

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));

startup.csのConfigureServicesメソッドの本体に。

e)サンプルのコマンドとクエリを実行する方法の例:

await _mediator.Send(new EmptyCommand());
var result = await _mediator.Send(new EmptyQuery());
6
GoldenAge

MediatRは、汎用ハンドラーへの通知のディスパッチをサポートしています( polymorphic dispatch )。例えば:

_public class GenericHandler<TNotification> : INotificationHandler<TNotification> 
    where TNotification : INotification
{
    public Task Handle(TNotification notification, CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}
_

このハンドラーは、Publish()を介して公開されるすべての通知に対して呼び出されます。同じことが要求(クエリ/コマンド)にも当てはまります。 behaviors もご覧ください。

ASP.NET CoreでMediatRを使用している場合、すべてのハンドラーを一緒に配線する MediatR.Extensions.Microsoft.DependencyInjection ライブラリを使用することをお勧めします。

5
Henk Mollema