diff --git a/.github/workflows/release-pipeline.yml b/.github/workflows/release-pipeline.yml new file mode 100644 index 0000000..6560f3b --- /dev/null +++ b/.github/workflows/release-pipeline.yml @@ -0,0 +1,3 @@ +# Placeholder for the release pipeline. +# Triggers and jobs will be added when release automation is defined. +name: Release Pipeline diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1608518 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/bin/ +**/obj/ +.vs/ diff --git a/README.md b/README.md index 38d3d37..6aa41d6 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# demo-application \ No newline at end of file +# Demo Application + +API-first ASP.NET Core solution targeting .NET 10. + +## Architecture + +- `DemoApplication.Api` owns HTTP endpoints, middleware, health checks, and static SPA hosting. +- `DemoApplication.Application` owns use-case contracts and application orchestration. +- `DemoApplication.Domain` owns business entities and rules; it has no framework dependencies. +- `DemoApplication.Infrastructure` owns external integrations and implements Application abstractions. + +Dependencies point inward: API references Application and Infrastructure; Infrastructure references Application and Domain; Application references Domain. + +## Run + +From the repository root on Windows, Linux, or macOS: + +```bash +dotnet restore src/DemoApplication.slnx +dotnet run --project src/DemoApplication.Api +``` + +The API listens on the URL printed by the host. Verify startup with `GET /health` and `GET /api/status`. diff --git a/docs/learning-paths.md b/docs/learning-paths.md new file mode 100644 index 0000000..516c959 --- /dev/null +++ b/docs/learning-paths.md @@ -0,0 +1 @@ +# Learning Paths diff --git a/src/DemoApplication.Api/Controllers/StatusController.cs b/src/DemoApplication.Api/Controllers/StatusController.cs new file mode 100644 index 0000000..998bd4b --- /dev/null +++ b/src/DemoApplication.Api/Controllers/StatusController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DemoApplication.Api.Controllers; + +/// Exposes the API status contract used by smoke checks. +[ApiController] +[Route("api/status")] +public sealed class StatusController : ControllerBase +{ + /// Returns a stable response envelope for a running API. + [HttpGet] + public ActionResult> GetStatus() + { + return Ok(ApiResponse.Success(new { status = "ok" })); + } +} + +/// Represents a consistent API response envelope. +public sealed record ApiResponse(bool Succeeded, T? Data, IReadOnlyCollection Errors) +{ + /// Creates a successful response. + public static ApiResponse Success(T data) => new(true, data, Array.Empty()); +} diff --git a/src/DemoApplication.Api/DemoApplication.Api.csproj b/src/DemoApplication.Api/DemoApplication.Api.csproj new file mode 100644 index 0000000..3d663a6 --- /dev/null +++ b/src/DemoApplication.Api/DemoApplication.Api.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/src/DemoApplication.Api/DemoApplication.Api.http b/src/DemoApplication.Api/DemoApplication.Api.http new file mode 100644 index 0000000..27060ec --- /dev/null +++ b/src/DemoApplication.Api/DemoApplication.Api.http @@ -0,0 +1,6 @@ +@DemoApplication.Api_HostAddress = http://localhost:5271 + +GET {{DemoApplication.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/DemoApplication.Api/Program.cs b/src/DemoApplication.Api/Program.cs new file mode 100644 index 0000000..7790311 --- /dev/null +++ b/src/DemoApplication.Api/Program.cs @@ -0,0 +1,25 @@ +using DemoApplication.Application.Abstractions; +using DemoApplication.Api; +using DemoApplication.Infrastructure; +using Microsoft.AspNetCore.Diagnostics; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); +builder.Services.AddProblemDetails(); +builder.Services.AddHealthChecks(); +builder.Services.AddControllers(); +builder.Services.AddSingleton(); +builder.Services.AddHostedService(); + +WebApplication app = builder.Build(); +app.UseExceptionHandler(); +app.UseDefaultFiles(); +app.UseStaticFiles(); +app.MapHealthChecks("/health"); +app.MapControllers(); +app.MapFallbackToFile("index.html"); + +app.Run(); + +public partial class Program +{ +} diff --git a/src/DemoApplication.Api/Properties/launchSettings.json b/src/DemoApplication.Api/Properties/launchSettings.json new file mode 100644 index 0000000..d3e6584 --- /dev/null +++ b/src/DemoApplication.Api/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5271", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7167;http://localhost:5271", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/DemoApplication.Api/StartupValidationHostedService.cs b/src/DemoApplication.Api/StartupValidationHostedService.cs new file mode 100644 index 0000000..0a53038 --- /dev/null +++ b/src/DemoApplication.Api/StartupValidationHostedService.cs @@ -0,0 +1,51 @@ +using DemoApplication.Application.Abstractions; + +namespace DemoApplication.Api +{ + +/// +/// Runs registered startup checks during host startup so unavailable infrastructure prevents serving requests. +/// +public sealed class StartupValidationHostedService : IHostedService +{ + private readonly IReadOnlyCollection _startupChecks; + + /// + /// Initializes the hosted validator with the checks owned by the application composition root. + /// + /// Registered checks that validate required startup dependencies. + public StartupValidationHostedService(IEnumerable startupChecks) + { + ArgumentNullException.ThrowIfNull( + argument: startupChecks, + paramName: nameof(startupChecks)); + + _startupChecks = startupChecks.ToArray(); + } + + /// + /// Executes every startup check before the host begins accepting requests. + /// + /// Token that cancels startup validation. + /// A task that completes after all checks pass. + public async Task StartAsync(CancellationToken cancellationToken) + { + // Run each dependency check before the server starts so a failure prevents partial availability. + foreach (var startupCheck in _startupChecks) + { + // Await the check with the host cancellation token so shutdown interrupts pending validation. + await startupCheck.CheckAsync(cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Completes hosted-service shutdown without retaining startup resources. + /// + /// Token supplied by the host during shutdown. + /// A completed task because this validator owns no shutdown work. + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} +} diff --git a/src/DemoApplication.Api/appsettings.Development.json b/src/DemoApplication.Api/appsettings.Development.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/DemoApplication.Api/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/DemoApplication.Api/appsettings.Production.json b/src/DemoApplication.Api/appsettings.Production.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/DemoApplication.Api/appsettings.Production.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/DemoApplication.Api/appsettings.json b/src/DemoApplication.Api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/DemoApplication.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/DemoApplication.Api/wwwroot/index.html b/src/DemoApplication.Api/wwwroot/index.html new file mode 100644 index 0000000..e726677 --- /dev/null +++ b/src/DemoApplication.Api/wwwroot/index.html @@ -0,0 +1,2 @@ + +Demo Application

