question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Question] How can I override Autofac configuration to provide my own custom ConnectorClient?

See original GitHub issue

Bot Info

  • SDK Platform: .NET
  • SDK Version: 3.11
  • Active Channels: Direct Line
  • Deployment Environment: Azure App Service

Issue Description

I would like to override the Bot Builder’s Autofac configuration so that I can provide my own custom ConnectorClient sub class, instead of the default one.

I want to do this so that I can pass a static HttpClient to the ConnectorClient. We’re getting socket exhaustion problems in the cloud, and I think HttpClient reuse would help a lot. This ticket is just a question about overriding ConnectorClient in Autofac though, not the socket exhaustion issue (that’s logged as another ticket)

Code Example

Global.asax.cs

    /* Global.asax.cs configuration to inject my custom autofac module */
    protected async void Application_Start()
		{
			var containerBuilder = new ContainerBuilder();
			GlobalConfiguration.Configure(WebApiConfig.Register);
			MicrosoftAppCredentials.TrustServiceUrl(@"https://facebook.botframework.com", DateTime.MaxValue);
			MicrosoftAppCredentials.TrustServiceUrl(@"https://directline.botframework.com", DateTime.MaxValue);
			MicrosoftAppCredentials.TrustServiceUrl(@"https://bc-directline-weurope.azurewebsites.net", DateTime.MaxValue);

			ConfigureDependencyInjection(containerBuilder);
		}

		private static void ConfigureDependencyInjection(ContainerBuilder containerBuilder)
		{
			// register the Bot Builder module
			containerBuilder.RegisterModule(new DialogModule());
			// Override bot framework defaults.
			containerBuilder.RegisterModule(new MyOwnDiModule());
			var config = GlobalConfiguration.Configuration;

			// Register your Web API controllers.
			containerBuilder.RegisterApiControllers(Assembly.GetExecutingAssembly());
			containerBuilder.RegisterWebApiFilterProvider(config);

			// Set the dependency resolver to be Autofac.
			var container = containerBuilder.Build();
			config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
		}

MyOwnDiModule - where I try to tell the bot framework to use my custom connectorclient

       public class MyOwnDiModule: Module
	{
		static HttpClient _globalHttpClient = new HttpClient();

		protected override void Load(ContainerBuilder builder)
		{
			base.Load(builder);
			
			//ConnectorClient override that takes a global static HttpClientHandler.
			builder
				.Register(c => new MyCustomConnectorClient(_globalHttpClient, new Uri(c.Resolve<IAddress>().ServiceUrl), c.Resolve<MicrosoftAppCredentials>(), _globalHttpClientHandler))
				.As<IConnectorClient>()
				.InstancePerMatchingLifetimeScope(typeof(DialogModule));

		}
	}

I have not created the MyCustomConnectorClient yet, but setting a breakpoint on the Autofac IConnectorClient registration shows that my custom Register() delegate is never called.

A dialog that should use MyCustomConnectorClient internally when sending messages back to the user

Nothing fancy here. This is my root dialog, and when context.PostAsync is called, I would like MyCustomConnectorClient to be used internally.

       [Serializable]
	public class MyRootDialog: IDialog<string>
	{
		public async Task StartAsync(IDialogContext context)
		{
			await context.PostAsync($"Testing dialog started. Hit me.");
			context.Wait(ResumeHere);
		}

		private async Task ResumeHere(IDialogContext context, IAwaitable<IMessageActivity> result)
		{
			
			await context.PostAsync($"Msg received thanks. time:{DateTime.Now.ToLongTimeString()}");
		}
	}

MessagesController

The messageController does not do anything special, except use Conversation to launch the root dialog:

		/// <summary>
		/// POST: api/Messages
		/// </summary>
		public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
		{
			if (activity.Type == ActivityTypes.Message)
			{
				await Conversation.SendAsync(activity, () => new MyRootDialog());
			}
			return Request.CreateResponse(HttpStatusCode.OK);
		}

How do I supply a custom sub class of ConnectorClient? (or alternatively, how do I use a static instance of HttpClient?)

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:3
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

5reactions
willemodendaalcommented, Jan 26, 2018

Here is what we did eventually, and it had a big positive impact on socket usage. Worth creating a PR for?

Global.asax.cs

Here we tell the bot framework to use our own custom stateClient and connectorClient (via our BotFrameworkOverrideDefaultsModule )

protected async void Application_Start()
{
    var containerBuilder = new ContainerBuilder();
    GlobalConfiguration.Configure(WebApiConfig.Register);
    MicrosoftAppCredentials.TrustServiceUrl(@"https://facebook.botframework.com", DateTime.MaxValue);
    MicrosoftAppCredentials.TrustServiceUrl(@"https://directline.botframework.com", DateTime.MaxValue);
    MicrosoftAppCredentials.TrustServiceUrl(@"https://bc-directline-weurope.azurewebsites.net", DateTime.MaxValue);

    var botFrameworkOverrideDefaultsModule = new BotFrameworkOverrideDefaultsModule();
    containerBuilder.RegisterModule(botFrameworkOverrideDefaultsModule);
    
    //To ensure that when we call the bot framework's Conversation.SendAsync(), that our custom module is registered with autofac. 
    //  Conversation.cs's static constructor does its own "registerModule" bit, so this is necessary; Otherwise
    //  the bot framework does not use our overrides.
    Conversation.UpdateContainer(bld => bld.RegisterModule(botFrameworkOverrideDefaultsModule));
    
    var container = containerBuilder.Build();

    //Hook up the DIContainer to ASP.NET WebApi
    var config = GlobalConfiguration.Configuration;
    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}

