问题
New to Dependency Injection, so this is probably a simple matter, but i have tried and cant figure it out, i am using Simple Injector.
I have a WebApi that uses SimpleInjector perfectly fine, now i would like to implement security using OAuth.
To do this i started to follow this tutorial, which is very helpful, but doesnt use Dependancy Injection
http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
I have my global.asax file looking like this, to setup dependancy injection (working perfect)
protected void Application_Start()
{
SimpleInjectorConfig.Register();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
I have created a Startup.Auth.cs file to configure 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());
}
}
Now as i commented above, MyAuthorizationServerProvider is the problem. it takes a parameter of IUserService which i usually inject. I do not want to empty constructor because my IUserService also injects a repository. Here is the file
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);
}
}
How can i get this working with Dependency Injection? This must happen quite a lot and must be able to do something to handle it. I am sure its something simple, but i am still learning.
回答1:
I took some time to find out if it would be possible to register OAuthAuthorizationServerOptions
in the Owin pipeling using the app.Use()
method directly, instead of app.UseOAuthAuthorizationServer()
which is just an extension method over app.Use()
. app.Use()
has an overload where you could register a delegate which you could use to construct the OAuthAuthorizationServerOptions
.
Unfortunately this effort hit a dead end, because it seems that even if we'd use a delegate for the construction, this will be most likely only called once by the Owin pipeline which leads to the same result, namely a singleton instance of the OAuthAuthorizationServerOptions
and thus all dependencies of this class will be singleton as well.
So the only solution to keep things working as they should be, is to pull a new instance of your UserService
every time the GrantResourceOwnerCredentials()
method is called.
But to follow the Simple Injector design principles it would be bad design to keep a dependency on the container in the ApiAuthorizationServerProvider
class, like the original code shows.
A better way to do this would be to use a factory for the UserService
class instead of directly pulling it from the container. The next code shows an example of how you could do this:
First of all, clean out the Application_Start()
method in your global.asax file and place all your startup code in the Owin Startup()
method. The code of the Startup()
method:
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());
}
}
Notice how I changed the signature of the SimpleInjectorConfig.Register()
function by returning the completly configured Simple Injector container to the caller so it can be used directly.
Now change the constructor of your ApiAuthorizationServerProvider
class, so the factory method can be injected:
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...
}
}
This way you get a new UserService
everytime the GrantResourceOwnerCredentials()
method is called and the complete dependency graph behind the UserService
class will follow the lifetimes you defined in your Simple Injector configuration, while you depend on the container only in the composition root of the application.
回答2:
When you start with Dependency Injection, Owin is probably not the most friendly API to start with.
I noticed this part in your code:
IUserService service = Startup.Container.GetInstance<IUserService>();
You are probably doing this as a workaround before you find out how to use the constructor. But I think that's your answer right there. The OAuthAuthorizationServerProvider is a singleton, so your IUserService will be a singleton also and all the dependencies of this class will be singleton as well.
You mentioned you use a repository in your user service. Your probably don't want this repository to be singleton as I suppose this repository will use a DbContext of some kind.
So the intermediate answer could be the solution you made already. Maybe there is a more elegant solution if you do some research on what the UseOAuthAuthorizationServer method does exactly. The source code of Katana can be found here: Katana source code
For the registration of the other asp.net identity classes the link in the comment of DSR will give you a good starting point.
回答3:
Firstly, this is a late answer. I just wrote this down in case somebody else come across the similar issue and somehow get linked to this page (like me) in the future.
The previous answer is reasonable, but will not solve the problem if the service is actually registered per Web API request, which I believe is what people usually do if they want to use dependency injection for identity framework object like UserManager.
The problem is when GrantResourceOwnerCredentials get called (usually when people hit the 'token' endpoint), simple injector won't start a api request life cycle. To solve this, all you need to do is start one.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//......
using (Startup.Container.BeginExecutionContextScope())
{
var userService= Startup.Container.GetInstance<IUserService>();
// do your things with userService..
}
//.....
}
With BeginExecutionContextScope, simple injector will start a new context scope. However, remember it need to be disposed explicitly.
回答4:
As long as you are registering the dependency resolver for your webapi in your App_Start SimpleInjectorConfig.Register();
Like this
GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
And if you are using the recommended AsyncScopedLifestyle
Then you can use the dependency resolver to get a new instance of your services like this
using (var scope = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver.BeginScope())
{
var _userService = scope.GetService(typeof(IUserService)) as IUserService;
//your code to use the service
}
来源:https://stackoverflow.com/questions/25997592/dependency-injection-using-simpleinjector-and-oauthauthorizationserverprovider