Fly.Shared.Core 1.9.0

dotnet add package Fly.Shared.Core --version 1.9.0
                    
NuGet\Install-Package Fly.Shared.Core -Version 1.9.0
                    
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="Fly.Shared.Core" Version="1.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fly.Shared.Core" Version="1.9.0" />
                    
Directory.Packages.props
<PackageReference Include="Fly.Shared.Core" />
                    
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 Fly.Shared.Core --version 1.9.0
                    
#r "nuget: Fly.Shared.Core, 1.9.0"
                    
#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 Fly.Shared.Core@1.9.0
                    
#: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=Fly.Shared.Core&version=1.9.0
                    
Install as a Cake Addin
#tool nuget:?package=Fly.Shared.Core&version=1.9.0
                    
Install as a Cake Tool

Fly.Shared.Core

Core shared library for building FlyOS-compatible business app microservices on .NET 9.

Installation

<PackageReference Include="Fly.Shared.Core" Version="1.*" />

What's Included

Module Namespace What It Provides
Multi-tenancy Fly.Shared.Core.MultiTenancy ITenantContext, TenantContext, TenantEntity, AuditableEntity, TenantMiddleware, MultiTenantDbContext with automatic global query filters
Authentication Fly.Shared.Core.Auth ClaimsExtensions for extracting tenant/user from JWT, FlyAuthorizeAttribute for Cerbos-based authorization
Caching Fly.Shared.Core.Caching IFlyCache / RedisFlyCache — Redis-backed cache with consistent key conventions
i18n Fly.Shared.Core.I18n TranslatableEntity<T> base class for multilingual entities
Middleware Fly.Shared.Core.Middleware CorrelationIdMiddleware — injects/propagates X-Correlation-ID on every request
API Models Fly.Shared.Core.Models ApiResponse<T>, ApiResponse, ApiError, PagedRequest — standard envelope types
Swagger / OpenAPI Fly.Shared.Core.Swagger IncludeFlyOpenApiXmlComments() — registers entry-assembly + every Fly.Shared.*.xml doc-comments file with Swashbuckle. ApiDocsManifest record — declares OpenAPI document location for the gateway aggregator (sent on POST /api/apps/{appId}/onboard). ApiDocsPathValidator.IsValid(path, out reason) — shared SSRF-safe path validator with fixed-point decode loop; used by Controller onboarding (write-time) and Gateway upstream proxy (fetch-time defence-in-depth).

Quick Start

Service Registration

var builder = WebApplication.CreateBuilder(args);

// Registers TenantContext, IFlyCache (Redis), IHttpContextAccessor, MemoryCache
builder.Services.AddFlyCore();

// Add your DbContext using the shared multi-tenant base
builder.Services.AddDbContext<MyAppDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));

var app = builder.Build();

// Resolves tenant from JWT sub-claim and populates ITenantContext
app.UseMiddleware<TenantMiddleware>();
app.UseMiddleware<CorrelationIdMiddleware>();
app.MapControllers();
app.Run();

Multi-Tenant Entity

using Fly.Shared.Core.MultiTenancy;

public class Project : TenantEntity
{
    public string Name { get; set; } = string.Empty;
    public bool IsActive { get; set; } = true;
}

Multi-Tenant DbContext

using Fly.Shared.Core.MultiTenancy;

public class MyAppDbContext : MultiTenantDbContext
{
    public MyAppDbContext(DbContextOptions<MyAppDbContext> options, ITenantContext tenant)
        : base(options, tenant) { }

    public DbSet<Project> Projects => Set<Project>();
}

MultiTenantDbContext automatically applies a global query filter e.TenantId == currentTenantId on all TenantEntity sets, and stamps TenantId on SaveChanges.

API Response Envelope

[ApiController]
[Route("api/projects")]
public class ProjectsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAll()
    {
        var projects = _service.GetAll();
        return Ok(ApiResponse<List<ProjectDto>>.Ok(projects));
    }

    [HttpPost]
    public IActionResult Create(CreateProjectRequest req)
    {
        if (!ModelState.IsValid)
            return BadRequest(ApiResponse.Fail("VALIDATION_ERROR", "Invalid input"));

        var project = _service.Create(req);
        return Created($"api/projects/{project.Id}", ApiResponse<ProjectDto>.Ok(project));
    }
}

Caching

public class ProjectService
{
    private readonly IFlyCache _cache;

    public async Task<Project?> GetByIdAsync(Guid id)
    {
        return await _cache.GetOrSetAsync(
            $"project:{id}",
            () => _db.Projects.FindAsync(id).AsTask(),
            TimeSpan.FromMinutes(15));
    }
}

Swagger / OpenAPI

AddFlyApiDocs is the bundled "good defaults" registration — single call, replaces the SwaggerDoc / XML / Bearer-security boilerplate every backend would otherwise repeat:

using Fly.Shared.Core.Swagger;

