Csharp Dotnet logo

Csharp Dotnet

Community
ericrisco
csharp-dotnet

Use when writing, reviewing, testing, or shipping C# / .NET code — ASP.NET Core APIs (minimal APIs vs controllers), EF Core data access, async correctness, solution layout in .cs/.csproj/.sln. NOT a Java/Spring backend (that is spring-boot), NOT a Node/TypeScript backend (that is nestjs), NOT framework-neutral REST naming (that is api-design).

Overview

Publisherericrisco
Repositoryrsc-harness
Skill namecsharp-dotnet
Stars
239
Forks
24
Bundled files
5
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 5 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by ericrisco on GitHub. Read the source before you install it.

Installation

Install the Csharp Dotnet AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/ericrisco/rsc-harness/tree/main/skills/csharp-dotnet
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/ericrisco/rsc-harness.git /tmp/rsc-harness
mkdir -p .claude/skills
cp -r /tmp/rsc-harness/skills/csharp-dotnet .claude/skills/csharp-dotnet
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Csharp Dotnet in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Csharp Dotnet on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Csharp Dotnet is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Modern C# and .NET services

You write and review C#, ASP.NET Core APIs, and EF Core data access. You own the .NET idioms, the HTTP-contract patterns as expressed in .NET, async correctness, and the .NET-specific quality/security gates. You delegate everything language- agnostic to the right sibling.

Target: .NET 10 (LTS, released 2025-11-11, supported 3 years) with C# 14, EF Core 10, ASP.NET Core 10. .NET 9 / C# 13 is the immediate STS predecessor — still in support but shorter-lived. Features gate on the target framework moniker (TFM): a net10.0 project gets C# 14 by default; a net9.0 project caps at C# 13. When you use a C# 14 feature (field, extension members, null-conditional assignment), say which TFM it requires.

When NOT to use — delegate

SituationRoute to
Java / Spring Boot backendspring-boot
Node / NestJS TypeScript backendnestjs
Language-agnostic threat modeling, OWASP authz/abuse review../secure-coding/SKILL.md
Raw Postgres schema/index/query tuning../postgresdb/SKILL.md
Framework-neutral REST resource taxonomyapi-design
Dockerfile / CI pipeline / deploy target../deployment/SKILL.md
Recording per-project conventions in the workspace wiki../harness/SKILL.md

The .NET expression of an API contract and async correctness live HERE. Resource naming theory lives in api-design; the JVM and Node counterparts are spring-boot and nestjs.

Project & solution layout + dotnet CLI

bash
dotnet new sln -n Shop                         # solution
dotnet new webapi -n Shop.Api                  # minimal API (default in .NET 10)
dotnet new webapi -n Shop.Api --use-controllers # opt into MVC controllers instead
dotnet new xunit -n Shop.Api.Tests             # test project
dotnet sln add Shop.Api Shop.Api.Tests         # wire into the solution
dotnet add Shop.Api package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet ef migrations add Initial -p Shop.Api   # create a migration
dotnet ef database update -p Shop.Api          # apply migrations
dotnet build -warnaserror                      # compile; analyzers + NRT as errors
dotnet test                                    # run tests
dotnet format                                  # apply style; --verify-no-changes in CI
dotnet publish -c Release                       # produce deployable output

Recommended shape:

text
Shop.sln
Directory.Packages.props    # central package management: <PackageVersion> here, no versions in csproj
Directory.Build.props       # shared <Nullable>enable</Nullable>, <TreatWarningsAsErrors>, LangVersion
src/Shop.Api/               # endpoints grouped by feature module (Products/, Orders/)
src/Shop.Domain/            # entities, value objects (no EF/ASP.NET dependency)
tests/Shop.Api.Tests/

Central package management: turn it on with <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> and list versions once in Directory.Packages.props. Why: one source of truth, no version drift across projects.

NRT on for every project — <Nullable>enable</Nullable> in Directory.Build.props. Why: the compiler turns whole classes of NullReferenceException into build-time warnings. Never disable it to silence a warning — fix the nullability.

Modern C# idioms (C# 14 / .NET 10)

DTO as a record, not a class:

csharp
// Bad: mutable class, no value equality, hand-written boilerplate
public class ProductDto { public int Id { get; set; } public string Name { get; set; } }

// Good: immutable record DTO
public record ProductDto(int Id, string Name, decimal Price);

Why: value equality, with expressions, no boilerplate — and never reuse an EF entity as the wire DTO (see anti-patterns).

Pattern matching over if-chains:

csharp
// Bad
if (shape is Circle) { var c = (Circle)shape; return Math.PI * c.R * c.R; }

// Good
return shape switch
{
    Circle c      => Math.PI * c.R * c.R,
    Rectangle r   => r.W * r.H,
    _             => throw new ArgumentOutOfRangeException(nameof(shape)),
};

field keyword — field-backed property without a hand-written backing field (requires C# 14 / net10.0):

csharp
// Bad: explicit backing field just to trim a string
private string _name = "";
public string Name { get => _name; set => _name = value.Trim(); }

// Good (C# 14)
public string Name { get; set => field = value.Trim(); }

Primary constructors, required/init, collection expressions:

csharp
public class OrderService(IOrderRepository repo, ILogger<OrderService> log)  // primary ctor: DI in one line
{
    public required string Region { get; init; }                            // must be set at construction, then immutable
    private static readonly int[] DefaultTiers = [1, 2, 3];                  // collection expression
}

Prefer NRT annotations over defensive null checks: declare string? note when null is valid and let the compiler force callers to handle it, instead of if (x == null) guards scattered everywhere.

Dispose deterministically: using / await using (or using declarations) for anything IDisposable/IAsyncDisposable. Why: leaked connections and handles.

async/await correctness

This is the highest-leverage area to get right. Core rules in body; the full catalog (ConfigureAwait, ValueTask, IAsyncEnumerable, Channels, parallelism, deadlock cases) is in references/async.md — read it before any non-trivial async review.

  • No async void except top-level event handlers. Why: exceptions escape onto the thread pool and crash the process; the caller cannot await or catch it. Use async Task.
  • Never .Result / .Wait() / GetAwaiter().GetResult() on the request path. This is the classic deadlock and the answer to "my endpoint hangs under load":
csharp
// Bad: blocks the thread on an async call -> thread-pool starvation / deadlock under load
public IActionResult Get() => Ok(_svc.LoadAsync().Result);

// Good: await all the way up
public async Task<IActionResult> Get(CancellationToken ct) => Ok(await _svc.LoadAsync(ct));
  • Flow CancellationToken from the endpoint down into every async call — SaveChangesAsync, queries, outbound HTTP. Why: a dropped token means the request keeps running after the client gives up.
  • ValueTask only for hot paths that usually complete synchronously; default to Task. Never await a ValueTask twice.
  • IAsyncEnumerable<T> for streaming results instead of materializing a huge list.

ASP.NET Core APIs

Decision: minimal API for new services; controllers only when you need MVC machinery (model binding conventions, action filters at scale, views). Both can coexist. Why minimal by default: less ceremony, first-class in .NET 10.

Production minimal API shape — route group + DataAnnotations validation (built in for minimal API parameters in .NET 10) + ProblemDetails + OpenAPI 3.1:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();                 // OpenAPI 3.1, JSON Schema 2020-12
builder.Services.AddProblemDetails();          // RFC 9457 error bodies
builder.Services.AddDbContext<ShopDb>(o => o.UseNpgsql(builder.Configuration.GetConnectionString("Shop")));

var app = builder.Build();
app.MapOpenApi();                              // serves the OpenAPI document
app.UseExceptionHandler();                     // emits ProblemDetails on unhandled errors

var products = app.MapGroup("/products").WithTags("Products");
products.MapGet("/{id:int}", async (int id, ShopDb db, CancellationToken ct) =>
    await db.Products.AsNoTracking()
        .Where(p => p.Id == id)
        .Select(p => new ProductDto(p.Id, p.Name, p.Price))   // project to DTO, no entity leak
        .FirstOrDefaultAsync(ct) is { } dto
        ? Results.Ok(dto)
        : Results.NotFound());
products.MapPost("/", async (CreateProduct req, ShopDb db, CancellationToken ct) =>
{   // DataAnnotations on CreateProduct are validated automatically in .NET 10
    var entity = new Product { Name = req.Name, Price = req.Price };
    db.Products.Add(entity);
    await db.SaveChangesAsync(ct);
    return Results.Created($"/products/{entity.Id}", new ProductDto(entity.Id, entity.Name, entity.Price));
});
app.Run();

DI lifetimes: Singleton (one for the app), Scoped (one per request — DbContext lives here; it is not thread-safe, so sharing one across requests corrupts change tracking), Transient (new each resolve). Never inject a Scoped service into a Singleton (captive dependency — see anti-patterns). Middleware ordering, endpoint filters, the options pattern, and auth defaults are in references/aspnetcore.md.

EF Core 10

csharp
// Bad: tracking + N+1 — loads orders, then one query per order's customer
var orders = await db.Orders.ToListAsync(ct);
foreach (var o in orders) { var name = o.Customer.Name; /* lazy/round-trip per row */ }

// Good: no-tracking read + single query via projection
var rows = await db.Orders.AsNoTracking()
    .Select(o => new OrderRow(o.Id, o.Customer.Name))   // EF translates to one JOIN
    .ToListAsync(ct);
  • AsNoTracking() on every read-only query — skips change-tracking overhead.
  • Kill N+1 with projection (Select) or Include/ThenInclude; use AsSplitQuery() when an Include cartesian explosion hurts.
  • Migrations: dotnet ef migrations add <Name> then review the generated Up/Down before database update. Never hand-edit applied migrations.
  • EF Core 10 highlights: native json column type, vector type + VECTOR_DISTANCE() for embeddings/RAG, LeftJoin/RightJoin LINQ operators, and named query filters (multiple filters per entity, selectively disabled).

Raw SQL only via parameterized FromSql/ExecuteSql interpolated strings (EF parameterizes the holes). Query patterns, migration workflow, the EF Core 10 features, and raw-SQL safety in full are in references/efcore.md. Schema/index tuning is ../postgresdb/SKILL.md.

.NET security controls

Keep only the .NET-specific controls here; threat modeling and OWASP review go to ../secure-coding/SKILL.md.

  • Parameterized data access — EF LINQ / parameterized FromSql / ADO.NET SqlParameter. Never concatenate user input into SQL. Why: SQL injection.
  • Secrets — dotnet user-secrets in dev, Key Vault / environment in prod. Never in appsettings.json or source.
  • Antiforgery for cookie-auth browser POSTs; Data Protection for cookies/tokens (configure a persisted key ring across instances).
  • Auth defaults — authenticate then authorize via middleware; prefer policy-based authorization; ASP.NET Core 10 is secure-by-default but still verify HTTPS redirection and HSTS in production.

Testing

  • xUnit is the default. Unit-test domain logic with no framework dependency.
  • Integration tests via WebApplicationFactory<Program> — boots the real pipeline in-memory and lets you HttpClient your endpoints.
  • Testcontainers to run a real Postgres in a container for EF integration tests instead of the in-memory provider (which lies about relational behavior).
  • Gate everything with scripts/verify.sh (format + build-warnaserror + test + vuln scan).

Anti-patterns

Anti-patternWhy it bitesDo instead
async void (non-event-handler)Exceptions crash the process; uncatchable by callerasync Task
.Result / .Wait() on request pathDeadlock / thread-pool starvation under loadawait all the way
Scoped service injected into a SingletonCaptive dependency: stale/DbContext reused across requestsInject IServiceScopeFactory, or make the consumer scoped
Reusing an EF entity as the wire DTOOver-posting, serialization cycles, leaked columnsSeparate record DTO + mapping
Tracking queries on read-only readsWasted memory/CPU on change trackingAsNoTracking()
Lazy navigation in a loopN+1 round-tripsProjection or Include
Missing CancellationTokenWork continues after client disconnectsFlow the token to all I/O
catch (Exception) { } swallowHides failures; corrupt state continuesHandle specifically or let UseExceptionHandler map it
<Nullable>disable</Nullable> to mute a warningRe-opens the NRE class of bugsKeep NRT on; fix the nullability

Project grounding

Record per-project conventions (target TFM, package manager, lint rules, deploy target) in the workspace wiki via ../harness/SKILL.md, not inline assumptions. Before calling a C# change done, run scripts/verify.sh from the solution/project root.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Csharp Dotnet AI skill do?

Use when writing, reviewing, testing, or shipping C# / .NET code — ASP.NET Core APIs (minimal APIs vs controllers), EF Core data access, async correctness, solution layout in .cs/.csproj/.sln. NOT a Java/Spring backend (that is spring-boot), NOT a Node/TypeScript backend (that is nestjs), NOT framework-neutral REST naming (that is api-design).

Why use Csharp Dotnet on TypingMind?

Because you install it once and use it with any model. Csharp Dotnet is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Csharp Dotnet in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/ericrisco/rsc-harness/tree/main/skills/csharp-dotnet. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Csharp Dotnet?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Csharp Dotnet?

As many as you like. As long as a model supports skills, you can use Csharp Dotnet with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Csharp Dotnet AI skill free?

Yes. It is published on GitHub by ericrisco under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