Search code examples
c#asp.net-mvc-5autofacbearer-tokenautorest

How should i inject Autorest Client with TokenProvider using autofac in asp.net mvc 5?


I want to properly inject an autorest client dependency that uses an api(which users will have their own token to use after login but they can use api before login for some methods token is not required) by using autofac. I know it is not directly a autorest question it is more about autofac but i I want to give the exact example, so i can get a better recommendation(maybe i am doing all wrong it is a conceptual problem). I looked out for some examples i found some but in all of them they were implementation just for one user they didnt use tokenprovider they just passed a foreknown token(which is not a token for user it is for application).

What i tried is registering autorest client with a wrapped parameter(already registered multiple dependencies take each other as constructor parameters) into container.

This is how I register my services :

protected void Application_Start()
{

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


    var sp = ServicePointManager.FindServicePoint(new Uri(ConfigurationManager.AppSettings["WebApiBaseUrl"]));
    sp.ConnectionLeaseTimeout = 60 * 1000; // 1 minute
    builder.Register(c => new HttpContextWrapper(HttpContext.Current))
        .As<HttpContextBase>()
        .InstancePerRequest();
    builder.RegisterType<TokenProvider>().As<ITokenProvider>().InstancePerLifetimeScope();
    builder.RegisterType<TokenCredentials>().Keyed<ServiceClientCredentials>("credentials").InstancePerLifetimeScope();
    builder.RegisterType<WebApiClient>()
           .As<IWebApiClient>()
           .WithParameter("baseUri", new Uri(ConfigurationManager.AppSettings["WebApiBaseUrl"])
           ).WithParameter("credentials",
              new ResolvedParameter(
   (pi, ctx) => pi.ParameterType == typeof(ServiceClientCredentials),
   (pi, ctx) => ctx.ResolveKeyed<ServiceClientCredentials>(pi.Name))
   ).SingleInstance();


    IContainer container = builder.Build();
    DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

}

and my services :

public partial class WebApiClient : ServiceClient<WebApiClient>, IWebApiClient
{

    public WebApiClient(System.Uri baseUri, ServiceClientCredentials credentials = null, params DelegatingHandler[] handlers) : this(handlers)
    {
        if (baseUri == null)
        {
            throw new System.ArgumentNullException("baseUri");
        }

        BaseUri = baseUri;

        if (credentials != null)
        {
            Credentials = credentials;
            Credentials.InitializeServiceClient(this);
        }
    }
}
public class TokenProvider : ITokenProvider
{
    private readonly HttpContextBase _context;

    public TokenProvider(HttpContextBase context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public async Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
    {
        // this should be async i know(another topic to ask in mvc 5)
        var token =_context.Session["ServiceToken"]?.ToString();
        if (string.IsNullOrWhiteSpace(token))
        {
            throw new InvalidOperationException("Could not get an access token from HttpContext.");
        }

        return new AuthenticationHeaderValue("Bearer", token);
    }
}

public class TokenCredentials : ServiceClientCredentials
{
    //I want to use this constructor
    public TokenCredentials(ITokenProvider tokenProvider);
}

This is the exception I get

Inner exception Unable to cast object of type Autofac.Core.ResolvedParameter to type Microsoft.Rest.ServiceClientCredentials.


Solution

  • Unable to cast object of type Autofac.Core.ResolvedParameter to type Microsoft.Rest.ServiceClientCredentials.

    means that you are using a ResolvedParameter object whereas a ServiceClientCredentials is expected.

    In your code there is

    .WithParameter("credentials",
         new ResolvedParameter(
           (pi, ctx) => pi.ParameterType == typeof(ServiceClientCredentials),
           (pi, ctx) => ctx.ResolveKeyed<ServiceClientCredentials>(pi.Name))
         )
    

    The WithParameter has 3 overloads :

    • WithParameter(string parameterName, object parameterValue) : when you know the name of the parameter and you can provide it at registration time. Autofac will create a NamedParameter object for you.
    • WithParameter(Func<ParameterInfo, IComponentContext, bool> parameterSelector, Func<ParameterInfo, IComponentContext, object> valueProvider) : When you don't know the name of the parameter and/or when you can't provide the value at registration time. Autofac will create a ResolvedParameter object for you.
    • WithParameter(Parameter parameter) : to provide a Parameter object that you create yourself.

    In your case your are using the first option. Autofac will create a NamedParameter for you and you provide a ResolvedParameter as a value.

    To fix the error you should not use the first overload this way but you can use the second one :

    .WithParameter((pi, ctx) => pi.ParameterType == typeof(ServiceClientCredentials),
                   (pi, ctx) => ctx.ResolveKeyed<ServiceClientCredentials>(pi.Name)))