builder.Services.AddFlyApiDocs(opts =>
{
    opts.Title = "My API";              // optional; defaults to entry-assembly name
    opts.Description = "Optional summary shown in Swagger UI";
});

What that one call does:

  • registers SwaggerDoc(opts.Version, ...) with OpenApiInfo populated from Title / Version / Description;
  • calls IncludeFlyOpenApiXmlComments() so the entry-assembly XML and every Fly.Shared.*.xml next to the binary are picked up;
  • when AddSecurityScheme = true (default) registers a Bearer JWT security definition + global requirement so the Swagger UI Authorize dialog accepts a token;
  • finally invokes opts.PostConfigure?.Invoke(swaggerGenOptions) so callers can layer on tags, schema filters, OAuth2 flows, or anything else without giving up the bundled defaults.

For an explicitly anonymous service (e.g. a public landing doc) opt out of the Bearer scheme:

builder.Services.AddFlyApiDocs(opts => opts.AddSecurityScheme = false);

Lower-level access remains available when something more bespoke is needed:

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new() { Title = "My API", Version = "v1" });
    c.IncludeFlyOpenApiXmlComments();
});

The consuming project must enable <GenerateDocumentationFile>true</GenerateDocumentationFile> and <CopyDocumentationFileToOutputDirectory>true</CopyDocumentationFileToOutputDirectory> in its csproj for the entry-assembly XML to exist at runtime.

Declaring your OpenAPI document for the gateway aggregator

Build an ApiDocsManifest and pass it on the trailing ApiDocs parameter of AppOnboardingRequest when your service onboards. The Controller persists it next to your gateway route hints; the gateway-side aggregator reads it on its next poll cycle to populate the unified Swagger UI dropdown.

using Fly.Shared.Core.Swagger;

// AppOnboardingRequest has many positional parameters; use named args so this
// snippet compiles as written. Pass null for any sections you don't reconcile
// in this onboarding call.
var manifest = new AppOnboardingRequest(
    Permissions: null,
    Roles: null,
    NotificationTemplates: null,
    ServiceAccess: null,
    GatewayRoutes: null,
    HelpArticles: null,
    ApiDocs: new ApiDocsManifest(
        Path: "/swagger/v1/swagger.json",
        Title: "Circles API",
        Version: "1.0.0",
        RequiredScopes: new() { "fly-api" }));

To remove a previously published descriptor, send new ApiDocsManifest("") — the empty-path tombstone sentinel.

The Swagger/ namespace will grow as the gateway aggregator and richer Swashbuckle wiring land in upcoming releases.

Scoped inter-service calls

InterServiceTokenHandler (registered automatically by AddFlyServiceClient) attaches a cached client_credentials Bearer token to every outbound request. By default the token is acquired with the catch-all scope inter-service and cached per client_id. Callers that need a different scope set on a single call attach a hint to the HttpRequestMessage:

using Fly.Shared.Core.Http;

var req = new HttpRequestMessage(HttpMethod.Get, "https://controller/api/something");
req.WithInterServiceScopes("inter-service", "circles");
using var resp = await flyClient.HttpClient.SendAsync(req, ct);

The handler:

  • normalises the supplied set (deduplicated, case-insensitive, sorted) so ["A", "b"] and ["b", "A"] reach the same cache slot;
  • keys the token cache as fly:inter-service-token:{clientId}:{sortedScopes} so a default-scope token and a custom-scope token never overwrite each other;
  • requests the joined scope string from OpenIddict on cache miss, falling back to "inter-service" when no hint is set.

The stored OpenIddict client must already have been granted every requested scope — otherwise the token endpoint returns OpenIddict ID2051 and the call fails.

Configuration

Add to appsettings.json:

{
  "Redis": {
    "ConnectionString": "localhost:6379"
  }
}

Dependencies

  • Microsoft.AspNetCore.App (framework reference)
  • Microsoft.EntityFrameworkCore 9.x
  • Microsoft.EntityFrameworkCore.Relational 9.x
  • Microsoft.Extensions.Caching.StackExchangeRedis 9.x
  • StackExchange.Redis 2.x
  • FluentValidation.AspNetCore 11.x
  • OpenTelemetry.Extensions.Hosting 1.x
  • OpenTelemetry.Instrumentation.AspNetCore 1.x
  • Fly.Shared.Messaging — MassTransit/RabbitMQ wiring and platform event contracts. Depends on this package.
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 (1)

Showing the top 1 NuGet packages that depend on Fly.Shared.Core:

Package Downloads
Fly.Shared.Messaging

MassTransit/RabbitMQ wiring and platform event/command contracts for FlyOS business app integration. Provides AddFlyMessaging() extension, exponential retry, in-memory outbox, and all platform event records (TenantCreated, UserCreated, LicenseUpdated, etc.).

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.9.0 96 5/20/2026
1.0.5 119 4/29/2026
1.0.2 109 4/28/2026
1.0.0 123 4/1/2026