Demo Application API

diff --git a/src/DemoApplication.Application/Abstractions/IStartupCheck.cs b/src/DemoApplication.Application/Abstractions/IStartupCheck.cs new file mode 100644 index 0000000..c72205f --- /dev/null +++ b/src/DemoApplication.Application/Abstractions/IStartupCheck.cs @@ -0,0 +1,8 @@ +namespace DemoApplication.Application.Abstractions; + +/// Defines an application startup validation check. +public interface IStartupCheck +{ + /// Validates required application dependencies. + Task CheckAsync(CancellationToken cancellationToken); +} diff --git a/src/DemoApplication.Application/DemoApplication.Application.csproj b/src/DemoApplication.Application/DemoApplication.Application.csproj new file mode 100644 index 0000000..1e3bf36 --- /dev/null +++ b/src/DemoApplication.Application/DemoApplication.Application.csproj @@ -0,0 +1,13 @@ + + + + + + + + net10.0 + enable + enable + + + diff --git a/src/DemoApplication.Domain/DemoApplication.Domain.csproj b/src/DemoApplication.Domain/DemoApplication.Domain.csproj new file mode 100644 index 0000000..b760144 --- /dev/null +++ b/src/DemoApplication.Domain/DemoApplication.Domain.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/src/DemoApplication.Domain/Placeholder.cs b/src/DemoApplication.Domain/Placeholder.cs new file mode 100644 index 0000000..4403a06 --- /dev/null +++ b/src/DemoApplication.Domain/Placeholder.cs @@ -0,0 +1,6 @@ +namespace DemoApplication.Domain; + +/// Marks the domain boundary for future business entities. +public sealed class Placeholder +{ +} diff --git a/src/DemoApplication.Infrastructure/DemoApplication.Infrastructure.csproj b/src/DemoApplication.Infrastructure/DemoApplication.Infrastructure.csproj new file mode 100644 index 0000000..7aa6826 --- /dev/null +++ b/src/DemoApplication.Infrastructure/DemoApplication.Infrastructure.csproj @@ -0,0 +1,14 @@ + + + + + + + + + net10.0 + enable + enable + + + diff --git a/src/DemoApplication.Infrastructure/StartupCheck.cs b/src/DemoApplication.Infrastructure/StartupCheck.cs new file mode 100644 index 0000000..afd7866 --- /dev/null +++ b/src/DemoApplication.Infrastructure/StartupCheck.cs @@ -0,0 +1,13 @@ +using DemoApplication.Application.Abstractions; + +namespace DemoApplication.Infrastructure; + +/// Provides the baseline startup check for infrastructure wiring. +public sealed class StartupCheck : IStartupCheck +{ + /// Completes successfully when baseline infrastructure is available. + public Task CheckAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/src/DemoApplication.slnx b/src/DemoApplication.slnx new file mode 100644 index 0000000..8fd79bb --- /dev/null +++ b/src/DemoApplication.slnx @@ -0,0 +1,6 @@ + + + + + +