Claude-skill-registry backend-csharp-patterns

Use when editing C# backend files (.cs) in src/Backend/, src/Platform/, or src/PlatformExampleApp/. Provides CQRS patterns, repository patterns, validation patterns, entity patterns, background jobs, message bus consumers, data migrations, and fluent helpers for EasyPlatform .NET 9 development.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/backend-csharp-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-backend-csharp-patterns && rm -rf "$T"
manifest: skills/data/backend-csharp-patterns/SKILL.md
source content

Backend C# Code Patterns

When implementing backend C# code in EasyPlatform, follow these patterns exactly.

Full Pattern Reference

See the complete code patterns with examples: backend-code-patterns.md

Quick Reference

Pattern Index

#PatternKey Interface/Contract
1Clean ArchitectureDomain → Application → Persistence → Api layers
2Repository
IPlatformQueryableRootRepository<TEntity, TKey>
+ static expression extensions
3Repository API
CreateAsync
,
GetByIdAsync
,
GetAllAsync
,
FirstOrDefaultAsync
,
CountAsync
4Validation
PlatformValidationResult.And().AndAsync()
fluent chain, never throw
5Cross-Service
PlatformCqrsEntityEventBusMessageProducer
+
PlatformApplicationMessageBusConsumer
6Full-Text Search
searchService.Search(q, text, Entity.SearchColumns())
in query builder
7CQRS CommandCommand + Result + Handler in ONE file,
PlatformCqrsCommandApplicationHandler
8Query
PlatformCqrsPagedQuery
+
GetQueryBuilder()
+ parallel count/items
9Side EffectsEntity Event Handlers in
UseCaseEvents/
, never in command handlers
10Entity
RootEntity<T, TKey>
, static expressions,
[TrackFieldUpdatedDomainEvent]
, navigation properties
11DTO
PlatformEntityDto<T, TKey>.MapToEntity()
, DTO owns mapping, constructor from entity
12Fluent Helpers
.With()
,
.Then()
,
.EnsureFound()
,
.EnsureValid()
,
.ParallelAsync()
13Background Jobs
PlatformApplicationPagedBackgroundJobExecutor
,
[PlatformRecurringJob("cron")]
14Message Bus
PlatformApplicationMessageBusConsumer<TMessage>
,
TryWaitUntilAsync()
for deps
15Data Migration
PlatformDataMigrationExecutor<TDbContext>
,
OnlyForDbsCreatedBeforeDate
16Multi-Database
PlatformEfCorePersistenceModule
/
PlatformMongoDbPersistenceModule

Critical Rules

  1. Repository: Use
    IPlatformQueryableRootRepository<TEntity, TKey>
    - NEVER generic
    IPlatformRootRepository
  2. Validation: Use
    PlatformValidationResult
    fluent API (
    .And()
    ,
    .AndAsync()
    ) - NEVER
    throw ValidationException
  3. Side Effects: Handle in Entity Event Handlers (
    UseCaseEvents/
    ) - NEVER in command handlers
  4. DTO Mapping: DTOs own mapping via
    MapToEntity()
    or
    MapToObject()
    - NEVER map in handlers
  5. Command Structure: Command + Result + Handler in ONE file under
    UseCaseCommands/{Feature}/
  6. Cross-Service: Use RabbitMQ message bus - NEVER direct database access

Anti-Patterns

// ❌ Direct cross-service DB access → ✅ Use message bus
// ❌ Custom repository interface → ✅ Use platform repo + extensions
// ❌ Manual validation throw → ✅ Use PlatformValidationResult fluent API
// ❌ Side effects in handler → ✅ Use entity event handlers
// ❌ DTO mapping in handler → ✅ DTO owns mapping via MapToObject()/MapToEntity()

Templates

CQRS Command Template

public sealed class Save{Entity}Command : PlatformCqrsCommand<Save{Entity}CommandResult>
{
    public string Id { get; set; } = "";
    public string Name { get; set; } = "";

    public override PlatformValidationResult<IPlatformCqrsRequest> Validate()
        => base.Validate().And(_ => Name.IsNotNullOrEmpty(), "Name required");
}

public sealed class Save{Entity}CommandResult : PlatformCqrsCommandResult
{
    public {Entity}Dto Entity { get; set; } = null!;
}

internal sealed class Save{Entity}CommandHandler : PlatformCqrsCommandApplicationHandler<Save{Entity}Command, Save{Entity}CommandResult>
{
    protected override async Task<Save{Entity}CommandResult> HandleAsync(Save{Entity}Command req, CancellationToken ct)
    {
        var entity = req.Id.IsNullOrEmpty()
            ? req.MapToNewEntity().With(e => e.CreatedBy = RequestContext.UserId())
            : await repo.GetByIdAsync(req.Id, ct).Then(e => req.UpdateEntity(e));
        await entity.ValidateAsync(repo, ct).EnsureValidAsync();
        var saved = await repo.CreateOrUpdateAsync(entity, ct);
        return new Save{Entity}CommandResult { Entity = new {Entity}Dto(saved) };
    }
}

Detailed Instructions

For task-specific guidance, also reference: