CdsServiceClient with ASP.NET Core

One of the major reasons to use the new alpha release of the CDS SDK is that you can use it with the latest version of ASP.NET Core 3.1. Super cool, yes, so how do you add it to your ASP.NET Core project through dependency injection, and how do you add it to ensure that it is the most efficient where each request isn’t blocking each other. But only to the limits of the SDK throttling 😊. Let’s explore how you can start an ASP.NET Core project from the ground up and include the CdsServiceClient.

TL;DR

Add CdsServiceClient through dependency injection as a transient registration using CdsServiceClient.Clone() to ensure efficient non-blocking multiple requests on an ASP.NET Core 3.1 application.

services.AddCdsServiceClient(options => 
                Configuration.Bind("CdsServiceClient", options));

https://github.com/CdsWeb-app/CdsWeb

The details…

Understanding Dependency Injection and Service Registration Lifetime with ASP.NET

If you have worked with the modern version of ASP.NET Core, be it 1.0 all the way to the latest of 3.1 they all support dependency injection (DI) design pattern and actually pushes it as the preferred and documented approach. Effectively with DI you are registering a class/service with the ASP.NET application, and making it available to all parts of the application. So ideally you want to register CdsServiceClient or IOrganizationService so that its accessible in your controllers or other classes in the application. Registration of the class/service is done within the Startup.cs of the application so that when the ASP.NET site starts that class is available everywhere else.

A key part of that registration is the service lifetime. You are either registering it as a singleton (created once for the entire application), scoped (created for each request), or transient (created for each request for each container). Read more about service lifetimes to gain a better understanding, and a demonstration of how the lifetimes effect a registration. So how should the CdsServiceClient be registered you probably ask. The options are really Singleton and Transient. You might be thinking Singleton as you want one database connection for your application. However 1 connection with an application that can handle multiple requests at once will block each other. So you then maybe want to select Transient making every request make a new connection. However then there is a lot of overhead on every request. So what do you do?

Multiple Instances of CdsServiceClient Efficiently…Clone()

There is a method on CdsServiceClient and on the old CrmServiceClient that is here to help you create copies efficiently…Clone(). What is clone you ask, well it clones the CdsServiceClient, and makes a new copy of it. It actually duplicates the token, the web proxy client and a bunch of things that would already exist and saves a lot of the startup work that occurs when you call the CdsServiceClient constructors. This is great because it gives you another instance of CdsServiceClient without the overhead of actually creating one from scratch.

var cdsClient1 = new CdsServiceClient("<connection string>");
var cdsClient2 = cdsClient1.Clone();

Applying CdsServiceClient.Clone() and ASP.NET Service Registration Together

So how do you now on every request in every container clone an existing CdsServiceClient and have that as a service registration? Ideally we have a Singleton CdsServiceClient sitting at the application level that is available to be constantly cloned so that we can add the cloned CdsServiceClient as a Transient lifetime for service registration. But if we have the CdsServiceClient class registered as both Singleton and Transient, when we try to get the CdsServiceClient through dependency injection in our classes it won’t know which to get.

To handle this we can create a little wrapper class and make CdsServiceClient a property of that class, then register the wrapper class as a Singleton.

public class CdsServiceClientWrapper
{
    public readonly CdsServiceClient CdsServiceClient;

    public CdsServiceClientWrapper(string connectionString)
    {
        CdsServiceClient = new CdsServiceClient(connectionString);
    }
}

Then register that class as a Singleton during startup.

services.AddSingleton(sp => 
                new CdsServiceClientWrapper("<connection string>"));

To get our cloned version that will be used in all our other containers through dependency injection add a clone of the wrapper CdsServiceClient property using the IOrganizationService as the service using the CdsServiceClient implementation.

services.AddTransient<IOrganizationService, CdsServiceClient>(sp =>
                sp.GetService<CdsServiceClientWrapper>().CdsServiceClient.Clone());

