Read or download the complete SDK project, then follow its setup on your computer. The separate C# companion practice below runs in SharpRune’s .NET 9 console environment.
Use the actual .NET 10 validation implementation
ASP.NET Core 10 supplies automatic validation for supported Minimal API models when AddValidation is registered. The implementation uses generated metadata to discover annotated types in the assembly where that registration is called. The external project keeps the registration, endpoint and model together. In the tested WebSDK 10.0.403 environment the validation assembly is already available through the shared framework; a redundant explicit package reference produced NU1510 and was removed. This current setup detail does not imply that every non-web project automatically references validation. The companion remains ordinary portable policy code and does not attempt to run the framework generator.
Annotations describe a bounded structural contract
CreateItem requires a nonempty Name of at least two string units and a Quantity from one through ten. The endpoint accepts that model, so validation can run after binding and before its handler body. These checks do not establish catalog existence, permissions or a configured purchase limit. They describe properties of the bound model. The portable companion separately accepts rune with quantity three and rejects an empty name or quantity zero. Its output is accepted, rejected, rejected. Those values test a stated predicate, while actual HTTP validation behavior must come from the compiled external server.
Use a side-effect counter to locate the handler boundary
A valid JSON POST produces status 200 and increments AcceptedCounter to one. An empty-name POST and a quantity-zero POST each produce status 400 validation details. Reading /accepted afterward still reports one, demonstrating that those invalid models did not enter the successful handler path. The counter uses atomic operations because requests can overlap; it is an observation tool, not durable storage. This sequence is stronger evidence than checking only that the invalid requests returned some error, because a handler that mutated state before rejecting data could otherwise look superficially similar to the caller.
Keep binding, syntax and domain errors distinct
Malformed JSON can fail before model validation has a usable object to examine. A missing body, an annotation failure and a later business-policy conflict have different causes even when some share a 400 status. The project does not customize all failure documents into one universal error schema, and it does not reject every possible extra JSON member. Do not infer a complete security or business contract from attributes on a DTO. A valid model still needs authorization where appropriate, a domain decision and a clear persistence or side-effect boundary before real application work is committed.
Practice: isolate each model requirement
Create independent requests with an omitted Name, a one-character Name, whitespace-only Name, zero Quantity, eleven Quantity and a valid boundary quantity of ten. Write expected status and accepted-counter behavior before running them. Use one violation in each body so an earlier failure cannot conceal an absent later check. Keep malformed JSON as a separate parser case. The hint is to verify that invalid bodies leave the counter unchanged, while a valid boundary body increments it exactly once. Do not compare localized validation text as the sole evidence of which requirement applied.
Practice: add a separate business limit
Keep the annotation range at one through ten, then introduce an application rule allowing at most three units for a specific operation. Submit quantity four and explain why it can pass model validation while failing the later business rule. Return an explicit application outcome instead of pretending the annotations rejected it. Preserve the counter boundary so only genuinely accepted operations increment it. This exercise prepares for the capstone's configured limit and prevents a common conflation: changing an operational policy does not necessarily mean changing the public model's representable range or its binding behavior.
Check discovery, model rules and execution separately
A review should verify that AddValidation executes in the assembly containing the discovered endpoint types, that the model declares intended requirements and that invalid data cannot reach committed work. The external build and real requests establish those facts for this project; the console cannot substitute for them. If models move into referenced assemblies, follow the versioned registration guidance instead of assuming one call discovers every dependency automatically. Keep generated-code and framework diagnostics visible during upgrades, and test both valid inputs and independent failures. Validation helps establish a precise boundary only when its actual discovery and execution are observed.
- Keep validation registration and type discovery explicit when splitting assemblies.
- Use handler-boundary evidence alongside status checks; annotations do not replace business policy.
.NET 10 Minimal API validation — real ASP.NET Core 10 project
Set up the SDK project
- Save all files into one new empty directory, preserving their relative paths. Install the official .NET 10 SDK if it is not already available.
- From that directory run: dotnet restore WebLab.csproj --source https://api.nuget.org/v3/index.json
- Start only on loopback: ASPNETCORE_ENVIRONMENT=Development dotnet run --project WebLab.csproj --no-launch-profile --urls http://127.0.0.1:5080
- Use another terminal for the requests below, then press Ctrl+C in this project’s terminal to stop its server.
- POST application/json to /items with {"name":"rune","quantity":3}; repeat with an empty name and quantity zero, then GET /accepted.
Download complete SDK project ↗
Project files
WebLab.csproj
Download file ↗<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Program.cs
Download file ↗using System.ComponentModel.DataAnnotations;
var builder = WebApplication.CreateBuilder(args); builder.Services.AddValidation(); builder.Services.AddSingleton<AcceptedCounter>(); var app = builder.Build();
app.MapPost("/items", (CreateItem item, AcceptedCounter counter) => TypedResults.Ok(new { item.Name, item.Quantity, accepted = counter.Next() }));
app.MapGet("/accepted", (AcceptedCounter counter) => new { count = counter.Count }); app.Run();
public sealed class CreateItem {
[Required, MinLength(2)] public string Name { get; set; } = "";
[Range(1, 10)] public int Quantity { get; set; }
}
public sealed class AcceptedCounter { private int count; public int Count => Volatile.Read(ref count); public int Next() => Interlocked.Increment(ref count); }
Expected result
A valid POST /items returns 200 and accepted=1. Empty Name or Quantity=0 returns 400 validation details without incrementing /accepted.
Use the listed SDK and dependencies for this project. Open C# practice runs the separate portable exercise.
C# companion practice
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
foreach (var item in new[] { ("rune", 3), ("", 3), ("rune", 0) }) Console.WriteLine(!string.IsNullOrWhiteSpace(item.Item1) && item.Item1.Length >= 2 && item.Item2 is >= 1 and <= 10 ? "accepted" : "rejected");
C# practice output
accepted rejected rejected
AddValidation belongs to the model/endpoint assembly; a valid model still needs its business policy.