c# - Inyección de dependencia(utilizando SimpleInjector) y OAuthAuthorizationServerProvider
asp.net asp.net-mvc (4)
Cuando comienzas con Dependency Injection, Owin probablemente no sea la API más amigable para empezar.
Noté esta parte en tu código:
IUserService service = Startup.Container.GetInstance<IUserService>();
Probablemente esté haciendo esto como una solución antes de descubrir cómo usar el constructor. Pero creo que esa es tu respuesta allí mismo. El OAuthAuthorizationServerProvider es un singleton, por lo que su IUserService también será un singleton y todas las dependencias de esta clase también serán singleton.
Usted mencionó que utiliza un repositorio en su servicio de usuario. Probablemente no desee que este repositorio sea singleton ya que supongo que este repositorio usará un DbContext de algún tipo.
Así que la respuesta intermedia podría ser la solución que ya hiciste. Tal vez exista una solución más elegante si investiga qué hace exactamente el método UseOAuthAuthorizationServer. El código fuente de Katana se puede encontrar aquí: Código fuente de Katana
Para el registro de las otras clases de identidad asp.net, el enlace en el comentario de DSR le dará un buen punto de partida.
Nuevo en la inyección de dependencia, por lo que este es probablemente un asunto simple, pero lo he intentado y no puedo resolverlo, estoy usando Simple Injector.
Tengo un WebApi que usa SimpleInjector perfectamente bien, ahora me gustaría implementar la seguridad usando OAuth.
Para hacer esto, comencé a seguir este tutorial, que es muy útil, pero no usa la inyección de dependencia.
http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
Tengo mi archivo global.asax con este aspecto, para configurar la inyección de dependencia (funciona perfectamente)
protected void Application_Start()
{
SimpleInjectorConfig.Register();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
He creado un archivo Startup.Auth.cs para configurar 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());
}
}
Ahora, como he comentado anteriormente, MyAuthorizationServerProvider es el problema. toma un parámetro de IUserService que normalmente inyecto. No quiero vaciar el constructor porque mi IUserService también inyecta un repositorio. Aquí está el archivo
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<IUserService>();
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);
}
}
¿Cómo puedo conseguir que esto funcione con inyección de dependencia? Esto debe suceder bastante y debe ser capaz de hacer algo para manejarlo. Estoy seguro de que es algo simple, pero todavía estoy aprendiendo.
En primer lugar, esta es una respuesta tardía. Acabo de escribir esto en caso de que alguien más se encuentre con un problema similar y, de alguna manera, quede vinculado a esta página (como yo) en el futuro.
La respuesta anterior es razonable, pero no resolverá el problema si el servicio está realmente registrado por solicitud de API web, lo que creo que es lo que la gente suele hacer si quiere usar la inyección de dependencia para el objeto de marco de identidad como UserManager.
El problema es que cuando se llama a GrantResourceOwnerCredentials (generalmente cuando la gente llega al punto final del "token"), el inyector simple no inicia un ciclo de vida de solicitud de API. Para resolver esto, todo lo que necesitas hacer es comenzar uno.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//......
using (Startup.Container.BeginExecutionContextScope())
{
var userService= Startup.Container.GetInstance<IUserService>();
// do your things with userService..
}
//.....
}
Con BeginExecutionContextScope, el inyector simple iniciará un nuevo ámbito de contexto. Sin embargo, recuerde que debe ser eliminado explícitamente.
Me tomé un tiempo para averiguar si sería posible registrar OAuthAuthorizationServerOptions
en la canalización de Owin utilizando el método app.Use()
directamente, en lugar de app.UseOAuthAuthorizationServer()
que es solo un método de extensión sobre app.Use()
. app.Use()
tiene una sobrecarga donde puede registrar un delegado que puede usar para construir OAuthAuthorizationServerOptions
.
Desafortunadamente, este esfuerzo llegó a un callejón sin salida, porque parece que incluso si usáramos un delegado para la construcción, lo más probable es que el oleoducto de Owin solo lo llame una vez, lo que lleva al mismo resultado, es decir, a una instancia única de OAuthAuthorizationServerOptions
y así, todas las dependencias de esta clase serán singleton también.
Entonces, la única solución para que las cosas sigan funcionando como deben ser, es extraer una nueva instancia de su servicio de UserService
cada vez que se GrantResourceOwnerCredentials()
método GrantResourceOwnerCredentials()
.
Pero para seguir los principios de diseño del Simple Injector , sería un mal diseño mantener una dependencia del contenedor en la clase ApiAuthorizationServerProvider
, como muestra el código original.
Una mejor manera de hacer esto sería usar una fábrica para la clase UserService
lugar de extraerla directamente del contenedor. El siguiente código muestra un ejemplo de cómo puedes hacer esto:
En primer lugar, limpie el método Application_Start()
en su archivo global.asax y coloque todo su código de inicio en el método Owin Startup()
. El código del método Startup()
:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = SimpleInjectorConfig.Register();
GlobalConfiguration.Configure(WebApiConfig.Register);
Func<IUserService> userServiceFactory = () =>
container.GetInstance<IUserService>();
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());
}
}
Observe cómo cambié la firma de la función SimpleInjectorConfig.Register()
devolviendo el contenedor de Simple Injector completamente configurado a la persona que llama para que se pueda usar directamente.
Ahora cambie el constructor de su clase ApiAuthorizationServerProvider
, para que se pueda inyectar el método de fábrica:
public class ApiAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
private Func<IUserService> userServiceFactory;
public ApiAuthorizationServerProvider(Func<IUserService> 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...
}
}
De esta manera, obtendrá un nuevo UserService
cada vez que se GrantResourceOwnerCredentials()
método GrantResourceOwnerCredentials()
y el gráfico de dependencia completo detrás de la clase UserService
seguirá los UserService
vida que definió en su configuración de Simple Injector, mientras que solo depende del contenedor en la raíz de la composición de la aplicación. .
Mientras esté registrando la resolución de dependencias para su webapi en su App_Start SimpleInjectorConfig.Register();
Me gusta esto
GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
Y si está usando el AsyncScopedLifestyle
recomendado, entonces puede usar el resolvedor de dependencias para obtener una nueva instancia de sus servicios como este
using (var scope = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.BeginScope())
{
var _userService = scope.GetService(typeof(IUserService)) as IUserService;
//your code to use the service
}