Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac, Owin, Webapi and injecting to AuthorizationServerProvider

After reading questions and articles about using autofac with owin and webapi, I came across a solution to inject services but it does not work. Here is my code:

public class StartUp
{
    public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);

        var builder = new ContainerBuilder();                                    // Create the container builder.
        builder.RegisterApiControllers(Assembly.GetExecutingAssembly());         // Register the Web API controllers.

        var authcontext = new AuthContext();

        builder.RegisterInstance(authcontext).AsSelf().SingleInstance();

        //Updated
        //var simpleauth = new SimpleAuthorizationServerProvider();
        //Updated
        // builder.RegisterInstance(simpleauth).SingleInstance().AsSelf().PropertiesAutowired();

        builder.Register(x => new UserStore<IdentityUser>(authcontext)).As<IUserStore<IdentityUser>>();

        //updated

        builder.Register(x =>
        {
            var p = new SimpleAuthorizationServerProvider();
            var userStore = x.Resolve<IUserStore<IdentityUser>>();
            p.userManager = new UserManager<IdentityUser>(userStore);
            return p;
        }).AsSelf().PropertiesAutowired();

        builder.RegisterType<AuthRepository>().As<IAuthRepository>().InstancePerRequest().PropertiesAutowired();

        var container = builder.Build();

        var resolver = new AutofacWebApiDependencyResolver(container);           // Create an assign a dependency resolver for Web API to use.
        config.DependencyResolver = resolver;

        app.UseAutofacMiddleware(container);

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

        ConfigureOAuth(app, resolver);
    }

    public void ConfigureOAuth(IAppBuilder app, AutofacWebApiDependencyResolver resolver)
    {
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
           //updated
            Provider =  new SimpleAuthorizationServerProvider()
//resolver.GetService(typeof(SimpleAuthorizationServerProvider)) as SimpleAuthorizationServerProvider
        };

        // Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

    }


}

But in SimpleAuthorizationServerProvider class, when a method like ValidateClientAuthentication is begin called, all the services are null, here is the code:

        public readonly IAuthRepository repository;
        public readonly UserManager<IdentityUser> userManager;
        public readonly AuthContext dbContext;

        public SimpleAuthorizationServerProvider()
        {

        }

        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId;
            string clientSecret;

            if (context.TryGetFormCredentials(out clientId, out clientSecret))
            {


                try
                {
                    Client client = await repository.FindClientById(clientId);
                }
            }
        }

Would you help me please ?

Updated

If in ConfigureOAuth method I use the following approach:

            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = resolver.GetService(typeof(SimpleAuthorizationServerProvider)) as SimpleAuthorizationServerProvider
            };

I get error:

An exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll but was not handled in user code

Additional information: No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.
like image 895
Babak Fakhriloo Avatar asked Jan 10 '23 13:01

Babak Fakhriloo


1 Answers

When you register an instance of an object rather than a type, even if you specify PropertiesAutowired that will not take effect because Autofac assumes you've done all the work you want when you create the instance. If you want properties wired in, you need to do that in an OnActivated handler.

There are actually a lot of things at play in this sample code that will not work.

  • The values in SimpleAuthorizationServerProvider are fields not properties so PropertiesAutowired won't work on them.
  • The fields are marked readonly and they're never set.
  • You have UserManager<IdentityUser> registered as a lambda but also have PropertiesAutowired which won't work - you can only use PropertiesAutowired on a reflection-based component (e.g., RegisterType<T>).

Consider registering a lambda for your provider and set everything in the lambda:

builder.Register(c => {
  var p = new SimpleAuthorizationServerProvider();
  p.repository = c.Resolve<UserManager<IdentityUser>>();
  // ...and so on
  return p;
}).AsSelf().SingleInstance();

