Reputation: 2068
Environment: ASP.NET WebAPI 2, NInject Web API (3.3.0.0)
Use Case: I am setting up custom Owin authentication using OwinStartup class. The class uses ASP.NET Identity.IUserStore to setup users. My challenge is getting the configured resolver to query for this reference. However, the resolver retrieval like in MVC, doesn't works in WebAPI
var userStore = DependencyResolver.Current.GetService(typeof(IUserStore<ExtendedUser, string>)) as IUserStore<ExtendedUser, string>;
My startup config as below:-
public partial class Startup
{
System.Web.Http.Dependencies.IDependencyResolver resolver;
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
// Due to "new" config, the underlying resolver gets empty
this.resolver = httpConfig.DependencyResolver;
this.ConfigureOAuthTokenGeneration(app);
// ... other code removed
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Fails due to resolver being empty
var userStore = this.resolver.GetService(typeof(IUserStore<ExtendedUser, string>)) as IUserStore<ExtendedUser, string>;
UserService.UserStore = userStore;
app.CreatePerOwinContext<UserService>(UserService.Create);
// Other code removed
}
}
UserService class:-
public class UserService : UserManager<ExtendedUser, string>
{
public UserService(IUserStore<ExtendedUser, string> store)
: base(store)
{
}
//[Ninject.Inject]
public static IUserStore<ExtendedUser, string> UserStore { get; set; }
public static UserService Create(IdentityFactoryOptions<UserService> options, IOwinContext context)
{
// var userStore = DependencyResolver.Current.GetService(typeof(IUserStore<ExtendedUser, string>)) as IUserStore<ExtendedUser, string>;
var manager = new UserService(UserStore);
....
}
}
The interface is configured in the NInject configuration properly. NInject otherwise is setup correctly as without Owin integration, I can resolve my services.
kernel.Bind<Microsoft.AspNet.Identity.IUserStore<ExtendedUser, string>>().To<UserStore<ExtendedUser, string>>();
Attempts already made
Update 1: Another use case of similar situation: I am trying to setup a custom logger on ExceptionFilter as follows:-
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Exception handling
config.Filters.Add(new ExceptionFilters());
}
}
public class ExceptionFilters : ExceptionFilterAttribute
{
public ExceptionFilters(ZLogging.ILogger logger)
{
this.logger = logger;
}
public override void OnException(HttpActionExecutedContext context)
{
this.logger.Log(....)
}
}
Here again the call to ExceptionFilters constructor gets blocked out in the Register method of WebApiConfig.
Within the WebApiConfig, we can get the DependencyResolver using the HttpConfiguration instance passed and then get the logger service. Hence here its not a problem. But wherever the HttpConfiguration is not accessible there facing issues getting the resolver out
Update 2 Using DI setup within Startup class as follows:-
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.DependencyResolver = new NinjectDependencyResolver(new Ninject.StandardKernel() );
this.resolver = httpConfig.DependencyResolver;
.... }
Although this clears out the issue with Owin reference resolution, it breaks the exception filters in WebAPI (see update 1) due to Global.asax being resolved first before Startup in the execution cycle :(
Need help in getting the resolver to get the service out. Any necessary insights can be provided.
Upvotes: 2
Views: 1901
Reputation: 2068
After multiple combinations of different ways of sequencing code for 2 full days, finally came up to a solution which works across. Posting my solution so to save others some headache :)
NuGet packages required
Files/Classes involved (in sequence of runtime execution)
Note that execution sequence is importance as for the dependencies to be available by the time they are required for another context.
NInjectWebCommon (initializes DI framework, injecting it in the execution pipeline). Full code posted to help those who couldn't get it via NInject.Web.WebAPi.WebHost Nuget package)
The namespace will be decorated by the WebActivator attribute as
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(NinjectWebCommon), "Stop")]
Underlying class
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
public static void Stop()
{
bootstrapper.ShutDown();
}
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ILogger>().To<NLogLogger>().InSingletonScope();
// .... other services as below
kernel.Bind<IEmployeeRepository>().To<EmployeeRepository>();
}
}
Global.asax (All other configuration regarding routing and DI to be removed from here)
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Startup.HttpConfiguration = GlobalConfiguration.Configuration;
}
}
Startup.cs (contains Owin + WebApi brief)
public partial class Startup
{
public static HttpConfiguration HttpConfiguration { get; set; }
public void Configuration(IAppBuilder app)
{
this.ConfigureOAuthTokenGeneration(app);
this.ConfigureOAuthTokenConsumption(app);
this.ConfigureWebApi();
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(HttpConfiguration);
HttpConfiguration.EnsureInitialized();
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
var userStore = HttpConfiguration.DependencyResolver.GetService(typeof(IUserStore<ExtendedUser, string>)) as IUserStore<ExtendedUser, string>;
UserService.UserStore = userStore;
app.CreatePerOwinContext<UserService>(UserService.Create);
app.CreatePerOwinContext<SignInService>(SignInService.Create);
...
}
.... other methods removed for simplification
private void ConfigureWebApi()
{
HttpConfiguration.MapHttpAttributeRoutes();
WebApiConfig.Register(HttpConfiguration);
}
}
WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Exception handling
var logger = config.DependencyResolver.GetService(typeof(ILogger)) as ILogger;
config.Filters.Add(new ExceptionFilters(logger));
}
}
(Update: Freeze configuration) Updated Startup to ensure configuration is finalized (due to multiple places its being setup)
Lo and Behold Everything works smoothly :) If this helps, please smile and hug your team mates :)
Upvotes: 2