Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Placeholder for the release pipeline.
# Triggers and jobs will be added when release automation is defined.
name: Release Pipeline
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/bin/
**/obj/
.vs/
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
# demo-application
# 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`.
1 change: 1 addition & 0 deletions docs/learning-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Learning Paths
23 changes: 23 additions & 0 deletions src/DemoApplication.Api/Controllers/StatusController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc;

namespace DemoApplication.Api.Controllers;

/// <summary>Exposes the API status contract used by smoke checks.</summary>
[ApiController]
[Route("api/status")]
public sealed class StatusController : ControllerBase
{
/// <summary>Returns a stable response envelope for a running API.</summary>
[HttpGet]
public ActionResult<ApiResponse<object>> GetStatus()
{
return Ok(ApiResponse<object>.Success(new { status = "ok" }));
}
}

/// <summary>Represents a consistent API response envelope.</summary>
public sealed record ApiResponse<T>(bool Succeeded, T? Data, IReadOnlyCollection<string> Errors)
{
/// <summary>Creates a successful response.</summary>
public static ApiResponse<T> Success(T data) => new(true, data, Array.Empty<string>());
}
14 changes: 14 additions & 0 deletions src/DemoApplication.Api/DemoApplication.Api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\DemoApplication.Application\DemoApplication.Application.csproj" />
<ProjectReference Include="..\DemoApplication.Infrastructure\DemoApplication.Infrastructure.csproj" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions src/DemoApplication.Api/DemoApplication.Api.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@DemoApplication.Api_HostAddress = http://localhost:5271

GET {{DemoApplication.Api_HostAddress}}/weatherforecast/
Accept: application/json

###
25 changes: 25 additions & 0 deletions src/DemoApplication.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -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<IStartupCheck, StartupCheck>();
builder.Services.AddHostedService<StartupValidationHostedService>();

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
{
}
23 changes: 23 additions & 0 deletions src/DemoApplication.Api/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
51 changes: 51 additions & 0 deletions src/DemoApplication.Api/StartupValidationHostedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using DemoApplication.Application.Abstractions;

namespace DemoApplication.Api
{

/// <summary>
/// Runs registered startup checks during host startup so unavailable infrastructure prevents serving requests.
/// </summary>
public sealed class StartupValidationHostedService : IHostedService
{
private readonly IReadOnlyCollection<IStartupCheck> _startupChecks;

/// <summary>
/// Initializes the hosted validator with the checks owned by the application composition root.
/// </summary>
/// <param name="startupChecks">Registered checks that validate required startup dependencies.</param>
public StartupValidationHostedService(IEnumerable<IStartupCheck> startupChecks)
{
ArgumentNullException.ThrowIfNull(
argument: startupChecks,
paramName: nameof(startupChecks));

_startupChecks = startupChecks.ToArray();
}

/// <summary>
/// Executes every startup check before the host begins accepting requests.
/// </summary>
/// <param name="cancellationToken">Token that cancels startup validation.</param>
/// <returns>A task that completes after all checks pass.</returns>
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);
}
}

/// <summary>
/// Completes hosted-service shutdown without retaining startup resources.
/// </summary>
/// <param name="cancellationToken">Token supplied by the host during shutdown.</param>
/// <returns>A completed task because this validator owns no shutdown work.</returns>
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
9 changes: 9 additions & 0 deletions src/DemoApplication.Api/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
9 changes: 9 additions & 0 deletions src/DemoApplication.Api/appsettings.Production.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
9 changes: 9 additions & 0 deletions src/DemoApplication.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
2 changes: 2 additions & 0 deletions src/DemoApplication.Api/wwwroot/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>Demo Application</title></head><body><main><h1>Demo Application API</h1></main></body></html>
8 changes: 8 additions & 0 deletions src/DemoApplication.Application/Abstractions/IStartupCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace DemoApplication.Application.Abstractions;

/// <summary>Defines an application startup validation check.</summary>
public interface IStartupCheck
{
/// <summary>Validates required application dependencies.</summary>
Task CheckAsync(CancellationToken cancellationToken);
}
13 changes: 13 additions & 0 deletions src/DemoApplication.Application/DemoApplication.Application.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\DemoApplication.Domain\DemoApplication.Domain.csproj" />
</ItemGroup>

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
9 changes: 9 additions & 0 deletions src/DemoApplication.Domain/DemoApplication.Domain.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
6 changes: 6 additions & 0 deletions src/DemoApplication.Domain/Placeholder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DemoApplication.Domain;

/// <summary>Marks the domain boundary for future business entities.</summary>
public sealed class Placeholder
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<ProjectReference Include="..\DemoApplication.Application\DemoApplication.Application.csproj" />
<ProjectReference Include="..\DemoApplication.Domain\DemoApplication.Domain.csproj" />
</ItemGroup>

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
13 changes: 13 additions & 0 deletions src/DemoApplication.Infrastructure/StartupCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using DemoApplication.Application.Abstractions;

namespace DemoApplication.Infrastructure;

/// <summary>Provides the baseline startup check for infrastructure wiring.</summary>
public sealed class StartupCheck : IStartupCheck
{
/// <summary>Completes successfully when baseline infrastructure is available.</summary>
public Task CheckAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
6 changes: 6 additions & 0 deletions src/DemoApplication.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Solution>
<Project Path="DemoApplication.Api/DemoApplication.Api.csproj" />
<Project Path="DemoApplication.Application/DemoApplication.Application.csproj" />
<Project Path="DemoApplication.Domain/DemoApplication.Domain.csproj" />
<Project Path="DemoApplication.Infrastructure/DemoApplication.Infrastructure.csproj" />
</Solution>