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

ASP.NET Core MediatRの処理にFluentValidationを組み込む

ASP.NET CoreMedietRを使用したプログラムにFluentValidationを組み込み、バリデーション処理を追加します。

ASP.NET Coreに導入したMediatRのラッパークラスを作成するで作成したソースに対して、修正しています。

パッケージのインストール

以下の2つのパッケージをApplicationプロジェクトにインストールします。

Visual Studioの場合は「NuGetパッケージの管理」からインストールできます。

VSCodeの場合は以下のURLからdotnetコマンドなどを確認できます。

IPipelineBehaviorの作成

MediatRにはハンドラ処理の前後に処理を組み込む仕組みがあります。

IPipelineBehaviorを継承したクラスを作成することにより、この仕組みを実現します。

以下のようなクラスを作成します。TRequestには入力クラス、TResponseには出力クラスが対応しています。

whereで入力クラスと出力クラスを指定することにより、特定のハンドラのみパイプライン処理を行うような動作が可能です。

今回はすべてのハンドラでバリデーションチェックを行いたいので、以下のように指定しています。

入力クラスはIRequest<TResponse>としているため、実質すべての入力クラスが対象となります。(MediatRを使用する場合は全ての入力クラスはIRequestを継承しているため)

IResultASP.NET Coreに導入したMediatRのラッパークラスを作成するで定義したインターフェースです。全ての出力クラスはIResultを継承するように作成していますので、こちらも実質全ての出力クラスが対象となります。

ValidationBehavior.cs
using System.Threading;
using FluentValidation;
using FluentValidation.Results;
using MediatR;

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
    where TResponse : IResult
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var context = new ValidationContext<TRequest>(request);
        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Any())
        {
            throw new ValidationException(failures);
        }

        return await next();
    }
}

作成したクラスをDIコンテナに登録します。

ASP.NET CoreにMediatRを導入するで作成したアプリケーション層のDependencyInjection.csに追記します。

DependencyInjection.cs
using System.Reflection;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using FluentValidation;

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(this IServiceCollection services)
    {
        services.AddMediatR(Assembly.GetExecutingAssembly());
        services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
        services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
        return services;
    }
}

バリデーションチェックの作成

各ハンドラに対応するバリデーションチェックを作成します。

AbstractValidator<入力クラス>を継承したクラスのコンストラクタでバリデーションルールを記載します。

using FluentValidation;

public class UserFindByIdQueryValidator : AbstractValidator<UserFindByIdQuery>
{
    public UserFindByIdQueryValidator()
    {
        RuleFor(c => c.Id).NotEmpty().WithName("ユーザーID");
    }
}

すべてのハンドラでバリデーションパイプラインを通過するように実装しましたが、ハンドラに対応するバリデーションチェックが実装されていない場合でも、例外は発生しません。(バリデーションチェックが不要なハンドラはバリデーションルールのクラスは作成不要)

以上で、バリデーションチェックの導入ができました。


関連記事