agentleFS
martincjarvismartincjarvis/development-skills/skills/dotnet-mapping-standard/SKILL.md

dotnet-mapping-standard

Skill4 starsChanged 10 months agoMITView on GitHub

Use when mapping between DTOs, contracts, and persistence models in .NET - prefer source-generated mappers with explicit boundary conversions over reflection-based mapping.

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 8 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈8

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Apply DI gating rules: Use static mappers unless mapper has injected dependencies

    line 29
  2. Distinctive:

    Verify side-effect free: Ensure mappings have no side effects or external calls

    line 31
  3. Distinctive:

    Prefer open-source, source-generated mapping tools.

    line 37
  4. Distinctive:

    Prefer projection mappings that avoid materialising large object graphs when not needed.

    line 62
  5. Distinctive:

    Keep read models separate where pragmatic.

    line 63
  6. Very specific:

    If a mapper is injected into a constructor (controller/handler/service), reviewers must check whether the mapper implementation itself has …

    line 74
  7. Distinctive:

    If the mapper has no injected dependencies (pure/stateless mapping), require refactor to a static mapper and remove DI registration.

    line 76
  8. Distinctive:

    When reviewing mappers registered in DI:

    line 133
  9. Distinctive:

    NO: Remove unused dependencies

    line 139

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Overview

Standardise mapping between DTOs, domain models, and persistence models using source-generated mappers. Mappings live at explicit boundaries (API, infrastructure) with deterministic, testable conversion paths and no runtime reflection magic.

When to Use

  • Mapping between API request/response DTOs and domain models
  • Converting domain entities to/from persistence models
  • Handling integration event contracts at service boundaries
  • Reviewing PRs that introduce new mapping logic
  • Refactoring existing reflection-based mappers to source-generated alternatives

Core Workflow

  1. Identify mapping boundaries: Locate where DTOs cross into domain or persistence layers
  2. Choose source-generated mapper: Select Mapperly or similar compile-time mapper
  3. Create explicit mappers: Implement mapper classes at boundary projects (API, Infrastructure)
  4. Handle typed IDs explicitly: Map strongly typed IDs and value objects with explicit conversions
  5. Apply DI gating rules: Use static mappers unless mapper has injected dependencies
  6. Add mapping tests: Include round-trip tests for critical boundary conversions
  7. Verify side-effect free: Ensure mappings have no side effects or external calls

Core

Defaults

  • Prefer open-source, source-generated mapping tools.
  • Mappings live at boundaries (API project, Infrastructure project), not scattered across the domain.
  • Typed IDs and value objects must be mapped explicitly (see dotnet-domain-primitives).

Anti-patterns to avoid

  • Reflection-based "magic mapping" that fails at runtime.
  • Mapping logic embedded inside domain entities.
  • Implicit conversions that obscure boundary crossings.

Review rules

  • New DTO/domain/persistence types must have deterministic, testable mapping paths.
  • Mapping must be side-effect free.

Load: examples

  • API: map request DTO -> command with explicit typed ID parsing/creation.
  • Persistence: map EF entity -> domain aggregate using explicit value object construction.
  • Integration: map external event contract -> internal event with boundary validations.

Load: advanced

Projections / query scenarios

  • Prefer projection mappings that avoid materialising large object graphs when not needed.
  • Keep read models separate where pragmatic.

Error strategy

  • Invalid boundary inputs fail fast at the boundary (validation).
  • Domain invariants enforced by constructors/factories.

Load: enforcement

Review heuristic: mapper DI gating

  • If a mapper is injected into a constructor (controller/handler/service), reviewers must check whether the mapper implementation itself has injected dependencies.
  • If the mapper has no injected dependencies (pure/stateless mapping), require refactor to a static mapper and remove DI registration.
  • Injection is permitted only when mapping depends on external collaborators and those collaborators are injected into the mapper.
  • "Inject for testability" is not sufficient when mapping is deterministic; test the mapper directly.
  • Reject PRs introducing runtime reflection mapping unless justified per dotnet-source-generation-first.
  • Require mapping tests for critical boundary conversions (typed IDs and value objects).

Load: testing

Mapping Unit Test Template

public class OrderMapperTests
{
    [Fact]
    public void ToEntity_WithValidDto_MapsAllProperties()
    {
        // Arrange
        var dto = new CreateOrderRequest
        {
            CustomerId = "cust-123",
            Amount = 99.99m,
            Currency = "GBP"
        };

        // Act
        var entity = OrderMapper.ToEntity(dto);

        // Assert
        entity.CustomerId.Value.Should().Be("cust-123");
        entity.Amount.Value.Should().Be(99.99m);
        entity.Amount.Currency.Should().Be(Currency.GBP);
    }

    [Fact]
    public void ToDto_RoundTrip_PreservesData()
    {
        // Arrange
        var original = new Order(
            CustomerId.From("cust-123"),
            Money.From(99.99m, Currency.GBP));

        // Act
        var dto = OrderMapper.ToDto(original);
        var roundTripped = OrderMapper.ToEntity(dto);

        // Assert
        roundTripped.Should().BeEquivalentTo(original);
    }
}

DI Gating Checklist

When reviewing mappers registered in DI:

  • Does the mapper have zero injected dependencies?
    • YES: Refactor to static mapper, remove DI registration
    • NO: Proceed to next check
  • Are all injected dependencies used during mapping?
    • NO: Remove unused dependencies
    • YES: Proceed
  • Is injection for "testability only"?
    • YES: Reject - test static mappers directly
    • NO: Injection acceptable

Red Flags - STOP

These statements indicate mapping anti-patterns:

ThoughtReality
"AutoMapper conventions will handle it"Explicit mapping prevents runtime surprises; be deliberate
"Mapping logic belongs in the entity"Keep entities clean; mappers handle boundary concerns
"Inject the mapper for testability"Pure mappers don't need DI; test static mappers directly
"Runtime reflection is fine"Source-generated mappers are faster and fail at compile time
"Implicit conversions are convenient"Explicit conversions make boundary crossings visible
"Round-trip tests aren't worth it"Round-trip tests catch subtle mapping bugs; always include them

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.