Configuring your APP
If you created your app with the Benevia solution template (see Quick start), then this work is probably done for you already.
Adding packages
//TODO: Update this documentation. Maybe eliminate the "Recommended packages" and "Optional packages" and put the info in the command line in "Installing NuGet packages" and add comments for the description.
Recommended Packages
| Package | Purpose |
|---|---|
| Benevia.Core.API.Events | Complete API + Events integration (recommended) |
| Benevia.Core.Postgres | Automatic database migration tool for PostgreSQL |
| Benevia.Core.Annotations | Entity annotations to configure your API and Events. |
| Benevia.Core.Events.DataTypes | Built-in data types and business logic |
| Benevia.Core.API | RESTful OData API infrastructure only |
| Benevia.Core.Events | Event-driven business logic framework only |
Optional Add-ons
Additional packages are available for specific features:
- Benevia.Core.API.Workflows - Multi-step workflow functionality
- Benevia.Core.API.Postgres - PostgreSQL full-text search via OData $search
- Benevia.Core.Contacts - Contact/address entities and business logic
- Benevia.Core.Blobs - Blob storage (Azure/Local)
- Benevia.Core.DataGenerator.LargeData - Generate blank, demo, or other types of seed data
Installing NuGet Packages
You'll need to add the private NuGet source:
- Verify access to the repo: Benevia NuGet
- Get a classic PAT from GitHub settings with
read:packagesscope. - Run this command to add the NuGet source:
dotnet nuget add source --username YourGithubUsername --password GithubPAT --store-password-in-clear-text --name github-packages "https://nuget.pkg.github.com/beneviasoftware/index.json"
Then install the packages:
dotnet add package Benevia.Core.API.Events
dotnet add package Benevia.Core.Postgres
dotnet add package Benevia.Core.Events.DataTypes
Direct dependency injection container use
Core references only DI abstractions. Projects that call BuildServiceProvider, including the standalone Events example below,
need the full container package:
dotnet add package Microsoft.Extensions.DependencyInjection --version 10.0.5
Shared loopback address helper
Benevia.Core.LoopbackAddress.FromServerAddresses(addresses) selects an HTTP listening address when available,
otherwise the first address, and replaces wildcard hosts with loopback hosts. It returns null for no addresses.
Pass the server's listening addresses; callers own any configured override, fallback and PathBase handling.
Register services
We heavily use .NET's built in Dependency Injection container IServiceCollection/IServiceProvider. There are various extension methods to add the required services, and a few initialization extensions to use the libraries.
In your Program.cs (example is an ASP.NET app):
var builder = WebApplication.CreateBuilder(args);
builder
.AddCoreOpenTelemetry()
.AddErpBlobs()
.AddCorsFromConfig();
builder.Services
.AddCorePostgres()
.AddCoreDataGeneration()
.AddMyAppModel() //generated extension from your model project named 'MyAppModel'.
.AddMyAppBL() //generated extension from your business logic project named 'MyAppBL'.
//.AddMyAppBL2() //Each business logic project must have its logic service registered.
.AddMyAppEndpoints() //generated extension from your API project named 'MyApp'.
.AddCoreApiEvents((o, t) => o.UseNpgsql(t?.ConnectionString));
//Optional services
builder.Services
.AddCoreLargeData()
.AddCoreContacts()
.AddCoreApiWorkflows()
.AddPostgresFullTextSearch();
var app = builder.Build();
app.UseCoreTracing();
app.Logger.LogInformation("Starting app...");
app.Services.UseBusinessLogicEvents();
app.UseCors("AllowClientApp");
app.MapHealthChecks("/status", CustomHealthResponseWriter.healthCheckOptions);
var tenants = app.Services
.GetAllTenantsMapped(t => new Tenant(t.Id, t.ConnectionString));
await app.Services.UseCorePostgres(tenants);
await app.Services.InitializeDataGeneratorAsync();
app.UseCoreApi();
app.Run();
Configure your tenant
Add tenant configuration to your appsettings.Development.json:
{
"Tenants": {
"Demo": {
"ConnectionString": "Host=localhost;Database=myapp_db;Username=postgres;Password=postgres",
"EncryptionKey": "e8a916b18c496995374f11beb0922b5231093e1c9ca0f31b34d63edafb25b10c",
"AdminUsername": "admin",
"AdminPassword": "Admin@123"
}
}
}
Production deployment:
IMPORTANT: For production environments, use environment variables instead of storing secrets in configuration files:
Tenants__Demo__ConnectionString="Host=prod-db;Database=myapp;Username=app;Password=***"
Tenants__Demo__EncryptionKey="***"
Configure account emails and sign-in security
Core sends invitation and password reset emails through Azure Communication Services. Add an EmailSettings section to your configuration:
{
"EmailSettings": {
"ConnectionString": "endpoint=https://...;accesskey=...",
"FromAddress": "no-reply@example.com",
"InviteBaseUrl": "https://app.example.com/accept-invite",
"ResetBaseUrl": "https://app.example.com/reset-password",
"InvitationExpirationHours": 24,
"PasswordResetCooldownMinutes": 5,
"QueueCapacity": 200
}
}
| Key | Default | Purpose |
|---|---|---|
ConnectionString or Endpoint |
none | Azure Communication Services connection. Without it (or without FromAddress) no email is sent. |
FromAddress |
none | Sender address. |
InviteBaseUrl |
none | Page that accepts an invitation. Core appends ?tenantId=...&code=.... |
ResetBaseUrl |
none | Page that resets a password. Core appends ?tenantId=...&code=.... |
InvitationExpirationHours |
24 | How long an invitation link stays valid. |
PasswordResetCooldownMinutes |
5 | Minutes during which an unused reset link blocks further links for the same account. Requests inside the window get the usual reply but send no email. Set 0 to disable. |
QueueCapacity |
200 | Size of the in-process email queue. |
Sign-in locks an account after 5 failed password attempts for 15 minutes and answers HTTP 429 while locked. Override the policy after AddCoreApiEvents:
builder.Services.PostConfigure<IdentityOptions>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 3;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
});
What happens on startup?
- Business logic is loaded and registered
- Database schema is automatically created or upgraded to match your entities
- API endpoints are configured for all
[ApiEntity]types - Server starts and is ready to handle requests
Automatic Database Migrations: The
Benevia.Core.Postgrespackage compares your entity model with the actual database schema and automatically applies changes (add/remove tables, columns, indexes, etc.) on startup. No manual migrations needed!
Test and use your API
Your entities are now automatically exposed via OData endpoints. See the OData documentation for more details.
See Using the API
Using parts of the Benevia platform
Using Events Without API
If you need business logic without the API layer (e.g., client apps, batch processing):
dotnet add package Benevia.Core.Events
In your Program.cs:
var serviceCollection = new ServiceCollection();
serviceCollection
.AddMyAppBL() //generated extension from your model
.AddCoreEvents(o => o.UseInMemory());
var services = serviceCollection.BuildServiceProvider();
services.UseBusinessLogicEvents();
Using API Without Events
If you only need a simple CRUD API:
dotnet add package Benevia.Core.API
In your Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMyAppModel() //generated extension from your model
.AddMyAppEndpoints() //generated extension from your model
.AddCoreApi((o, t) => o.UseNpgsql(t?.ConnectionString));
var app = builder.Build();
app.UseCoreApi();
app.Run();
Set up Claude Code skills
The Benevia.Core package ships a set of Claude Code skills that teach the AI assistant how to use Benevia Core correctly. Once installed in your repository, Claude Code auto-loads the right skill when you write Core code, and the index skill /benevia-core lists the full documentation map.
Installation is opt-in: the package does not touch your repository until you set the install flag.
Enable the install
Add this property to your project. For a single-project app, put it in the project's <PropertyGroup>; for multi-project solutions, put it in Directory.Build.props at the repo root so it applies to every project:
<PropertyGroup>
<BeneviaInstallClaudeSkills>true</BeneviaInstallClaudeSkills>
</PropertyGroup>
Then build your solution normally:
dotnet build YourSolution.sln
The build copies seven skill folders under <repo-root>\.claude\skills\ (plus a small .benevia-core.stamp marker the installer uses to short-circuit subsequent builds). The repo root is detected as the git repository root — the nearest ancestor directory containing .git; set BeneviaClaudeSkillsRoot to override it. If no git root is found, the install is skipped with a build warning:
| Skill | When it activates |
|---|---|
benevia-core |
Documentation map. Invoke manually with /benevia-core. |
benevia-core-entity-model |
Defining entities, properties, and relationships |
benevia-core-events |
Writing [Logic] classes and event subscribers |
benevia-core-rbac |
Defining responsibilities and permissions |
benevia-core-odata-api |
Calling or querying the OData API |
benevia-core-database-upgrade |
Writing data migrations on schema changes |
benevia-core-client |
Building Blazor pages with metadata-driven UI |
Verify
The build log should show:
Benevia: installed Claude Code skills into <repo-root>\.claude\skills