Also, keep in mind that if you register an instance (or register something as SingleInstance the properties will be resolved one time and that's it. So if you have some dependencies that are InstancePerDependency or InstancePerRequest, that's not going to work the way you think - they'll be resolved one time and effectively be singletons after that.


Update 1

Based on the original and updated code, it occurs to me that it would be good if you could check out some of the Autofac doc to understand better how it works. For example, the use of fields in SimpleAuthorizationServerProvider shows you may not totally get how injection works in Autofac or how to properly register things so Autofac can do the work for you.

  • Registration concepts
  • Web API integration
  • Working with per-request lifetime

For example, looking at the updates...

  • You now have a lambda registered for the SimpleAuthorizationServerProvider but I don't see where you set the repository field there.
  • You don't need PropertiesAutowired on the SimpleAuthorizationServerProvider registration because you're registering a lambda and the properties will not be autowired (as noted earlier).
  • The only component I see as being registered InstancePerRequest is the AuthRepository but, like I said, I don't see where that's being resolved or set - and that's the only thing that would generate the exception you noted. There is an FAQ on dealing with that exact exception that you should look into.

Also, you are showing two different versions of the OAuthServerOptions being initialized and it's hard to tell which one is "real."

I would recommend a fairly major refactoring to enable things to actually use DI correctly.

Change the SimpleAuthorizationServerProvider to stop using public fields and add them as constructor parameters so Autofac can wire the stuff up for you.

public class SimpleAuthorizationServerProvider
{
  public IAuthRepository Repository { get; private set; }
  public UserManager<IdentityUser> UserManager {get; private set; }
  public AuthContext Context { get; private set; }
  public SimpleAuthorizationServerProvider(
    IAuthRepository repository,
    UserManager<IdentityUser> userManager,
    AuthContext context)
  {
    this.Repository = repository;
    this.UserManager = userManager;
    this.AuthContext = context;
  }
}

During startup, fix your registrations to remove extraneous stuff and take advantage of the Autofac auto-wiring goodness.

public class StartUp
{
  public void Configuration(IAppBuilder app)
  {
    var config = new HttpConfiguration();
    WebApiConfig.Register(config);

    var builder = new ContainerBuilder();
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

    // Register the auth context instance but skip
    // the extra .AsSelf() and .SingleInstance() because
    // it's implicit.
    builder.RegisterInstance(new AuthContext());

    // Use the lambda to resolve the auth context rather
    // than making a closure over an instance.
    builder.Register(c => new UserStore<IdentityUser>(c.Resolve<AuthContext>()))
           .As<IUserStore<IdentityUser>>();

    // Just register the provider type and let Autofac
    // do the work without all this manual stuff. Skip
    // the .AsSelf() because it's implicit if you don't
    // specify other interfaces and don't auto-wire properties
    // because you don't need it.
    builder.RegisterType<SimpleAuthorizationProvider>();

    // This is fine, but I can't tell where it's used - if
    // you are using it at app startup or OUTSIDE a request,
    // you will get that exception you noted. Also, unless
    // you're actually using property injection, lose the
    // .PropertiesAutowired() call.
    builder.RegisterType<AuthRepository>()
           .As<IAuthRepository>()
           .InstancePerRequest()
           .PropertiesAutowired();

    var container = builder.Build();
    var resolver = new AutofacWebApiDependencyResolver(container);
    config.DependencyResolver = resolver;

    app.UseAutofacMiddleware(container);
    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    app.UseWebApi(config);

    ConfigureOAuth(app, resolver);
  }

  public void ConfigureOAuth(IAppBuilder app, AutofacWebApiDependencyResolver resolver)
  {
    var options = new OAuthAuthorizationServerOptions()
    {
      AllowInsecureHttp = true,
      TokenEndpointPath = new PathString("/token"),
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),

      // If you want the values to be wired up, you have
      // to do a resolve. Note, however, that since you're
      // doing this wire-up at app startup, there's no request
      // scope, so if something in here is registered `InstancePerRequest`
      // you will get an exception.
      Provider =  resolver.GetService(typeof(SimpleAuthorizationServerProvider)) as SimpleAuthorizationServerProvider
    };

    app.UseOAuthAuthorizationServer(options);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
  }
}

Under the assumption that's all the code you should be OK. If things aren't set - like one of the SimpleAuthorizationServerProvider properties comes through as null, or if you get an exception because it's missing a dependency, or if you get the exception about there being no request scope... then there's something else going on that you haven't put in your question.

Again, please take the time to check out the docs and familiarize yourself with Autofac. I think many of the troubles you're running into are the result of some misunderstanding on how things get wired up.

like image 148
Travis Illig Avatar answered May 04 '23 15:05

Travis Illig