zukucode
主にWEB関連の情報を技術メモとして発信しています。

ASP.NET Coreに導入したMediatRのラッパークラスを作成する

ASP.NET CoreにMediatRを導入するASP.NET CoreWebAPIのプロジェクトにMediatRを導入しましたが、今回はラッパークラスを作成し、もっと汎用的に実装できるように修正します。

処理は主に検索系Queryと更新系Commandの2つに分けられるので汎用的なクラスを2パターン作成します。

目的

外部ライブラリを直接参照すると、外部ライブラリの仕様変更があると、参照しているファイルをすべて修正する必要が出てくるため、このようなラッパークラスを作成することがあります。

よくあるケースとして、データベースのアクセスライブラリのラッパークラスがイメージしやすいかと思います。

また、MediatRについては、抽象化しておくことによりパイプライン(IPipelineBehavior)の実装がやりやすくなります。

インターフェースの作成

出力クラスを抽象化した、IResultというインターフェースを作成します。

全て(QueryCommand両方)の出力クラスはこのインターフェースを継承するようにします。

IResult.cs
public interface IResult
{
}

さらに、Query用とCommand用でそれぞれインターフェースを作成します。

IQueryResult.cs
public interface IQueryResult : IResult
{
}
ICommandResult.cs
public interface ICommandResult : IResult
{
}

次に、入力クラスを抽象化したインターフェースをQuery用とCommand用でそれぞれ作成します。

入力クラスはIRequest<出力クラス>のインターフェースを継承して作成しますが、出力クラスはジェネリックで設定できるようにします。

IQuery.cs
using MediatR;

public interface IQuery<TQueryResult> : IRequest<TQueryResult>
{
}
ICommand.cs
using MediatR;

public interface ICommand<TQueryResult> : IRequest<TQueryResult>
{
}

次に、ハンドラクラスを抽象化したクラスをQuery用とCommand用でそれぞれ作成します。

IRequestHandler<入力クラス, 出力クラス>の形式で指定します。

入力クラスはジェネリック型とし、IQueryまたはICommandを継承したクラスを指定できるようにします。

QueryHandler.cs
using MediatR;

public abstract class QueryHandler<TQuery, TQueryResult> : IRequestHandler<TQuery, TQueryResult> where TQuery : class, IQuery<TQueryResult>
{
    public abstract Task<TQueryResult> Handle(TQuery request, CancellationToken cancellationToken);
}
CommandHandler.cs
using MediatR;

public abstract class CommandHandler<TCommand, TCommandResult> : IRequestHandler<TCommand, TCommandResult> where TCommand : class, ICommand<TCommandResult>
{
    public abstract Task<TCommandResult> Handle(TCommand request, CancellationToken cancellationToken);
}

各処理の修正

ASP.NET CoreにMediatRを導入するで実装した各クラスを上記の抽象化したインターフェースなどを継承するように修正します。

Queryのみ記載していますが、Commandの場合も考え方は同じです。

UserFindByIdQuery.cs
using MediatR;

public class UserFindByIdQuery : IRequest<UserFindByIdQueryResult>
public class UserFindByIdQuery : IQuery<UserFindByIdQueryResult>
{
    public UserFindByIdQuery(string id)
    {
        Id = id;
    }

    public string Id { get; }
}
UserFindByIdQueryResult.cs
public class UserFindByIdQueryResult
public class UserFindByIdQueryResult : IQueryResult
{
    public UserFindByIdQueryResult(string id, string  name)
    {
        Id = id;
        Name = name;
    }

    public string Id { get; }
    public string Name { get; }
}
UserFindByIdQueryHandler.cs
using MediatR;

public class UserFindByIdQueryHandler : IRequestHandler<UserFindByIdQuery, UserFindByIdQueryResult>
public class UserFindByIdQueryHandler : QueryHandler<UserFindByIdQuery, UserFindByIdQueryResult>
{
    private readonly IUserQueryService _queryService;

    public UserFindByIdQueryHandler(IUserQueryService queryService)
    {
        _queryService = queryService;
    }

    public async Task<UserFindByIdQueryResult> Handle(UserFindByIdQuery query, CancellationToken cancellationToken)
    public override async Task<UserFindByIdQueryResult> Handle(UserFindByIdQuery query, CancellationToken cancellationToken)
    {
        return new UserFindByIdQueryResult("1", "ユーザーテスト");
        // 実際は以下のようにDBなどから取得することになります。
        // return await _queryService.FindById(query.Id);
    }
}

各ファイルでusing MediatR;が不要になることを確認できます。(直接MediatRを使用しない)

コントローラーの修正

型変換ではなく、コントローラーのレスポンスはIActionResultという抽象化したインターフェースで返却することができますので、以下のように修正します。

[HttpGet("{id}")]
public async Task<UserFindByIdQueryResult> FindById(string id)
public async Task<IActionResult> FindById(string id)
{
    return await Mediator.Send(new UserFindByIdQuery(id));
   return Ok(await Mediator.Send(new UserFindByIdQuery(id)));
}

Okファンクションはステータスコード200で、引数の値をレスポンスします。

ほかにもNoContentBadRequestなど、レスポンス用のファンクションがあります。

swaggerなどで、レスポンスの型を明示する場合は以下のようにします。

[HttpGet("{id}")]
[ProducesResponseType(typeof(UserFindByIdQueryResult), StatusCodes.Status200OK)]
public async Task<IActionResult> FindById(string id)
{
   return Ok(await Mediator.Send(new UserFindByIdQuery(id)));
}

以上で、抽象化したインターフェースを使用して、処理を実装することができました。


関連記事