Benevia.Core.API 0.9.16

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Benevia.Core.API --version 0.9.16
                    
NuGet\Install-Package Benevia.Core.API -Version 0.9.16
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Benevia.Core.API" Version="0.9.16" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Benevia.Core.API" Version="0.9.16" />
                    
Directory.Packages.props
<PackageReference Include="Benevia.Core.API" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Benevia.Core.API --version 0.9.16
                    
#r "nuget: Benevia.Core.API, 0.9.16"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Benevia.Core.API@0.9.16
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Benevia.Core.API&version=0.9.16
                    
Install as a Cake Addin
#tool nuget:?package=Benevia.Core.API&version=0.9.16
                    
Install as a Cake Tool

Benevia.Core.API

Introduction

Benevia.Core.API provides RESTful OData API infrastructure for exposing entities through HTTP endpoints. It automatically generates CRUD endpoints for entities decorated with [ApiEntity], handles database access via Entity Framework, and supports rich OData querying capabilities.

Use this library when you need:

  • RESTful API with minimal boilerplate
  • OData query support (filtering, sorting, expansion, pagination)
  • Multi-tenant database architecture
  • Integration with Entity Framework Core

Note: This library works standalone or integrates with Benevia.Core.Events for business logic on entities.

Getting Started

1. Install the NuGet package

dotnet add package Benevia.Core.API

2. Create your model

Entities can be simple POCOs only decorated with [ApiEntity]. Use Data Annotations to provide Entity Framework with the needed db info.

namespace MyApp.Model;

[ApiEntity]
[NaturalKey(nameof(Product.Sku))]
public partial class Product : EntityBase
{
    [Required]
    [MaxLength(20)]
    public partial string Sku { get; set; }

    public partial string Description { get; set; }

    public partial decimal Price { get; set; }

    public partial decimal StockQuantity { get; set; }
}

3. Add & use the model & endpoints

In your Program.cs:

// ...

builder.Services
    .AddMyAppModel()     //generated extension from your model
    .AddMyAppEndpoints() //generated extension from your model
    .AddCoreApi((o, t) => o.UseNpgsql(t?.ConnectionString));

// ...

app.UseCoreApi();
app.Run();

// ...

4. Set up configuration

Add tenant and authentication configuration to your appsettings.Development.json:

{
  "Tenants": {
    "Demo": {
      "ConnectionString": "Host=localhost;Database=demo_benevia_erp;Username=postgres;Password=postgres",
      "EncryptionKey": "e8a916b18c496995374f11beb0922b5231093e1c9ca0f31b34d63edafb25b10c"
    }
  },
  "JWT": {
    "Issuer": "yourcompany",
    "Audience": "yourcompany",
    "AccessTokenExpirationHours": 0.5,
    "RefreshTokenExpirationDays": 14
  }
}

JWT settings provided in API by default, but If you add your own JWT configuration section, make sure you pass it in Program.cs so it overwrites defaults:

.AddCoreApiEvents((o, t) => o.UseNpgsql(t?.ConnectionString), 
    jwtOptions: _ => builder.Configuration.GetSection("JWT").Get<JwtOptions>())

Production deployment:
For production environments, use environment variables instead of storing secrets in configuration files. The application uses .NET's IConfiguration, which automatically reads from environment variables and more. Example:

Tenants__Demo__ConnectionString="Host=prod-db;Database=erp;Username=app;Password=***"
Tenants__Demo__EncryptionKey="***"

Feel free to use a different Entity Framework database provider (such as Microsoft SQL, SQLite, etc).

5. Configure API Options (Optional)

You can customize API behavior through the ApiOptions section in your configuration:

{
  "ApiOptions": {
    "EnableLowerCamelCase": true,
    "MaxTop": 500,
    "MaxNodeCount": 100,
    "MaxExpansionDepth": 5
  }
}
Option Default Description
EnableLowerCamelCase false When true, OData JSON responses use camelCase property names (e.g., unitPrice). When false, uses PascalCase (e.g., UnitPrice).
MaxTop 500 Maximum number of items returned by a single query using $top.
MaxNodeCount 100 Maximum number of nodes allowed in a query.
MaxExpansionDepth 5 Maximum depth for $expand operations.

Note: Unlike standard ASP.NET Core JSON options, OData uses its own Entity Data Model (EDM) for serialization. The EnableLowerCamelCase option configures the EDM builder directly to ensure consistent casing across all OData responses.

Using the API

The Benevia.Core.API package automatically exposes your entities as OData RESTful endpoints. OData (Open Data Protocol) is a standardized protocol for building and consuming RESTful APIs, providing rich querying capabilities.

What is OData?

OData allows clients to:

  • Filter data with $filter (e.g., ?$filter=Price gt 100)
  • Sort results with $orderby (e.g., ?$orderby=Name desc)
  • Select specific fields with $select (e.g., ?$select=Sku,Description)
  • Expand related entities with $expand (e.g., ?$expand=Category)
  • Paginate with $top and $skip (e.g., ?$top=10&$skip=20)
  • Count results with $count (e.g., ?$count=true)

For full OData syntax, see the OData documentation.

CRUD Operations

Each entity decorated with [ApiEntity] gets a full CRUD API:

Create (POST)
POST /api/Products
Content-Type: application/json

{
  "Sku": "PROD-001",
  "Description": "Premium Widget",
  "Price": 29.99,
  "StockQuantity": 100
}
Read All (GET)
GET /api/Products
Read One (GET by Key)
GET /api/Products(123e4567-e89b-12d3-a456-426614174000)
Update (PATCH)
PATCH /api/Products(123e4567-e89b-12d3-a456-426614174000)
Content-Type: application/json

{
  "Price": 24.99,
  "StockQuantity": 85
}
Delete (DELETE)
DELETE /api/Products(123e4567-e89b-12d3-a456-426614174000)

Calling c# methods (Functions and Actions)

Methods can be created in c# and automatically be exposed to the OData API. Methods are defined in your c# class in this way:

See Core.Events documentation

Query Examples

Filter by price:

GET /api/Products?$filter=Price gt 20 and Price lt 50

Sort by SKU descending:

GET /api/Products?$orderby=Sku desc

Get only specific fields:

GET /api/Products?$select=Sku,Description,Price

Combine multiple queries:

GET /api/Products?$filter=StockQuantity gt 0&$orderby=Price&$top=10

Expand related entities (if you have navigation properties):

GET /api/Products?$expand=Category,Supplier

Count results:

GET /api/Products?$count=true&$filter=Price lt 100

Authentication

The API uses JWT (JSON Web Token) authentication for securing endpoints.

Sign In

POST /api/auth/signin
Content-Type: application/json

{
  "TenantId": "Demo",
  "Username": "myuser",
  "Password": "MyPassword@123"
}

Returns an access token and refresh token. Include the access token in the Authorization header for all API requests:

GET /api/Products
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Token Refresh

When the access token expires (default: 30 minutes), use the refresh token to get a new one:

POST /api/auth/refresh
Content-Type: application/json

{
  "RefreshToken": "your-refresh-token-here"
}

Note: Refresh tokens are one-time use only and expire after 14 days. Always use the new refresh token returned in each response.

Roles and Hierarchy

Roles support a tree structure through an optional supervisor (parent) role.

Rules:

  • A role can have at most one parent.
  • Role inheritance is transitive: assigning a parent role grants effective access from all descendant roles.
  • Cycles are rejected (A -> B -> A is invalid).
  • A role with child roles cannot be deleted until children are re-parented or removed.

Role endpoints:

  • Existing CRUD routes are unchanged.
  • GET /api/roles/tree returns the role hierarchy for role-management UI clients.

Personal Access Tokens (PAT)

For third-party integrations (for example Power BI, import jobs, CRM sync, or external AI tools) that cannot perform the interactive login flow, you can create a Personal Access Token (PAT).

Create:

POST /api/user/pats
Authorization: Bearer <interactive-user-token>
Content-Type: application/json

{
  "name": "Sales Integration",
  "expirationDays": 90,
  "responsibilityIds": [
    "22222222-2222-2222-2222-222222222222",
    "33333333-3333-3333-3333-333333333333"
  ]
}

Rules:

  • expirationDays: 1..365
  • responsibilityIds: required explicit scope for normal PATs; may be empty only for the full-access fallback case described below
  • PAT scope is fixed to selected responsibilities
  • PAT owner-scope checks use effective inherited access from role hierarchy
  • PAT secret is shown only at create/regenerate response time (not retrievable later)

Emergency fallback mode (for apps that have not set up roles/responsibilities yet):

  • If responsibilityIds is empty, Core can create a full-access fallback PAT only when:
    • owner has HasFullAccess = true, and
    • owner has zero effective roles.
  • Fallback PATs return isFullAccessFallback: true.
  • Fallback PATs auto-disable when owner later gets any role (or loses full access).
  • Legacy PAT rows with no responsibilities and isFullAccessFallback = false do not get implicit access.

