Search code examples
c#azure.net-coreazure-functionsdispose

Implement custom dispose for AddScoped services in .NET Core built-in DI Container


I want to register a TelemetryClient in a ServiceCollection using the AddScoped method and call Flush when the client is disposed. I don't have access to the TelemetryClient after the scope is finished to call it explicitly, and also I need to pass the TelemetryClient to a method of a third party method so I cannot use a wrapper.

I'm using the built-in Di container of .Net Core in Azure Functions.

I'm registering it like this:

services.AddScoped(x =>
            {
                return new TelemetryClient();
            });

I would like to have a method like OnRelease in Autofac, so I could register it like the following. Notice the call to OnRelease:

services.AddScoped(x =>
            {
                return new TelemetryClient();
            }).OnRelease(x=>
            {
                x.Flush();
            });

Solution

  • You can wrap the TelemetryClient like this:

    public interface ILogsStuff
    {
        void LogSomething(string message);
    }
    
    public class TelemetryClientLogger : ILogsStuff, IDisposable
    {
        private readonly TelemetryClient _telemetryClient;
    
        public TelemetryClientLogger(TelemetryClient telemetryClient)
        {
            _telemetryClient = telemetryClient;
        }
    
        public void LogSomething(string message)
        {
            _telemetryClient.TrackTrace(message);
        }
    
        public void Dispose()
        {
            _telemetryClient.Flush();
        }
    }
    

    There's an added benefit, which is that whatever class you're injecting this into won't depend directly on TelemetryClient. You can define an interface that describes whatever you want to use the client for, and that's easier to both mock and replace with some other implementation if needed.

    The suggestion was made to make TelemetryClient implement IDisposable and have it call Flush() when disposed, and this was the recommended solution.


    The update to your question says:

    I need to pass the TelemetryClient to a method of a third party method so I cannot use a wrapper.

    That changes things somewhat, as now the intention is not to use the TelemetryClient but to resolve it and then pass it to a method of a 3rd-party library. That's peculiar because it means that this library forces code that uses it to have a hard dependency on TelemetryClient, and then presumably doesn't handle that client as expected.

    At this point there are still solutions, but they're all ugly because they are solutions to a weird problem that shouldn't exist (and wasn't created by your code.)

    If you're passing TelemetryClient to a method, that method writes to it, and then it must be flushed, why doesn't that library flush it?

    You can likely solve that problem by passing the TelemetryClient to the method when you execute it and then flushing the client after the method executes.

    CallSomeMethod(someArgument, _telemtryClient);
    telemetryClient.Flush();
    

    Or, to get the result closer to your question, you could do this, which would obtain the result but is still awkward and weird:

    public class TelemetryClientWrapper : IDisposable
    {
        private readonly TelemetryClient _telemetryClient;
    
        public TelemetryClientWrapper(TelemetryClient telemetryClient)
        {
            _telemetryClient = telemetryClient;
        }
    
        public TelemetryClient Client => _telemetryClient;
    
        public void Dispose()
        {
            _telemetryClient.Flush();
        }
    }
    

    You can inject this class and pass it to the library's method like this:

    CallSomeMethod(someArgument, _telemtryClientWrapper.Client);
    

    The problem now is that your class has a hard dependency on TelemetryClient even though it doesn't use it, all so it can fix what the other library (presumably) isn't doing correctly with that client.

    Having said all of that, perhaps in your scenario this is the answer:

    The built-in service container is meant to serve the needs of the framework and most consumer apps. We recommend using the built-in container unless you need a specific feature that it doesn't support. Some of the features supported in 3rd party containers not found in the built-in container:

    In other words, if you need what Autofac does, you can still use Autofac with IServiceCollection.