Our custom Autofac module

public class BotFrameworkOverrideDefaultsModule: Module
{
    protected override void Load (ContainerBuilder builder)
    {
        base.Load(builder);

        if (ConfigurationManager.AppSettings["BotFrameworkUseStaticHttpClient"] == "true")
        {
            //ConnectorClient override that takes a global static HttpClient
            builder
                .Register(c => new HttpClientReuseConnectorClient(new Uri(c.Resolve<IAddress>().ServiceUrl), c.Resolve<MicrosoftAppCredentials>()))
                .As<IConnectorClient>()
                .InstancePerMatchingLifetimeScope(typeof(DialogModule));

            //StateClient override that takes a static HttpClient. I realize this client is being deprecated soon.
            builder
                .Register(c => new HttpClientReuseStateClient(new Uri(c.Resolve<IAddress>().ServiceUrl), c.Resolve<MicrosoftAppCredentials>()))
                .As<IStateClient>()
                .InstancePerLifetimeScope();
        }
    }
}

Custom StateClient and ConnectorClient sub-classes that don’t dispose their HttpClient.

A static HttpClient is created per uri.

public class HttpClientReuseStateClient: StateClient
{
    //List of reusable httpClients, by uri.
    public static ConcurrentDictionary<string, HttpClient> StaticHttpClients { get; set; } = new ConcurrentDictionary<string, HttpClient>();

    public static HttpClientHandler StaticClientHandler = new HttpClientHandler();
    public static HttpClient StaticHttpClient;

    public HttpClientReuseStateClient(Uri uri, MicrosoftAppCredentials microsoftAppCredentials)
        : base(uri, microsoftAppCredentials)
    {
        base.HttpClient = StaticHttpClients.GetOrAdd(
            uri.ToString(),
            u => new HttpClient(
                    handler: StaticClientHandler,
                    //To keep the connection open after use.
                    disposeHandler: false)
                { BaseAddress = new Uri(u.ToString()) });
    }

    protected override void Dispose(bool disposing)
    {
        //*Don't call base to dispose; We want to keep the HttpClient connection open otherwise we get
        // socket exhaustion errors under load.
        //base.Dispose(false);
    }
}

public class HttpClientReuseConnectorClient: ConnectorClient
{
    //List of reusable httpClients, by uri.
    public static ConcurrentDictionary<string, HttpClient> StaticHttpClients { get; set; } = new ConcurrentDictionary<string, HttpClient>();

    public static HttpClientHandler StaticClientHandler = new HttpClientHandler();
    public static HttpClient StaticHttpClient;

    public HttpClientReuseConnectorClient(Uri uri, MicrosoftAppCredentials microsoftAppCredentials)
        : base(uri, microsoftAppCredentials)
    {
        base.HttpClient = GetStaticHttpClient(uri);
    }

    public HttpClientReuseConnectorClient(Uri uri)
        : base(uri)
    {
        base.HttpClient = GetStaticHttpClient(uri);
    }

    private static HttpClient GetStaticHttpClient(Uri uri)
    {
        return StaticHttpClients.GetOrAdd(
            uri.ToString(),
            u => new HttpClient(
                    handler: StaticClientHandler,
                    //To keep the connection open after use.
                    disposeHandler: false)
                { BaseAddress = new Uri(u.ToString()) });
    }

    protected override void Dispose(bool disposing)
    {
        //*Don't call base to dispose; We want to keep the HttpClient connection open otherwise we get
        // socket exhaustion errors under load.
        //base.Dispose(disposing);
    }
}
0reactions
EricDahlvangcommented, Feb 15, 2018

Hey @willemodendaal

There’s an alpha release of v4 here: https://github.com/Microsoft/botbuilder-dotnet

And, there’s already been some discussion about static http client here: https://github.com/Microsoft/botbuilder-dotnet/issues/106

Read more comments on GitHub >

github_iconTop Results From Across the Web

Autofac XML Configuration & Properties Override or Provide if ...
1 Answer 1 ... The problem is in your <files> section. Since this is handled by FileElementCollection , a configuration class inheriting the ......
Read more >
How do I pick a service implementation by context?
When you register things in Autofac, you might have registrations that look like this: var builder = new ContainerBuilder(); ...
Read more >
Modules — Autofac 6.0.0 documentation
The module exposes a deliberate, restricted set of configuration parameters that can vary independently of the components used to implement the module. The ......
Read more >
How to make use of Autofac in IServicesConfigurator
1: The Configuration ... To do this with Autofac, you need to create your own ... Register our custom services via a module....
Read more >
Autofac Documentation - Read the Docs
Learn about Autofac configuration options that allow you to better manage your component registrations. 1.6 Need Help? • You can ask ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found