Skip to main content

API Builder Runtime

The API Builder Runtime is the execution engine that hosts and serves your configured APIs. It's implemented as an ASP.NET Core application that reads your API Builder configuration from Dataverse and exposes it as REST endpoints.

Runtime Architecture

┌───────────────────────────────────────────────────────────────────────┐
│ API Builder RUNTIME ARCHITECTURE │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────────────────────────┐ │
│ │ Client │ │ API Builder Runtime │ │
│ │ Application │ │ │ │
│ └──────┬──────┘ │ ┌─────────────────────────────┐ │ │
│ │ │ │ ASP.NET Core Host │ │ │
│ │ HTTP Request │ │ │ │ │
│ ▼ │ │ ┌───────────────────────┐ │ │ │
│ ┌─────────────┐ │ │ │ REST Server │ │ │ │
│ │ Gateway │───────►│ │ │ (Flowon.Xrm.Api) │ │ │ │
│ │ (Optional) │ │ │ └───────────┬───────────┘ │ │ │
│ └─────────────┘ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌───────────────────────┐ │ │ │
│ │ │ │ Metadata Cache │ │ │ │
│ │ │ │ (API Configuration) │ │ │ │
│ │ │ └───────────┬───────────┘ │ │ │
│ │ │ │ │ │ │
│ │ └──────────────┼──────────────┘ │ │
│ │ │ │ │
│ └─────────────────┼───────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Dataverse / Dataverse │ │
│ │ │ │
│ │ ┌───────────┐ ┌─────────────┐ │ │
│ │ │ Flowon │ │ Business │ │ │
│ │ │ Config │ │ Data │ │ │
│ │ └───────────┘ └─────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘

Hosting Project Structure

A API Builder Runtime project is a standard ASP.NET Core application with the Flowon packages installed.

Required NuGet Packages

<PackageReference Include="Flowon.Xrm.Api.AspNetCore" Version="x.x.x" />

Project Structure

MyCompany.Api/
├── Program.cs # Application entry point
├── Bootstrapper.cs # Service configuration
├── UserResolver.cs # Custom user resolution
├── appsettings.json # Base configuration
├── appsettings.Development.json # Development overrides
└── appsettings.ext.json # External configuration (secrets)

Bootstrapper Configuration

The Bootstrapper class configures all services required for the API Builder Runtime:

using Flowon.Xrm.Api.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Authentication.JwtBearer;

public static class Bootstrapper
{
public static void Run(string apiDisplayName, string projectName, string apiName)
{
var builder = WebApplication.CreateBuilder();

// Load configuration from multiple sources
builder.Configuration
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.ext.json", optional: true);

builder.Services.AddControllers();

// Configure JWT authentication
ConfigureAuthentication(builder);

// Get Dataverse connection string
var dataverseConnectionString = builder.Configuration["Dynamics365ConnectionString"];

// Configure API Builder Runtime
var flowonApiBuilder = builder.Services
.AddFlowOnApi(
projectName,
apiName,
sp => new ServiceClient(dataverseConnectionString))
.AddRestServer(o =>
{
if (builder.Environment.IsDevelopment())
{
o.VersioningStrategy = new DraftVersionApiVersioningStrategy();
}
else
{
o.VersioningStrategy = new LatestVersionApiVersioningStrategy();
}
})
.AddUserResolver<UserResolver>();

// Enable metadata auto-refresh in development
if (builder.Environment.IsDevelopment())
{
flowonApiBuilder.AddMetadataWatcher(c => c.TimeIntervalInMinutes = 3);
}

// Build and configure the application
var app = builder.Build();

app
.UseHttpsRedirection()
.UseCors("CorsPolicy")
.UseAuthentication()
.UseRouting()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
endpoints.UseRestServer();
});

app.Run();
}
}

Key Configuration Components

AddFlowOnApi

The AddFlowOnApi method registers the API Builder services:

builder.Services.AddFlowOnApi(
projectName, // Flowon Project name
apiName, // API Builder name
serviceClientFactory // Factory for Dataverse connection
);
ParameterDescription
projectNameThe name of the Flowon Project containing the API
apiNameThe name of the API Builder to host
serviceClientFactoryFunc<IServiceProvider, IOrganizationServiceAsync2> that creates the Dataverse service client

AddFlowOnApi returns a FlowOnApiBuilder; the AddRestServer, AddUserResolver, and AddMetadataWatcher calls chain off it. There are overloads that take just (projectName, apiName), an Action<FlowOnApiServerOptions>, or the service-client factory shown above.

AddRestServer

The AddRestServer method configures the REST server. It emits the generated controllers and mounts them via endpoints.UseRestServer() (which calls MapControllers). The generated routes are rooted at the host — the library adds no /api/ prefix; add one yourself with a reverse proxy or UsePathBase if you need it.

.AddRestServer(options =>
{
options.VersioningStrategy = new LatestVersionApiVersioningStrategy();
});
GraphQL (experimental)

A GraphQL surface (AddGraphQLServer() / endpoints.UseGraphQLServer(), built on HotChocolate and served at /graphql) is present in the Flowon.Xrm.Api.AspNetCore.GraphQL package. In the current build its schema wiring is not enabled, so treat GraphQL as experimental/preview. REST is the supported runtime surface.

Versioning Strategies

