Add web api skill and tests#613
Conversation
Skill Coverage Report
Uncovered:
|
There was a problem hiding this comment.
Pull request overview
Adds a new dotnet-webapi skill under the dotnet-aspnet plugin plus corresponding eval coverage, extending the repository's ASP.NET guidance for endpoint design, OpenAPI wiring, and centralized API error handling.
Changes:
- Added
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.mdwith end-to-end guidance for controllers vs minimal APIs, DTOs, status codes, OpenAPI, exception handling, services, and.httpfiles. - Added
tests/dotnet-aspnet/dotnet-webapi/eval.yamlwith three scenarios covering new minimal APIs, ProblemDetails-based exception handling, and extending existing controller-based APIs.
Show a summary per file
| File | Description |
|---|---|
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md |
New ASP.NET Core Web API skill content covering API style selection, DTO conventions, OpenAPI setup, error handling, and verification steps. |
tests/dotnet-aspnet/dotnet-webapi/eval.yaml |
New eval scenarios and assertions for validating the dotnet-webapi skill behavior. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (4)
tests/dotnet-aspnet/dotnet-webapi/eval.yaml:123
- This negative regex is also applied to the full response, so a correct controller-based answer can fail just by saying "don't use MapGet/MapPost here". Because the scenario is specifically about preserving controller style, forbidding any mention of minimal API methods makes the eval brittle and likely to reject good explanations.
- type: "output_not_matches"
pattern: "app\\.(MapGet|MapPost|MapPut|MapDelete|MapPatch)|\\b(MapGet|MapPost|MapPut|MapDelete|MapPatch)\\b"
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md:273
AddOpenApi()is not universally available "with no additional packages required". This repository already treatsMicrosoft.AspNetCore.OpenApias an explicit package dependency (for example tests/dotnet-nuget/convert-to-cpm/simple-solution/Web/Web.csproj:7 and plugins/dotnet-upgrade/skills/migrate-dotnet9-to-dotnet10/SKILL.md:133). If an existing app doesn't already reference that package, following this guidance will produce uncompilable code.
**For .NET 9+ projects**, use the built-in ASP.NET Core OpenAPI support
(`builder.Services.AddOpenApi()` + `app.MapOpenApi()` in development).
This is all that is needed — no additional packages required.
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md:366
- This sample writes
ProblemDetailswithWriteAsJsonAsync, which uses the normal JSON content type. That means the response isapplication/jsoninstead of RFC 7807'sapplication/problem+json, so the example does not actually implement the "all error responses use Problem Details format" contract described above.
httpContext.Response.StatusCode = statusCode;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = statusCode,
Title = title,
// Do not use exception.Message here — it may leak sensitive internal details.
// Use a safe, user-facing message instead.
Detail = title,
Instance = httpContext.Request.Path
}, cancellationToken);
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md:314
- The wording here says to configure enum serialization "for both minimal APIs and controllers" and then shows
ConfigureHttpJsonOptionsandAddControllers().AddJsonOptions(...). In a minimal-API project that encourages registering MVC services unnecessarily, which conflicts with the earlier "do not mix styles" guidance. The instruction should say to apply the configuration block for the API style already in use.
**Enum serialization (strings by default):** Configure JSON serialization so
enums appear as readable strings in both API responses and OpenAPI schemas.
Always add this configuration unless the user explicitly requests integer
enum serialization. Configure it for both minimal APIs and controllers, as
they use different option types:
```csharp
// Minimal APIs
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()));
// Controllers / MVC
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
</details>
- **Files reviewed:** 2/2 changed files
- **Comments generated:** 2
|
/evaluate |
Skill Validation Results
Model: claude-opus-4.6 | Judge: claude-opus-4.6 🔍 Full Results - additional metrics and failure investigation steps ▶ Sessions Visualisation -- interactive replay of all evaluation sessions |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (6)
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md:367
- This
IExceptionHandlerexample writesProblemDetailswithWriteAsJsonAsync, which produces a normal JSON response instead of the RFC 7807application/problem+jsonpayload that the surrounding guidance requires. It also bypasses anyAddProblemDetails()customizations, so consumers following this sample won't actually get the standardized problem-details response format.
httpContext.Response.StatusCode = statusCode;
await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = statusCode,
Title = title,
// Do not use exception.Message here — it may leak sensitive internal details.
// Use a safe, user-facing message instead.
Detail = title,
Instance = httpContext.Request.Path
}, cancellationToken);
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md:452
- Step 7 makes
.httprequest files part of the skill's workflow, but none of the new eval scenarios assert on that output. This means the skill can regress on one of the PR's advertised features without any test failure.
### Step 7: Create a .http test file
After implementing endpoints, create a `.http` file in the project root that
demonstrates how to call every new endpoint. This serves as living
documentation and a quick manual test harness.
```http
@baseUrl = http://localhost:5000
### Get all products
GET {{baseUrl}}/api/products
### Get product by ID
GET {{baseUrl}}/api/products/1
### Create a product
POST {{baseUrl}}/api/products
Content-Type: application/json
{
"name": "Wireless Mouse",
"price": 29.99,
"category": "Electronics"
}
### Delete a product
DELETE {{baseUrl}}/api/products/1
Include at least one request per endpoint with realistic bodies. Show error
paths (e.g., non-existent IDs). Match the port to launchSettings.json.
**tests/dotnet-aspnet/dotnet-webapi/eval.yaml:131**
* This scenario asks for only a collection GET and a POST, but the rubric requires `CreatedAtAction` on the POST. `CreatedAtAction` needs a concrete target action (normally a `GetById` route), so a model can only satisfy the rubric by inventing an extra endpoint that the prompt never requested or by generating a broken location link.
Add a new OrdersController with a GET endpoint to list orders and a
POST endpoint to create an order. Each order has a customer ID, a
placedAt timestamp, a total amount, and an OrderStatus (Pending,
Processing, Shipped, Delivered, Cancelled).
assertions:
- type: "output_contains"
value: "ControllerBase"
- type: "output_contains"
value: "[ApiController]"
- type: "output_contains"
value: "CancellationToken"
- type: "output_not_matches"
pattern: "app\\.(MapGet|MapPost|MapPut|MapDelete|MapPatch)|\\b(MapGet|MapPost|MapPut|MapDelete|MapPatch)\\b"
- type: "output_contains"
value: "DateTimeOffset"
- type: "output_matches"
pattern: "/// <summary>"
rubric:
- "Continues with the controller pattern since the existing project uses controllers (does not mix minimal APIs)"
- "Includes CancellationToken in all endpoint signatures"
- "POST create returns CreatedAtAction with 201 status, not Ok with 200"
**plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md:241**
* This example assumes every POST has a corresponding `GetById` action or route. For create-only/collection-only APIs, following it literally either forces the model to invent an extra endpoint or produces a `Location` link to an action that doesn't exist. The guidance should explain that `CreatedAtAction`/`CreatedAtRoute` is only valid when a retrievable resource route is actually present.
POST 201 responses: Always return a Location header pointing to the
newly created resource.
- Controllers: use
CreatedAtAction(nameof(GetById), new { id = ... }, response) - Minimal APIs: use
TypedResults.Created($"/api/products/{id}", response)
**tests/dotnet-aspnet/dotnet-webapi/eval.yaml:123**
* This negative regex is so broad that it also rejects perfectly valid controller-based answers that merely mention minimal APIs in prose (for example, 'don't use `MapGet` here because the project already uses controllers'). That makes the scenario brittle and can fail correct responses for talking about the forbidden pattern instead of actually using it.
- type: "output_not_matches"
pattern: "app\\.(MapGet|MapPost|MapPut|MapDelete|MapPatch)|\\b(MapGet|MapPost|MapPut|MapDelete|MapPatch)\\b"
**plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.md:416**
* Using `AddScoped` as the default example is unsafe for the in-memory storage pattern this skill promotes elsewhere. A scoped service is recreated for every request, so if the implementation keeps its in-memory collection in instance state, newly created items disappear on the next request and the sample API won't behave like a persistent store even within one process.
Register with the interface, not the concrete type:
// In Program.cs
builder.Services.AddScoped<IProductService, ProductService>();- Files reviewed: 2/2 changed files
- Comments generated: 2
|
/evaluate |
1 similar comment
|
/evaluate |
Skill Validation Results
Model: claude-opus-4.6 | Judge: claude-opus-4.6 🔍 Full Results - additional metrics and failure investigation steps ▶ Sessions Visualisation -- interactive replay of all evaluation sessions |
|
@BrennanConroy this PR is now in good shape. It's the same PR as before with a minor update from copilot. You should be able to approve and merge now. cc @mikekistler |
mikekistler
left a comment
There was a problem hiding this comment.
Carrying forward my approval from #565
|
/evaluate |
Skill Validation Results
Model: claude-opus-4.6 | Judge: claude-opus-4.6 🔍 Full Results - additional metrics and failure investigation steps ▶ Sessions Visualisation -- interactive replay of all evaluation sessions |
Adds the dotnet-webapi skill to the dotnet-aspnet plugin. This skill guides the creation and modification of ASP.NET Core Web API endpoints with correct HTTP semantics, OpenAPI metadata, and error handling.
I'm running the skill validator now and I will add the results as a comment when it completes.
This PR replaces the closed PR #493
What's included
New skill:
plugins/dotnet-aspnet/skills/dotnet-webapi/SKILL.mdA comprehensive skill covering the following areas:
sealed recordDTOs with proper naming conventions (CreateXxxRequest,XxxResponse), XML doc comments, andDateTimeOffsetfor timestamps.TypedResultswith explicitResults<T1, T2>return types,CancellationTokenforwarding, and OpenAPI endpoint metadata (WithName,WithSummary,WithDescription).[ApiController]conventions withActionResult<T>return types andProducesResponseTypeattributes.AddOpenApi()/MapOpenApi()instead of Swashbuckle; includes XML doc comment integration andJsonStringEnumConverterfor enum serialization.IExceptionHandlerwith RFC 7807ProblemDetails, exception-to-status-code mapping, and a sealed handler class in aMiddleware/folder..httptest files — Generates test files for verifying endpoints directly from the IDE.