web-dev-qa-db-ja.com

属性で依存性注入を使用する方法は?

私が作成しているMVCプロジェクトには、特定の権限を必要とするアクションを実行する次のRequirePermissionAttributeがあります(この例では簡略化されています)。

public class RequirePermissionAttribute : ActionFilterAttribute, IAuthorizationFilter
{
    public Operation Permissions { get; set; }

    public RequirePermissionAttribute() { }

    public RequirePermissionAttribute(Operation permissions)
    {
        this.Permissions = permissions;
    }

    public bool AuthorizeCore(HttpContextBase httpContext)
    {
        IAuthorizationService authServ = new ASPNETAuthorizationService();
        return authServ.Authorize(httpContext);
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        Enforce.ArgNotNull(filterContext);

        if (this.AuthorizeCore(filterContext.HttpContext))
        {
            // code snipped.
        }
        else
        {
            // code snipped.
        }
    }
}

したがって、明らかにこれに関する問題は、authorize属性が、作成したASPNETAuthorizationServiceに依存していることです。属性はコンパイル時にチェックされるため、コンストラクターを使用することはできません。

言及すべきことの1つは、自分で作成した小さなIoCを使用していることであり、プロパティインジェクションをサポートしていません(まだ)。もちろん、プロパティインジェクションルートに行った場合は、サポートを追加する必要があります(これについては調査する必要があります)。

属性クラスに何かを注入する最良の方法は何ですか?

51
TheCloudlessSky

私はもともとこれは不可能だと思っていましたが、私は修正しました。 Ninjectの例を次に示します。

http://codeclimber.net.nz/archive/2009/02/10/how-to-use-ninject-to-inject-dependencies-into-asp.net-mvc.aspx

更新2016-10-13

これは今ではかなり古い質問であり、フレームワークはかなり変更されています。 Ninjectでできるようになりました 特定の属性の存在に基づいて、次のようなコードで特定のフィルターにバインディングを追加します。

// LogFilter is applied to controllers that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Controller, 0)
     .WhenControllerHas<LogAttribute>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to actions that have the LogAttribute
this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenActionHas<LogAttribute>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to all actions of the HomeController
this.BindFilter<LogFilter>(FilterScope.Action, 0)
     .WhenControllerTypeIs<HomeController>()
     .WithConstructorArgument("logLevel", Level.Info);

// LogFilter is applied to all Index actions
this.BindFilter(FilterScope.Action, 0)
     .When((controllerContext,  actionDescriptor) =>
                actionDescriptor.ActionName == "Index")
     .WithConstructorArgument("logLevel", Level.Info);

これは原則と一致しており、 Mark Seemanによる および Simple Injectorの作成者による であり、アクションフィルターのロジックをカスタムとは別にする必要があるということです。属性クラス。

MVC 5および6を使用すると、属性に値を挿入するのがはるかに簡単になります それでも、アクションフィルターを属性から分離することは、実際に行うのに最適な方法です。

11

属性クラスに何かを注入する最良の方法は何ですか?

厳密に言えば、依存性注入を使用して属性に依存性を注入することはできません。 属性はメタデータ用 動作ではありません。 [AttributeSpecification()]は、参照型を引数として禁止することでこれを促進します。

おそらく探しているのは 属性とフィルターを一緒に使用し、フィルターに依存関係を挿入する です。属性はメタデータを追加します。メタデータはフィルターを適用するかどうかを決定し、フィルターは挿入された依存関係を受け取ります。

属性で依存性注入を使用する方法は?

これを行う理由はほとんどありません。

ただし、属性への挿入を意図している場合は、ASP.NET Core MVC IApplicationModelProviderを使用できます。フレームワークは依存関係をプロバイダーのコンストラクターに渡し、プロバイダーは属性のプロパティまたはメソッドに依存関係を渡すことができます。

スタートアップで、プロバイダーを登録します。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.TryAddEnumerable(ServiceDescriptor.Transient
            <IApplicationModelProvider, MyApplicationModelProvider>());

        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}

プロバイダーでコンストラクター注入を使用し、それらの依存関係を属性に渡します。

using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Routing;

public class MyApplicationModelProvider : IApplicationModelProvider
{
    private IUrlHelperFactory _urlHelperFactory;

    // constructor injection
    public MyApplicationModelProvider(IUrlHelperFactory urlHelperFactory)
    {
        _urlHelperFactory = urlHelperFactory;
    }

    public int Order { get { return -1000 + 10; } }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        foreach (var controllerModel in context.Result.Controllers)
        {
            // pass the depencency to controller attibutes
            controllerModel.Attributes
                .OfType<MyAttribute>().ToList()
                .ForEach(a => a.UrlHelperFactory = _urlHelperFactory);

            // pass the dependency to action attributes
            controllerModel.Actions.SelectMany(a => a.Attributes)
                .OfType<MyAttribute>().ToList()
                .ForEach(a => a.UrlHelperFactory = _urlHelperFactory);
        }
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        // intentionally empty
    }
}

依存関係を受け取ることができるパブリックセッターを持つ属性を作成します。

using System;
using Microsoft.AspNetCore.Mvc.Routing;

public sealed class MyAttribute : Attribute
{
    private string _someParameter;

    public IUrlHelperFactory UrlHelperFactory { get; set; }

    public MyAttribute(string someParameter)
    {
        _someParameter = someParameter;
    }
}

コントローラーまたはアクションに属性を適用します。

using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[MyAttribute("SomeArgument")]
public class ValuesController : Controller
{
    [HttpGet]
    [MyAttribute("AnotherArgument")]
    public string Get()
    {
        return "Foobar";
    }
}

上記は、まれなユースケースの場合、属性に依存関係を挿入できる1つの方法を示しています。これを行う正当な理由がわかった場合は、コメントに投稿してください。

7
Shaun Luttin