ASP.NET Coreに導入したMediatRのラッパークラスを作成する
ASP.NET CoreにMediatRを導入するでASP.NET Core
のWebAPI
のプロジェクトにMediatR
を導入しましたが、今回はラッパークラスを作成し、もっと汎用的に実装できるように修正します。
処理は主に検索系Query
と更新系Command
の2つに分けられるので汎用的なクラスを2パターン作成します。
目的
外部ライブラリを直接参照すると、外部ライブラリの仕様変更があると、参照しているファイルをすべて修正する必要が出てくるため、このようなラッパークラスを作成することがあります。
よくあるケースとして、データベースのアクセスライブラリのラッパークラスがイメージしやすいかと思います。
また、MediatR
については、抽象化しておくことによりパイプライン(IPipelineBehavior
)の実装がやりやすくなります。
インターフェースの作成
出力クラスを抽象化した、IResult
というインターフェースを作成します。
全て(Query
とCommand
両方)の出力クラスはこのインターフェースを継承するようにします。
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で、引数の値をレスポンスします。
ほかにもNoContent
やBadRequest
など、レスポンス用のファンクションがあります。
swagger
などで、レスポンスの型を明示する場合は以下のようにします。
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserFindByIdQueryResult), StatusCodes.Status200OK)]
public async Task<IActionResult> FindById(string id)
{
return Ok(await Mediator.Send(new UserFindByIdQuery(id)));
}
以上で、抽象化したインターフェースを使用して、処理を実装することができました。