claudient 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
> 🇫🇷 This is the French translation. [English version](../csharp.md).
|
|
2
|
+
|
|
3
|
+
# Compétence C#/.NET
|
|
4
|
+
|
|
5
|
+
## Quand activer
|
|
6
|
+
- Construire une Web API .NET (minimal API ou basée sur des controllers)
|
|
7
|
+
- Configurer Entity Framework Core avec des migrations
|
|
8
|
+
- Configurer le conteneur d'injection de dépendances .NET
|
|
9
|
+
- Rédiger des services en arrière-plan avec `IHostedService` ou `BackgroundService`
|
|
10
|
+
- Implémenter des composants de pipeline middleware
|
|
11
|
+
- Rédiger des requêtes LINQ et comprendre l'exécution différée
|
|
12
|
+
- Configurer async/await correctement dans ASP.NET Core
|
|
13
|
+
|
|
14
|
+
## Quand NE PAS utiliser
|
|
15
|
+
- Services Node.js ou Python
|
|
16
|
+
- Bases de code legacy .NET Framework (antérieures à .NET 5) — les patterns diffèrent
|
|
17
|
+
- Frontend Blazor ou MAUI — préoccupations différentes
|
|
18
|
+
- Développement de jeux Unity — runtime différent
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
### Structure du projet
|
|
23
|
+
```
|
|
24
|
+
MyApi/
|
|
25
|
+
├── MyApi.sln
|
|
26
|
+
├── src/
|
|
27
|
+
│ └── MyApi/
|
|
28
|
+
│ ├── Program.cs # Point d'entrée + conteneur DI
|
|
29
|
+
│ ├── appsettings.json
|
|
30
|
+
│ ├── appsettings.Development.json
|
|
31
|
+
│ ├── Controllers/ # API basée sur des controllers
|
|
32
|
+
│ ├── Endpoints/ # Extensions minimal API
|
|
33
|
+
│ ├── Models/ # Entités EF Core
|
|
34
|
+
│ ├── DTOs/ # Formes de requête/réponse
|
|
35
|
+
│ ├── Services/ # Interfaces + implémentations de logique métier
|
|
36
|
+
│ ├── Data/
|
|
37
|
+
│ │ └── AppDbContext.cs
|
|
38
|
+
│ └── Middleware/
|
|
39
|
+
└── tests/
|
|
40
|
+
└── MyApi.Tests/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Program.cs — configuration minimal API
|
|
44
|
+
```csharp
|
|
45
|
+
// Program.cs — .NET 6+ instructions de niveau supérieur + minimal API
|
|
46
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
47
|
+
|
|
48
|
+
// Enregistrer les services
|
|
49
|
+
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
50
|
+
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
|
|
51
|
+
|
|
52
|
+
builder.Services.AddScoped<IUserService, UserService>();
|
|
53
|
+
builder.Services.AddControllers();
|
|
54
|
+
builder.Services.AddEndpointsApiExplorer();
|
|
55
|
+
builder.Services.AddSwaggerGen();
|
|
56
|
+
|
|
57
|
+
var app = builder.Build();
|
|
58
|
+
|
|
59
|
+
if (app.Environment.IsDevelopment())
|
|
60
|
+
{
|
|
61
|
+
app.UseSwagger();
|
|
62
|
+
app.UseSwaggerUI();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
app.UseHttpsRedirection();
|
|
66
|
+
app.UseAuthentication();
|
|
67
|
+
app.UseAuthorization();
|
|
68
|
+
app.MapControllers();
|
|
69
|
+
|
|
70
|
+
// Endpoints minimal API
|
|
71
|
+
app.MapGroup("/api/v1").MapUserEndpoints();
|
|
72
|
+
|
|
73
|
+
app.Run();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Entité EF Core + DbContext
|
|
77
|
+
```csharp
|
|
78
|
+
// Models/User.cs
|
|
79
|
+
public class User
|
|
80
|
+
{
|
|
81
|
+
public Guid Id { get; set; } = Guid.NewGuid();
|
|
82
|
+
public required string Email { get; set; }
|
|
83
|
+
public required string PasswordHash { get; set; }
|
|
84
|
+
public bool IsActive { get; set; } = true;
|
|
85
|
+
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
|
86
|
+
|
|
87
|
+
public ICollection<Post> Posts { get; set; } = [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Data/AppDbContext.cs
|
|
91
|
+
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
|
|
92
|
+
{
|
|
93
|
+
public DbSet<User> Users => Set<User>();
|
|
94
|
+
public DbSet<Post> Posts => Set<Post>();
|
|
95
|
+
|
|
96
|
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
97
|
+
{
|
|
98
|
+
modelBuilder.Entity<User>(e =>
|
|
99
|
+
{
|
|
100
|
+
e.HasIndex(u => u.Email).IsUnique();
|
|
101
|
+
e.Property(u => u.Email).HasMaxLength(320);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Injection de dépendances — services
|
|
108
|
+
```csharp
|
|
109
|
+
// Services/IUserService.cs
|
|
110
|
+
public interface IUserService
|
|
111
|
+
{
|
|
112
|
+
Task<UserDto> GetByIdAsync(Guid id, CancellationToken ct = default);
|
|
113
|
+
Task<UserDto> CreateAsync(CreateUserRequest request, CancellationToken ct = default);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Services/UserService.cs
|
|
117
|
+
public class UserService(AppDbContext db) : IUserService
|
|
118
|
+
{
|
|
119
|
+
public async Task<UserDto> GetByIdAsync(Guid id, CancellationToken ct = default)
|
|
120
|
+
{
|
|
121
|
+
var user = await db.Users
|
|
122
|
+
.AsNoTracking()
|
|
123
|
+
.FirstOrDefaultAsync(u => u.Id == id, ct)
|
|
124
|
+
?? throw new NotFoundException($"User {id} not found");
|
|
125
|
+
|
|
126
|
+
return new UserDto(user.Id, user.Email, user.CreatedAt);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public async Task<UserDto> CreateAsync(CreateUserRequest request, CancellationToken ct = default)
|
|
130
|
+
{
|
|
131
|
+
if (await db.Users.AnyAsync(u => u.Email == request.Email, ct))
|
|
132
|
+
throw new ConflictException("Email already in use");
|
|
133
|
+
|
|
134
|
+
var user = new User
|
|
135
|
+
{
|
|
136
|
+
Email = request.Email,
|
|
137
|
+
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password)
|
|
138
|
+
};
|
|
139
|
+
db.Users.Add(user);
|
|
140
|
+
await db.SaveChangesAsync(ct);
|
|
141
|
+
return new UserDto(user.Id, user.Email, user.CreatedAt);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### API basée sur des controllers
|
|
147
|
+
```csharp
|
|
148
|
+
[ApiController]
|
|
149
|
+
[Route("api/v1/[controller]")]
|
|
150
|
+
public class UsersController(IUserService userService) : ControllerBase
|
|
151
|
+
{
|
|
152
|
+
[HttpGet("{id:guid}")]
|
|
153
|
+
[ProducesResponseType<UserDto>(StatusCodes.Status200OK)]
|
|
154
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
155
|
+
public async Task<IActionResult> GetUser(Guid id, CancellationToken ct)
|
|
156
|
+
{
|
|
157
|
+
var user = await userService.GetByIdAsync(id, ct);
|
|
158
|
+
return Ok(user);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
[HttpPost]
|
|
162
|
+
[ProducesResponseType<UserDto>(StatusCodes.Status201Created)]
|
|
163
|
+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
164
|
+
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
|
165
|
+
public async Task<IActionResult> CreateUser(
|
|
166
|
+
[FromBody] CreateUserRequest request,
|
|
167
|
+
CancellationToken ct)
|
|
168
|
+
{
|
|
169
|
+
var user = await userService.CreateAsync(request, ct);
|
|
170
|
+
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Endpoints minimal API (pattern de méthode d'extension)
|
|
176
|
+
```csharp
|
|
177
|
+
// Endpoints/UserEndpoints.cs
|
|
178
|
+
public static class UserEndpoints
|
|
179
|
+
{
|
|
180
|
+
public static RouteGroupBuilder MapUserEndpoints(this RouteGroupBuilder group)
|
|
181
|
+
{
|
|
182
|
+
var users = group.MapGroup("/users").WithTags("Users");
|
|
183
|
+
|
|
184
|
+
users.MapGet("/{id:guid}", async (Guid id, IUserService svc, CancellationToken ct) =>
|
|
185
|
+
{
|
|
186
|
+
var user = await svc.GetByIdAsync(id, ct);
|
|
187
|
+
return Results.Ok(user);
|
|
188
|
+
})
|
|
189
|
+
.WithName("GetUser")
|
|
190
|
+
.Produces<UserDto>();
|
|
191
|
+
|
|
192
|
+
return group;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Middleware
|
|
198
|
+
```csharp
|
|
199
|
+
// Middleware/RequestLoggingMiddleware.cs
|
|
200
|
+
public class RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
|
|
201
|
+
{
|
|
202
|
+
public async Task InvokeAsync(HttpContext context)
|
|
203
|
+
{
|
|
204
|
+
var sw = Stopwatch.StartNew();
|
|
205
|
+
try
|
|
206
|
+
{
|
|
207
|
+
await next(context);
|
|
208
|
+
}
|
|
209
|
+
finally
|
|
210
|
+
{
|
|
211
|
+
sw.Stop();
|
|
212
|
+
logger.LogInformation(
|
|
213
|
+
"{Method} {Path} {StatusCode} in {Elapsed}ms",
|
|
214
|
+
context.Request.Method,
|
|
215
|
+
context.Request.Path,
|
|
216
|
+
context.Response.StatusCode,
|
|
217
|
+
sw.ElapsedMilliseconds);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Enregistrer dans Program.cs avant les autres middlewares :
|
|
223
|
+
app.UseMiddleware<RequestLoggingMiddleware>();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Services en arrière-plan
|
|
227
|
+
```csharp
|
|
228
|
+
// Services/CleanupService.cs
|
|
229
|
+
public class CleanupService(IServiceProvider services, ILogger<CleanupService> logger)
|
|
230
|
+
: BackgroundService
|
|
231
|
+
{
|
|
232
|
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
233
|
+
{
|
|
234
|
+
while (!stoppingToken.IsCancellationRequested)
|
|
235
|
+
{
|
|
236
|
+
await DoCleanupAsync(stoppingToken);
|
|
237
|
+
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private async Task DoCleanupAsync(CancellationToken ct)
|
|
242
|
+
{
|
|
243
|
+
// Utiliser un nouveau scope pour chaque itération — BackgroundService est singleton
|
|
244
|
+
using var scope = services.CreateScope();
|
|
245
|
+
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
246
|
+
var cutoff = DateTimeOffset.UtcNow.AddDays(-30);
|
|
247
|
+
await db.Sessions.Where(s => s.ExpiresAt < cutoff).ExecuteDeleteAsync(ct);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Enregistrer : builder.Services.AddHostedService<CleanupService>();
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Bonnes pratiques LINQ
|
|
255
|
+
```csharp
|
|
256
|
+
// Toujours utiliser AsNoTracking() pour les requêtes en lecture seule
|
|
257
|
+
var users = await db.Users.AsNoTracking().Where(u => u.IsActive).ToListAsync(ct);
|
|
258
|
+
|
|
259
|
+
// Sélectionner uniquement les colonnes nécessaires — éviter de charger des entités complètes pour les projections
|
|
260
|
+
var emails = await db.Users
|
|
261
|
+
.Where(u => u.IsActive)
|
|
262
|
+
.Select(u => u.Email)
|
|
263
|
+
.ToListAsync(ct);
|
|
264
|
+
|
|
265
|
+
// Utiliser ExecuteUpdateAsync/ExecuteDeleteAsync pour les opérations en masse — évite de charger des entités
|
|
266
|
+
await db.Users
|
|
267
|
+
.Where(u => !u.IsActive && u.CreatedAt < cutoff)
|
|
268
|
+
.ExecuteDeleteAsync(ct);
|
|
269
|
+
|
|
270
|
+
// Éviter N+1 : utiliser Include() pour les données liées
|
|
271
|
+
var posts = await db.Posts
|
|
272
|
+
.Include(p => p.Author)
|
|
273
|
+
.Include(p => p.Tags)
|
|
274
|
+
.Where(p => p.Published)
|
|
275
|
+
.AsNoTracking()
|
|
276
|
+
.ToListAsync(ct);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### DTOs et records
|
|
280
|
+
```csharp
|
|
281
|
+
// Utiliser des records pour les DTOs immuables
|
|
282
|
+
public record UserDto(Guid Id, string Email, DateTimeOffset CreatedAt);
|
|
283
|
+
public record CreateUserRequest(
|
|
284
|
+
[property: Required, EmailAddress] string Email,
|
|
285
|
+
[property: Required, MinLength(8)] string Password
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Types de réponse avec problem details (intégré dans .NET)
|
|
289
|
+
// Retourner Results.Problem() ou lever des exceptions catchées par le middleware
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Exemple
|
|
293
|
+
|
|
294
|
+
**Utilisateur :** Ajouter une ressource `BlogPost` à une Web API .NET : endpoints CRUD, entité EF Core, migrations et un job en arrière-plan qui publie les articles planifiés.
|
|
295
|
+
|
|
296
|
+
**Sortie attendue :**
|
|
297
|
+
- `Models/BlogPost.cs` — entité avec `Id`, `Title`, `Body`, `AuthorId` (FK vers User), `PublishedAt` (nullable), `ScheduledFor` (nullable)
|
|
298
|
+
- `DTOs/BlogPostDtos.cs` — record `BlogPostDto`, record `CreateBlogPostRequest` avec validation `[Required]`
|
|
299
|
+
- `Services/IBlogPostService.cs` + `BlogPostService.cs` — méthodes CRUD, `GetPendingScheduledAsync` pour le job en arrière-plan
|
|
300
|
+
- `Controllers/BlogPostsController.cs` — tous les CRUD avec codes de statut appropriés
|
|
301
|
+
- `Services/PostPublisherService.cs` — `BackgroundService` qui vérifie toutes les minutes et publie les articles dus
|
|
302
|
+
- Migration EF Core : `dotnet ef migrations add AddBlogPosts`
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
> **Travaillez avec nous :** Claudient est soutenu par [Uitbreiden](https://uitbreiden.com/) — nous construisons des produits IA et des solutions B2B avec des communautés de développeurs. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
> 🇳🇱 Dit is de Nederlandse vertaling. [Engelse versie](../csharp.md).
|
|
2
|
+
|
|
3
|
+
# C#/.NET Skill
|
|
4
|
+
|
|
5
|
+
## Wanneer te activeren
|
|
6
|
+
- Een .NET Web API bouwen (minimale API of op controllers gebaseerd)
|
|
7
|
+
- Entity Framework Core instellen met migraties
|
|
8
|
+
- De .NET dependency injection-container configureren
|
|
9
|
+
- Achtergrondservices schrijven met `IHostedService` of `BackgroundService`
|
|
10
|
+
- Middleware-pipeline-componenten implementeren
|
|
11
|
+
- LINQ-queries schrijven en uitgestelde uitvoering begrijpen
|
|
12
|
+
- async/await correct instellen in ASP.NET Core
|
|
13
|
+
|
|
14
|
+
## Wanneer NIET te gebruiken
|
|
15
|
+
- Node.js of Python-services
|
|
16
|
+
- .NET Framework (pre-.NET 5) legacy-codebases — patronen verschillen
|
|
17
|
+
- Blazor of MAUI frontend — andere concerns
|
|
18
|
+
- Unity-spelontwikkeling — andere runtime
|
|
19
|
+
|
|
20
|
+
## Instructies
|
|
21
|
+
|
|
22
|
+
### Projectstructuur
|
|
23
|
+
```
|
|
24
|
+
MyApi/
|
|
25
|
+
├── MyApi.sln
|
|
26
|
+
├── src/
|
|
27
|
+
│ └── MyApi/
|
|
28
|
+
│ ├── Program.cs # Ingangspunt + DI-container
|
|
29
|
+
│ ├── appsettings.json
|
|
30
|
+
│ ├── appsettings.Development.json
|
|
31
|
+
│ ├── Controllers/ # Op controllers gebaseerde API
|
|
32
|
+
│ ├── Endpoints/ # Minimale API-extensies
|
|
33
|
+
│ ├── Models/ # EF Core-entiteiten
|
|
34
|
+
│ ├── DTOs/ # Aanvraag/respons-vormen
|
|
35
|
+
│ ├── Services/ # Bedrijfslogica-interfaces + -implementaties
|
|
36
|
+
│ ├── Data/
|
|
37
|
+
│ │ └── AppDbContext.cs
|
|
38
|
+
│ └── Middleware/
|
|
39
|
+
└── tests/
|
|
40
|
+
└── MyApi.Tests/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Program.cs — minimale API-instelling
|
|
44
|
+
```csharp
|
|
45
|
+
// Program.cs — .NET 6+ top-level statements + minimale API
|
|
46
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
47
|
+
|
|
48
|
+
// Services registreren
|
|
49
|
+
builder.Services.AddDbContext<AppDbContext>(options =>
|
|
50
|
+
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
|
|
51
|
+
|
|
52
|
+
builder.Services.AddScoped<IUserService, UserService>();
|
|
53
|
+
builder.Services.AddControllers();
|
|
54
|
+
builder.Services.AddEndpointsApiExplorer();
|
|
55
|
+
builder.Services.AddSwaggerGen();
|
|
56
|
+
|
|
57
|
+
var app = builder.Build();
|
|
58
|
+
|
|
59
|
+
if (app.Environment.IsDevelopment())
|
|
60
|
+
{
|
|
61
|
+
app.UseSwagger();
|
|
62
|
+
app.UseSwaggerUI();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
app.UseHttpsRedirection();
|
|
66
|
+
app.UseAuthentication();
|
|
67
|
+
app.UseAuthorization();
|
|
68
|
+
app.MapControllers();
|
|
69
|
+
|
|
70
|
+
// Minimale API-endpoints
|
|
71
|
+
app.MapGroup("/api/v1").MapUserEndpoints();
|
|
72
|
+
|
|
73
|
+
app.Run();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### EF Core-entiteit + DbContext
|
|
77
|
+
```csharp
|
|
78
|
+
// Models/User.cs
|
|
79
|
+
public class User
|
|
80
|
+
{
|
|
81
|
+
public Guid Id { get; set; } = Guid.NewGuid();
|
|
82
|
+
public required string Email { get; set; }
|
|
83
|
+
public required string PasswordHash { get; set; }
|
|
84
|
+
public bool IsActive { get; set; } = true;
|
|
85
|
+
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
|
86
|
+
|
|
87
|
+
public ICollection<Post> Posts { get; set; } = [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Data/AppDbContext.cs
|
|
91
|
+
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
|
|
92
|
+
{
|
|
93
|
+
public DbSet<User> Users => Set<User>();
|
|
94
|
+
public DbSet<Post> Posts => Set<Post>();
|
|
95
|
+
|
|
96
|
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
97
|
+
{
|
|
98
|
+
modelBuilder.Entity<User>(e =>
|
|
99
|
+
{
|
|
100
|
+
e.HasIndex(u => u.Email).IsUnique();
|
|
101
|
+
e.Property(u => u.Email).HasMaxLength(320);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Dependency injection — services
|
|
108
|
+
```csharp
|
|
109
|
+
// Services/IUserService.cs
|
|
110
|
+
public interface IUserService
|
|
111
|
+
{
|
|
112
|
+
Task<UserDto> GetByIdAsync(Guid id, CancellationToken ct = default);
|
|
113
|
+
Task<UserDto> CreateAsync(CreateUserRequest request, CancellationToken ct = default);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Services/UserService.cs
|
|
117
|
+
public class UserService(AppDbContext db) : IUserService
|
|
118
|
+
{
|
|
119
|
+
public async Task<UserDto> GetByIdAsync(Guid id, CancellationToken ct = default)
|
|
120
|
+
{
|
|
121
|
+
var user = await db.Users
|
|
122
|
+
.AsNoTracking()
|
|
123
|
+
.FirstOrDefaultAsync(u => u.Id == id, ct)
|
|
124
|
+
?? throw new NotFoundException($"User {id} not found");
|
|
125
|
+
|
|
126
|
+
return new UserDto(user.Id, user.Email, user.CreatedAt);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public async Task<UserDto> CreateAsync(CreateUserRequest request, CancellationToken ct = default)
|
|
130
|
+
{
|
|
131
|
+
if (await db.Users.AnyAsync(u => u.Email == request.Email, ct))
|
|
132
|
+
throw new ConflictException("Email already in use");
|
|
133
|
+
|
|
134
|
+
var user = new User
|
|
135
|
+
{
|
|
136
|
+
Email = request.Email,
|
|
137
|
+
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password)
|
|
138
|
+
};
|
|
139
|
+
db.Users.Add(user);
|
|
140
|
+
await db.SaveChangesAsync(ct);
|
|
141
|
+
return new UserDto(user.Id, user.Email, user.CreatedAt);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Op controllers gebaseerde API
|
|
147
|
+
```csharp
|
|
148
|
+
[ApiController]
|
|
149
|
+
[Route("api/v1/[controller]")]
|
|
150
|
+
public class UsersController(IUserService userService) : ControllerBase
|
|
151
|
+
{
|
|
152
|
+
[HttpGet("{id:guid}")]
|
|
153
|
+
[ProducesResponseType<UserDto>(StatusCodes.Status200OK)]
|
|
154
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
155
|
+
public async Task<IActionResult> GetUser(Guid id, CancellationToken ct)
|
|
156
|
+
{
|
|
157
|
+
var user = await userService.GetByIdAsync(id, ct);
|
|
158
|
+
return Ok(user);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
[HttpPost]
|
|
162
|
+
[ProducesResponseType<UserDto>(StatusCodes.Status201Created)]
|
|
163
|
+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
164
|
+
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
|
165
|
+
public async Task<IActionResult> CreateUser(
|
|
166
|
+
[FromBody] CreateUserRequest request,
|
|
167
|
+
CancellationToken ct)
|
|
168
|
+
{
|
|
169
|
+
var user = await userService.CreateAsync(request, ct);
|
|
170
|
+
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Minimale API-endpoints (extensiemethodepatroon)
|
|
176
|
+
```csharp
|
|
177
|
+
// Endpoints/UserEndpoints.cs
|
|
178
|
+
public static class UserEndpoints
|
|
179
|
+
{
|
|
180
|
+
public static RouteGroupBuilder MapUserEndpoints(this RouteGroupBuilder group)
|
|
181
|
+
{
|
|
182
|
+
var users = group.MapGroup("/users").WithTags("Users");
|
|
183
|
+
|
|
184
|
+
users.MapGet("/{id:guid}", async (Guid id, IUserService svc, CancellationToken ct) =>
|
|
185
|
+
{
|
|
186
|
+
var user = await svc.GetByIdAsync(id, ct);
|
|
187
|
+
return Results.Ok(user);
|
|
188
|
+
})
|
|
189
|
+
.WithName("GetUser")
|
|
190
|
+
.Produces<UserDto>();
|
|
191
|
+
|
|
192
|
+
return group;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Middleware
|
|
198
|
+
```csharp
|
|
199
|
+
// Middleware/RequestLoggingMiddleware.cs
|
|
200
|
+
public class RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
|
|
201
|
+
{
|
|
202
|
+
public async Task InvokeAsync(HttpContext context)
|
|
203
|
+
{
|
|
204
|
+
var sw = Stopwatch.StartNew();
|
|
205
|
+
try
|
|
206
|
+
{
|
|
207
|
+
await next(context);
|
|
208
|
+
}
|
|
209
|
+
finally
|
|
210
|
+
{
|
|
211
|
+
sw.Stop();
|
|
212
|
+
logger.LogInformation(
|
|
213
|
+
"{Method} {Path} {StatusCode} in {Elapsed}ms",
|
|
214
|
+
context.Request.Method,
|
|
215
|
+
context.Request.Path,
|
|
216
|
+
context.Response.StatusCode,
|
|
217
|
+
sw.ElapsedMilliseconds);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Registreer in Program.cs vóór andere middleware:
|
|
223
|
+
app.UseMiddleware<RequestLoggingMiddleware>();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Achtergrondservices
|
|
227
|
+
```csharp
|
|
228
|
+
// Services/CleanupService.cs
|
|
229
|
+
public class CleanupService(IServiceProvider services, ILogger<CleanupService> logger)
|
|
230
|
+
: BackgroundService
|
|
231
|
+
{
|
|
232
|
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
233
|
+
{
|
|
234
|
+
while (!stoppingToken.IsCancellationRequested)
|
|
235
|
+
{
|
|
236
|
+
await DoCleanupAsync(stoppingToken);
|
|
237
|
+
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private async Task DoCleanupAsync(CancellationToken ct)
|
|
242
|
+
{
|
|
243
|
+
// Gebruik een nieuwe scope voor elke iteratie — BackgroundService is singleton
|
|
244
|
+
using var scope = services.CreateScope();
|
|
245
|
+
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
246
|
+
var cutoff = DateTimeOffset.UtcNow.AddDays(-30);
|
|
247
|
+
await db.Sessions.Where(s => s.ExpiresAt < cutoff).ExecuteDeleteAsync(ct);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Registreer: builder.Services.AddHostedService<CleanupService>();
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### LINQ-beste praktijken
|
|
255
|
+
```csharp
|
|
256
|
+
// Gebruik altijd AsNoTracking() voor alleen-lezen queries
|
|
257
|
+
var users = await db.Users.AsNoTracking().Where(u => u.IsActive).ToListAsync(ct);
|
|
258
|
+
|
|
259
|
+
// Selecteer alleen benodigde kolommen — vermijd laden van volledige entiteiten voor projecties
|
|
260
|
+
var emails = await db.Users
|
|
261
|
+
.Where(u => u.IsActive)
|
|
262
|
+
.Select(u => u.Email)
|
|
263
|
+
.ToListAsync(ct);
|
|
264
|
+
|
|
265
|
+
// Gebruik ExecuteUpdateAsync/ExecuteDeleteAsync voor bulk-operaties — slaat laden van entiteiten over
|
|
266
|
+
await db.Users
|
|
267
|
+
.Where(u => !u.IsActive && u.CreatedAt < cutoff)
|
|
268
|
+
.ExecuteDeleteAsync(ct);
|
|
269
|
+
|
|
270
|
+
// Vermijd N+1: gebruik Include() voor gerelateerde data
|
|
271
|
+
var posts = await db.Posts
|
|
272
|
+
.Include(p => p.Author)
|
|
273
|
+
.Include(p => p.Tags)
|
|
274
|
+
.Where(p => p.Published)
|
|
275
|
+
.AsNoTracking()
|
|
276
|
+
.ToListAsync(ct);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### DTO's en records
|
|
280
|
+
```csharp
|
|
281
|
+
// Gebruik records voor onveranderlijke DTO's
|
|
282
|
+
public record UserDto(Guid Id, string Email, DateTimeOffset CreatedAt);
|
|
283
|
+
public record CreateUserRequest(
|
|
284
|
+
[property: Required, EmailAddress] string Email,
|
|
285
|
+
[property: Required, MinLength(8)] string Password
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Antwoordtypen met problem details (ingebouwd .NET)
|
|
289
|
+
// Retourneer Results.Problem() of gooi uitzonderingen gevangen door middleware
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Voorbeeld
|
|
293
|
+
|
|
294
|
+
**Gebruiker:** Voeg een `BlogPost`-resource toe aan een .NET Web API: CRUD-endpoints, EF Core-entiteit, migraties en een achtergrondtaak die geplande berichten publiceert.
|
|
295
|
+
|
|
296
|
+
**Verwachte output:**
|
|
297
|
+
- `Models/BlogPost.cs` — entiteit met `Id`, `Title`, `Body`, `AuthorId` (FK naar User), `PublishedAt` (nullable), `ScheduledFor` (nullable)
|
|
298
|
+
- `DTOs/BlogPostDtos.cs` — `BlogPostDto`-record, `CreateBlogPostRequest`-record met `[Required]`-validatie
|
|
299
|
+
- `Services/IBlogPostService.cs` + `BlogPostService.cs` — CRUD-methoden, `GetPendingScheduledAsync` voor achtergrondtaak
|
|
300
|
+
- `Controllers/BlogPostsController.cs` — alle CRUD met juiste statuscodes
|
|
301
|
+
- `Services/PostPublisherService.cs` — `BackgroundService` die elke minuut controleert en vervallen berichten publiceert
|
|
302
|
+
- EF Core-migratie: `dotnet ef migrations add AddBlogPosts`
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
> **Werk met ons:** Claudient wordt ondersteund door [Uitbreiden](https://uitbreiden.com/) — we bouwen AI-producten en B2B-oplossingen met ontwikkelaarsgemeenschappen. [uitbreiden.com](https://uitbreiden.com/)
|