This then allows us in any class, like an API controller you can have IOrganizationService as a constructor parameter on that class so you can utilize it throughout that class. As well you can cast IOrganizationService into CdsServiceClient. Here is an example of a simple controller using the account entity.

[ApiController]
[Route("[controller]")]
public class AccountController : ControllerBase
{
    private readonly ILogger<AccountController> _logger;
    private readonly IOrganizationService _orgService;
    private readonly CdsServiceClient _cdsServiceClient;

    public AccountController(ILogger<AccountController> logger, IOrganizationService orgService)
    {
        _logger = logger;
        _orgService = orgService;
        _cdsServiceClient = (CdsServiceClient)orgService;
    }

    [HttpGet]
    public IEnumerable<AttributeMetadata> Get()
    {
        var response = _cdsServiceClient.GetAllAttributesForEntity("account");

        return response;
    }


    [HttpGet("{id}")]
    public string Get(Guid id)
    {
        var response = _orgService.Retrieve("account", id, new ColumnSet(true));

        return response.GetAttributeValue<string>("fullname");
    }
}

Make it simple with an extension method

When you are adding to ConfigureServices in Startup.cs you often will see quick one line methods that do a bunch of work. That is done with an extension method. We can wrap all of the functionality within here into an AddCdsServiceClient extension method as well that makes it easy to add to any application as well as get the options like the connection string or including IOrganizationService or OrganizationServiceContext as registrations too.

Our extension method could look something like this:

public static void AddCdsServiceClient(this IServiceCollection services, Action<CdsServiceClientOptions> configureOptions)
{
    CdsServiceClientOptions cdsServiceClientOptions = new CdsServiceClientOptions();
    configureOptions(cdsServiceClientOptions);

    services.AddSingleton(sp =>
        new CdsServiceClientWrapper(cdsServiceClientOptions.ConnectionString));

    services.AddTransient<IOrganizationService, CdsServiceClient>(sp =>
        sp.GetService<CdsServiceClientWrapper>().CdsServiceClient.Clone());

    if (cdsServiceClientOptions.IncludeOrganizationServiceContext)
    {
        services.AddTransient(sp =>
            new OrganizationServiceContext(sp.GetService<IOrganizationService>()));
    }
}

Added here is also optional the OrganizationServiceContext as a service registration if you prefer to use LINQ based queries.

To support the options there is an options class to support pulling configuration from the appsettings.json.

public class CdsServiceClientOptions
{
    /// <summary>
    /// <see cref="CdsServiceClient"/> constructors for connection string
    /// </summary>
    public string ConnectionString { get; set; }

    /// <summary>
    /// Parameter to allow for transient OrganizationServiceContext service based on Clone
    /// of singleton CdsServiceClient.
    /// </summary>
    /// <see cref="OrganizationServiceContext"/>
    public bool IncludeOrganizationServiceContext {get; set;}
}

Then within the Startup.cs it simply becomes:

services.AddCdsServiceClient(options => Configuration.Bind("CdsServiceClient", options));

In appsettings.json are the values of the options:

"CdsServiceClient": {
    "ConnectionString": "",
    "IncludeOrganizationServiceContext": false
  }

Starting fresh then start with all this and more included in CdsWeb an ASP.NET Core 3.1 project available in the following Git repo:

https://github.com/CdsWeb-app/CdsWeb

3 thoughts on “CdsServiceClient with ASP.NET Core

  1. I was just setting up an API today on a trial instance but have failed to get the connection to work.
    Wondering what you recommend for connection string type. I have had no success with “Office365”
    “AuthType=Office365;Url=https://##.crm3.dynamics.com;Username=##@##.onmicrosoft.com;Password=****;”
    In XRM toolbox same connection string connects.

    Within CdsServiceClientWrapper after CdsServiceClient the client attributes are all blank or default values.
    Notable attributes.
    ActiveAuthenticationType: InvalidConnection
    LastCdsExcheption and LastCdsError are both empty.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s