### Start Docker Compose Environment Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Navigate to the compose directory and start the services using Docker Compose. Ensure Docker and the Compose plugin are installed. ```sh cd backend/dev/compose docker compose up ``` -------------------------------- ### Install lncdproj .NET Template Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Navigate to the cloned exampleapp directory and install the lncdproj .NET template. ```sh dotnet new install . ``` -------------------------------- ### Build CoreLibrary Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/README.md Use this command to build the CoreLibrary project. No specific setup is required beyond having the .NET SDK installed. ```sh dotnet build ``` -------------------------------- ### Clone LeanCode CoreLibrary Example App Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Clone the repository containing the lncdproj template to begin your project setup. ```sh git clone https://github.com/leancodepl/exampleapp.git ``` -------------------------------- ### Start API with Tilt Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Initiate the application API using the tilt up command with the projectname-api target. The API will be available at https://projectname.local.lncd.pl/api. ```sh tilt up projectname-api ``` -------------------------------- ### Configure Integration Test Environment Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/tests/integration_tests/index.md Sets up the test environment for an example application, including database configuration, authentication, and logging. Ensure environment variables like WAIT_FOR_DEBUGGER are set. ```csharp public class ExampleAppTestApp : LeanCodeTestFactory { public readonly Guid SuperAdminId = Guid.Parse("4d3b45e6-a2c1-4d6a-9e23-94e0d9f8ca01"); // Ensure that you add these variables to your environment // variables before executing the tests. protected override ConfigurationOverrides Configuration { get; } = new(connectionStringBase: "Database__ConnectionStringBase", connectionStringKey: "Database:ConnectionString"); static ExampleAppTestApp() { if (!string.IsNullOrWhiteSpace( Environment.GetEnvironmentVariable("WAIT_FOR_DEBUGGER"))) { Console.WriteLine("Waiting for debugger to be attached..."); while (!Debugger.IsAttached) { Thread.Sleep(100); } } } protected override IEnumerable GetTestAssemblies() { yield return typeof(ExampleAppTestApp).Assembly; } protected override IHostBuilder CreateHostBuilder() { // The `BuildMinimalHost` method includes environment // variables as part of the configuration and configures // Kestrel as the web server. Ensure that you pass your // API's `Startup` class for proper functionality. return LeanProgram .BuildMinimalHost() .ConfigureDefaultLogging( "ExampleApp-tests", new[] { typeof(Program).Assembly }) .UseEnvironment(Environments.Development); } protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); // Remember to set correct content root path. This is the easiest option. builder.UseSolutionRelativeContentRoot( "tests/ExampleApp.IntegrationTests/ExampleApp.IntegrationTests.csproj"); builder.ConfigureServices(services => { // Incorporate a hosted service responsible for creating // and dropping the database after each test execution. services.AddHostedService>(); // `AddTestAuthenticationHandler` is an extension method from // the `TestAuthenticationHandler` defined further below. services.AddAuthentication(TestAuthenticationHandler.SchemeName) .AddTestAuthenticationHandler(); }); } } ``` -------------------------------- ### API for Generated ID (RawGuid Example) Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/id/index.md Illustrates the common API members available for generated IDs, such as parsing, creation, and checking for emptiness. This example is for a RawGuid-based ID. ```csharp public readonly partial record struct ID : IEquatable, IComparable, ISpanFormattable, IUtf8SpanFormattable, ISpanParsable, IEqualityOperators { public static readonly ID Empty; public Guid Value { get; } public bool IsEmpty { get; } // Parsing from backing type (if the ID is just a wrapper for raw backing type) public static ID Parse(Guid v); public static ID? ParseNullable(Guid? id); public static bool TryParse([NotNullWhen(true)] Guid? v, out ID id); public static bool IsValid([NotNullWhen(true)] Guid? v); public static ID New(); // Only if generation is possible } ``` -------------------------------- ### Initialize and Apply Terraform Configuration Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Initialize Terraform and apply cluster configuration, including Traefik Helm release, for local Kubernetes setup. ```sh # Add and update the Helm repository for Traefik. helm repo add traefik https://helm.traefik.io/traefik helm repo update # Apply cluster configuration. terraform init terraform apply -target data.local_file.kubeconfig -auto-approve terraform apply -target helm_release.traefik -auto-approve terraform apply -auto-approve ``` -------------------------------- ### Authenticated Test Application Setup Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/tests/integration_tests/index.md Initializes HTTP executors for queries, commands, and operations with authentication. It configures the HTTP client with an authorization header using a test authentication handler. ```csharp public class AuthenticatedExampleAppTestApp : ExampleAppTestApp { private ClaimsPrincipal claimsPrincipal = new(); public HttpQueriesExecutor Query { get; private set; } = default!; public HttpCommandsExecutor Command { get; private set; } = default!; public HttpOperationsExecutor Operation { get; private set; } = default!; public AuthenticatedExampleAppTestApp() { } public override async ValueTask InitializeAsync() { AuthenticateAsTestSuperUser(); void ConfigureClient(HttpClient hc) => { hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( TestAuthenticationHandler.SchemeName, TestAuthenticationHandler.SerializePrincipal( claimsPrincipal)); }; await base.InitializeAsync(); Query = CreateQueriesExecutor(ConfigureClient); Command = CreateCommandsExecutor(ConfigureClient); Operation = CreateOperationsExecutor(ConfigureClient); await WaitForBusAsync(); } public void AuthenticateAsTestSuperUser() { claimsPrincipal = new( new ClaimsIdentity( new Claim[] { new("sub", SuperAdminId.ToString()), new("role", "user"), new("role", "admin"), }, authenticationType: TestAuthenticationHandler.SchemeName, nameType: "sub", roleType: "role" ) ); } public override async ValueTask DisposeAsync() { Command = default!; Query = default!; Operation = default!; await base.DisposeAsync(); } } ``` -------------------------------- ### Example Command Handler Implementation Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/command/index.md This C# code demonstrates a basic command handler that finds a project by its ID and updates its name. It relies on a repository for data access and assumes validation has already occurred. ```csharp namespace ExampleApp.CQRS.Projects; public class UpdateProjectNameCH : ICommandHandler { private readonly IRepository projects; public UpdateProjectNameCH(IRepository projects) { this.projects = projects; } public Task ExecuteAsync(HttpContext context, UpdateProjectName command) { // We find a project with specified Id assuming that it exists, // it should be the responsibility of validation to ensure // that project exists. var project = await projects.FindAndEnsureExistsAsync( ProjectId.Parse(command.ProjectId), context.RequestAborted); project.UpdateName(command.Name); // We only notify the repository that entity has been updated, // we let other part of the code commit the database transaction. projects.Update(project); } } ``` -------------------------------- ### Prefixed ID Usage Example Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/id/index.md Shows a practical example of parsing a prefixed ID, destructuring it, and accessing the raw Ulid value. ```csharp var id = OrderId.Parse("order_01ARZ3NDEKTSV4RRFFQ69G5FAV"); var (prefix, ulid) = id.Destructure(); // ("order", Ulid) var rawUlid = id.Ulid; // Access raw Ulid directly ``` -------------------------------- ### Configure Kratos Authentication and API Clients Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/authorization_ory_kratos/index.md This example shows how to configure Kratos authentication using `AddKratos` and register Kratos HTTP clients. It demonstrates setting claim types, a custom claims extractor for assigning roles based on email domain, and configuring clients for both admin and public endpoints. ```csharp public override void ConfigureServices(IServiceCollection services) { // . . . services .AddAuthentication() .AddKratos(options => { options.NameClaimType = KnownClaims.UserId; options.RoleClaimType = KnownClaims.Role; options.ClaimsExtractor = (s, o, c) => { // Every identity is a valid User c.Add(new(o.RoleClaimType, "user")); if ( s.Identity?.VerifiableAddresses?.Any(kvia => kvia.Via == KratosVerifiableIdentityAddress.ViaEnum.Email && kvia.Value.EndsWith("@leancode.pl", false, CultureInfo.InvariantCulture) && kvia.Verified ) ?? false ) { c.Add(new(o.RoleClaimType, "admin")); } }; }); services.AddKratos(builder => { builder.UseProvider(); builder.AddKratosHttpClients(builder: hcb => _ = hcb.Name switch { nameof(ICourierApi) or nameof(IIdentityApi) // Kratos admin endpoint => hcb.ConfigureHttpClient(hc => hc.BaseAddress = new("")), nameof(IFrontendApi) or nameof(IMetadataApi) // Kratos public endpoint => hcb.ConfigureHttpClient(hc => hc.BaseAddress = new("")), _ => throw new NotSupportedException($"Unexpected client name: '{hcb.Name}'.") } ); }); // Api key which will be send by Kratos services.AddSingleton(new LeanCode.Kratos.KratosWebHookHandlerConfig("")); // . . . } ``` -------------------------------- ### Example CQRS Cache Policy Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/output_caching/index.md An example cache policy for a CQRS query, demonstrating how to configure cache lookup and storage, and access request/response payloads. ```csharp public class ExampleCachePolicy : CQRSQueryOutputCachePolicy { public override ValueTask CacheRequestAsync( OutputCacheContext context, CancellationToken cancellationToken) { context.AllowCacheLookup = true; context.AllowCacheStorage = true; // Get the query/operation object var query = context.HttpContext.GetCQRSRequestPayload(); // Use query properties to configure caching context.CacheVaryByRules.VaryByValues["id"] = query.Id; return ValueTask.CompletedTask; } public override ValueTask ServeResponseAsync( OutputCacheContext context, CancellationToken cancellationToken) { // Get the result object (only available after handler execution) var result = context.HttpContext.GetCQRSRequiredResultPayload(); // Conditionally disable storing in cache basing on result if (result is null) { context.AllowCacheStorage = false; } return ValueTask.CompletedTask; } } ``` -------------------------------- ### Indexing TimestampTz in PostgreSQL Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/timestamp_tz/index.md Example of how to create a database index on a TimestampTz property in PostgreSQL using raw SQL. ```APIDOC ## Indexing TimestampTz in PostgreSQL ### Description Demonstrates how to create a database index on a `TimestampTz` property stored in PostgreSQL, specifically when the index needs to be on an expression involving both the UTC timestamp and the time zone ID. ### SQL Example ```sql create index "IX_Events_StartTime" on "public"."Events"(("StartTime_UtcTimestamp" at time zone "StartTime_TimeZoneId")); ``` ### Notes - This SQL is intended to be used with `MigrationBuilder.Sql` in EF Core migrations. - Replace `"IX_Events_StartTime"`, `"public"."Events"`, and `"StartTime_UtcTimestamp"`, `"StartTime_TimeZoneId"` with your actual index name, table name, and property names. ``` -------------------------------- ### Implementing a Custom Middleware Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/pipeline/adding_custom_middlewares.md Define a custom middleware by inheriting from `IMiddleware` and implementing the `InvokeAsync` method. This example shows a simple logging middleware. ```csharp public class LoggingMiddleware : IMiddleware { private readonly ILogger> _logger; public LoggingMiddleware(ILogger> logger) { _logger = logger; } public async Task InvokeAsync(object? context, RequestDelegate next) { _logger.LogInformation("Processing request..."); await next(context); _logger.LogInformation("Request processed."); } } ``` -------------------------------- ### Unauthenticated Test Application Setup Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/tests/integration_tests/index.md Initializes HTTP executors for queries, commands, and operations without authentication. This is suitable for testing unauthenticated API endpoints. ```csharp public class UnauthenticatedExampleAppTestApp : ExampleAppTestApp { public HttpQueriesExecutor Query { get; private set; } = default!; public HttpCommandsExecutor Command { get; private set; } = default!; public HttpOperationsExecutor Operation { get; private set; } = default!; public override async ValueTask InitializeAsync() { await base.InitializeAsync(); Query = CreateQueriesExecutor(); Command = CreateCommandsExecutor(); Operation = CreateOperationsExecutor(); await WaitForBusAsync(); } public override async ValueTask DisposeAsync() { Command = default!; Query = default!; Operation = default!; await base.DisposeAsync(); } } ``` -------------------------------- ### Configuring CQRS Pipeline Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/pipeline/adding_custom_middlewares.md Configure the CQRS pipeline for commands, queries, and operations. This example shows how to chain various operations like validation, transaction commitment, and event publishing. ```csharp services.AddCQRS(cqrs => { cqrs.Commands = c => c.BlockEmployees() .Validate() .CommitTransaction() .PublishEvents(); cqrs.Queries = c => c.BlockEmployees() .Secure(); cqrs.Operations = c => c.BlockEmployees() .Secure() .CommitTransaction() .PublishEvents(); }); ``` -------------------------------- ### Log Application Events with Serilog Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/logging_serilog/index.md Demonstrates how to use the `ILogger` interface to log information about application events, such as updating a project's name. This example shows logging a specific event with contextual data like `ProjectId`. ```csharp public class UpdateProjectNameCH : ICommandHandler { private readonly ILogger logger; // . . . public Task ExecuteAsync(HttpContext context, UpdateProjectName command) { // . . . logger.Information( "Project's {ProjectId} name has been updated", project.Id); } } ``` -------------------------------- ### Configure Kratos Webhook for Registration Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/authorization_ory_kratos/handling_webhooks.md Define a webhook in Kratos configuration to trigger after user registration. This example shows how to set the URL, method, authentication, and body mapping for the webhook. ```yaml # . . . flows: registration: ui_url: https://${domain}/registration lifespan: 1h enabled: true after: password: hooks: - hook: web_hook config: url: "${api}/kratos/sync-identity" method: POST auth: type: api_key config: in: header name: X-Api-Key value: "${web_hook_api_key}" body: file:///etc/kratos/webhook.identity.mapper.jsonnet response: ignore: true parse: false # . . . ``` -------------------------------- ### Query Cache Policy Example Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/output_caching/index.md Extends CQRSQueryOutputCachePolicy to enable caching for a specific query. Configures cache duration and varies by query parameters. ```csharp using LeanCode.CQRS.OutputCaching.BasePolicies; using Microsoft.AspNetCore.OutputCaching; public class GetProjectQuery : IQuery { public string ProjectId { get; set; } } public class GetProjectCachePolicy : CQRSQueryOutputCachePolicy { public override ValueTask CacheRequestCoreAsync( OutputCacheContext context, CancellationToken cancellationToken) { // Enable caching for this query context.AllowCacheLookup = true; context.AllowCacheStorage = true; // Cache for 5 minutes context.ResponseExpirationTimeSpan = TimeSpan.FromMinutes(5); // Vary cache by ProjectId var query = context.HttpContext.GetCQRSRequestPayload();; context.CacheVaryByRules.VaryByValues["projectId"] = query.ProjectId; return ValueTask.CompletedTask; } public override ValueTask ServeResponseAsync( OutputCacheContext context, CancellationToken cancellationToken) { // Optionally inspect the result to decide if it should be cached var result = context.HttpContext.GetCQRSRequiredResultPayload(); if (result is null) { // Don't cache null results context.AllowCacheStorage = false; } return ValueTask.CompletedTask; } } ``` -------------------------------- ### Define Custom Employee Password Sanitizer Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/logging_serilog/sanitization.md Create a custom sanitizer by inheriting from `BaseSanitizer` and overriding the `TrySanitize` method. Ensure the sanitizer assembly is provided to `ConfigureDefaultLogging`. This example redacts an employee's password. ```csharp public class RegisterEmployeeSanitizer : BaseSanitizer { protected override RegisterEmployee TrySanitize(RegisterEmployee obj) { if (obj.Password != Placeholder) { return new RegisterEmployee { Name = obj.Name, Password = Placeholder, }; } else { return null; } } } ``` -------------------------------- ### Define Project Aggregate Root Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/aggregate/index.md Defines a `Project` aggregate root implementing `IAggregateRoot`. It includes a `ProjectId` using a prefixed GUID format and manages a list of `Assignment` objects. Use this to model entities that group related domain objects. ```csharp [TypedId(TypedIdFormat.PrefixedGuid, CustomPrefix = "project")] public readonly partial record struct ProjectId; public class Project : IAggregateRoot { private readonly List assignments = new(); public ProjectId Id { get; private init; } public EmployeeId OwnerId { get; private init; } public string Name { get; private set; } public IReadOnlyList Assignments => assignments; DateTime IOptimisticConcurrency.DateModified { get; set; } private Project() { } public static Project Create( ProjectId projectId, string name, EmployeeId ownerId) { return new Project { Id = projectId, Name = name, OwnerId = ownerId, }; } } ``` -------------------------------- ### TimestampTz DTO Example Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/timestamp_tz/index.md An example DTO for representing TimestampTz data when serializing or deserializing, suitable for API contracts. ```APIDOC ## TimestampTzDTO ### Description A Data Transfer Object (DTO) for representing `TimestampTz` data, typically used in API contracts for readback or input. ### Fields - **Timestamp** (DateTimeOffset): The timestamp value. - **TimeZoneId** (string): The IANA ID of the time zone. ### Error Codes - **InvalidTimeZoneId** (int): Constant for invalid time zone ID errors (10001). ``` -------------------------------- ### Integration Test for Project Creation Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/tests/integration_tests/index.md Demonstrates creating a project using `CreateProject` command in an authenticated environment and verifying its creation and details. Requires environment variables `Database__ConnectionStringBase` and `Database__ConnectionString` to be set. ```csharp public class Tests : IAsyncLifetime { private readonly AuthenticatedExampleAppTestApp app; public Tests() { app = new AuthenticatedExampleAppTestApp(); } [Fact] public async Task Project_is_correctly_created() { await app.Command.RunSuccessAsync( new CreateProject { Name = "Project" }); var projects = await app.Query.GetAsync(new AllProjects()); var project = Assert.Single(projects); Assert.Equal("Project", project.Name); Assert.Matches("^project_[0-7][0-9A-HJKMNP-TV-Z]{25}$", project.Id); } public ValueTask InitializeAsync() => app.InitializeAsync(); public ValueTask DisposeAsync() => app.DisposeAsync(); } public static class ApiClientHelpers { public static async Task RunSuccessAsync( this HttpCommandsExecutor executor, ICommand command) { var result = await executor.RunAsync(command); result.ValidationErrors.Should().BeEmpty( "command {0} needs to pass validation", command.GetType().Name); } } ``` -------------------------------- ### Configure PrefixedGuid Typed ID Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/id/index.md Use PrefixedGuid format for IDs that combine a custom prefix with a GUID. The 'New' factory method generates a new GUID. ```csharp [TypedId(TypedIdFormat.PrefixedGuid, CustomPrefix = "employee")] public readonly partial record struct VeryLongEmployeeId; // The `VeryLongEmployeeId` will have format `employee_(guid)`, with `New` using `Guid.NewGuid` as random source. ``` -------------------------------- ### Pack CoreLibrary Packages Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/README.md After successful testing, use this command to pack the library into NuGet packages. Ensure the output directory is specified. ```sh dotnet pack -c Release -o $PWD/publish ``` -------------------------------- ### Send Meeting Started Push Notification Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/push_notifications_fcm/index.md Sends a push notification to a specific user indicating a meeting has started. Requires a configured FCMClient and defined message templates. ```csharp public class PushNotificationSender { private readonly FCMClient fcmClient; public PushNotificationSender(FCMClient fcmClient) { this.fcmClient = fcmClient; } public async Task SendMeetingStartedPN( Guid userId, string content, Uri absoluteImageUrl) { var message = new MulticastMessage { Notification = fcmClient .Localize(Consts.DefaultUserCulture) .Title("notifications.meeting-started.title") .Body("notifications.meeting-started.body", content) .RawImageUrl(absoluteImageUrl) .Build(), Data = new Dictionary { // Data specific to Flutter, required to open // a particular screen upon clicking the notification. ["click_action"] = "FLUTTER_NOTIFICATION_CLICK", ["type"] = "MeetingHasStarted", ["name"] = content, }, }; // If no token is found for the specified user, // no notification will be sent. await fcmClient.SendToUserAsync( userId, message, context.RequestAborted); } } ``` -------------------------------- ### Configure Force Update Support Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/features/force_update/index.md Use the AddForceUpdate extension method in Startup.cs after AddCQRS to configure force update settings for Android and iOS. Ensure LeanCode.ForceUpdate.Contracts is added. ```csharp public override void ConfigureServices(IServiceCollection services) { // ... services.AddCQRS(CQRSTypes, CQRSTypes) .AddForceUpdate( new AndroidVersionsConfiguration( new Version(AndroidMinimumRequiredVersion), new Version(AndroidCurrentlySupportedVersion)), new IOSVersionsConfiguration( new Version(IOSMinimumRequiredVersion), new Version(IOSCurrentlySupportedVersion))); // ... } ``` -------------------------------- ### Source Generated IDs - API Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/id/index.md This snippet outlines the common API operations supported by source-generated IDs, using `RawGuid` as an example. ```APIDOC ## Source Generated IDs - API ### Description The generated ID structs provide a rich API for managing and interacting with IDs. The following example illustrates the API for a `RawGuid` based ID. ### API Signature ```cs public readonly partial record struct ID : IEquatable, IComparable, ISpanFormattable, IUtf8SpanFormattable, ISpanParsable, IEqualityOperators { public static readonly ID Empty; public Guid Value { get; } public bool IsEmpty { get; } // Parsing from backing type public static ID Parse(Guid v); public static ID? ParseNullable(Guid? id); public static bool TryParse([NotNullWhen(true)] Guid? v, out ID id); public static bool IsValid([NotNullWhen(true)] Guid? v); public static ID New(); // Only if generation is possible } ``` ``` -------------------------------- ### Generate Initial Migrations Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Use the dotnet ef tool to generate initial database migrations. Ensure the PostgreSQL connection string is exported before running. ```sh # It does not need to point to a real database export PostgreSQL__ConnectionString='Host=localhost;Database=app;Username=app;Password=Passw12#' dotnet ef migrations add --context ContextNameDbContext -o Migrations InitialMigration ``` -------------------------------- ### Operation Cache Policy Example Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/output_caching/index.md Extends CQRSOperationOutputCachePolicy for operations. Caches historical reports based on date range and varies by all operation parameters. ```csharp public class GenerateProjectReportOperation : IOperation { public string ProjectId { get; set; } public DateOnly StartDate { get; set; } public DateOnly EndDate { get; set; } public ReportFormat Format { get; set; } // PDF, CSV, etc. } public class GenerateProjectReportCachePolicy : CQRSOperationOutputCachePolicy { public override ValueTask CacheRequestCoreAsync( OutputCacheContext context, CancellationToken cancellationToken) { var operation = context.HttpContext.GetCQRSRequestPayload(); // Enable caching for completed historical reports // (Don't cache if the date range includes today - data is still changing) if (operation.EndDate < DateOnly.FromDateTime(DateTime.UtcNow)) { context.AllowCacheLookup = true; context.AllowCacheStorage = true; // Cache historical reports for 1 hour (they're expensive to generate) context.ResponseExpirationTimeSpan = TimeSpan.FromHours(1); // Vary by all operation parameters context.CacheVaryByRules.VaryByValues["projectId"] = operation.ProjectId; context.CacheVaryByRules.VaryByValues["startDate"] = operation.StartDate.ToString("yyyy-MM-dd"); context.CacheVaryByRules.VaryByValues["endDate"] = operation.EndDate.ToString("yyyy-MM-dd"); context.CacheVaryByRules.VaryByValues["format"] = operation.Format.ToString(); } return ValueTask.CompletedTask; } public override ValueTask ServeResponseAsync( OutputCacheContext context, CancellationToken cancellationToken) { var result = context.HttpContext.GetCQRSRequiredResultPayload(); // Don't cache if report generation failed if (result?.FileContent == null) { context.AllowCacheStorage = false; } return ValueTask.CompletedTask; } } ``` -------------------------------- ### Test CoreLibrary Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/README.md Run this command in the repository root to execute unit tests. For integration tests requiring external services like SQL Server or PostgreSQL, use docker-compose as shown. ```sh dotnet test ``` ```sh # For running tests with SQL Server $ DB=sqlserver docker-compose run test ``` ```sh # For running tests with PostgreSQL $ DB=postgres docker-compose run test ``` -------------------------------- ### Custom Middleware with Request Context Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/pipeline/adding_custom_middlewares.md Example of a middleware that accesses and modifies the request context. This middleware adds a correlation ID to outgoing requests. ```csharp public class CorrelationIdMiddleware : IMiddleware { private readonly IHttpContextAccessor _httpContextAccessor; public CorrelationIdMiddleware(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public async Task InvokeAsync(object? context, RequestDelegate next) { var correlationId = Guid.NewGuid().ToString(); _httpContextAccessor.HttpContext?.Response.Headers.Add("X-Correlation-ID", correlationId); await next(context); } } ``` -------------------------------- ### Run Integration Tests with Tilt Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Execute the integration tests for the application using the tilt up command with the projectname-integration_tests target. ```sh tilt up projectname-integration_tests ``` -------------------------------- ### Apply Migrations with Tilt Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Execute the database migrations using the tilt up command with the projectname-migrations target. ```sh tilt up projectname-migrations ``` -------------------------------- ### Basic .NET CoreLibrary Project Template Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/README.md This is a minimal project template for a .NET CoreLibrary. TargetFramework should be managed externally. ```xml ``` -------------------------------- ### Configure Consumer with Audit Logs Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/features/audit_logs/index.md Add `.UseAuditLogs(context)` to your consumer configuration to enable audit logging. The order of configuration matters; place this after `UseDomainEventsPublishing` if you wish to exclude MT inbox/outbox tables from audit collection. ```csharp protected override void ConfigureConsumer( IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator consumerConfigurator, IRegistrationContext context ) { endpointConfigurator.UseRetry( r => r.Immediate(1) .Incremental(3, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)) ); endpointConfigurator.UseEntityFrameworkOutbox(context); endpointConfigurator.UseDomainEventsPublishing(context); endpointConfigurator.UseAuditLogs(context); } ``` -------------------------------- ### ConfigCat JSON Flag Overrides Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/feature_flags_configcat/index.md Define feature flag values in a `configcat.json` file for local overrides. This example shows how to set the `SendEmployeeAssignedToAssignmentEmails` flag to `false`. ```json { "flags": { "SendEmployeeAssignedToAssignmentEmails": false, } } ``` -------------------------------- ### IHttpResponseFeature Interface Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/adrs/2025-11-05_output-cache-vs-local-cqrs.md Defines the contract for ASP.NET Core's response features, including callbacks for starting and completing the response. Middleware relies on these for finalizing headers and cleanup. ```csharp public interface IHttpResponseFeature { int StatusCode { get; set; } string? ReasonPhrase { get; set; } IHeaderDictionary Headers { get; set; } Stream Body { get; set; } bool HasStarted { get; } void OnStarting(Func callback, object state); void OnCompleted(Func callback, object state); } ``` -------------------------------- ### Configure Custom Distributed Cache Store (Redis) Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/output_caching/index.md Set up a custom cache store for distributed scenarios, such as using Redis. This replaces the default in-memory store for scalability. ```csharp public override void ConfigureServices(IServiceCollection services) { // Register CQRS services.AddCQRS(TypesCatalog.Of(), TypesCatalog.Of()); // Register CQRS output caching services.AddCQRSOutputCache(TypesCatalog.Of()); // Configure output cache to use Redis for distributed caching services.AddStackExchangeRedisOutputCache(options => { options.Configuration = "localhost:6379"; }); } ``` -------------------------------- ### Implement a Query Handler Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/query/index.md Implements a query handler for the `AllProjects` query. It retrieves projects from the database using Entity Framework, applying a name filter if provided. ```csharp namespace ExampleApp.CQRS.Projects; public class AllProjectsQH : IQueryHandler> { private readonly CoreDbContext dbContext; public ProjectsQH(CoreDbContext dbContext) { this.dbContext = dbContext; } public async Task> ExecuteAsync( HttpContext context, Projects query) { // Here, we use Entity Framework but you are free // to use other mechanisms to get the data var dbQuery = dbContext.Projects.AsQueryable(); if (!string.IsNullOrEmpty(query.NameFilter)) { dbQuery = dbQuery.Where(p => p.Name.Contains(query.NameFilter)); } return await dbQuery .Select(p => new ProjectDTO { Id = p.Id, Name = p.Name }) .ToListAsync(context.RequestAborted); } } ``` -------------------------------- ### Configure OpenTelemetry Services Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/observability_open_telemetry/index.md Sets up OpenTelemetry tracing and metrics with OTLP exporter. Includes CoreLibrary instrumentation and a custom processor for identity attributes from baggage. Only configured if an OTLP endpoint is provided. ```csharp public override void ConfigureServices(IServiceCollection services) { // . . . // OpenTelemetry exporter endpoint. var otlp = ""; if (!string.IsNullOrWhiteSpace(otlp)) { services .AddOpenTelemetry() .ConfigureResource(r => r.AddService( serviceName: "ExampleApp.Api", serviceInstanceId: Environment.MachineName)) .WithTracing(builder => { builder .AddProcessor() .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() // Collect CoreLibrary traces - cqrs contract's // full name if CQRSTracingMiddleware was added. .AddLeanCodeTelemetry() .AddOtlpExporter(cfg => cfg.Endpoint = new(otlp)); }) .WithMetrics(builder => { builder .AddAspNetCoreInstrumentation() .AddHttpClientInstrumentation() // Collect CoreLibrary metrics - cqrs success/failure // counters with reason when failed. .AddLeanCodeMetrics() .AddOtlpExporter(cfg => cfg.Endpoint = new(otlp)); }); } // . . . } ``` -------------------------------- ### Create New Project with lncdproj Template Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/quick_start/index.md Generate a new project using the lncdproj template, specifying project name, context, and script execution. ```sh dotnet new lncdproj --project-name ProjectName --context ContextName --allow-scripts Yes ``` -------------------------------- ### Creating a Database Index for TimestampTz Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/domain/timestamp_tz/index.md Demonstrates how to manually create a database index on a TimestampTz property using raw SQL within EF Core migrations. This is necessary because EF Core does not directly support indexing properties of complex types. ```sql migrationBuilder.Sql( """ create index "IX_Events_StartTime" on "public"."Events" (("StartTime_UtcTimestamp" at time zone "StartTime_TimeZoneId")); """ ); ``` -------------------------------- ### Add Audit Logs Consumer to MassTransit Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/features/audit_logs/index.md Integrate the audit logs consumer into your MassTransit configuration to process audit logs asynchronously. This is typically done within the `AddCQRSMassTransitIntegration` setup. ```csharp public override void ConfigureServices(IServiceCollection services) { // . . . services.AddCQRSMassTransitIntegration(cfg => { // . . . cfg.AddAuditLogsConsumer(); // . . . }); // . . . } ``` -------------------------------- ### Defining Application Roles and Permissions Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/authorization/index.md Define your application's roles and associated permissions by implementing `IRoleRegistration`. The `Role` constructor takes the role name and a list of permissions. ```csharp internal class AppRoles : IRoleRegistration { public IEnumerable Roles { get; } = new[] { new Role("employee", "employee"), new Role("admin", "admin"), }; } ``` -------------------------------- ### Command Handler Committing Transaction Directly (Avoid) Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/pipeline/avoid_commiting_transactions_in_handlers.md An example of a command handler that directly commits the database transaction after updating a project and assigning an employee. This pattern should be avoided as it can lead to inconsistencies. ```csharp public class AssignEmployeeToAssignmentCH : ICommandHandler { private readonly IRepository projects; private readonly CoreDbContext dbContext; public AssignEmployeeToAssignmentCH( IRepository projects, CoreDbContext dbContext) { this.projects = projects; this.dbContext = dbContext; } public Task ExecuteAsync(HttpContext context, UpdateProjectName command) { var project = await projects.FindAndEnsureExistsAsync( ProjectId.Parse(command.ProjectId), context.RequestAborted); project.AssignEmployeeToAssignment( AssignmentId.Parse(command.AssignmentId), EmployeeId.Parse(command.EmployeeId)); projects.Update(project); // Directly committing the transaction in the command handler, // which should be avoided. await dbContext.SaveChangesAsync(context.RequestAborted) } } ``` -------------------------------- ### Handle Response Start Callback in Output Caching Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/adrs/2025-11-05_output-cache-vs-local-cqrs.md The middleware uses `StartResponse` to track when the response begins, setting `ResponseStarted` and `ResponseTime`. This is crucial for distinguishing between unstarted and completed responses, a state not accurately reflected by `NullHttpResponse.HasStarted`. ```csharp async Task ExecuteResponseAsync() { // Hook up to listen to the response stream ShimResponseStream(context); try { await _next(httpContext); // ← Your CQRS middlewares run here // The next middleware might change the policy foreach (var policy in policies) { await policy.ServeResponseAsync(context, httpContext.RequestAborted); } // If there was no response body, check the response headers now... StartResponse(context); ``` ```csharp private bool OnStartResponse(OutputCacheContext context) { if (!context.ResponseStarted) { context.ResponseStarted = true; context.ResponseTime = _options.TimeProvider.GetUtcNow(); return true; } return false; } ``` -------------------------------- ### Configure CQRS Pipeline with Transaction Commitment Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/pipeline/avoid_commiting_transactions_in_handlers.md Configures the application pipeline to include transaction commitment and event publishing for commands and operations. This setup ensures that changes are saved to the database within a transaction before events are published. ```csharp protected override void ConfigureApp(IApplicationBuilder app) { // . . app.UseEndpoints(endpoints => { endpoints.MapRemoteCQRS( "/api", cqrs => { cqrs.Commands = c => c.Secure() .Validate() .CommitTransaction() .PublishEvents(); cqrs.Queries = q => q.Secure(); cqrs.Operations = o => o.Secure() .CommitTransaction() .PublishEvents(); } ); }); } ``` -------------------------------- ### Configure Serilog Logging with LeanCode CoreLibrary Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/logging_serilog/index.md Sets up default Serilog logging for a LeanCode CoreLibrary application using `ConfigureDefaultLogging`. This method integrates environment variables for configuration and can direct logs to Seq if `Logging:SeqEndpoint` is provided. Customize log levels and internal logging via environment variables. ```sh export Logging__EnableDetailedInternalLogs=true export Logging__MinimumLevel=Verbose ``` ```csharp public class Program { public static Task Main() => CreateWebHostBuilder().Build().RunAsync(); public static IHostBuilder CreateWebHostBuilder() { // The `BuildMinimalHost` method includes environment // variables as part of the configuration and configures // Kestrel as the web server. return LeanProgram .BuildMinimalHost() .ConfigureDefaultLogging( "ExampleApp.Api", new[] { typeof(Program).Assembly }); } } ``` -------------------------------- ### CQRS Pipeline with Multiple Middlewares Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/pipeline/adding_custom_middlewares.md Demonstrates adding multiple custom middlewares to the CQRS pipeline. The `Use` method can be called multiple times to chain middlewares. ```csharp builder.Services.AddCqrs(assemblies: new[] { typeof(CreateUserCommand).Assembly }) .AddPipeline(pipeline => pipeline .Use>() .Use>() .Use>()); ``` -------------------------------- ### Integrate Employee Blocker Middleware in Pipeline Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/pipeline/adding_custom_middlewares.md Demonstrates how to integrate the custom `BlockEmployees` middleware into the application pipeline using `UseEndpoints` and `MapRemoteCQRS`. ```csharp protected override void ConfigureApp(IApplicationBuilder app) { // . . app.UseEndpoints(endpoints => { endpoints.MapRemoteCQRS( "/api", cqrs => { cqrs.Commands = c => c .BlockEmployees() .Secure() ``` -------------------------------- ### Configure MassTransit Integration with CQRS Pipeline Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/messaging_masstransit/index.md This code configures MassTransit integration for the CQRS pipeline, including the Entity Framework outbox, consumer registration, and conditional setup for in-memory transport (development) or Azure Service Bus (production). It utilizes helper methods for common bus configurations. ```csharp public override void ConfigureServices(IServiceCollection services) { // . . . services.AddCQRSMassTransitIntegration(cfg => { // Adds MassTransit's Inbox & Outbox pattern implementation. // More information in Inbox & Outbox section below. cfg.AddEntityFrameworkOutbox(outboxCfg => { outboxCfg.LockStatementProvider = // Using CustomPostgresLockStatementProvider vendored from // https://github.com/MassTransit/MassTransit/blob/9e6c78573ad211a70b624fad31382faa331dc4d8/src/Persistence/MassTransit.EntityFrameworkIntegration/EntityFrameworkIntegration/SqlLockStatementProvider.cs // as MassTransit uses EF8 incompatible API new LeanCode.CQRS.MassTransitRelay.LockProviders.CustomPostgresLockStatementProvider(); outboxCfg.UseBusOutbox(); }); // Adds consumers with default configuration. // More information in Consumer definition section below. cfg.AddConsumersWithDefaultConfiguration( // Array of assemblies where handlers are located AllHandlers.Assemblies.ToArray(), typeof(DefaultConsumerDefinition<>) ); if (hostEnv.IsDevelopment()) { cfg.AddDelayedMessageScheduler(); cfg.UsingInMemory( (ctx, cfg) => { cfg.UseDelayedMessageScheduler(); ConfigureBusCommon( ctx, cfg ); } ); } else { cfg.AddServiceBusMessageScheduler(); cfg.UsingAzureServiceBus( (ctx, cfg) => { cfg.Host( // Azure Service Bus endpoint taken from Configuration new Uri(Config.MassTransit.AzureServiceBus.Endpoint( Configuration)), host => { host.RetryLimit = 5; host.RetryMinBackoff = TimeSpan.FromSeconds(3); // Helper method to create Azure.Core.TokenCredential from Configuration host.TokenCredential = DefaultLeanCodeCredential.Create(Configuration); } ); cfg.UseServiceBusMessageScheduler(); ConfigureBusCommon( ctx, cfg ); } ); } static void ConfigureBusCommon( IBusRegistrationContext ctx, TConfigurator cfg ) where TConfigurator : IBusFactoryConfigurator where TReceiveConfigurator : IReceiveEndpointConfigurator { cfg.ConfigureEndpoints(ctx); cfg.ConfigureJsonSerializerOptions(KnownConverters.AddAll); cfg.ConnectBusObservers(ctx); } }); // . . . } ``` -------------------------------- ### Default Localization File (Strings.resx) Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/features/localization/index.md Define the default localization strings in a .resx file. This file should be placed in the same directory as the marker class. ```xml Meeting {0} has started. ``` -------------------------------- ### Register ConfigCat Client with Local Overrides Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/feature_flags_configcat/index.md Use `AddConfigCat` to register the ConfigCat client, specifying a local JSON file for flag overrides and setting the behavior to `LocalOnly` to bypass the ConfigCat CDN. ```csharp public override void ConfigureServices(IServiceCollection services) { // . . services.AddConfigCat( sdkKey: null, flagOverridesFilePath: "YOUR_PATH/configcat.json", overrideBehaviour: OverrideBehaviour.LocalOnly); // . . } ``` -------------------------------- ### Enable Caching and Set Eviction Tags Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/output_caching/index.md Configure cache policy to enable lookup and storage, and add tags for cache eviction. This is useful for managing cache invalidation based on data changes. ```csharp public override ValueTask CacheRequestCoreAsync( OutputCacheContext context, CancellationToken cancellationToken) { // Enable caching context.AllowCacheLookup = true; context.AllowCacheStorage = true; // Set tags for eviction context.Tags.Add("projects"); return ValueTask.CompletedTask; } ``` -------------------------------- ### Add and Remove Tokens from PushNotificationTokenStore Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/push_notifications_fcm/index.md Demonstrates adding and removing user tokens from the IPushNotificationTokenStore. Ensure the token store is properly configured. ```csharp public class PushNotificationTokenStore { private readonly IPushNotificationTokenStore tokenStore; public PushNotificationTokenStore( IPushNotificationTokenStore tokenStore) { this.tokenStore = tokenStore; } public Task AddAsync( Guid userId, string token, CancellationToken cancellationToken) { return tokenStore.AddUserTokenAsync( userId, token, cancellationToken); } public Task RemoveAsync( Guid userId, string token, CancellationToken cancellationToken) { return tokenStore.RemoveUserTokenAsync( userId, token, cancellationToken); } } ``` -------------------------------- ### German Localization File (Strings.de.resx) Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/features/localization/index.md Create culture-specific .resx files for each language you want to support. For German, use the 'de' culture code. ```xml Besprechung {0} hat begonnen. ``` -------------------------------- ### Configure SendGrid Client and Razor View Renderer Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/external_integrations/emails_sendgrid/index.md Add SendGrid client and Razor view renderer services to your application's service collection. Ensure API key is provided and templates are configured. ```csharp private static readonly RazorViewRendererOptions ViewOptions = new("Templates"); // . . . public override void ConfigureServices(IServiceCollection services) { // . . . services.AddRazorViewRenderer(ViewOptions); // Add SendGrid ApiKey services.AddSendGridClient(new SendGridClientOptions { ApiKey = "" }); // . . . } ``` -------------------------------- ### Configure Global Cache Settings Source: https://github.com/leancodepl/corelibrary/blob/v11.0-preview/docs/cqrs/output_caching/index.md Define default settings for output caching, including expiration times, maximum body size, and total size limits. This applies global policies to all cached responses. ```csharp public override void ConfigureServices(IServiceCollection services) { services.AddCQRSOutputCache( TypesCatalog.Of(), options => { options.DefaultExpirationTimeSpan = TimeSpan.FromMinutes(10); options.MaximumBodySize = 1024 * 1024; // 1MB options.SizeLimit = 100 * 1024 * 1024; // 100MB total }); } ```