StrategyDescriptionUse Case
DraftVersionApiVersioningStrategyUses the Draft versionDevelopment and testing
LatestVersionApiVersioningStrategyUses the latest published version (default)Production deployments
ExactVersionApiVersioningStrategyUses a specific version (set its ApiVersion property)Pinned deployments

All strategies derive from the abstract ApiVersioningStrategy. LatestVersionApiVersioningStrategy is the default when AddRestServer is called without setting a strategy. Metadata auto-refresh only works with strategies whose SupportsMetadataWatching is true (Draft and Latest); ExactVersionApiVersioningStrategy does not support watching.

AddUserResolver

The AddUserResolver method registers a custom user resolver:

.AddUserResolver<UserResolver>();

A user resolver implements IWebUserResolver. It receives the ASP.NET ClaimsPrincipal (populated by the host's authentication) and returns an IWebUser, populating the user's properties and role identifiers from the token claims:

using System.Security.Claims;
using System.Threading.Tasks;
using FlowOn.Xrm.Api;

public class UserResolver : IWebUserResolver
{
public Task<IWebUser> ResolveUser(IWebUserType webUserType, ClaimsPrincipal claimsPrincipal)
{
var isAuthenticated = claimsPrincipal.Identity?.IsAuthenticated ?? false;

// Map claims into user properties and role identifiers.
var roles = claimsPrincipal.FindAll(ClaimTypes.Role).Select(c => c.Value);

// webUserType.New(...) builds the IWebUser exposed on the execution context.
var user = webUserType.New(isAuthenticated /*, properties, roles */);

return Task.FromResult(user);
}
}

The default resolver (DefaultWebUserResolver) simply marks the user authenticated or not; register a custom resolver whenever your authorization policies rely on user properties or roles. The resolved user is exposed on the execution context as context.User (IsAuthenticated, IsRegistered, Roles, UserType). Roles here are application-defined string identifiers you derive from the principal's claims — they are not automatically mapped from Dataverse security roles.

AddMetadataWatcher

The AddMetadataWatcher enables automatic metadata refresh:

.AddMetadataWatcher(config => 
{
config.TimeIntervalInMinutes = 3; // Check for changes every 3 minutes
});

This is useful during development to pick up configuration changes without restarting the application.

Application Configuration

appsettings.json

{
"FLOWON_PROJECT_NAME": "CustomerPortal",
"FLOWON_API_NAME": "CustomerPortalAPI",
"FLOWON_API_DISPLAY_NAME": "Customer Portal API",
"FLOWON_URL_PATH": "/customer-portal",
"Dynamics365ConnectionString": "AuthType=ClientSecret;Url=https://org.crm.dynamics.com;ClientId=xxx;ClientSecret=xxx",
"TokenOptions": {
"Issuer": "https://identity.company.com",
"Audience": "customer-portal-api",
"SymmetricSigningKey": "base64-encoded-key"
}
}

Environment Variables

VariableDescription
FLOWON_PROJECT_NAMEFlowon Project name
FLOWON_API_NAMEAPI Builder name
FLOWON_API_DISPLAY_NAMEDisplay name for Swagger
FLOWON_URL_PATHBase URL path for the API
Dynamics365ConnectionStringDataverse connection string

Authentication Configuration

API Builder Runtime supports JWT Bearer authentication:

builder.Services.AddAuthentication(cfg =>
{
cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience
};
});

Swagger / OpenAPI Integration

In development, the runtime automatically generates OpenAPI documentation:

if (builder.Environment.IsDevelopment())
{
builder.Services.AddEndpointsApiExplorer().AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo()
{
Version = "v1",
Title = "Customer Portal API (Development)"
});

// Add Bearer token authentication to Swagger UI
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer"
});
});
}

Access Swagger UI at: https://localhost:port/swagger

Observability

API Builder Runtime supports OpenTelemetry for distributed tracing and metrics:

builder.Services
.AddOpenTelemetry()
.ConfigureResource(resource =>
{
resource
.AddService(serviceName)
.AddAttributes(new Dictionary<string, object>
{
["deployment.environment"] = builder.Environment.EnvironmentName,
["k8s.namespace.name"] = Environment.GetEnvironmentVariable("NAMESPACE"),
["k8s.pod.name"] = Environment.GetEnvironmentVariable("HOSTNAME")
});
})
.WithTracing(builder =>
builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter())
.WithMetrics(builder =>
builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter());

Deployment Considerations

Container Deployment

API Builder Runtime is typically deployed as a container:

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["MyCompany.Api/MyCompany.Api.csproj", "MyCompany.Api/"]
RUN dotnet restore "MyCompany.Api/MyCompany.Api.csproj"
COPY . .
WORKDIR "/src/MyCompany.Api"
RUN dotnet build -c Release -o /app/build

FROM build AS publish
RUN dotnet publish -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyCompany.Api.dll"]

Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
name: customer-portal-api
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: myregistry/customer-portal-api:latest
env:
- name: FLOWON_PROJECT_NAME
value: "CustomerPortal"
- name: FLOWON_API_NAME
value: "CustomerPortalAPI"
- name: Dynamics365ConnectionString
valueFrom:
secretKeyRef:
name: api-secrets
key: dynamics365-connection-string
- name: ASPNETCORE_ENVIRONMENT
value: "Production"

Health Checks

Add health check endpoints for Kubernetes liveness and readiness probes:

builder.Services.AddHealthChecks()
.AddCheck("dynamics365", new DataverseHealthCheck(connectionString));

app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false // Liveness: just check if app is running
});

app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});