使用 Ninject BindHttpFilter 时的范围问题

Posted

技术标签:

【中文标题】使用 Ninject BindHttpFilter 时的范围问题【英文标题】:Scope issues when using Ninject BindHttpFilter 【发布时间】:2014-10-29 18:27:50 【问题描述】:

我有一个 WebApi 服务,我正在尝试使用 Ninject BindHttpFilter 添加身份验证。

使用 BindHttpFilter 可以将身份验证过滤器绑定到特定属性。 AuthenticationFilter 接受一个构造函数参数(IAuthenticationService),它本身是由 Ninject 创建的。

kernel.BindHttpFilter<AuthenticationHttpFilter>(System.Web.Http.Filters.FilterScope.Action)
    .WhenActionMethodHas<AuthenticationFilterAttribute>()
    .WithConstructorArgument("service", x => x.Kernel.Get<IAuthenticationService>());

AuthenticationService 的具体实现采用了一个构造函数参数 INonceRepository,通过 Ninject 注入:

public AuthenticationService(INonceRepository nonceRepository, ...)

NonceRepository 的具体实现采用了一个通过 Ninject 注入的构造函数 ISession:

public NonceRepository(ISession session)

Ninject 绑定如下所示:

kernel.Bind<INonceRepository>().To<NonceRepository>();
kernel.Bind<IAuthenticationService>().To<AuthenticationService>()

var session = sessionFactory.OpenSession();

Bind<ISession>().ToMethod(c => session).InRequestScope();

当代码运行时,AuthenticationService 的具体实现只被实例化一次,因此 NonceRepositiory 只被实例化一次。这意味着 ISession 有效并打开第一个请求,但 ISession 在第二次调用时关闭,并且第二次调用 AuthenticationService 的构造函数。这似乎是一个范围问题,但我无法弄清楚什么没有正确的范围来使 AuthenticationService 根据请求重新创建。

我尝试将 BindHttpScope 请求从 FilterScope.Controller 更改为 FilterScope.Action(认为这会导致 AuthenticationService 的范围是它是每个 Action 调用创建的),但这并没有解决问题。

下面是代码的有趣点:

public class AuthenticationHttpFilter : IAuthenticationFilter

    private readonly IAuthenticationService authenticationService;

    public AuthenticationHttpFilter(IAuthenticationService service)
    
        this.authenticationService = service;
    

    public bool AllowMultiple  get; private set; 

    public Task AuthenticateAsync(HttpAuthenticationContext authenticationContext, CancellationToken cancellationToken)
    
        authenticationService.DoAuth();
        return Task.FromResult(0);
    

    public Task ChallengeAsync(HttpAuthenticationChallengeContext authenticationChallengeContext, CancellationToken cancellationToken)
    
    ...
    


public class AuthenticationService : IAuthenticationService

    private readonly INonceRepository nonceRepo;

    public AuthenticationService(INonceRepository nonceRepo)...

    public void DoAuth()
    
        this.nonceRepo.Add(...);
    


public class NonceRepository : INonceRepository

    private readonly ISession _session;

    public NonceRepository(ISession session)
    
        this._session = session;
    

    public void Add(Nonce nonce)
    
        this._session.Save(nonce);
    

【问题讨论】:

肯特,你解决过这个问题吗?我有一个非常相似的问题。 Jason,我回来了一点,但是......我相信问题是: var session = sessionFactory.OpenSession(); Bind().ToMethod(c => session).InRequestScope();会话是在请求中创建的,但按请求使用。解决方案是将会话的打开移动到每个请求调用中: Bind().ToMethod( c => sessionFactory.OpenSession()).InRequestScope();我与之抗争的另一件事是确保您始终在使用 WebApi 与 MVC 时保持同步。很多时候名称是相同的,只是名称空间不同。 感谢您的回复。我的问题最终是 WebAPI 缓存了过滤器,所以我的构造函数依赖项只注入了一次。 【参考方案1】:

我发现当我使用 WebApi 2 时,一切正常。它在 RequestScope 中的行为与我预期的一样。但是,在使用 MVC 5 时,过滤器会一直缓存在管道中,这会将构造函数参数保持在“Singleton”范围内。

我通过像这样进入actionContext 的配置解决了这个问题:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class EventGridFilterAttribute : ActionFilterAttribute

    private readonly Type serviceType = typeof(IEventGridService);

    public EventGridFilterAttribute()
    
    

    public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
    
        var service = actionExecutedContext?.ActionContext?.ControllerContext?.Configuration?.DependencyResolver?.GetService(serviceType) as IEventGridService;

        if (service != null && actionExecutedContext.Exception == null)
        
            await service.PublishEventsAsync();
        

        await base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
    

我的依赖注入器保存在这里。

GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);

我也不使用BindHttpFilter,而是像普通过滤器一样注册我的过滤器。

public static void RegisterGlobalFilters(GlobalFilterCollection filters)

    filters.Add(new EventGridFilterAttribute());

【讨论】:

以上是关于使用 Ninject BindHttpFilter 时的范围问题的主要内容,如果未能解决你的问题,请参考以下文章

Ninject 在使用 NinjectHttpModule 时创建一个额外的实例

Ninject之旅之七:Ninject依赖注入

在构造函数中使用带有附加参数的 Ninject 创建实例

Ninject

Ninject之旅之五:Ninject XML配置

ninject 继承安全规则违反了类型:mvc4 中的“Ninject.Web.Mvc.Filter.FilterContextParameter”