13

MVC では、依存関係を取ることができるモデル バリデーターを作成できます。通常、これには FluentValidation を使用します。これにより、たとえば、メールアドレスが使用されていないことをアカウント登録で確認できます (注意: これは簡単な例です!):

public class RegisterModelValidator : AbstractValidator<RegisterModel> {
    private readonly MyContext _context;
    public RegisterModelValidator(MyContext context) {
        _context = context;
    }
    public override ValidationResult Validate(ValidationContext<RegisterModel> context) {
        var result = base.Validate(context);
        if (context.Accounts.Any(acc => acc.Email == context.InstanceToValidate.Email)){
            result.Errors.Add(new ValidationFailure("Email", "Email has been used"));
        }
        return result;
    }
}

Web API と FluentValidation のそのような統合は存在しません。これにはいくつか試みがありましたが、どちらも依存性注入の側面に取り組んでおらず、静的バリデーターでのみ機能します。

これが難しい理由は、MVC と Web API では ModelValidatorProvider と ModelValidator の実装が異なるためです。MVC では、これらはリクエストごとにインスタンス化されます (したがって、コンテキストの挿入は簡単です)。Web API では、これらは静的であり、ModelValidatorProvider は型ごとに ModelValidators のキャッシュを維持して、すべての要求で不要なリフレクション ルックアップを回避します。

必要な統合を自分で追加しようとしましたが、 Dependency Scope を取得しようとして行き詰まりました。代わりに、一歩下がって、問題に対する他の解決策があるかどうかを尋ねようと思いました-依存関係を注入できるモデル検証を実行するための解決策を誰かが思いついた場合。

コントローラー内で検証を実行したくありません (これを分離するためにValidationActionFilterを使用しています)。これは、コントローラーのコンストラクター注入から何の助けも得られないことを意味します。

4

6 に答える 6

7

GetDependencyScope()拡張メソッドを使用して、リクエストから Web API 依存関係リゾルバーに登録してアクセスすることができました。これにより、検証フィルターの実行中にモデル バリデーターにアクセスできます。

これで依存性注入の問題が解決しない場合は、お気軽にお知らせください。

Web API 構成 (Unity を IoC コンテナーとして使用):

public static void Register(HttpConfiguration config)
{
    config.DependencyResolver   = new UnityDependencyResolver(
        new UnityContainer()
        .RegisterInstance<MyContext>(new MyContext())
        .RegisterType<AccountValidator>()

        .RegisterType<Controllers.AccountsController>()
    );

    config.Routes.MapHttpRoute(
        name:           "DefaultApi",
        routeTemplate:  "api/{controller}/{id}",
        defaults:       new { id = RouteParameter.Optional }
    );
}

検証アクション フィルタ:

public class ModelValidationFilterAttribute : ActionFilterAttribute
{
    public ModelValidationFilterAttribute() : base()
    {
    }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var scope   = actionContext.Request.GetDependencyScope();

        if (scope != null)
        {
            var validator   = scope.GetService(typeof(AccountValidator)) as AccountValidator;

            // validate request using validator here...
        }

        base.OnActionExecuting(actionContext);
    }
}

モデルバリデーター:

public class AccountValidator : AbstractValidator<Account>
{
    private readonly MyContext _context;

    public AccountValidator(MyContext context) : base()
    {
        _context = context;
    }

    public override ValidationResult Validate(ValidationContext<Account> context)
    {
        var result      = base.Validate(context);
        var resource    = context.InstanceToValidate;

        if (_context.Accounts.Any(account => String.Equals(account.EmailAddress, resource.EmailAddress)))
        {
            result.Errors.Add(
                new ValidationFailure("EmailAddress", String.Format("An account with an email address of '{0}' already exists.", resource.EmailAddress))
            );
        }

        return result;
    }
}

API コントローラ アクション メソッド:

[HttpPost(), ModelValidationFilter()]
public HttpResponseMessage Post(Account account)
{
    var scope = this.Request.GetDependencyScope();

    if(scope != null)
    {
        var accountContext = scope.GetService(typeof(MyContext)) as MyContext;
        accountContext.Accounts.Add(account);
    }

    return this.Request.CreateResponse(HttpStatusCode.Created);
}

型式(例):

public class Account
{
    public Account()
    {
    }

    public string FirstName
    {
        get;
        set;
    }

    public string LastName
    {
        get;
        set;
    }

    public string EmailAddress
    {
        get;
        set;
    }
}

public class MyContext
{
    public MyContext()
    {
    }

    public List<Account> Accounts
    {
        get
        {
            return _accounts;
        }
    }
    private readonly List<Account> _accounts = new List<Account>();
}
于 2013-03-01T07:43:33.700 に答える
1

WebApi で Fluent Validators を使用して DI を問題なく使用しています。私は、バリデーターが頻繁に呼び出されることを発見しました。これらの種類の重いロジックの検証は、モデルバリデーターには適していません。私の意見では、モデルバリデーターは、データの形状をチェックする軽量化を目的としています。電子メールのように見え、発信者が 、または のいずれかを提供しますかEmailFirstNameLastNameMobileHomePhone?

Can this email be registeredのようなロジック検証は、コントローラーではなく、サービス レイヤーに属します。私の実装では、暗黙的なデータ コンテキストも共有していません。これはアンチパターンだと思うからです。

このための現在の NuGet パッケージには MVC3 依存関係があると思うので、ソースを直接見て、独自のNinjectFluentValidatorFactory.

App_Start/NinjectWebCommon.csは、次のものがあります。

    /// <summary>
    /// Set up Fluent Validation for WebApi.
    /// </summary>
    private static void FluentValidationSetup(IKernel kernel)
    {
        var ninjectValidatorFactory
                        = new NinjectFluentValidatorFactory(kernel);

        // Configure MVC
        FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure(
            provider => provider.ValidatorFactory = ninjectValidatorFactory);

        // Configure WebApi
        FluentValidation.WebApi.FluentValidationModelValidatorProvider.Configure(
            System.Web.Http.GlobalConfiguration.Configuration,
            provider => provider.ValidatorFactory = ninjectValidatorFactory);

        DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
    }

上記に必要な他のパッケージは次のとおりだと思います。

  <package id="FluentValidation" version="5.1.0.0" targetFramework="net451" />
  <package id="FluentValidation.MVC5" version="5.1.0.0" targetFramework="net451" />
  <package id="FluentValidation.WebApi" version="5.1.0.0" targetFramework="net451" />
  <package id="Ninject" version="3.2.0.0" targetFramework="net451" />
  <package id="Ninject.MVC3" version="3.2.0.0" targetFramework="net451" />
  <package id="Ninject.Web.Common" version="3.2.0.0" targetFramework="net451" />
于 2014-05-15T05:48:06.973 に答える
1

クラスが内部であるため、これはお勧めできませんが、WebApi 構成で IModelValidatorCache サービスを削除できます。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Services.Clear(Type.GetType("System.Web.Http.Validation.IModelValidatorCache, System.Web.Http"));
    }
}
于 2015-08-27T20:52:06.990 に答える
-1

FluentValidation はかなり長い間 WebApi をサポートしていました (質問の日付がそれより前かどうかはわかりません): https://fluentvalidation.codeplex.com/discussions/533373

スレッドからの引用:

{
   GlobalConfiguration.Configuration.Services.Add(typeof(System.Web.Http.Validation.ModelValidatorProvider),
       new WebApiFluentValidationModelValidatorProvider()
       {
           AddImplicitRequiredValidator = false //we need this otherwise it invalidates all not passed fields(through json). btw do it if you need
       });
       FluentValidation.ValidatorOptions.ResourceProviderType = typeof(FluentValidationMessages); // if you have any related resource file (resx)
       FluentValidation.ValidatorOptions.CascadeMode = FluentValidation.CascadeMode.Continue; //if you need!

問題なく WebApi2 プロジェクトで使用しています。

于 2014-05-12T17:54:58.037 に答える