dependency injection(使用SimpleInjector)和OAuthAuthorizationServerProvider

对dependency injection的新手,所以这可能是一件简单的事情,但我已经尝试过并且无法弄明白,我正在使用Simple Injector。

我有一个完全使用SimpleInjector的WebApi,现在我想使用OAuth实现安全性。

为此,我开始学习本教程,这非常有用,但不使用Dependancy Injection

Token Based Authentication using ASP.NET Web API 2, Owin, and Identity

我的global.asax文件看起来像这样,设置dependency injection(完美工作)

protected void Application_Start() { SimpleInjectorConfig.Register(); GlobalConfiguration.Configure(WebApiConfig.Register); } 

我创建了一个Startup.Auth.cs文件来配置OAuth

 public class Startup { public void Configuration(IAppBuilder app) { var OAuthServerOptions = new OAuthAuthorizationServerOptions() { AllowInsecureHttp = true, TokenEndpointPath = new PathString("/token"), AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), Provider = new MyAuthorizationServerProvider() // here is the problem }; // Token Generation app.UseOAuthAuthorizationServer(OAuthServerOptions); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); } } 

现在我在上面评论过,MyAuthorizationServerProvider就是问题所在。 它需要我通常注入的IUserService参数。 我不想清空构造函数,因为我的IUserService也注入了一个存储库。 这是文件

 public class ApiAuthorizationServerProvider : OAuthAuthorizationServerProvider { private IUserService _service; public ApiAuthorizationServerProvider (IUserService service) { _service = service; } public override async Task ValidateClientAuthentication( OAuthValidateClientAuthenticationContext context) { context.Validated(); } public override async Task GrantResourceOwnerCredentials( OAuthGrantResourceOwnerCredentialsContext context) { context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); IUserService service = Startup.Container.GetInstance(); User user = _service.Query(e => e.Email.Equals(context.UserName) && e.Password.Equals(context.Password)).FirstOrDefault(); if (user == null) { context.SetError("invalid_grant", "The user name or password is incorrect."); return; } var identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim("sub", context.UserName)); identity.AddClaim(new Claim("role", "user")); context.Validated(identity); } } 

我怎样才能使用dependency injection? 这必须发生很多,必须能够做一些事情来处理它。 我确信它很简单,但我还在学习。

我花了一些时间来确定是否可以直接使用app.Use()方法在Owin管道中注册OAuthAuthorizationServerOptions ,而不是app.UseOAuthAuthorizationServer() ,这只是app.UseOAuthAuthorizationServer()的扩展方法。 app.Use()有一个重载,您可以在其中注册一个可用于构造OAuthAuthorizationServerOptions

不幸的是,这种努力达到了死胡同,因为看起来即使我们使用委托进行构造,这很可能只会被Owin管道调用一次导致相同的结果,即OAuthAuthorizationServerOptions的单例实例因此,这个类的所有依赖项也将是单例。

因此,保持工作正常运行的唯一解决方案是每次调用GrantResourceOwnerCredentials()方法时拉出UserService的新实例。

但是要遵循Simple Injector设计原则 ,在ApiAuthorizationServerProvider类中保持对容器的依赖是不好的设计,就像原始代码所示。

更好的方法是使用UserService类的工厂,而不是直接从容器中提取它。 下一个代码显示了如何执行此操作的示例:

首先,清除global.asax文件中的Application_Start()方法,并将所有启动代码放在Owin Startup()方法中。 Startup()方法的代码:

 public class Startup { public void Configuration(IAppBuilder app) { var container = SimpleInjectorConfig.Register(); GlobalConfiguration.Configure(WebApiConfig.Register); Func userServiceFactory = () => container.GetInstance(); var OAuthServerOptions = new OAuthAuthorizationServerOptions() { AllowInsecureHttp = true, TokenEndpointPath = new PathString("/token"), AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), Provider = new ApiAuthorizationServerProvider(userServiceFactory) }; // Token Generation app.UseOAuthAuthorizationServer(OAuthServerOptions); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); } } 

请注意我是如何通过将完全配置的Simple Injector容器返回给调用者来更改SimpleInjectorConfig.Register()函数的签名,以便可以直接使用它。

现在更改ApiAuthorizationServerProvider类的构造函数,以便可以注入工厂方法:

 public class ApiAuthorizationServerProvider : OAuthAuthorizationServerProvider { private Func userServiceFactory; public ApiAuthorizationServerProvider(Func userServiceFactory) { this.userServiceFactory = userServiceFactory; } // other code deleted for brevity... private IUserService userService { get { return this.userServiceFactory.Invoke(); } } public override async Task GrantResourceOwnerCredentials( OAuthGrantResourceOwnerCredentialsContext context) { // other code deleted for brevity... // Just use the service like this User user = this.userService.Query(e => e.Email.Equals(context.UserName) && e.Password.Equals(context.Password)).FirstOrDefault(); // other code deleted for brevity... } } 

这样,每次GrantResourceOwnerCredentials()方法时都会获得一个新的UserService ,并且UserService类后面的完整依赖关系图将遵循您在Simple Injector配置中定义的生命周期,而依赖于仅在应用程序的组合根中的容器。

当您从dependency injection开始时,Owin可能不是最友好的API。

我在你的代码中注意到了这一部分:

 IUserService service = Startup.Container.GetInstance(); 

在找到如何使用构造函数之前,您可能正在将此作为一种解决方法。 但我认为那是你的答案。 OAuthAuthorizationServerProvider是一个单例,因此您的IUserService也将是一个单例,并且此类的所有依赖项也将是单例。

您提到您在用户服务中使用存储库。 你可能不希望这个存储库是单例,因为我想这个存储库将使用某种DbContext。

所以中间答案可能就是你已经做出的解决方案。 如果您对UseOAuthAuthorizationServer方法的确切做法进行一些研究,也许有更优雅的解决方案。 Katana的源代码可以在这里找到: Katana源代码

对于其他asp.net身份类的注册,DSR注释中的链接将为您提供一个良好的起点。

首先,这是一个迟到的答案。 我刚写下来以防其他人遇到类似的问题,并在未来以某种方式链接到这个页面(像我一样)。

以前的答案是合理的,但是如果服务实际上是按照Web API请求注册的话,那么就不能解决问题,我相信,如果他们想对UserManager这样的身份框架对象使用dependency injection,人们通常会这样做。

问题是当调用GrantResourceOwnerCredentials时(通常当人们点击’token’端点时),简单的注入器将不会启动api请求生命周期。 要解决这个问题,您需要做的就是启动一个。

 public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { //...... using (Startup.Container.BeginExecutionContextScope()) { var userService= Startup.Container.GetInstance(); // do your things with userService.. } //..... } 

使用BeginExecutionContextScope,简单的注入器将启动新的上下文范围。 但是,请记住它需要明确处理。

只要您在App_Start SimpleInjectorConfig.Register();为webapi注册依赖项解析程序SimpleInjectorConfig.Register();

像这样

 GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container); 

如果您使用推荐的AsyncScopedLifestyle那么您可以使用依赖项解析器来获取此服务的新实例,如下所示

 using (var scope = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.BeginScope()) { var _userService = scope.GetService(typeof(IUserService)) as IUserService; //your code to use the service }