5
votes

I have a WebApi service that I am trying to add authentication to using Ninject BindHttpFilter.

Using the BindHttpFilter allows me to bind the authentication filter to a specific attribute. The AuthenticationFilter takes a constructor parameter (IAuthenticationService) which itself is created by Ninject.

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

The concrete implementation of AuthenticationService takes a constructor parameter INonceRepository which is injected via Ninject:

public AuthenticationService(INonceRepository nonceRepository, ...)

The concrete implementation of NonceRepository takes a constructor ISession which is injected via Ninject:

public NonceRepository(ISession session)

Here is what the Ninject bindings look like:

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

var session = sessionFactory.OpenSession();

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

When the code runs the concrete implementation of AuthenticationService is only instantiated once and therefore NonceRepositiory is only instantiated once. This means that the ISession is valid and Open the first request but the ISession is closed on the second call and the constructor of AuthenticationService is never called the second time. It seems like it is a scoping issue but I can't figure out what doesn't have the correct scoping to make AuthenticationService get recreated per request.

I've tried to change the BindHttpScope request from FilterScope.Controller to FilterScope.Action (thinking that would cause the scope of AuthenticationService to be that it was create per Action call) but that didn't solve it.

Here is what the the interesting points of code looks like:

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);
    }
}
1
Kent, did you ever resolve this? I'm having a very similar issue. - JasonCoder
Jason, I'm reaching back a bit but... I believe the issue was: var session = sessionFactory.OpenSession(); Bind<ISession>().ToMethod(c => session).InRequestScope(); The session was being created in the request but was used per request. The solution was to move the opening of the session into the per request call: Bind<ISession>().ToMethod( c => sessionFactory.OpenSession()).InRequestScope(); The other thing that I battled with is making sure that you are always in sync of working with WebApi versus MVC. Many times names are the same, just namespace differences. - Kent Fehribach
thanks for replying. My problem ended up being that WebAPI caches filters so my constructor dependencies were only injected once. - JasonCoder

1 Answers

0
votes

I found that that when I am using WebApi 2, everything works fine. It behaves in RequestScope like I would expect. However, when using MVC 5, the filter stays cached in the pipeline, which keeps the constructor argument in a "Singleton" scope.

I fixed this problem by reaching into the configuration of the actionContext as such:

[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);
    }
}

My dependency injector is saved here.

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

I also do not use the BindHttpFilter, but register my filter like a normal filter.

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new EventGridFilterAttribute());
}