A lightweight, strongly-typed client for the Dynamics 365 Business Central OData API.
- Typed OData querying with fluent filter composition
- Built-in OAuth2 client credentials authentication
- Automatic token caching and refresh
- Fluent paging, ordering and selection helpers
- Clean DI integration
- No runtime dependencies beyond HttpClient and System.Text.Json
dotnet add package Dynamics365.BusinessCentralservices.AddBusinessCentral(options =>
{
options.TenantId = "your-tenant-id";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.Company = "CRONUS AG";
options.BaseUrl = "https://api.businesscentral.dynamics.com/v2.0/{tenant}/Production/ODataV4";
options.TokenEndpoint = "https://login.microsoftonline.com/{TenantId}/oauth2/v2.0/token";
options.Scope = "https://api.businesscentral.dynamics.com/.default";
});
public class MyService
{
private readonly IBusinessCentralClient _client;
public MyService(IBusinessCentralClient client)
{
_client = client;
}
}Simple Query
var orders = await client.QueryAsync<Order>("salesOrders");With Filters
var filter =
Filter.Equals("Status", "Open")
.And(Filter.GreaterThan("Amount", 100));
var orders = await client.QueryAsync<Order>("salesOrders", filter);Paging and Ordering
var orders = await client.QueryAsync<Order>(
"salesOrders",
Filter.Equals("Status", "Open"),
o => o.WithTop(100).OrderByAsc("No"));Query All
var allOrders = await client.QueryAllAsync<Order>("salesOrders");Raw Query
var raw = await client.QueryRawAsync<JsonElement>("salesOrders?$top=5");Patch
await client.PatchAsync(
"salesOrders",
"No='1000'",
new { Status = "Released" });| Method | Expression |
|---|---|
Filter.Equals |
field eq value |
Filter.NotEquals |
field ne value |
Filter.GreaterThan |
field gt value |
Filter.GreaterOrEqual |
field ge value |
Filter.LessThan |
field lt value |
Filter.LessOrEqual |
field le value |
Filter.Contains |
contains(field,value) |
Filter.StartsWith |
startswith(field,value) |
Filter.EndsWith |
endswith(field,value) |
Filter.In |
field in (...) |
Filter.IsNull |
field eq null |
Filter.IsNotNull |
field ne null |
public static class SmokeTestEndpoints
{
public static void MapSmokeTests(this WebApplication app)
{
app.MapGet("/bc/orders", async (IBusinessCentralClient client) =>
{
var orders = await client.QueryAsync<dynamic>(
"LDATProductionOrd1er",
Filter.Equals("Status", "Released"),
o => o.WithTop(5));
return Results.Ok(orders);
});
app.MapGet("/bc/orders/all", async (IBusinessCentralClient client) =>
{
var orders = await client.QueryAllAsync<dynamic>("salesOrders");
return Results.Ok(orders.Count);
});
}
}