An ASP.NET Core change with Claude Code should begin with a behavior a client can observe. Suppose an existing orders endpoint accepts an optional pageSize query argument, but forwards negative values into its query path. The task is to reject invalid page sizes while preserving everything clients already rely on.
That is a small change with several boundaries. Query binding, validation, authentication, tenant filtering, response shaping, and tests can all matter. Claude Code's job is not to redesign those systems. It is to identify the relevant contracts and make the smallest complete change.
Information checked October 9, 2026. Claude Code behavior was verified against current official documentation. The isolated .NET 10 sample was compiled and checked locally on October 9, 2026: omitted input, both valid boundaries, three out-of-range values, and malformed input returned the expected statuses and applicable response bodies. No production application or database was involved.
Define the endpoint change before delegating to Claude Code
“Add pagination validation” sounds precise, but it leaves important decisions unstated. What is the default? Is zero allowed? Is there a maximum? What error format does the API use? Does an invalid input reach the database?
For this example, the contract is deliberately narrow. Omitting pageSize retains a default of 20. Integers from 1 through 100 are accepted. Integers outside that range return the existing API's 400 response format and do not reach the query service.
Those numbers are illustrative acceptance criteria, not ASP.NET Core defaults. The framework does not know the application's page-size policy. In a real repository, derive the intended range and default from approved requirements and existing behavior.
The change must also preserve authorization, tenant filtering, sort order, cancellation behavior, and the successful response shape. If those behaviors cannot be established from the repository, report the uncertainty before editing.
This is a better boundary than “only change the handler file.” A complete behavior change may require a handler, regression tests, and API documentation. The unit of scope is the endpoint contract, not an arbitrary file count.
Ask Claude Code to trace the existing request
The official Claude Code overview documents repository reads, edits, and command execution. Its common workflows describe finding relevant code and tracing interactions before applying changes.
For an existing endpoint, ask for a route-to-query trace first. Claude Code should identify where the query argument is bound, where the default is applied, which validation layer runs, and which service performs the query.
Here is a discovery-only launch against a fictional checkout:
Set-Location 'C:\repos\OrderService'
claude "Trace GET /orders from query binding to the query service and its HTTP tests. Identify the current pageSize default, authorization, tenant filtering, error format, and ordering. Do not edit files, install packages, run builds or tests, contact databases, commit, or publish."
The desired output is a concrete explanation with file references. A generic paragraph about Minimal APIs is not enough. Neither is a proposed implementation that bypasses an existing validation filter the agent never inspected.
Repository instructions help supply conventions, but the official memory documentation treats them as context rather than enforcement. The request above should be paired with appropriate runtime permissions, especially if the environment contains credentials or connected external tools.
Do not let discovery quietly become execution. Reading a test file and running that test are different actions. The latter may start services or interact with configured dependencies and needs the intended validation environment.
Distinguish binding failures from validation failures
ASP.NET Core converts request data into handler parameters through parameter binding. The current Microsoft Minimal API binding documentation describes query, route, body, service, and other binding sources.
A numeric query parameter illustrates two separate questions. Can the framework parse the input into the declared type? If parsing succeeds, does the value satisfy the application rule?
pageSize=abc is not the same path as pageSize=-1. The first involves binding an invalid integer representation. The second supplies a valid integer that the application's range policy rejects. A test for one does not automatically prove the other.
Omitted input is another path. Changing an optional int? parameter into a required int can alter existing client behavior. Preserve the optional contract if that is what the endpoint already exposes.
Also distinguish validation from query behavior. A query operation that happens to return an empty list for a negative value is not equivalent to rejecting the request. The client-visible status and downstream activity are part of the contract.
These distinctions help a reviewer explain exactly why a small conditional belongs at one boundary instead of another. They also help you explain to Claude Code why validation belongs in a layer that can return the API's standard error.
Preserve the application's existing request pipeline
Authorization might live on a route group rather than the individual handler. Tenant filtering might live in a query service. Error shaping might live in an endpoint filter. A seemingly harmless route extraction can move code away from those existing behaviors.
Before asking Claude Code to edit, inspect the surrounding registrations and conventions. If the route belongs to an authenticated group, keep it there. If a service already applies tenant constraints, do not replace it with an unscoped direct query merely because that produces a shorter sample.
Understanding middleware ordering and short-circuiting helps explain the wider request boundary. The page-size task does not authorize reorganizing that pipeline.
Persistence deserves the same care. There is no reason for a range-check change to require a migration. If the proposed implementation introduces a schema or package change, that is a signal to revisit scope rather than approve it as incidental cleanup.
Observability should remain intact too. Existing structured logging or tracing may be part of how invalid requests are investigated. A validation edit should not become an unrelated logging configuration change.
A complete illustration of the changed boundary
The following example isolates page-size behavior so the control flow is easy to inspect. It is not a production application scaffold. It intentionally has no authentication, tenant model, database, logging provider, or networked dependency. Do not transplant it over those components in an existing service.
For remote validation of the illustration, place the project file and Program.cs below in the same scratch directory, using an approved .NET 10 SDK. No external NuGet packages are required by the shown project:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
The baseline route excerpt shows the missing range check. Its catalog dependency and response type are supplied by the complete program below:
app.MapGet("/orders", (int? pageSize, OrderCatalog catalog) =>
{
var resolvedPageSize = pageSize ?? 20;
return Results.Ok(new OrderPage(
resolvedPageSize,
catalog.ReadPage(resolvedPageSize)));
});
This excerpt is not a separate complete program. It demonstrates the behavior being changed: every parsed integer proceeds to ReadPage. A collection operation's handling of that integer does not implement the requested HTTP rejection contract.
The complete changed illustration checks the range before accessing the catalog:
using System.Collections.Immutable;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<OrderCatalog>();
var app = builder.Build();
app.MapGet("/orders", IResult (int? pageSize, OrderCatalog catalog) =>
{
var resolvedPageSize = pageSize ?? 20;
if (resolvedPageSize < 1 || resolvedPageSize > 100)
{
return Results.BadRequest(new ApiError(
"invalid_page_size",
"pageSize must be between 1 and 100."));
}
return Results.Ok(new OrderPage(
resolvedPageSize,
catalog.ReadPage(resolvedPageSize)));
});
app.Run();
public sealed class OrderCatalog
{
private readonly ImmutableArray<OrderSummary> _orders =
[
new(1, "First order"),
new(2, "Second order"),
new(3, "Third order")
];
public ImmutableArray<OrderSummary> ReadPage(int pageSize)
{
return _orders.Take(pageSize).ToImmutableArray();
}
}
public sealed record OrderSummary(int Id, string Description);
public sealed record OrderPage(
int PageSize,
ImmutableArray<OrderSummary> Items);
public sealed record ApiError(string Code, string Message);
The sample spells out IResult for clarity. Both Results.Ok and Results.BadRequest already return IResult, so this handler can also infer its return type. The current Microsoft Minimal API response documentation documents Results, TypedResults, and result union types. Typed results offer stronger concrete result typing and metadata, but converting an existing endpoint to that style is not required by a range-check task.
The catalog is immutable seeded data for the illustration, not a substitute for a persistence abstraction. Its three rows are enough to explain limiting results, but they do not prove a 100-row boundary by themselves. That boundary needs an assertion about acceptance and the reported page size, or a fixture with sufficient data when item count is the claim.
The example also has a bounded purpose: reject parsed out-of-range integers. It does not promise that every binding failure uses the same custom ApiError shape. That is why malformed input belongs in a separate HTTP test for the real application.
Define regression evidence before Claude Code implements
For the hypothetical existing endpoint, the acceptance matrix should cover omitted input, both valid boundaries, values just outside each boundary, and malformed input. Each row should identify the expected status, response contract, and whether the downstream query may run.
The omitted case proves compatibility with the default. Values 1 and 100 prove the inclusive boundaries. Values 0 and 101 prove nearby rejection. A negative value makes the original defect explicit. Nonnumeric input proves the binding path remains understood.
Do not replace this with a request for Claude Code to write “seven tests.” One parameterized test can cover several input cases, while separate tests may be needed for authorization and downstream activity. What matters is the behavior asserted.
The processing-pipeline testing guide illustrates a related principle: rejection and faults need direct evidence rather than inference from a successful path.
In an existing repository, write the regression test first and demonstrate that it fails on the baseline in the approved validation environment. Then apply the implementation and show that the same test passes. Without the baseline failure, a green result may reflect a test that never exercised the bug.
Ask Claude Code for tests that match the boundary
A handler unit test can check an isolated range policy or concrete result. It cannot by itself establish route registration, query binding, middleware, authorization, and serialized HTTP output.
An HTTP integration test should send a request through the application and inspect the status and response body. If preserving tenant filtering is part of the change's risk, the fixture must exercise that relationship rather than mocking away the relevant behavior.
The distinction between unit and integration verification is useful here. Real module interactions and isolated logic answer different questions, even when both tests run under the same framework.
Checking that invalid input does not query storage requires appropriate evidence. A collaborator assertion can prove that a handler did not call its query abstraction in an isolated test. A real integration fixture can establish more of the runtime path. Neither should be described as proving every possible persistence side effect.
Avoid conditional assertions that only run when the response happens to be successful. If success is required, assert it unconditionally before examining the body. If rejection is required, assert rejection directly. A test that skips its important assertion on the failing path is not useful regression coverage.
Keep execution and publishing outside the Claude Code edit request
The current official permissions documentation explains runtime-enforced rules and mode differences. Those controls should reflect the allowed work rather than treating every developer command as interchangeable.
A build can invoke custom targets. A test can start a fixture with external dependencies. A migration can change persistent state. A publishing command can expose the result to users. A page-size task does not implicitly authorize all of them.
When validation is remote, record the exact revision, command, environment, and result. Ensure the revision validated is the revision reviewed. A green check from an earlier commit does not establish the final code's behavior.
The isolated illustration was compiled and served for seven HTTP checks. Those checks established the shown default, boundaries, range-error payload, and malformed-input status. They do not establish the behavior of an existing application's authorization, tenant filtering, persistence, or error middleware.
Review the diff as a contract change
After Claude Code implements the change, inspect more than the conditional. Check whether the parameter remains optional, whether defaults changed, whether response types remain compatible, and whether the route kept its authorization context.
Then inspect the tests. Do they send the rejected values? Do they assert the expected response? Does the fixture contain enough data for item-count claims? Are unrelated failures being hidden by skips or broad exception handling?
Finally, inspect file scope. A new package, schema change, logging rewrite, or route relocation should have a specific justification. If it is not necessary for the acceptance criteria, remove it from this change.
An application integration example such as the existing ASP.NET Core agent-client tutorial reinforces why dependency ownership and explicit failures matter. A bounded edit should preserve those established boundaries rather than simplifying them away.
Frequently asked questions
Why start with a trace instead of generated code?
The trace establishes where the input, default, validation, and query behavior live. Without it, a plausible implementation can bypass existing contracts or duplicate a rule in the wrong layer.
Is pageSize 20 an ASP.NET Core default?
No. It is an illustrative application requirement in this article. A real endpoint's default and accepted range must come from its own contract.
Does a nonnumeric value test the same path as zero?
No. A nonnumeric value concerns binding, while zero is a parsed integer evaluated by the range policy. HTTP tests should distinguish those behaviors.
Is the sample ready to copy over a production endpoint?
No. It isolates validation with anonymous, in-memory data; its isolated HTTP checks do not cover your production contracts. Preserve the real application's authentication, tenancy, persistence, observability, and error conventions.
Does a handler unit test prove the HTTP contract?
Not completely. Routing, binding, middleware, authorization, and serialization require appropriate integration evidence. Isolated tests remain useful for the smaller logic boundary.
What should Claude Code report when validation was not run?
It should say the sample or change was authored but not executed, identify missing checks, and avoid claiming success. The review can then distinguish code inspection from runtime evidence.
A bounded change preserves more than it adds
An ASP.NET Core change with Claude Code is reviewable when the new rule is explicit and the existing contracts remain visible. A small validation condition is only one part of that work.
The complete result includes a correct scope, meaningful regression evidence, and an honest handoff. That keeps attention on what clients observe rather than on how confidently the agent describes its implementation.