PAT endpoints:

  • POST /api/user/pats
  • GET /api/user/pats
  • POST /api/user/pats/{patId}/revoke
  • POST /api/user/pats/{patId}/regenerate

Basic Authentication with PAT

Some OData clients (like Excel or PowerBI) may not support Bearer token authentication easily. For these scenarios, the API supports Basic Authentication using your PAT.

Username: pat Password: <your-pat-token>

Example header: Authorization: Basic <base64-encoded-credentials>

Where credentials are pat:your-long-jwt-token.

Request flow

  1. Token is validated (JWT or PAT).
  2. Snapshot resolver loads current user state from the database.
  3. For user-session JWTs: roles/responsibilities are resolved from current assignments (cross-request role cache uses RoleCacheExpirationMinutes).
  4. For PATs: selected PAT responsibilities are resolved and intersected with the owner's current responsibilities.
  5. Permission checks use resolved UserAccessContext and responsibility permissions (ResponsibilityAccessCache).

PAT cache behavior:

  • PAT scope cache is keyed by patId + tokenHash.
  • PAT scope cache TTL uses ResponsibilityCacheExpirationMinutes (0 disables it).
  • revoke/regenerate invalidates PAT cache immediately.

PAT onboarding checklist (non-ERP teams)

  1. Define responsibilities in BL.
  2. Ensure responsibility synchronization runs at startup.
  3. Create roles and assign responsibilities.
  4. Assign roles to users.
  5. Create scoped PATs by selecting explicit responsibilities.

Integration Testing

Benevia.Core.API works seamlessly with ASP.NET Core's WebApplicationFactory for integration testing. This allows you to test your API endpoints with a real HTTP client and database.

What's happening:

  1. WebApplicationFactory<Program> creates a test server hosting your application
  2. CreateClient() provides an HTTP client configured to send requests to the test server
  3. Requests are processed through the full API pipeline (routing, controllers, OData, database)
  4. You can use an in-memory database or test database for isolation
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net.Http.Json;
using Xunit;

public class ProductApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ProductApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
        await AuthenticateClient(_client);
    }
    
    [Fact]
    public async Task CreateProduct_ReturnsCreatedProduct()
    {
        // Arrange
        var product = new { Name = "Test Product", Price = 19.99 };
        
        // Act
        var response = await _client.PostAsJsonAsync("/api/Products", product);
        
        // Assert
        response.EnsureSuccessStatusCode();
        var created = await response.Content.ReadFromJsonAsync<Product>();
        Assert.Equal("Test Product", created.Name);
    }
}

More Info

For more information about the architecture of Benevia.Core.API, see ./ARCHITECTURE.md.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (8)

Showing the top 5 NuGet packages that depend on Benevia.Core.API:

Package Downloads
Benevia.Core.DataGenerator

Benevia Core DataGenerator library for generating test and sample data.

Benevia.Core.API.Events

Benevia Core API Events integration library combining API features with event-driven architecture.

Benevia.Core.Blobs

Benevia Core Blobs library with Azure Blob Storage integration for file and blob management.

Benevia.Core.API.Postgres

Benevia Core API PostgreSQL integration library combining API features with PostgreSQL database support.

Benevia.Core.API.Workflows

Benevia Core API Workflows library for workflow and business process automation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.17-ci.136 0 5/13/2026
0.9.17-ci.135 0 5/13/2026
0.9.17-ci.134 35 5/12/2026
0.9.17-ci.133 54 5/11/2026
0.9.17-ci.132 127 5/11/2026
0.9.17-ci.131 63 5/11/2026
0.9.17-ci.130 76 5/11/2026
0.9.17-ci.129 66 5/10/2026
0.9.17-ci.128 77 5/9/2026
0.9.17-ci.127 72 5/9/2026
0.9.17-ci.126 133 5/8/2026
0.9.17-ci.125 84 5/7/2026
0.9.17-ci.124 145 5/7/2026
0.9.17-ci.123 113 5/6/2026
0.9.17-ci.122 82 5/6/2026
0.9.17-ci.121 115 5/6/2026
0.9.17-ci.120 93 5/5/2026
0.9.17-ci.119 84 5/5/2026
0.9.17-ci.118 84 5/5/2026
0.9.16 248 4/28/2026
Loading failed