Dependency injection extensions for Microsoft.PowerPlatform.Dataverse.Client. Registers a singleton ServiceClient and a scoped IOrganizationServiceAsync2 (via Clone()) — the correct pattern most people get wrong.
- One-line DI registration for
ServiceClientwith proper singleton + scopedClone()lifecycle - Authentication via Azure.Identity (
DefaultAzureCredentialby default, anyTokenCredentialsupported) - Automatic logger wiring from the DI container
- Options validation at startup — fail fast on misconfiguration
- Targeted at ASP.NET Core and Azure Functions
Install the package from NuGet:
dotnet add package BauerApps.Dataverse.Extensions.DependencyInjectionRegister the client in Program.cs:
builder.Services.AddDataverseClient(options =>
{
options.OrganizationUrl = new Uri("https://my-org.crm4.dynamics.com");
});Inject IOrganizationServiceAsync2 anywhere:
public class AccountsController(IOrganizationServiceAsync2 dataverse) : ControllerBase
{
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id)
{
var entity = await dataverse.RetrieveAsync("account", id,
new ColumnSet("name", "revenue"));
return Ok(entity);
}
}Configure via DataverseClientOptions:
| Option | Required | Default | Description |
|---|---|---|---|
OrganizationUrl |
✔ | — | Base URL of your Dataverse environment (e.g. https://my-org.crm4.dynamics.com) |
TokenCredential |
DefaultAzureCredential |
Custom TokenCredential for authentication. Supports any Azure.Identity credential. |
|
DeferConnection |
false |
When true, connection to Dataverse is deferred until first use. |
Default (system-assigned managed identity / local dev):
builder.Services.AddDataverseClient(options =>
{
options.OrganizationUrl = new Uri("https://my-org.crm4.dynamics.com");
});User-assigned managed identity:
builder.Services.AddDataverseClient(options =>
{
options.OrganizationUrl = new Uri("https://my-org.crm4.dynamics.com");
options.TokenCredential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = "d0f19fa6-76ef-46cb-93ac-fcde5a4a6143"
});
});Client secret:
builder.Services.AddDataverseClient(options =>
{
options.OrganizationUrl = new Uri("https://my-org.crm4.dynamics.com");
options.TokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
});Bind the options directly from a configuration section:
builder.Services.AddDataverseClient(builder.Configuration.GetSection("Dataverse"));Only non-secret values bind from configuration. Authentication uses DefaultAzureCredential by
default; to supply a custom credential, layer it on with the options pattern:
builder.Services.AddDataverseClient(builder.Configuration.GetSection("Dataverse"));
builder.Services.PostConfigure<DataverseClientOptions>(options =>
options.TokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret));ServiceClient is registered as a singleton to share the underlying connection, metadata cache, and authentication token. However, using a single instance across concurrent requests can cause issues (e.g., CallerId leaking between requests).
Clone() creates a lightweight copy that shares the parent's connection pool but is safe for per-request use. This library registers IOrganizationServiceAsync2 as scoped, so each request gets its own clone automatically.