C# and .NET rules for AI coding agents

The most expensive .NET mistakes an agent makes are usually about async and lifetimes: .Result or .Wait() on a task in app code, a DbContext stored in a singleton or shared across parallel tasks, a query enumerated twice. The C# and .NET rules cover modern .NET with ASP.NET Core and EF Core, starting from the <TargetFramework> and <LangVersion> in the project file.

For APIs they ask for thin endpoints that bind, validate, call a service and map the result, using TypedResults so OpenAPI shows every outcome. For data access: AsNoTracking() on read-only queries and projections into DTOs. Smaller habits matter too, such as structured log templates instead of interpolation, DateTimeOffset.UtcNow or an injected TimeProvider, and throw; instead of rethrowing ex.

Folder: rules/csharp-dotnet/ · csharp-dotnet.mdc, CLAUDE.md, AGENTS.md

What the .NET rules tell your agent

First rule of each section in csharp-dotnet.mdc, quoted as shipped.

Mistakes these rules are written to prevent

Source: “Pitfalls to avoid”.

Checks the agent runs before it says done

Example commands from the .NET file (swap in your repo's own):

Run dotnet build -warnaserror, dotnet format --verify-no-changes and dotnet test, and report the results.

Before and after: EF Core: query review for tracking, projection and raw SQL

Prompt card 25 · Stack: C#, EF Core 8+ · Type: Performance and security review · Best model: a frontier reasoning model

What the card is for

Review LINQ-to-EF queries for change tracking on read paths, over-fetching whole entities, client-side evaluation traps, cartesian explosions from multiple Includes, SaveChanges in loops and SQL injection through FromSqlRaw.

Worked example from the card

Before

public async Task<List<CustomerSummary>> Search(string name)
{
    var customers = await _db.Customers
        .FromSqlRaw($"SELECT * FROM Customers WHERE Name LIKE '%{name}%'")
        .Include(c => c.Orders)
        .Include(c => c.Addresses)
        .ToListAsync();

    foreach (var c in customers) { c.LastSearchedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); }

    return customers.Select(c => new CustomerSummary(c.Id, c.Name, c.Orders.Count)).ToList();
}

Expected AI output (abridged)

Critical - FromSqlRaw with an interpolated string: the interpolation happens before EF
sees it, so `name` goes into the SQL text. Input `x' OR 1=1 --` returns every customer.
High - Two collection Includes on a single query: a customer with 50 orders and 3
addresses returns 150 rows. Only Orders.Count is used and Addresses not at all.
High - SaveChangesAsync per customer: one round trip per row. Use ExecuteUpdateAsync.
Medium - Tracking enabled on a read path. Projection removes the need.

After

public async Task<List<CustomerSummary>> Search(string name, CancellationToken ct)
{
    var pattern = $"%{name}%";
    var matches = _db.Customers.Where(c => EF.Functions.Like(c.Name, pattern));

    var summaries = await matches
        .OrderBy(c => c.Name)
        .Take(100)
        .Select(c => new CustomerSummary(c.Id, c.Name, c.Orders.Count))
        .ToListAsync(ct);

    var ids = summaries.Select(s => s.Id).ToList();
    await _db.Customers.Where(c => ids.Contains(c.Id))
        .ExecuteUpdateAsync(s => s.SetProperty(c => c.LastSearchedAt, DateTime.UtcNow), ct);

    return summaries;
}

Excerpt from card 25; the prompt itself is in the pack.

More .NET prompt cards in the pack

Using these rules in Cursor, Claude Code and AGENTS.md tools

Cursor

Put _base.mdc and csharp-dotnet.mdc in .cursor/rules/. Globs: src/**/*.cs, **/*.cs, **/*.csproj, appsettings*.json.

Claude Code

Root CLAUDE.md, base first. Path-scoped variant .claude/rules/csharp-dotnet.md for **/*.cs, **/*.csproj; PostToolUse hook: dotnet format.

AGENTS.md tools (Codex, Gemini CLI, Aider)

Base then stack in one AGENTS.md. Gemini CLI needs context.fileName; Aider needs --read AGENTS.md. Put this file in the folder with the .sln file. Agents use the AGENTS.md nearest to the file being edited, so a nested copy overrides a root one for that subtree.

Related stacks

← All stacks