Skip to content

Commit c094a88

Browse files
authored
Upgrade to .NET 10, Aspire 13, and System.CommandLine 2.0 (#794)
### Summary & Motivation Upgrade the codebase to .NET 10 and adopt new C# 14 language features. - Update all projects to target .NET 10. - Update to Aspire 13 and use `Aspire.Hosting.JavaScript` over the deprecated `Aspire.Hosting.NodeJS`. - Update Docker images to use secure chiseled base images: `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra` for projects using Entity Framework, and `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled` for AppGateway. - Upgrade `System.CommandLine` to 2.0.0 and migrate from `NamingConventionBinder` to the new `SetAction` API. - Convert extension methods to C# 14 extension members syntax using `extension(Type param)`. - Update properties with backing fields to use the new `field` keyword. - Remove `params` keyword from extension methods to avoid Roslyn compiler errors ([dotnet/roslyn#80024](dotnet/roslyn#80024)). ### Downstream projects Downstream projects must make the following changes to align with this upgrade: 1. **Update all `*.csproj` files** to target .NET 10: ```diff - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> ``` 2. **Update all WebApp `.esproj` files** (e.g., `application/your-self-contained-system/WebApp/YourSystem.WebApp.esproj`): ```diff - <TargetFrameworkMoniker>net9.0</TargetFrameworkMoniker> + <TargetFrameworkMoniker>net10.0</TargetFrameworkMoniker> ``` 3. **Update Dockerfiles** to use secure chiseled base images: - For API and Workers projects (that use Entity Framework): ```diff - FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine - RUN apk add --no-cache icu-libs - ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false + FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled-extra ``` 4. **Remove WebApp `.esproj` references from AppHost** in `application/AppHost/AppHost.csproj`: ```diff - <ProjectReference Include="..\your-self-contained-system\WebApp\YourSystem.WebApp.esproj"/> ``` With `Aspire.Hosting.JavaScript` using the new `AddJavaScriptApp()` over `AddNpmApp()` eliminates the need for `.esproj` project references in AppHost. 5. **Update `application/your-self-contained-system/Tests/EndpointBaseTest.cs`** to use the new `field` keyword for auto-implemented properties with backing fields: ```diff - private ServiceProvider? _provider; - protected ServiceProvider Provider => _provider ??= Services.BuildServiceProvider(); + protected ServiceProvider Provider => field ??= Services.BuildServiceProvider(); ``` Also update the reference in `EndpointBaseTest` constructor: ```diff - using var serviceScope = Provider.CreateScope(); + using var serviceScope = Provider!.CreateScope(); ``` 6. **Fix extension method calls in SharedKernel** that had the `params` keyword removed due to [Roslyn issue #80024](dotnet/roslyn#80024): - Methods like `AddApiServices()`, `AddSharedInfrastructure()`, `AddWorkerServices()`, and similar extension methods in SharedKernel no longer use `params` for their assembly parameters. - Update calls to pass arrays explicitly: `services.AddApiServices([assembly1, assembly2])` instead of `services.AddApiServices(assembly1, assembly2)`. - Example from `application/your-self-contained-system/Api/Program.cs`: ```diff - builder.Services.AddApiServices(Assembly.GetExecutingAssembly(), typeof(Configuration).Assembly); + builder.Services.AddApiServices([Assembly.GetExecutingAssembly(), typeof(Configuration).Assembly]); ``` 7. **If you have a custom developer CLI**, update any custom commands to use System.CommandLine 2.0.0 syntax: - Replace `System.CommandLine.NamingConventionBinder` with `System.CommandLine.Invocation`. - Replace `Handler = CommandHandler.Create<...>(Execute)` with `this.SetAction(parseResult => Execute(...))`. - Update option definitions from inline construction to explicit `Options.Add()` calls. - See `developer-cli/Commands/CheckCommand.cs` for a complete example. 8. **Optional: Adopt C# 14 features:** - Convert extension methods to use the new `extension(Type param)` syntax (see `application/shared-kernel/SharedKernel/ApiResults/ApiResultExtensions.cs` for examples). - Update properties with explicit backing fields to use the new `field` keyword (see the `EndpointBaseTest.cs` change in step 5). 9. **Developers need to install .NET 10 SDK.** Aspire's AppHost automatically handles SSL certificate creation and management when running the application locally. If you encounter certificate-related errors after upgrading, the AppHost will regenerate certificates automatically on the next run. ### Checklist - [x] I have added tests, or done manual regression tests - [x] I have updated the documentation, if necessary
2 parents a1e14c5 + b53873f commit c094a88

File tree

83 files changed

+1497
-1226
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+1497
-1226
lines changed

.cursor/rules/main.mdc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,18 @@ This is a mono repository with multiple self-contained systems (SCS), each being
4242
- [application](mdc:application): Contains application code:
4343
- [account-management](mdc:application/account-management): An SCS for tenant and user management:
4444
- [WebApp](mdc:application/account-management/WebApp): A React, TypeScript SPA.
45-
- [Api](mdc:application/account-management/Api): .NET 9 minimal API.
46-
- [Core](mdc:application/account-management/Core): .NET 9 Vertical Sliced Architecture.
45+
- [Api](mdc:application/account-management/Api): .NET 10 minimal API.
46+
- [Core](mdc:application/account-management/Core): .NET 10 Vertical Sliced Architecture.
4747
- [Workers](mdc:application/account-management/Workers): A .NET Console job.
4848
- [Tests](mdc:application/account-management/Tests): xUnit tests for backend.
4949
- [back-office](mdc:application/back-office): An empty SCS that will be used to create tools for Support and System Admins:
5050
- [WebApp](mdc:application/back-office/WebApp): A React, TypeScript SPA.
51-
- [Api](mdc:application/back-office/Api): .NET 9 minimal API.
52-
- [Core](mdc:application/back-office/Core): .NET 9 Vertical Sliced Architecture.
51+
- [Api](mdc:application/back-office/Api): .NET 10 minimal API.
52+
- [Core](mdc:application/back-office/Core): .NET 10 Vertical Sliced Architecture.
5353
- [Workers](mdc:application/back-office/Workers): A .NET Console job.
5454
- [Tests](mdc:application/back-office/Tests): xUnit tests for backend.
55-
- [AppHost](mdc:application/AppHost): .NET Aspire project for orchestrating SCSs and Docker containers. Never run directly—typically running in watch mode.
56-
- [AppGateway](mdc:application/AppGateway): Main entry point using .NET YARP as reverse proxy for all SCSs.
55+
- [AppHost](mdc:application/AppHost): Aspire project for orchestrating SCSs and Docker containers. Never run directly—typically running in watch mode.
56+
- [AppGateway](mdc:application/AppGateway): Main entry point using YARP as reverse proxy for all SCSs.
5757
- [shared-kernel](mdc:application/shared-kernel): Reusable .NET backend shared by all SCSs.
5858
- [shared-webapp](mdc:application/shared-webapp): Reusable frontend shared by all SCSs.
5959
- [cloud-infrastructure](mdc:cloud-infrastructure): Bash and Azure Bicep scripts (IaC).

.github/workflows/app-gateway.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
- name: Setup .NET Core SDK
119119
uses: actions/setup-dotnet@v4
120120
with:
121-
dotnet-version: 9.0.x
121+
global-json-file: application/global.json
122122

123123
- name: Restore .NET Tools
124124
working-directory: application

.windsurf/rules/backend/commands.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ public sealed class CreateUserValidator : AbstractValidator<CreateUserCommand>
6363
public CreateUserValidator()
6464
{
6565
// ✅ DO: Use the same message for better user experience and easier localization
66-
RuleFor(x => x.Name)
67-
.NotEmpty().WithMessage("Name must be between 1 and 50 characters.")
68-
.MaximumLength(50).WithMessage("Name must be between 1 and 50 characters.");
66+
RuleFor(x => x.Name).Length(1, 50).WithMessage("Name must be between 1 and 50 characters.");
6967
}
7068
}
7169

