Skip to content
This repository was archived by the owner on Feb 2, 2025. It is now read-only.

Commit 4154c34

Browse files
committed
docs: Add Todo sample
1 parent cfebe89 commit 4154c34

36 files changed

+1369
-368
lines changed

.config/dotnet-tools.json

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
"commands": [
2626
"dotnet-outdated"
2727
]
28+
},
29+
"dotnet-ef": {
30+
"version": "8.0.6",
31+
"commands": [
32+
"dotnet-ef"
33+
]
2834
}
2935
}
3036
}

.vscode/launch.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"version": "0.2.0",
33
"configurations": [
44
{
5-
"name": "Debug AppHost",
5+
"name": "Debug GettingStarted:AppHost",
66
"type": "coreclr",
77
"request": "launch",
88
"preLaunchTask": "build",
@@ -11,6 +11,17 @@
1111
"cwd": "${workspaceFolder}/samples/GettingStarted/AppHost",
1212
"stopAtEntry": false,
1313
"console": "externalTerminal"
14+
},
15+
{
16+
"name": "Debug Todo:AppHost",
17+
"type": "coreclr",
18+
"request": "launch",
19+
"preLaunchTask": "build",
20+
"program": "${workspaceFolder}/samples/Todo/AppHost/bin/Debug/net8.0/AppHost.dll",
21+
"args": [],
22+
"cwd": "${workspaceFolder}/samples/Todo/AppHost",
23+
"stopAtEntry": false,
24+
"console": "externalTerminal"
1425
}
1526
]
1627
}

README.md

+16-18
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,32 @@ dotnet add package Nall.Aspire.Hosting.DependsOn.All
2828
```csharp
2929
// AppHost/Program.cs
3030
var builder = DistributedApplication.CreateBuilder(args);
31-
builder.Services.Configure<DependsOnOptions>(builder.Configuration.GetRequiredSection("DependsOnOptions"));
3231

33-
var db = builder.AddSqlServer("sql")
34-
.WithHealthCheck()
35-
.AddDatabase("db");
32+
var admin = builder.AddParameter("postgres-admin", secret: true);
33+
var password = builder.AddParameter("postgres-password", secret: true);
3634

37-
var rabbit = builder.AddRabbitMQ("rabbit")
38-
.WithHealthCheck();
35+
builder.Services.Configure<DependsOnOptions>(builder.Configuration.GetSection("DependsOnOptions"));
3936

40-
var console = builder.AddProject<Projects.ConsoleApp>("console");
37+
var dbServer = builder.AddPostgres("db-server", admin, password, 5432).WithHealthCheck();
4138

42-
var api0 = builder.AddProject<Projects.WebApplication2>("api-unhealthy-for-a-little-bit")
43-
.WithHealthCheck();
39+
dbServer.WithPgAdmin(c => c.WithHostPort(5050).WaitFor(dbServer));
4440

45-
builder
46-
.AddProject<Projects.WebApplication1>("api")
47-
.WithExternalHttpEndpoints()
41+
var db = dbServer.AddDatabase("db");
42+
43+
var migrator = builder
44+
.AddProject<Projects.MigrationService>("migrator")
45+
.WithReference(db)
46+
.WaitFor(db);
47+
48+
var api = builder
49+
.AddProject<Projects.Api>("api")
4850
.WithReference(db)
49-
.WithReference(rabbit)
50-
.WaitFor(db)
51-
.WaitFor(rabbit)
52-
.WaitFor(api0)
53-
.WaitForCompletion(console);
51+
.WaitForCompletion(migrator);
5452

5553
builder.Build().Run();
5654
```
5755

58-
![alt](/assets/example-1.png)
56+
![alt](/assets/demo-depends-on.gif)
5957

6058
## Build and Development
6159

assets/demo-depends-on.gif

+3
Loading

samples/Todo/Api/Api.csproj

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" />
11+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
12+
<PackageReference Include="Microsoft.EntityFrameworkCore" />
13+
<PackageReference Include="MiniValidation" />
14+
<PackageReference Include="Swashbuckle.AspNetCore" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="..\ServiceDefaults\ServiceDefaults.csproj" />
19+
<ProjectReference Include="..\Data\Data.csproj" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<InternalsVisibleTo Include="$(AssemblyName).IntegrationTests" />
24+
</ItemGroup>
25+
26+
</Project>

samples/Todo/Api/Api.http

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@Api_HostAddress = http://localhost:5057
2+
3+
GET {{Api_HostAddress}}/weatherforecast/
4+
Accept: application/json
5+
6+
###
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
namespace TodoApi;
2+
3+
using System.Reflection;
4+
using Microsoft.AspNetCore.Http.Metadata;
5+
using MiniValidation;
6+
7+
public static class ValidationFilterExtensions
8+
{
9+
public static TBuilder WithParameterValidation<TBuilder>(
10+
this TBuilder builder,
11+
params Type[] typesToValidate
12+
)
13+
where TBuilder : IEndpointConventionBuilder
14+
{
15+
builder.Add(endpointBuilder =>
16+
{
17+
var methodInfo = endpointBuilder.Metadata.OfType<MethodInfo>().FirstOrDefault();
18+
19+
if (methodInfo is null)
20+
{
21+
return;
22+
}
23+
24+
// Track the indices of validatable parameters
25+
List<int>? parameterIndexesToValidate = null;
26+
foreach (
27+
var p in methodInfo
28+
.GetParameters()
29+
.Where(p => typesToValidate.Contains(p.ParameterType))
30+
)
31+
{
32+
parameterIndexesToValidate ??= [];
33+
parameterIndexesToValidate.Add(p.Position);
34+
}
35+
36+
if (parameterIndexesToValidate is null)
37+
{
38+
// Nothing to validate so don't add the filter to this endpoint
39+
return;
40+
}
41+
42+
// We can respond with problem details if there's a validation error
43+
endpointBuilder.Metadata.Add(
44+
new ProducesResponseTypeMetadata(
45+
typeof(HttpValidationProblemDetails),
46+
400,
47+
"application/problem+json"
48+
)
49+
);
50+
51+
AddValidation(endpointBuilder, parameterIndexesToValidate);
52+
});
53+
54+
return builder;
55+
}
56+
57+
private static void AddValidation(
58+
EndpointBuilder endpointBuilder,
59+
List<int>? parameterIndexesToValidate
60+
) =>
61+
endpointBuilder.FilterFactories.Add(
62+
(context, next) =>
63+
ctx =>
64+
{
65+
foreach (var index in parameterIndexesToValidate!)
66+
{
67+
if (
68+
ctx.Arguments[index] is { } arg
69+
&& !MiniValidator.TryValidate(arg, out var errors)
70+
)
71+
{
72+
return new ValueTask<object?>(Results.ValidationProblem(errors));
73+
}
74+
}
75+
76+
return next(ctx);
77+
}
78+
);
79+
80+
// Equivalent to the .Produces call to add metadata to endpoints
81+
private sealed class ProducesResponseTypeMetadata(Type type, int statusCode, string contentType)
82+
: IProducesResponseTypeMetadata
83+
{
84+
public Type Type { get; } = type;
85+
public int StatusCode { get; } = statusCode;
86+
public IEnumerable<string> ContentTypes { get; } = [contentType];
87+
}
88+
}

samples/Todo/Api/Program.cs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Data;
2+
using TodoApi;
3+
4+
var builder = WebApplication.CreateBuilder(args);
5+
6+
builder.AddServiceDefaults();
7+
8+
builder.AddNpgsqlDbContext<TodoDbContext>("db");
9+
builder.Services.AddEndpointsApiExplorer();
10+
builder.Services.AddSwaggerGen();
11+
12+
var app = builder.Build();
13+
14+
if (app.Environment.IsDevelopment())
15+
{
16+
app.UseSwagger();
17+
app.UseSwaggerUI();
18+
}
19+
app.MapDefaultEndpoints();
20+
21+
app.MapTodos();
22+
23+
await app.RunAsync();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"$schema": "http://json.schemastore.org/launchsettings.json",
3+
"iisSettings": {
4+
"windowsAuthentication": false,
5+
"anonymousAuthentication": true,
6+
"iisExpress": {
7+
"applicationUrl": "http://localhost:25756",
8+
"sslPort": 44375
9+
}
10+
},
11+
"profiles": {
12+
"http": {
13+
"commandName": "Project",
14+
"dotnetRunMessages": true,
15+
"launchBrowser": true,
16+
"launchUrl": "swagger",
17+
"applicationUrl": "http://localhost:5057",
18+
"environmentVariables": {
19+
"ASPNETCORE_ENVIRONMENT": "Development"
20+
}
21+
},
22+
"https": {
23+
"commandName": "Project",
24+
"dotnetRunMessages": true,
25+
"launchBrowser": true,
26+
"launchUrl": "swagger",
27+
"applicationUrl": "https://localhost:7174;http://localhost:5057",
28+
"environmentVariables": {
29+
"ASPNETCORE_ENVIRONMENT": "Development"
30+
}
31+
},
32+
"IIS Express": {
33+
"commandName": "IISExpress",
34+
"launchBrowser": true,
35+
"launchUrl": "swagger",
36+
"environmentVariables": {
37+
"ASPNETCORE_ENVIRONMENT": "Development"
38+
}
39+
}
40+
}
41+
}

samples/Todo/Api/Todos/Todo.cs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace Api.Todos;
2+
3+
using System.ComponentModel.DataAnnotations;
4+
using Data;
5+
6+
public record class TodoItemViewModel
7+
{
8+
public int Id { get; set; }
9+
10+
[Required]
11+
public string Title { get; set; } = default!;
12+
public bool IsComplete { get; set; }
13+
}
14+
15+
public static class TodoMappingExtensions
16+
{
17+
public static TodoItemViewModel AsTodoItem(this TodoItem todo) =>
18+
new()
19+
{
20+
Id = todo.Id,
21+
Title = todo.Title,
22+
IsComplete = todo.IsComplete,
23+
};
24+
}

0 commit comments

Comments
 (0)