Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving dependencies in OWIN WEB API Startup.cs with ninject

I have a Web Api 2 App, with two classes that both depend on another class, and i'm using ninject to resolve the dependancies.

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    private IUserService _userService;


    public AuthorizationServerProvider(IUserService userService)
    {
        _userService = userService;
    }

}

public class RefreshTokenProvider : IAuthenticationTokenProvider
{
    private IUserService _userService;

    public RefreshTokenProvider(IUserService userService)
    {
        _userService = userService;
    }

In the startup.cs class i need to use the above two classes, but of course i cannot use constructor injection in the startup class, as that is initialised before Ninject is.

What's a way around this so that the references to _tokenProvider and _authServerProvider in the ConfigureAuth method?

public class Startup
{
    private AuthorizationServerProvider _authServerProvider;        
    private RefreshTokenProvider _tokenProvider;

    public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
    public void Configuration(IAppBuilder app)
    {
       var config = new HttpConfiguration();

        app.UseNinjectMiddleware(CreateKernel);
        app.UseNinjectWebApi(config);

        ConfigureOAuth(app);

        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.UseWebApi(config);

    }

    public void ConfigureOAuth(IAppBuilder app)
    {

        var oAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true, //TODO: HTTPS
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            Provider = _authServerProvider, 
            RefreshTokenProvider = _tokenProvider
        };

}

Here's the CreateKernel Method

private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        try
        {
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();


            RegisterServices(kernel);
            return kernel;
        }
        catch
        {
            kernel.Dispose();
            throw;
        }
    }

And here's where I register my services

private static void RegisterServices(IKernel kernel)
    {

        kernel.Bind<SimpleAuthorizationServerProvider>().ToSelf();
        kernel.Bind<SimpleRefreshTokenProvider>().ToSelf();

}

I've followed the advice in the ninject docs, but to no avail.

like image 816
MrBliz Avatar asked Aug 17 '14 16:08

MrBliz


1 Answers

You're going about this the wrong way. Well, partially the wrong way anyways. You can't get OWIN to inject dependencies into the Startup class. So, you'll have to use the kernel that gets configured with app.UseNinjectMiddleware() to resolve your options configuration class. We'll do this with a Lazy kernel.

First, you should be configuring this in Startup.Auth.cs. Also, you've got some redundancy in there.. app.UseNinjectWebApi(config) will call app.UseWebApi(config) (see the source). I also don't know why you're calling WebApiConfig.Register() there because that's typically called in Global.asax.cs

In any event, it should look like this (I haven't tested this, but it should be close):

First, we're going to move your kernel creation to a lazy method, and then have your UseNinjectMiddleware() call in Startup.Configuration() method use the lazy kerel in the Startup class as a member. This works best I think as a simple lambda delegate, rather than creating a static CreateKernel method.

public partial class Startup
{
    private readonly Lazy<IKernel> _kernel = new Lazy<IKernel>(() =>
    {
        var kernel = new StandardKernel();

        kernel.Load(Assembly.GetExecutingAssembly());

        // here for brevity, move this to a RegisterServices or similar method,
        // 
        kernel.Bind<IOAuthAuthorizationServerOptions>()
            .To<MyOAuthAuthorizationServerOptions>();
        kernel.Bind<IOAuthAuthorizationServerProvider>()
            .To<AuthorizationServerProvider>();
        kernel.Bind<IAuthenticationTokenProvider>().To<RefreshTokenProvider>();
        kernel.Bind<IUserService>().To<MyUserService>();
        return kernel;
    });

    public void Configuration(IAppBuilder app)
    {
        app.UseNinjectMiddleware(() => _kernel.Value);
        var config = new HttpConfiguration();
        app.UseNinjectWebApi(config);

        ConfigureAuth(app);
    }
}

Then in your ConfigureAuth()

public void ConfigureAuth(IAppBuilder app)
{
   // .... other auth code

   // Yes, boo hiss, service location, not much choice...
   // Setup Authorization Server
   app.UseOAuthAuthorizationServer(_kernel.Value
       .Get<MyOAuthAuthorizationServerOptions>().GetOptions());
}

Then create an interface:

public interface IOAuthAuthorizationServerOptions 
{
    OAuthAuthorizationServerOptions GetOptions();
};

Create your implementation:

public class MyOAuthAuthorizationServerOptions : IOAuthAuthorizationServerOptions 
{
     private IOAuthAuthorizationServerProvider _provider;
     private IAuthenticationTokenProvider _tokenProvider;

     public MyOAuthAuthorizationServerOptions(IAuthenticationTokenProvider tProvider,
         IOAuthAuthorizationServerProvider provider)
     {
         _provider = provider;
         _tokenProvider = tProvider;
     }
     public OAuthAuthorizationServerOptions GetOptions()
     {
         return new OAuthAuthorizationServerOptions()
                    {
                       AllowInsecureHttp = true, //TODO: HTTPS
                       TokenEndpointPath = new PathString("/token"),
                       AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
                       Provider = _provider, 
                       RefreshTokenProvider = _tokenProvider
                    };
     }
}

EDIT (4/6/15):

Upon further thought on this, I think the Lazy<T> adds an addition reference that is really unnecessary. This can be modified to achieve the same results in a much cleaner way as such:

Create a new Startup.Ninject.cs class, and put it in App_Start:

public partial class Startup
{
    public IKernel ConfigureNinject(IAppBuilder app)
    {
        var config = new HttpConfiguration();
        var kernel = CreateKernel();
        app.UseNinjectMiddleware(() => kernel)
           .UseNinjectWebApi(config);

        return kernel;
    }

    public IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        kernel.Load(Assembly.GetExecutingAssembly());
        return kernel;
    }
}

public class NinjectConfig : NinjectModule
{
    public override void Load()
    {
        RegisterServices();
    }

    private void RegisterServices()
    {
        kernel.Bind<IOAuthAuthorizationServerOptions>()
            .To<MyOAuthAuthorizationServerOptions>();
        kernel.Bind<IOAuthAuthorizationServerProvider>()
            .To<AuthorizationServerProvider>();
        kernel.Bind<IAuthenticationTokenProvider>().To<RefreshTokenProvider>();
        kernel.Bind<IUserService>().To<MyUserService>();
    }
}

Then, in Startup do this:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var kernel = ConfigureNinject(app);
        ConfigureAuth(app, kernel);
    }
}

Finally, Modify ConfigureAuth to take the second parameter and use that instead.

public void ConfigureAuth(IAppBuilder app, IKernel kernel)
{
   // .... other auth code

   // Yes, boo hiss, service location, not much choice...
   // Setup Authorization Server
   app.UseOAuthAuthorizationServer(
       kernel.Get<MyOAuthAuthorizationServerOptions>().GetOptions());
}
like image 196
Erik Funkenbusch Avatar answered Nov 15 '22 12:11

Erik Funkenbusch