@@ -99,7 +97,7 @@ public sealed class CreateUserValidator : AbstractValidator<CreateUserCommand>
9997
{
10098
public CreateUserValidator()
10199
{
102-
// ❌ DON'T: Use different validation messages for the same property
100+
// ❌ DON'T: Use different validation messages for the same property and redundant validation rules
103101
RuleFor(x => x.Name)
104102
.NotEmpty().WithMessage("Name must not be empty.")
105103
.MaximumLength(50).WithMessage("Name must not be more than 50 characters.");

.windsurf/rules/main.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,18 @@ This is a mono repository with multiple self-contained systems (SCS), each being
4242
- [application](/application): Contains application code:
4343
- [account-management](/application/account-management): An SCS for tenant and user management:
4444
- [WebApp](/application/account-management/WebApp): A React, TypeScript SPA.
45-
- [Api](/application/account-management/Api): .NET 9 minimal API.
46-
- [Core](/application/account-management/Core): .NET 9 Vertical Sliced Architecture.
45+
- [Api](/application/account-management/Api): .NET 10 minimal API.
46+
- [Core](/application/account-management/Core): .NET 10 Vertical Sliced Architecture.
4747
- [Workers](/application/account-management/Workers): A .NET Console job.
4848
- [Tests](/application/account-management/Tests): xUnit tests for backend.
4949
- [back-office](/application/back-office): An empty SCS that will be used to create tools for Support and System Admins:
5050
- [WebApp](/application/back-office/WebApp): A React, TypeScript SPA.
51-
- [Api](/application/back-office/Api): .NET 9 minimal API.
52-
- [Core](/application/back-office/Core): .NET 9 Vertical Sliced Architecture.
51+
- [Api](/application/back-office/Api): .NET 10 minimal API.
52+
- [Core](/application/back-office/Core): .NET 10 Vertical Sliced Architecture.
5353
- [Workers](/application/back-office/Workers): A .NET Console job.
5454
- [Tests](/application/back-office/Tests): xUnit tests for backend.
55-
- [AppHost](/application/AppHost): .NET Aspire project for orchestrating SCSs and Docker containers. Never run directly—typically running in watch mode.
56-
- [AppGateway](/application/AppGateway): Main entry point using .NET YARP as reverse proxy for all SCSs.
55+
- [AppHost](/application/AppHost): Aspire project for orchestrating SCSs and Docker containers. Never run directly—typically running in watch mode.
56+
- [AppGateway](/application/AppGateway): Main entry point using YARP as reverse proxy for all SCSs.
5757
- [shared-kernel](/application/shared-kernel): Reusable .NET backend shared by all SCSs.
5858
- [shared-webapp](/application/shared-webapp): Reusable frontend shared by all SCSs.
5959
- [cloud-infrastructure](/cloud-infrastructure): Bash and Azure Bicep scripts (IaC).

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Built to demonstrate seamless flow—backend contracts feed a fully-typed React
2727

2828
## What's inside
2929

30-
* **Backend** - .NET 9 and C# adhering to the principles of vertical slice architecture, DDD, CQRS, and clean code
30+
* **Backend** - .NET 10 and C# 14 adhering to the principles of vertical slice architecture, DDD, CQRS, and clean code
3131
* **Frontend** – React 19, TypeScript, TanStack Router & Query, React Aria for accessible and UI
3232
* **CI/CD** - GitHub actions for fast passwordless deployments of docker containers and infrastructure (Bicep)
3333
* **Infrastructure** - Cost efficient and scalable Azure PaaS services like Azure Container Apps, Azure SQL, etc.
@@ -59,7 +59,7 @@ For development, you need .NET, Docker, and Node. And GitHub and Azure CLI for s
5959
2. From an Administrator PowerShell terminal, use [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/) (preinstalled on Windows 11) to install any missing packages:
6060

6161
```powershell
62-
winget install Microsoft.DotNet.SDK.9
62+
winget install Microsoft.DotNet.SDK.10
6363
winget install Git.Git
6464
winget install Docker.DockerDesktop
6565
winget install OpenJS.NodeJS
@@ -125,10 +125,10 @@ Open a terminal and run the following commands (if not installed):
125125
sudo apt-get update
126126
```
127127

128-
- Install .NET SDK 9.0, Node, GitHub CLI
128+
- Install .NET SDK 10.0, Node, GitHub CLI
129129

130130
```bash
131-
sudo apt-get install -y dotnet-sdk-9.0 nodejs gh
131+
sudo apt-get install -y dotnet-sdk-10.0 nodejs gh
132132
```
133133

134134
- Install Azure CLI
@@ -161,7 +161,7 @@ We recommend you keep the commit history, which serves as a great learning and t
161161

162162
## 2. Run the Aspire AppHost to spin up everything on localhost
163163

164-
Using .NET Aspire, docker images with SQL Server, Blob Storage emulator, and development mail server will be downloaded and started. No need install anything, or learn complicated commands. Simply run this command, and everything just works 🎉
164+
Using Aspire, docker images with SQL Server, Blob Storage emulator, and development mail server will be downloaded and started. No need install anything, or learn complicated commands. Simply run this command, and everything just works 🎉
165165

166166
```bash
167167
cd application/AppHost
@@ -205,7 +205,7 @@ PlatformPlatform is a [monorepo](https://en.wikipedia.org/wiki/Monorepo) contain
205205
├─ .github # Separate GitHub workflows for deploying Infrastructure and app
206206
├─ .windsurf # Copy of .cursor for Windsurf AI editor (synchronized by CLI)
207207
├─ application # Contains the application source code
208-
│ ├─ AppHost # .NET Aspire project starting app and all dependencies in Docker
208+
│ ├─ AppHost # Aspire project starting app and all dependencies in Docker
209209
│ ├─ AppGateway # Main entry point for the app using YARP as a reverse proxy
210210
│ ├─ account-management # Self-contained system with account sign-up, user management, etc.
211211
│ │ ├─ WebApp # React SPA frontend using TypeScript and React Aria Components
@@ -233,12 +233,12 @@ PlatformPlatform is a [monorepo](https://en.wikipedia.org/wiki/Monorepo) contain
233233

234234
# Technologies
235235

236-
### .NET 9 Backend With Vertical Sliced Architecture, DDD, CQRS, Minimal API, and Aspire
236+
### .NET 10 Backend With Vertical Sliced Architecture, DDD, CQRS, Minimal API, and Aspire
237237

238238
The backend is built using the most popular, mature, and commonly used technologies in the .NET ecosystem:
239239

240-
- [.NET 9](https://dotnet.microsoft.com) and [C# 13](https://learn.microsoft.com/en-us/dotnet/csharp/tour-of-csharp)
241-
- [.NET Aspire](https://aka.ms/dotnet-aspire)
240+
- [.NET 10](https://dotnet.microsoft.com) and [C# 14](https://learn.microsoft.com/en-us/dotnet/csharp/tour-of-csharp)
241+
- [Aspire](https://aka.ms/dotnet-aspire)
242242
- [YARP](https://microsoft.github.io/reverse-proxy)
243243
- [ASP.NET Minimal API](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis)
244244
- [Entity Framework](https://learn.microsoft.com/en-us/ef)

application/AppGateway/ApiAggregation/ApiAggregationEndpoints.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,29 @@ namespace PlatformPlatform.AppGateway.ApiAggregation;
22

33
public static class Endpoints
44
{
5-
public static WebApplication ApiAggregationEndpoints(this WebApplication app)
5+
extension(WebApplication app)
66
{
7-
app.MapGet("/swagger", context =>
8-
{
9-
context.Response.Redirect("/openapi/v1");
10-
return Task.CompletedTask;
11-
}
12-
);
7+
public WebApplication ApiAggregationEndpoints()
8+
{
9+
app.MapGet("/swagger", context =>
10+
{
11+
context.Response.Redirect("/openapi/v1");
12+
return Task.CompletedTask;
13+
}
14+
);
1315

14-
app.MapGet("/openapi", context =>
15-
{
16-
context.Response.Redirect("/openapi/v1");
17-
return Task.CompletedTask;
18-
}
19-
);
16+
app.MapGet("/openapi", context =>
17+
{
18+
context.Response.Redirect("/openapi/v1");
19+
return Task.CompletedTask;
20+
}
21+
);
2022

21-
app.MapGet("/openapi/v1.json", async (ApiAggregationService apiAggregationService)
22-
=> Results.Content(await apiAggregationService.GetAggregatedOpenApiJson(), "application/json")
23-
).CacheOutput(c => c.Expire(TimeSpan.FromMinutes(5)));
23+
app.MapGet("/openapi/v1.json", async (ApiAggregationService apiAggregationService)
24+
=> Results.Content(await apiAggregationService.GetAggregatedOpenApiJson(), "application/json")
25+
).CacheOutput(c => c.Expire(TimeSpan.FromMinutes(5)));
2426

25-
return app;
27+
return app;
28+
}
2629
}
2730
}

application/AppGateway/AppGateway.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFramework>net10.0</TargetFramework>
55
<AssemblyName>PlatformPlatform.AppGateway</AssemblyName>
66
<RootNamespace>PlatformPlatform.AppGateway</RootNamespace>
77
<Nullable>enable</Nullable>

application/AppGateway/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine
1+
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled
22

33
WORKDIR /app
44
COPY ./AppGateway/publish .

application/AppGateway/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
);
3737
}
3838

39-
builder.AddNamedBlobStorages(("account-management-storage", "ACCOUNT_MANAGEMENT_STORAGE_URL"));
39+
builder.AddNamedBlobStorages([("account-management-storage", "ACCOUNT_MANAGEMENT_STORAGE_URL")]);
4040

4141
builder.WebHost.UseKestrel(option => option.AddServerHeader = false);
4242

application/AppHost/AppHost.csproj

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,25 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
2-
3-
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.2"/>
1+
<Project Sdk="Aspire.AppHost.Sdk/13.0.0">
42

53
<PropertyGroup>
64
<OutputType>Exe</OutputType>
7-
<TargetFramework>net9.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
86
<ImplicitUsings>enable</ImplicitUsings>
97
<Nullable>enable</Nullable>
108
<UserSecretsId>platformplatform-f817f2a1-ac57-4756-aef2-a57ca864bbd3</UserSecretsId>
119
</PropertyGroup>
1210

1311
<ItemGroup>
1412
<ProjectReference Include="..\account-management\Api\AccountManagement.Api.csproj"/>
15-
<ProjectReference Include="..\account-management\WebApp\AccountManagement.WebApp.esproj"/>
1613
<ProjectReference Include="..\account-management\Workers\AccountManagement.Workers.csproj"/>
1714
<ProjectReference Include="..\AppGateway\AppGateway.csproj"/>
1815
<ProjectReference Include="..\back-office\Api\BackOffice.Api.csproj"/>
19-
<ProjectReference Include="..\back-office\WebApp\BackOffice.WebApp.esproj"/>
2016
<ProjectReference Include="..\back-office\Workers\BackOffice.Workers.csproj"/>
2117
</ItemGroup>
2218

2319
<ItemGroup>
2420
<PackageReference Include="Aspire.Azure.Storage.Blobs"/>
25-
<PackageReference Include="Aspire.Hosting.AppHost"/>
2621
<PackageReference Include="Aspire.Hosting.Azure.Storage"/>
27-
<PackageReference Include="Aspire.Hosting.NodeJs"/>
22+
<PackageReference Include="Aspire.Hosting.JavaScript"/>
2823
<PackageReference Include="Aspire.Hosting.SqlServer"/>
2924
<PackageReference Include="Microsoft.Extensions.Configuration"/>
3025
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets"/>

0 commit comments

Comments
 (0)