1

I have code as below injected in the startup using .NET dependency injection feature. Do I have to explicitly dispose the ServiceClient?

services.AddScoped<ICrmRepository, CrmRepository>(
    c => new CrmRepository(
        svc: new ServiceClient(
            configRoot.GetConnectionString("Default"))));

Service Client is a dataverse library for dynamics 365.
https://learn.microsoft.com/en-us/power-apps/developer/data-platform/org-service/quick-start-org-service-console-app.

Update: I have an idea which i would AddScoped<ServiceClient>("connectionstring"); in startup. And in my crm repository, i would write as: private readonly ServiceClient _svc; and add a dispose function in the crm repository class and call it at the very end of any business component function using crm repository. Would it be better at managing the service client lifetime?

Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    What depends on whether your `CrmRepository` disposes its `ServiceClient` when [it itself is disposed](https://stackoverflow.com/q/40844151/11683). – GSerg Jun 05 '23 at 13:36
  • .NET 5 reached End Of Life a while ago. The oldest supported .NET (Core) version is .NET 6. Not that this changes the answer here - scoped services will be disposed when the scope closes. – Panagiotis Kanavos Jun 05 '23 at 15:01
  • @PanagiotisKanavos does it mean the scope services will also dispose the service client automatically? – Low Chen Thye Jun 05 '23 at 15:08
  • Only if it's scoped too. If you register `ServiceClient` as a scoped service, you won't have to call the constructor at all. DI will create both services and dispose them again when the scope closes. The more important question though is should `ServiceClient` be disposed and rebuilt each time or should it stay alive for longer? After all, re-authenticating on every call is expensive. – Panagiotis Kanavos Jun 05 '23 at 15:17
  • The [Parallel Requests](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/send-parallel-requests?tabs=sdk) page and other docs suggest that each ServiceClient serializes requests internally so that only one is executed at a time. If that happens, you lose any benefit you got from keeping a single authenticated instance. That counts *against* using a Singleton, or at least creating a pool of already connected clients. You should probably just start with Scoped registrations and look for more complex scenarios if you encounter delays – Panagiotis Kanavos Jun 05 '23 at 15:20

1 Answers1

0

The ServiceClient class you refer to implements IDisposible, so it should be disposed explicitly once you are done with it, otherwise you may end up with memory leaks or other issues. The issue you have here is that you are scoping CrmRepository, and not scoping ServiceClient, therefore not explicitly managing the ServiceClient's lifetime.

A "scoped" service exists in memory for as long as the scope lasts for. This is different depending on the context of the application. To answer accurately, we would need to know if this is a WebAPI app, an MVC app, or a Blazor app etc.

Generally speaking you should be registering as many dependencies as possible using your IoC container so you can manage the scope of every dependency independently. However, in this instance - purely because the ServiceClient class is a black box, I would simply treat the CrmRepository as a grey box, remove the ServiceClient dependency (which I can see has no further abstractions from the documentation - making it generally difficult to mock, and therefore making registering it as a dependency relatively pointless), and write integration tests for the CrmRepository itself.

Your code would therefore end up as:

Registration:

    services.Configure<CrmRepoOptions>("path:to:your:connectionString");
    services.AddScoped<ICrmRepository, CrmRepository>();

Implementation:

    public class CrmRepoOptions
    {
        public string ConnectionString { get; set; }
    }

    public class CrmRepository: ICrmRepository
    {
        private readonly CrmRepoOptions _opt;

        public CrmRepository(IOptions<CrmRepoOptions> opt)
        {
            _opt = opt.Value;
        }

        public Task DoSomethingWithServiceClient()
        {
            using(var serviceClient = new ServiceClient(_opt.ConnectionString))
            {
                // Do your magic with serviceClient
            }
            return Task.CompletedTask;
        }
    }

It is unlikely you would want the ServiceClient instance to exist for the entire scope of the CrmRepository (I expect you only need it when DoSomethingWithServiceClient is called), but if you did want it to exist for as long as the CrmRepository for a particular reason, you would be better off implementing IDisposible in the CrmRepository, creating the ServiceClient in the CrmRepository's constructor, and dispose of it in the Dispose method:

public class CrmRepository: IDisposable, ICrmRepository
    {
        private readonly CrmRepoOptions _opt;

        private Serviceclient _sc;

        public CrmRepository(IOptions<CrmRepoOptions> opt)
        {
            _opt = opt.Value;
            _sc = new ServiceClient(_opt.ConnectionString);
        }

        public Task DoSomethingWithServiceClient()
        {
            // Do your magic with _sc
            return Task.CompletedTask;
        }

        /// <inheritdoc />
        public void Dispose()
        {
            // Not production ready - implement a proper dispose pattern here
            _sc.Dispose();
        }
    }

Have a think about how you want to use ServiceClient and if you need a new instance of it every time you call your method or not. The documentation likely has details around how it manages its internal connections. It may be that a scoped or singleton instance is a really bad idea, and the example code is the recommended way to use it.

-- EDIT --

As comments here have eluded to, you can also register the ServiceClient as a scoped class in your IoC container, meaning you would not have to instantiate it directly when you register the CrmRepository. Whilst this is achievable, the issue you will have here is that if the ServiceClient is injected into other classes (transient or scoped) that are created as part of the same WebAPI request, then the same ServiceClient instance will be used there too.

I do not believe you want to do this here; I believe that the first code example in my response above is actually what you're looking to do; create the ServiceClient whenever you need it, then dispose of it immediately once you have finished working with it. As with many "connection" type classes provided by MS, it is very likely some kind of connection management and / or pooling is happening in the background, and creating and maintaining a persistent (either singleton or scoped) connection will cause you issues in the future. What you are trying to do is likely an over-optimisation in this instance.

Spikeh
  • 3,540
  • 4
  • 24
  • 49
  • Thank you for the valueable answer. The application is a web api. – Low Chen Thye Jun 05 '23 at 13:56
  • My initial motive to design it that way is that i inject the service client once in the constructor for crmrepository and using it for every function in crmrepository. private readonly IOrganizationServiceAsync _organizationServiceAsync; public CrmRepository(ServiceClient svc) { _organizationServiceAsync = svc; }CrmRepository simply just contain CRUD function using the serviceclient. – Low Chen Thye Jun 05 '23 at 14:31
  • What does IOptions have to do with the question here? IOptions doesn't have anything to do with scoping, it's a slightly controversial way of injecting configuration objects. There's no reason to introduce ServiceClient's configuration details into `CrmRepository` – Panagiotis Kanavos Jun 05 '23 at 14:58
  • @PanagiotisKanavos `IOptions` or `IConfiguration`, inject whatever you like, but the configuration should be injected somewhere. It may be that a wrapper class is a better way to go (in this case, `CrmRepository` is acting as the wrapper, but you may want to create a wrapper / adapter around your `ServiceClient` calls that make `CrmRepository` testable). Whenever DI is in use, there is a strong assumption that unit testing and mocking is also being used. Of course there are other benefits to using DI, but it's a large overhead if you're not intending to properly abstract your classes. – Spikeh Jun 06 '23 at 08:42