### Configuration Example (appsettings.json) Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Example of how to configure HTTP clients and their base URLs, timeouts, and default headers in appsettings.json. ```APIDOC ## Configuration Example (appsettings.json) ### Description Demonstrates the structure for configuring different API clients, including their base URLs, default timeouts, media types, and request headers. ### File `appsettings.json` ### Content ```json { "ApiEndpoints": { "PaymentApi": { "BaseUrl": "https://api.payment-provider.com", "DefaultTimeOut": 30, "DefaultMediaTypeWithQualityHeaderValue": "application/json", "DefaultRequestHeaders": { "X-Api-Version": "2.0", "X-Client-Id": "my-app" } }, "NotificationApi": { "BaseUrl": "https://api.notifications.com", "DefaultTimeOut": 10, "DefaultMediaTypeWithQualityHeaderValue": "application/json" } } } ``` ``` -------------------------------- ### Aether DbContext Setup Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/repository-pattern/README.md Provides an example of setting up a custom DbContext that inherits from AetherDbContext. This includes defining DbSet properties and overriding OnModelCreating for entity configurations. ```csharp public class MyDbContext : AetherDbContext { public DbSet Products { get; set; } public DbSet Orders { get; set; } public MyDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly); } } ``` -------------------------------- ### Client Implementation Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Example of implementing a custom HTTP client interface that inherits from IHttpClientWrapper. ```APIDOC ## Client Implementation Example ### Description Provides an example of how to create a concrete implementation of a custom API client interface, inheriting from `HttpClientWrapper` and utilizing its methods. ### Interface Definition ```csharp public interface IPaymentApiClient : IHttpClientWrapper { Task ProcessPaymentAsync( ProcessPaymentRequest request, CancellationToken cancellationToken = default); Task GetPaymentAsync( string paymentId, CancellationToken cancellationToken = default); } ``` ### Implementation ```csharp public class PaymentApiHttpClient : HttpClientWrapper, IPaymentApiClient { public PaymentApiHttpClient( IHttpClientFactory httpClientFactory, IAuthenticationStrategyFactory authStrategyFactory, ILogger logger) : base(httpClientFactory, authStrategyFactory, logger) { } public async Task ProcessPaymentAsync( ProcessPaymentRequest request, CancellationToken cancellationToken = default) { return await PostAsync( "payments/process", request, cancellationToken); } public async Task GetPaymentAsync( string paymentId, CancellationToken cancellationToken = default) { return await GetAsync( $"payments/{paymentId}", cancellationToken); } } ``` ``` -------------------------------- ### Basic Aether ASP.NET Core Setup with Compression Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/response-compression/README.md Demonstrates a minimal setup for an ASP.NET Core application using Aether, including service registration and middleware configuration for response compression. ```csharp var builder = WebApplication.CreateBuilder(args); // Configure services builder.Services.AddAetherAspNetCore(); var app = builder.Build(); // Add compression middleware app.UseAppResponseCompression(); app.MapGet("/api/products", async (IProductService service) => { var products = await service.GetAllAsync(); return Results.Ok(products); // Response will be compressed }); app.Run(); ``` -------------------------------- ### Using the Client Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Demonstrates how to inject and use a custom HTTP client in another service. ```APIDOC ## Using the Client Example ### Description Shows how to inject a custom HTTP client interface into a service and use its methods to interact with an external API. ### Example ```csharp public class OrderService { private readonly IPaymentApiClient _paymentClient; public OrderService(IPaymentApiClient paymentClient) { _paymentClient = paymentClient; } public async Task ProcessOrderAsync(Order order) { var paymentRequest = new ProcessPaymentRequest { Amount = order.TotalAmount, Currency = "USD", OrderId = order.Id.ToString() }; var result = await _paymentClient.ProcessPaymentAsync(paymentRequest); if (result.Success) { order.MarkAsPaid(); await _orderRepository.UpdateAsync(order); } } } ``` ``` -------------------------------- ### Minimal Aether Setup in ASP.NET Core Source: https://github.com/burgan-tech/aether/blob/master/README.md Configure Aether services within an ASP.NET Core application. This minimal setup includes core Aether services, infrastructure, database context, event bus, and telemetry. Ensure you have the necessary NuGet packages installed. ```csharp var builder = WebApplication.CreateBuilder(args); // Configure Aether services builder.Services.AddAetherCore(options => options.ApplicationName = "MyApp"); builder.Services.AddAetherInfrastructure(); builder.Services.AddAetherDbContext(options => options.UseNpgsql(connectionString)); builder.Services.AddAetherEventBus(options => options.PubSubName = "pubsub"); builder.Services.AddAetherTelemetry(builder.Configuration, builder.Environment); var app = builder.Build(); app.UseCorrelationId(); app.UseUnitOfWorkMiddleware(); app.Run(); ``` -------------------------------- ### Basic Aether Telemetry Setup Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/telemetry/README.md Use this for a straightforward integration of Aether's telemetry services with default configurations. ```csharp services.AddAetherTelemetry(configuration, environment); ``` -------------------------------- ### Combined Aspect Usage Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/aspects/README.md An example demonstrating the combined use of [Trace], [Log], [Metric], and [UnitOfWork] attributes on a method. Note the recommended attribute order for optimal functionality. ```csharp public class OrderAppService { private readonly IRepository _repository; [Trace(Tags = new[] { "domain:orders" })] [Log(LogArguments = true)] [Metric(Type = MetricType.Histogram)] [UnitOfWork(IsTransactional = true)] public async Task CreateOrderAsync([Enrich] CreateOrderDto dto) { var order = new Order(dto.CustomerName); foreach (var item in dto.Items) { order.AddItem(item.ProductId, item.Quantity); } await _repository.InsertAsync(order); return _mapper.Map(order); } } ``` -------------------------------- ### OpenTelemetry Setup Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/aspects/README.md Configures OpenTelemetry services for tracing and metrics. Ensure 'BBT.Aether.Aspects' is added as a source for tracing and a meter for metrics. ```csharp builder.Services.AddOpenTelemetry() .WithTracing(tracing => tracing .AddSource("BBT.Aether.Aspects") // Required for [Trace] .AddAspNetCoreInstrumentation() .AddOtlpExporter()) .WithMetrics(metrics => metrics .AddMeter("BBT.Aether.Aspects") // Required for [Metric] .AddAspNetCoreInstrumentation() .AddOtlpExporter()); ``` -------------------------------- ### Logging Setup with Serilog Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/aspects/README.md Sets up Serilog for logging, enabling configuration from application settings and enriching logs with context. This is required for [Log] enrichment. ```csharp builder.Host.UseSerilog((context, config) => config .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext()); // Required for [Log] enrichment ``` -------------------------------- ### Manually Generate GUID in Service Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Demonstrates injecting IGuidGenerator into a service to manually create GUIDs for entities. ```csharp public class OrderService { private readonly IGuidGenerator _guidGenerator; public async Task CreateOrderAsync() { var orderId = _guidGenerator.Create(); var order = new Order(orderId); await _repository.InsertAsync(order); return order; } } ``` -------------------------------- ### CrudEntityAppService Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/application-services/README.md Shows a CrudEntityAppService that operates directly on entities without using DTOs. This is useful for simpler scenarios where DTO mapping is not required. ```csharp public class CategoryService : CrudEntityAppService { // Works directly with entities } ``` -------------------------------- ### Install Aether Aspects Package Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/aspects/README.md Add the BBT.Aether.Aspects package to your project using the .NET CLI. ```bash dotnet add package BBT.Aether.Aspects ``` -------------------------------- ### Basic Mapping Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/mapper/README.md Perform a basic mapping from a source entity to a DTO using the injected `IObjectMapper`. ```csharp public class ProductService(IObjectMapper mapper, IRepository repository) { public async Task GetProductAsync(Guid id) { var product = await repository.GetAsync(id); return mapper.Map(product); } } ``` -------------------------------- ### Entity Examples by Use Case Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/ddd/README.md Demonstrates different entity types based on their use case, including simple reference data, content with tracking, soft-deletable entities, and aggregates with domain events. ```csharp // Simple reference data - no auditing public class Category : Entity { } ``` ```csharp // Content with tracking public class BlogPost : AuditedEntity { } ``` ```csharp // Soft-deletable with full audit public class Customer : FullAuditedAggregateRoot { } ``` ```csharp // Aggregate with domain events public class Order : AuditedAggregateRoot { public void PlaceOrder() { AddDistributedEvent(new OrderPlacedEvent(Id)); } } ``` -------------------------------- ### Simple CRUD Service Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/application-services/README.md Demonstrates a basic CRUD service inheriting from CrudAppService. It automatically provides GetAsync, GetListAsync, CreateAsync, UpdateAsync, and DeleteAsync operations. ```csharp public class ProductAppService : CrudAppService { public ProductAppService( IServiceProvider serviceProvider, IRepository repository) : base(serviceProvider, repository) { } // Inherits: GetAsync, GetListAsync, CreateAsync, UpdateAsync, DeleteAsync } ``` -------------------------------- ### Usage in OrderService Source: https://context7.com/burgan-tech/aether/llms.txt Example of how to inject and use the `IPaymentApiClient` within another service to process payments. ```APIDOC ## OrderService Usage ### Description Demonstrates injecting `IPaymentApiClient` into `OrderService` and using its methods to process payments for orders. ### Example ```csharp public class OrderService { private readonly IPaymentApiClient _paymentClient; public OrderService(IPaymentApiClient paymentClient) { _paymentClient = paymentClient; } public async Task ProcessOrderAsync(Order order) { var result = await _paymentClient.ProcessPaymentAsync(new ProcessPaymentRequest { Amount = order.TotalAmount.Amount, Currency = order.TotalAmount.Currency, OrderId = order.Id.ToString() }); if (result?.Success == true) { order.MarkAsPaid(); await _orderRepo.UpdateAsync(order); } } } ``` ``` -------------------------------- ### Configure Aether Core with SimpleGuidGenerator Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Adds Aether Core services, defaulting to SimpleGuidGenerator for GUID generation. ```csharp // Core uses SimpleGuidGenerator services.AddAetherCore(options => { }); ``` -------------------------------- ### Implement a Custom GUID Generator Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Create a custom implementation of the `IGuidGenerator` interface to define your own GUID generation logic. This allows for incorporating specific requirements like machine IDs or data center IDs. ```csharp public class CustomGuidGenerator : IGuidGenerator { public Guid Create() { // Your custom logic // e.g., incorporate machine ID, data center ID, etc. return GenerateCustomGuid(); } private Guid GenerateCustomGuid() { // Custom implementation var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var machineId = GetMachineId(); var random = Random.Shared.Next(); // Combine into GUID format return new Guid(/* ... */); } } // Register services.AddSingleton(); ``` -------------------------------- ### Domain-Driven Design Usage Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/README.md Demonstrates the usage of DDD building blocks like Aggregate Roots and Domain Events, along with Aether's application services and aspects. ```csharp public class Order : AuditedAggregateRoot { public void PlaceOrder() { Status = OrderStatus.Placed; AddDistributedEvent(new OrderPlacedEvent(Id)); } } public class OrderAppService : CrudAppService { [Trace] [Log] [UnitOfWork] public async Task PlaceOrderAsync(Guid id) { var order = await Repository.GetAsync(id); order.PlaceOrder(); await Repository.UpdateAsync(order); return await MapToGetOutputDtoAsync(order); } } ``` -------------------------------- ### ReadOnlyAppService Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/application-services/README.md Demonstrates a read-only service for querying data. It integrates with IReadOnlyRepository and supports custom filtering via a dedicated DTO. ```csharp public class ProductQueryService : ReadOnlyAppService { public ProductQueryService( IServiceProvider serviceProvider, IReadOnlyRepository repository) : base(serviceProvider, repository) { } } ``` -------------------------------- ### Add Business Logic to Service Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/application-services/README.md Illustrates how to add custom business logic to an application service. The example shows adding a 'PlaceOrderAsync' method that includes domain logic and unit of work attributes. ```csharp public class OrderAppService : CrudAppService { [UnitOfWork] public async Task PlaceOrderAsync(Guid id) { var order = await Repository.GetAsync(id); order.PlaceOrder(); // Domain logic await Repository.UpdateAsync(order); return await MapToGetOutputDtoAsync(order); } } ``` -------------------------------- ### Aether Core Services Setup Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/README.md Configure core Aether services, database context, event bus, and aspects in your ASP.NET Core application. ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); // Core services builder.Services.AddAetherCore(options => options.ApplicationName = "MyApp"); builder.Services.AddAetherDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); // Event bus builder.Services.AddAetherEventBus(options => { options.PubSubName = "pubsub"; options.DefaultSource = "myapp"; }); // Aspects builder.Services.AddAetherAspects(); var app = builder.Build(); app.UseAmbientServiceProvider(); app.UseUnitOfWorkMiddleware(); app.MapControllers(); app.Run(); ``` -------------------------------- ### Setup Unit of Work Middleware Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/unit-of-work/README.md Configure the UoW middleware to manage transactions, optionally enabling it, setting transactional behavior, and defining a filter for requests. ```csharp var app = builder.Build(); app.UseUnitOfWorkMiddleware(options => { options.IsEnabled = true; options.IsTransactional = true; options.Filter = context => !HttpMethods.IsGet(context.Request.Method); }); ``` -------------------------------- ### OAuth Authentication Strategy Source: https://context7.com/burgan-tech/aether/llms.txt Example implementation of an `IAuthenticationStrategy` for OAuth 2.0 Bearer token authentication. ```APIDOC ## OAuthStrategy Class ### Description An `IAuthenticationStrategy` implementation that handles OAuth 2.0 Bearer token authentication, including token caching and expiry. ### Methods - `Task AuthenticateAsync(HttpClient client, CancellationToken ct)`: Authenticates the `HttpClient` by adding the Bearer token to the request headers. Refreshes the token if expired or not present. ``` -------------------------------- ### Configure BBT.Aether Services in Program.cs Source: https://context7.com/burgan-tech/aether/llms.txt This snippet shows how to register all necessary BBT.Aether services in the DI container and configure middleware for an ASP.NET Core application. Ensure all required NuGet packages are installed and connection strings are configured. ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddAetherCore(options => { options.ApplicationName = "OrderService"; }); builder.Services.AddAetherInfrastructure(); // Register EF Core DbContext with AuditInterceptor + UoW + Repository builder.Services.AddAetherDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); builder.Services.AddAetherApplication(); builder.Services.AddAetherAspNetCore(); // PostSharp aspects (Trace, Log, Metric, UnitOfWork attributes) builder.Services.AddAetherAspects(); // Object mapping (Mapperly – compile-time, zero-reflection) builder.Services.AddAetherMapperlyMapper([typeof(OrderMapper), typeof(ProductMapper)]); // Event bus builder.Services.AddAetherEventBus(options => { options.PubSubName = "pubsub"; options.DefaultSource = "order-service"; options.TopicPrefix = builder.Environment.EnvironmentName.ToLower(); options.AutoDiscoverHandlers = true; options.HandlerAssemblies = [typeof(Program).Assembly]; }); // OpenTelemetry builder.Services.AddAetherTelemetry(builder.Configuration, builder.Environment); // Redis distributed cache + lock builder.Services.AddSingleton( ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("Redis")!)); builder.Services.AddRedisDistributedCache(); builder.Services.AddRedisDistributedLock(); var app = builder.Build(); app.UseAmbientServiceProvider(); // Required for PostSharp aspects app.UseCorrelationId(); app.UseCurrentUser(); app.UseUnitOfWorkMiddleware(options => { options.IsEnabled = true; options.IsTransactional = true; options.Filter = ctx => !HttpMethods.IsGet(ctx.Request.Method); }); app.MapControllers(); app.Run(); ``` -------------------------------- ### appsettings.json Configuration for API Endpoints Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Example JSON structure for configuring API endpoints, including base URL, timeouts, media type, and default request headers. ```json { "ApiEndpoints": { "PaymentApi": { "BaseUrl": "https://api.payment-provider.com", "DefaultTimeOut": 30, "DefaultMediaTypeWithQualityHeaderValue": "application/json", "DefaultRequestHeaders": { "X-Api-Version": "2.0", "X-Client-Id": "my-app" } }, "NotificationApi": { "BaseUrl": "https://api.notifications.com", "DefaultTimeOut": 10, "DefaultMediaTypeWithQualityHeaderValue": "application/json" } } } ``` -------------------------------- ### Application Setup for Dapr Scheduled Jobs Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/background-job/README.md Build your application and map the Dapr scheduled job endpoint to enable job processing. ```csharp var app = builder.Build(); app.UseDaprScheduledJobHandler(); // Map Dapr job endpoint app.Run(); ``` -------------------------------- ### Install Aether Core Packages Source: https://github.com/burgan-tech/aether/blob/master/README.md Use the .NET CLI to add the core Aether packages to your project. These packages provide essential abstractions and infrastructure for building applications with the Aether Framework. ```bash dotnet add package BBT.Aether.AspNetCore dotnet add package BBT.Aether.Infrastructure ``` -------------------------------- ### Logging Access Token Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Example of logging an access token for debugging authentication issues. Ensure sensitive information is handled appropriately in production. ```csharp _logger.LogDebug("Token: {Token}", await _tokenService.GetAccessTokenAsync()); ``` -------------------------------- ### Setup Schema Resolution Middleware Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/multi-schema/README.md Integrate the schema resolution middleware into the application pipeline. Ensure it is placed after 'UseRouting' and before other middleware that depends on schema information. ```csharp var app = Build(); app.UseRouting(); app.UseSchemaResolution(); // After UseRouting app.MapControllers(); ``` -------------------------------- ### Configure API Timeout Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Configuration example to set a default timeout for a specific API endpoint. Useful for handling slow or unresponsive APIs. ```json { "ApiEndpoints": { "SlowApi": { "DefaultTimeOut": 120 } } } ``` -------------------------------- ### DbContext Setup for Outbox and Inbox Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/inbox-outbox/README.md Configure your DbContext to implement IHasEfCoreOutbox and IHasEfCoreInbox, and include DbSet properties for OutboxMessage and InboxMessage. Ensure to call modelBuilder.ConfigureOutbox() and modelBuilder.ConfigureInbox() in OnModelCreating. ```csharp public class MyDbContext : AetherDbContext, IHasEfCoreOutbox, IHasEfCoreInbox { public DbSet OutboxMessages { get; set; } public DbSet InboxMessages { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ConfigureOutbox(); modelBuilder.ConfigureInbox(); } } ``` -------------------------------- ### Basic Application Service with Default Mapping Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/mapper/README.md Demonstrates a basic application service inheriting from CrudAppService, which automatically handles mapping between entities and DTOs for standard operations like Get, Create, and Update. ```csharp public class ProductAppService : CrudAppService { public ProductAppService(IServiceProvider sp, IRepository repo) : base(sp, repo) { } // GetAsync → maps Product → ProductDto automatically // CreateAsync → maps CreateProductDto → Product automatically // UpdateAsync → maps UpdateProductDto → Product automatically } ``` -------------------------------- ### Automatic GUID Generation for Entities Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Example of an entity that will have its Guid ID automatically generated by the AuditInterceptor upon insertion. ```csharp public class Product : Entity { public string Name { get; private set; } // Constructor without ID - will be generated automatically public Product(string name) { Name = name; // Id will be set by AuditInterceptor on insert } } // Usage var product = new Product("Test Product"); // Id is default(Guid) await _repository.InsertAsync(product); // Id is generated here Console.WriteLine(product.Id); // Now has a value ``` -------------------------------- ### Automatic Tracing for AspNetCore Requests Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/telemetry/README.md AspNetCore requests are automatically traced when Aether telemetry is configured. This example shows a traced GET request. ```csharp // AspNetCore requests are automatically traced [HttpGet("{id}")] public async Task Get(Guid id) { return await _productService.GetAsync(id); // Traced } ``` -------------------------------- ### Build the framework solution Source: https://github.com/burgan-tech/aether/blob/master/CLAUDE.md Use this command to build the entire framework solution. For a release build, include the `--configuration Release` flag. ```bash dotnet build framework/BBT.Aether.slnx ``` ```bash dotnet build framework/BBT.Aether.slnx --configuration Release ``` -------------------------------- ### Service Registration Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/application-services/README.md Shows how to register application services and the Aether AutoMapper with the dependency injection container. Ensure mapper setup is done after core service registration. ```csharp services.AddAetherApplication(); services.AddScoped(); // Mapper setup services.AddAetherAutoMapperMapper(new List { typeof(ApplicationMappingProfile) }); ``` -------------------------------- ### Automatic Tracing for HttpClient Calls Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/telemetry/README.md HttpClient calls are automatically traced when Aether telemetry is configured. This example demonstrates making a traced GET request. ```csharp // HttpClient calls are automatically traced var response = await _httpClient.GetAsync("https://api.example.com/products"); ``` -------------------------------- ### Current User Middleware Setup Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/ddd/README.md Enables the current user middleware in an ASP.NET Core application. This is typically required for automatic population of audit properties like CreatedBy and ModifiedBy. ```csharp services.AddAetherAspNetCore(); app.UseCurrentUser(); ``` -------------------------------- ### Idempotent Handler Example in C# Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/background-job/README.md Implement IBackgroundJobHandler to create jobs. Ensure handlers are idempotent by checking the current state before processing to prevent duplicate operations on retries. ```csharp public class ProcessPaymentJobHandler : IBackgroundJobHandler { public async Task HandleAsync(ProcessPaymentJobArgs args, CancellationToken ct) { var payment = await _repository.GetAsync(args.PaymentId, ct); // Check if already processed if (payment.Status == PaymentStatus.Completed) { _logger.LogInformation("Payment already processed"); return; } await ProcessPaymentLogicAsync(payment, ct); payment.MarkAsCompleted(); await _repository.UpdateAsync(payment, ct); } } ``` -------------------------------- ### DbContext Setup for Background Jobs Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/background-job/README.md Configure your DbContext to include the BackgroundJobInfo DbSet and call the necessary extension method for EF Core integration. ```csharp public class MyDbContext : AetherDbContext, IHasEfCoreBackgroundJobs { public DbSet BackgroundJobs { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ConfigureBackgroundJob(); } } ``` -------------------------------- ### Domain Event Dispatch Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/ddd/README.md Demonstrates adding distributed events to an aggregate root. The `OrderPlacedEvent` and `OrderCancelledEvent` are added using `AddDistributedEvent` and will be dispatched after a successful commit. ```csharp public class Order : AuditedAggregateRoot { public void PlaceOrder() { Status = OrderStatus.Placed; // Event dispatched after successful commit AddDistributedEvent(new OrderPlacedEvent(Id, CustomerId)); } public void Cancel() { Status = OrderStatus.Cancelled; AddDistributedEvent(new OrderCancelledEvent(Id)); } } ``` -------------------------------- ### Configure Default GUID Generator Source: https://context7.com/burgan-tech/aether/llms.txt Register `SequentialGuidGenerator` as the default for time-ordered GUIDs. Override this if you need standard random GUIDs. ```csharp services.AddSingleton(SequentialGuidGenerator.Instance); // UUIDv7 services.AddSingleton(SimpleGuidGenerator.Instance); // random ``` -------------------------------- ### Service Registration for HTTP Clients Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Demonstrates how to register typed HTTP clients and their corresponding implementations using dependency injection. ```csharp services.RegisterHttpClient(); services.RegisterHttpClient(); ``` -------------------------------- ### Test Real Sequential GUID Generator for Order Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Confirm that the `SequentialGuidGenerator` generates GUIDs in chronological order. This test generates two GUIDs with a small delay and asserts that the first GUID is chronologically smaller than the second. ```csharp [Fact] public void SequentialGuidGenerator_ShouldGenerateOrderedIds() { // Arrange var generator = SequentialGuidGenerator.Instance; // Act var guid1 = generator.Create(); Thread.Sleep(10); // Ensure different timestamp var guid2 = generator.Create(); // Assert Assert.True(guid1.CompareTo(guid2) < 0); // guid1 < guid2 } ``` -------------------------------- ### Simple Entity Example Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/ddd/README.md Defines a basic Entity with a unique identifier and a mutable property. Includes a protected constructor for ORM compatibility and a method to update the property. ```csharp public class Category : Entity { public string Name { get; private set; } protected Category() { } // For EF Core public Category(string name) { Name = name; } public void UpdateName(string newName) { Name = newName ?? throw new ArgumentNullException(nameof(newName)); } } ``` -------------------------------- ### Automatic GUID Assignment with AuditInterceptor Source: https://context7.com/burgan-tech/aether/llms.txt The `AuditInterceptor` automatically assigns a generated GUID to `IEntity` when its Id is `default` during save operations. ```csharp // Automatic via AuditInterceptor (recommended) public class Product : AuditedEntity { public string Name { get; private set; } = default!; public Product(string name) { Name = name; } // Id left as default } // AuditInterceptor sets product.Id on InsertAsync var product = new Product("Laptop"); await _repository.InsertAsync(product); Console.WriteLine(product.Id); // now a valid UUIDv7 ``` -------------------------------- ### Configure Aether Infrastructure for Sequential GUIDs Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Use `AddAetherInfrastructure()` to set the default GUID generation strategy to sequential. This is the recommended approach to avoid performance issues associated with random GUIDs in databases. ```csharp services.AddAetherInfrastructure(); ``` -------------------------------- ### Use Random GUIDs for Non-Database Scenarios Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Employ random GUIDs for scenarios like message IDs or correlation IDs where sequential ordering is not a requirement. `Guid.NewGuid()` can be used directly, or `SimpleGuidGenerator.Instance.Create()` for a simple random GUID. ```csharp // For message IDs, correlation IDs, etc. var correlationId = Guid.NewGuid(); var messageId = SimpleGuidGenerator.Instance.Create(); ``` -------------------------------- ### Implementing Pagination with Repository Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/repository-pattern/README.md Shows how to implement pagination using the repository's GetQueryableAsync method combined with LINQ extensions like ToPagedListAsync. Requires appropriate extension methods to be available. ```csharp public async Task> GetPagedAsync(int page, int pageSize) { var query = await _repository.GetQueryableAsync(); return await query .OrderBy(p => p.Name) .ToPagedListAsync(page, pageSize); } ``` -------------------------------- ### Manual GUID Generation Source: https://context7.com/burgan-tech/aether/llms.txt Inject `IGuidGenerator` into your services to manually create GUIDs for entities when needed. ```csharp // Manual use public class OrderFactory { private readonly IGuidGenerator _guidGen; public OrderFactory(IGuidGenerator guidGen) => _guidGen = guidGen; public Order Create(Guid customerId) { var orderId = _guidGen.Create(); // time-ordered UUIDv7 return new Order(orderId, customerId); } } ``` -------------------------------- ### UnitOfWork Attribute Examples Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/aspects/README.md Demonstrates different ways to use the [UnitOfWork] attribute for transaction management. It shows basic usage, explicit transaction control, setting isolation levels, and managing transaction scope. ```csharp // Basic transaction (non-transactional by default, uses SaveChanges) [UnitOfWork] public async Task CreateOrderAsync(OrderDto dto) { } ``` ```csharp // With explicit transaction [UnitOfWork(IsTransactional = true)] public async Task CreateOrderAsync(OrderDto dto) { } ``` ```csharp // With isolation level [UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task CreateOrderAsync(OrderDto dto) { } ``` ```csharp // Scope options [UnitOfWork(Scope = UnitOfWorkScopeOption.Required)] // Join existing or create new (default) [UnitOfWork(Scope = UnitOfWorkScopeOption.RequiresNew)] // Always create new transaction [UnitOfWork(Scope = UnitOfWorkScopeOption.Suppress)] // Non-transactional ``` -------------------------------- ### Create and Check Results Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/result-pattern/README.md Demonstrates how to create success and failure Result objects and how to check their state. ```csharp var success = Result.Ok(user); var failure = Result.Fail(Error.NotFound("user_not_found", "User not found")); if (result.IsSuccess) { var user = result.Value; } else { var error = result.Error; } ``` -------------------------------- ### Using IObjectMapper for Mapping Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/mapper/README.md Demonstrates how to use the `IObjectMapper` interface for mapping, showing both the non-generic and typed versions for direct injection. ```csharp // Via non-generic IObjectMapper (dispatches through MapperlyAdapter) public class ProductService(IObjectMapper mapper) { public ProductDto Get(Product product) => mapper.Map(product); } // Via typed IObjectMapper (direct injection) public class ProductService(IObjectMapper mapper) { public ProductDto Get(Product product) => mapper.Map(product); } // Via typed IMapperlyMapper (includes hook access) public class ProductService(IMapperlyMapper mapper) { public ProductDto Get(Product product) => mapper.Map(product); } ``` -------------------------------- ### AuditInterceptor Logic for GUID Generation Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Illustrates the internal logic of AuditInterceptor for checking and setting default GUID IDs on entities. ```csharp public class AuditInterceptor : SaveChangesInterceptor { private readonly IGuidGenerator _guidGenerator; private void CheckAndSetId(EntityEntry entry) { if (entry.Entity is IEntity entityWithGuidId) { TrySetGuidId(entry, entityWithGuidId); } } private void TrySetGuidId(EntityEntry entry, IEntity entity) { // Only set if ID is default (empty GUID) if (entity.Id != default) { return; } // Check for DatabaseGeneratedAttribute var idProperty = entry.Property("Id").Metadata.PropertyInfo!; var dbGeneratedAttr = ReflectionHelper .GetSingleAttributeOrDefault(idProperty); if (dbGeneratedAttr != null && dbGeneratedAttr.DatabaseGeneratedOption != DatabaseGeneratedOption.None) { return; // Let database generate } // Generate GUID EntityHelper.TrySetId( entity, () => _guidGenerator.Create(), true ); } } ``` -------------------------------- ### Basic Usage Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/result-pattern/README.md Demonstrates how to create and check Result objects for success or failure. ```APIDOC ## Basic Usage ### Description Demonstrates how to create and check Result objects for success or failure. ### Code Example ```csharp // Creating results var success = Result.Ok(user); var failure = Result.Fail(Error.NotFound("user_not_found", "User not found")); // Checking results if (result.IsSuccess) { var user = result.Value; } else { var error = result.Error; } ``` ``` -------------------------------- ### Implement a Custom GUID Generator Source: https://context7.com/burgan-tech/aether/llms.txt Create a custom `IGuidGenerator` implementation to embed specific data, like a shard ID, into the generated GUID. ```csharp // Custom generator public class ShardedGuidGenerator : IGuidGenerator { private readonly int _shardId; public ShardedGuidGenerator(int shardId) => _shardId = shardId; public Guid Create() { var bytes = Guid.CreateVersion7(DateTimeOffset.UtcNow).ToByteArray(); bytes[0] = (byte)_shardId; // embed shard ID in first byte return new Guid(bytes); } } services.AddSingleton(new ShardedGuidGenerator(shardId: 1)); ``` -------------------------------- ### Opt-out of Automatic GUID Generation Source: https://context7.com/burgan-tech/aether/llms.txt Use the `[DatabaseGenerated(DatabaseGeneratedOption.Identity)]` attribute to let the database generate the GUID, opting out of Aether's automatic assignment. ```csharp // Opt out (let database generate) public class LegacyEntity : Entity { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public override Guid Id { get; protected set; } } ``` -------------------------------- ### Scaffold new project using CLI tool Source: https://github.com/burgan-tech/aether/blob/master/CLAUDE.md Utilize the CLI tool to create new projects, specifying the project name, team name, type, and output path. ```bash dotnet run --project framework/src/BBT.Aether.Cli create PROJECT_NAME -tm TEAM_NAME -t api -o OUTPUT_PATH ``` -------------------------------- ### IDistributedCacheService Interface Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/distributed-cache/README.md The core interface for interacting with the distributed cache, providing methods for getting, setting, removing, refreshing, and getting or setting cache entries. ```APIDOC ## Interface IDistributedCacheService ### Methods - **GetAsync(string key, CancellationToken ct = default) where T : class** - Retrieves a cached item by its key. - **SetAsync(string key, T value, DistributedCacheEntryOptions? options = null, CancellationToken ct = default) where T : class** - Adds or updates an item in the cache with optional expiration settings. - **RemoveAsync(string key, CancellationToken ct = default)** - Removes an item from the cache by its key. - **RefreshAsync(string key, CancellationToken ct = default)** - Refreshes the expiration of an existing cache item. - **GetOrSetAsync(string key, Func> fetchFunc, DistributedCacheEntryOptions? options = null, CancellationToken ct = default) where T : class** - Retrieves an item from the cache, or if not found, fetches it using the provided function and stores it in the cache. ``` -------------------------------- ### Interface-based HTTP Client Registration Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Demonstrates registering an HTTP client implementation behind an interface for better abstraction and dependency injection. ```csharp // ✅ Good: Interface-based public interface IPaymentApiClient : IHttpClientWrapper { Task ProcessPaymentAsync(ProcessPaymentRequest request); } // Register interface services.RegisterHttpClient(); // Inject interface public OrderService(IPaymentApiClient paymentClient) { } ``` -------------------------------- ### Test Real Sequential GUID Generator for Uniqueness Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Verify that the `SequentialGuidGenerator` produces unique GUIDs when called multiple times. This test generates 1000 IDs and asserts that all are distinct. ```csharp [Fact] public void SequentialGuidGenerator_ShouldGenerateUniqueIds() { // Arrange var generator = SequentialGuidGenerator.Instance; var ids = new HashSet(); // Act for (int i = 0; i < 1000; i++) { ids.Add(generator.Create()); } // Assert Assert.Equal(1000, ids.Count); // All unique } ``` -------------------------------- ### Service Registration and Polly Policies Source: https://context7.com/burgan-tech/aether/llms.txt Demonstrates how to register the typed HTTP client and apply Polly policies for retries and circuit breakers. ```APIDOC ## Service Registration ### Description Registering `IPaymentApiClient` and `PaymentApiHttpClient` with dependency injection and configuring Polly policies. ### Code Examples ```csharp // Register typed client services.RegisterHttpClient(); // Configure HttpClient with Polly services.AddHttpClient() .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)), onRetry: (outcome, ts, retry, _) => logger.LogWarning("Retry {Retry} after {Delay}s", retry, ts.TotalSeconds))) .AddTransientHttpErrorPolicy(policy => policy.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))); ``` ``` -------------------------------- ### Build individual project Source: https://github.com/burgan-tech/aether/blob/master/CLAUDE.md Command to build a specific project within the framework, such as the infrastructure layer. ```bash dotnet build framework/src/BBT.Aether.Infrastructure ``` -------------------------------- ### Mock GUID Generator in Unit Tests Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Mock the `IGuidGenerator` interface in unit tests to control the generated GUIDs and verify the behavior of services that depend on them. This allows for predictable test outcomes. ```csharp public class OrderServiceTests { private readonly Mock _mockGuidGenerator; private readonly OrderService _service; [Fact] public async Task CreateOrder_ShouldUseGeneratedId() { // Arrange var expectedId = Guid.Parse("12345678-1234-1234-1234-123456789abc"); _mockGuidGenerator .Setup(g => g.Create()) .Returns(expectedId); // Act var order = await _service.CreateOrderAsync(); // Assert Assert.Equal(expectedId, order.Id); } } ``` -------------------------------- ### Use Default Sequential GUIDs for Database Entities Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Register Aether infrastructure services to automatically use sequential GUIDs for database entities by default. This is recommended for entities inheriting from AuditedAggregateRoot. ```csharp services.AddAetherInfrastructure(); public class Order : AuditedAggregateRoot { // Sequential GUID will be generated } ``` -------------------------------- ### Middleware Placement for Response Compression Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/response-compression/README.md Illustrates the correct order for middleware registration, emphasizing that UseAppResponseCompression should precede other middleware that might generate responses. ```csharp app.UseAppResponseCompression(); // First app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); ``` -------------------------------- ### Configure Response Compression in appsettings.json Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/response-compression/README.md Enable compression, specify providers (brotli, gzip), and define MIME types to be compressed or excluded. ```json { "ResponseCompression": { "Enable": true, "EnableForHttps": true, "Providers": ["brotli", "gzip"], "MimeTypes": [ "application/json", "application/xml", "text/plain", "text/csv" ], "ExcludedMimeTypes": [ "image/jpeg", "image/png", "video/mp4" ] } } ``` -------------------------------- ### Automatic Auditing Setup Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/ddd/README.md Configures the Aether DbContext to automatically handle auditing. This setup intercepts entity changes to set audit properties like CreatedAt, CreatedBy, ModifiedAt, ModifiedBy, and DeletedAt/DeletedBy. ```csharp // Automatic with AddAetherDbContext services.AddAetherDbContext(options => options.UseNpgsql(connectionString)); // AuditInterceptor automatically: // - Sets CreatedAt/CreatedBy on insert // - Sets ModifiedAt/ModifiedBy on update // - Sets DeletedAt/DeletedBy on soft delete ``` -------------------------------- ### Mocking HTTP Client for Unit Testing Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Demonstrates how to mock an HTTP client interface for unit testing. This allows isolation of the service logic from actual HTTP calls. ```csharp public class OrderServiceTests { private readonly Mock _mockPaymentClient; private readonly OrderService _service; [Fact] public async Task ProcessOrder_ShouldCallPaymentApi() { // Arrange var order = new Order { TotalAmount = 100m }; _mockPaymentClient .Setup(c => c.ProcessPaymentAsync(It.IsAny(), default)) .ReturnsAsync(new PaymentResponse { Success = true }); // Act await _service.ProcessOrderAsync(order); // Assert _mockPaymentClient.Verify( c => c.ProcessPaymentAsync( It.Is(r => r.Amount == 100m), default), Times.Once); } } ``` -------------------------------- ### Ensure Consistent GUID Strategy in Database Tables Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Avoid mixing sequential and random GUID generation strategies within the same database table to maintain optimal performance. Always use the same strategy for all entities in a table. ```csharp // ❌ Bad: Mixing strategies causes poor performance var order1 = new Order { Id = Guid.NewGuid() }; // Random var order2 = new Order { Id = _guidGenerator.Create() }; // Sequential // ✅ Good: Consistent strategy var order1 = new Order(); // Will get sequential GUID var order2 = new Order(); // Will get sequential GUID ``` -------------------------------- ### Define IGuidGenerator Interface Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md The core interface for all GUID generation strategies in Aether. ```csharp public interface IGuidGenerator { Guid Create(); } ``` -------------------------------- ### Manually Register Custom Guid Generator Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/guid-generation/README.md Registers a custom implementation of IGuidGenerator as a singleton service. ```csharp // Use custom implementation services.AddSingleton(); ``` -------------------------------- ### ProductService with IRepository Source: https://context7.com/burgan-tech/aether/llms.txt Demonstrates using the generic IRepository for CRUD operations and querying. The `[UnitOfWork]` attribute handles transaction management for methods that modify data. ```csharp public class ProductService { private readonly IRepository _repository; public ProductService(IRepository repository) => _repository = repository; // Get (throws EntityNotFoundException if missing) public Task GetAsync(Guid id) => _repository.GetAsync(id); // Find (returns null if missing) public Task FindAsync(Guid id) => _repository.FindAsync(id); // Filtered list public Task> GetActiveAsync() => _repository.GetListAsync(p => p.IsActive); // Paginated custom query public async Task> GetPagedAsync(int page, int pageSize) { var query = await _repository.GetQueryableAsync(); return await query .Where(p => p.IsActive) .OrderByDescending(p => p.CreatedAt) .ToPagedListAsync(page, pageSize); } [UnitOfWork] public async Task CreateAsync(CreateProductDto dto) { var product = new Product(dto.Name, new Money(dto.Price, "USD")); await _repository.InsertAsync(product); // autoSave defaults to false } [UnitOfWork] public async Task UpdateNameAsync(Guid id, string newName) { var product = await _repository.GetAsync(id); product.UpdateName(newName); await _repository.UpdateAsync(product); } [UnitOfWork] public Task DeleteAsync(Guid id) => _repository.DeleteAsync(id); // Bulk delete with predicate (no change tracking – direct SQL) [UnitOfWork] public Task PurgeExpiredAsync() => _repository.DeleteDirectAsync(p => p.ExpiresAt < DateTime.UtcNow); } ``` ```csharp public class MyDbContext : AetherDbContext { public DbSet Products { get; set; } = default!; public DbSet Orders { get; set; } = default!; public MyDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly); } } ``` -------------------------------- ### Bearer Token Authentication Strategy Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Example implementation of an authentication strategy for Bearer token authentication. ```APIDOC ## Bearer Token Authentication Strategy ### Description An example implementation of the `IAuthenticationStrategy` interface for handling Bearer token authentication. ### Implementation ```csharp public class BearerTokenAuthStrategy : IAuthenticationStrategy { private readonly ITokenService _tokenService; public async Task AuthenticateAsync(HttpClient client, CancellationToken cancellationToken) { var token = await _tokenService.GetAccessTokenAsync(cancellationToken); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); } } ``` ``` -------------------------------- ### Common CRUD Operations with Repository Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/repository-pattern/README.md Illustrates common Create, Read, Update, and Delete (CRUD) operations using the repository pattern. Includes fetching by ID, finding with nullability, retrieving lists with predicates, and various deletion methods. ```csharp // Get by ID (throws if not found) var product = await _repository.GetAsync(id); // Find by ID (returns null if not found) var product = await _repository.FindAsync(id); // Get list with predicate var products = await _repository.GetListAsync(p => p.Category == "Electronics"); // Insert await _repository.InsertAsync(product); // Update await _repository.UpdateAsync(product); // Delete by entity await _repository.DeleteAsync(product); // Delete by ID await _repository.DeleteAsync(id); // Bulk delete with predicate await _repository.DeleteDirectAsync(p => p.IsExpired); ``` -------------------------------- ### Get Value or Throw Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/result-pattern/README.md Retrieves the value from a successful Result, or throws an AetherException if the Result is a failure. ```csharp var user = result.GetValueOrThrow(); // Throws AetherException if failed ``` -------------------------------- ### Service Registration Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/http-client/README.md Shows how to register typed HTTP clients with the dependency injection container. ```APIDOC ## Service Registration ### Description Illustrates the process of registering custom HTTP client interfaces and their implementations with the dependency injection system. ### Example ```csharp services.RegisterHttpClient(); services.RegisterHttpClient(); ``` ``` -------------------------------- ### Executing Custom Queries with Repository Source: https://github.com/burgan-tech/aether/blob/master/framework/docs/repository-pattern/README.md Demonstrates how to perform custom queries by obtaining an IQueryable from the repository and applying LINQ methods such as Where, OrderBy, and Take. Requires EF Core's ToListAsync. ```csharp public async Task> GetTopSellingAsync(int count) { var query = await _repository.GetQueryableAsync(); return await query .Where(p => p.IsActive) .OrderByDescending(p => p.SalesCount) .Take(count) .ToListAsync(); } ```