Ching.Dapper.Clean.Solution.Template
1.1.3
dotnet new install Ching.Dapper.Clean.Solution.Template::1.1.3
Dapper Clean Architecture Solution Template
Plantilla dotnet new que genera una solución .NET 10 con Clean Architecture y Dapper, lista para producción. Auth completa (JWT + refresh + roles dinámicos), seguridad endurecida, observabilidad, y módulos opcionales — todo configurable al crear el proyecto.
dotnet new install Ching.Dapper.Clean.Solution.Template
dotnet new dapper-solution -n MyProject
Por qué esta plantilla
| Sin plantilla | Con esta plantilla | |
|---|---|---|
| Auth básico (JWT firmado + verify) | 2 días de research + bugs | Ya viene, probado |
| Refresh tokens rotativos | Otro día | Incluido |
| Logout que mata el JWT al instante | Casi nadie lo implementa | Blocklist JTI + cache |
| Roles dinámicos + permisos por endpoint | 3 días | [HasPermission("...")] y listo |
| Account lockout, CSRF, security headers, HSTS, CORS allowlist, rate limit | "Lo agregamos después" (nunca) | Configurado de fábrica |
| Health checks, logs estructurados, OpenTelemetry, Prometheus | 1 día por pieza | Cableado |
| Estructura limpia que escala | Discusiones eternas | Domain · Application · Infrastructure · Api |
No es opinated por opinated — es lo que debería estar en una API productiva del día 1.
Quick start (5 minutos)
# 1. Instalar plantilla
dotnet new install Ching.Dapper.Clean.Solution.Template
# 2. Generar proyecto
dotnet new dapper-solution -n Acme.Catalog \
--companyName "ACME" \
--companyEmail "dev@acme.io" \
--apiDocName "CATALOG" \
--dbName "acme_catalog"
cd Acme.Catalog
# 3. Configurar secreto JWT (mínimo 32 chars, NO commitear)
dotnet user-secrets init --project Acme.Catalog.Api
dotnet user-secrets set "Jwt:Secret" "$(((1..48 | ForEach-Object {[char]((33..126)|Get-Random)}) -join ''))" \
--project Acme.Catalog.Api
# 4. Aplicar esquema (DBA o pipeline; aquí mostramos manual)
for f in Database/*.sql; do mysql -u root -p acme_catalog < $f; done
# 5. Arrancar
dotnet run --project Acme.Catalog.Api
Swagger en https://localhost:7009/swagger/index.html. Postman collection en postman/.
Documentación
Cada tema tiene una guía profunda en docs/:
| Guía | Cuándo leerla |
|---|---|
| Architecture | Cómo está estructurada la solución, qué hace cada capa, cómo crece |
| Adding a module | Tutorial paso a paso: agregar la entidad Orders de cero |
| Authentication | JWT, refresh tokens, logout instantáneo (blocklist), Google OAuth, cookie vs header |
| Authorization | Roles dinámicos, catálogo de permisos, [HasPermission(...)], cómo crear un rol nuevo desde la UI |
| Security | Account lockout, CSRF, security headers, HSTS, rate limiting, password reset, email verification |
| Configuration | Todos los parámetros de plantilla, secciones de appsettings, env vars |
| Database | Scripts SQL, convención sin runner, schema versionado |
| Observability | Logs estructurados, correlation ID, OpenTelemetry, Prometheus, health checks, audit log |
| Deployment | Docker, env vars en producción, reverse proxy, prefijos |
| FAQ | Errores comunes, decisiones de diseño, troubleshooting |
¿Qué incluye?
Arquitectura
- 4 proyectos:
*.Domain,*.Application,*.Infrastructure,*.Api BaseEntitycon auditoría (Id,CreatedAt,UpdatedAt)RepositoryBase<TEntity, TKey>Dapper con paginación +CancellationToken- Auto-registro DI por feature vía
IServiceModule(zero-config para módulos nuevos) - API versioning
/api/v{version}/... - Swagger multi-documento configurable
ProducesResponseTypeaplicado por convención global a todos los endpoints
Auth
- JWT (HMAC-SHA256) + refresh tokens rotativos
- Logout instantáneo con blocklist de JTI (cache + BD)
- Toggle
--authMode={header|cookie|both}— el back inyecta cookies HttpOnly automáticamente en modo cookie - CSRF double-submit en modo cookie
- Roles dinámicos en BD + catálogo de permisos en código
- BCrypt para contraseñas, Google OAuth opcional
- Password reset + email verification (con
IEmailSenderabstraído) - Account lockout (5 intentos = 15 min bloqueado)
Seguridad
- HTTPS redirect + HSTS (1 año, preload) fuera de Development
- Security headers: CSP, XFO, COOP, CORP, Referrer-Policy, Permissions-Policy
- CORS desde allowlist en
appsettings - Rate limiting built-in: 100 req/min global, 5 req/min en
/api/v1/auth/* - Validación FluentValidation con filtro global
Operaciones
- Health checks
/health/livey/health/readycon ping MySQL - OpenTelemetry tracing + metrics (OTLP exporter)
- Prometheus scrape en
/metrics - Logging estructurado JSON (Serilog) + correlation ID propagado
- Audit log con
[Audit("action")]en operaciones sensibles - Background service de cleanup (tokens revocados/caducados) cada 6h
- Soporte de reverse proxy con
runtime_prefix(UsePathBase) - Dockerfile Linux, usuario no-root, build cacheado por capas
- GitHub Actions CI workflow
Datos
- Scripts SQL versionados en
Database/(sin runner — el DBA o pipeline los aplica) - MySQL 8.x
Tests
- xUnit + Testcontainers MySQL +
WebApplicationFactory
Parámetros (resumen)
Tabla completa en docs/configuration.md.
dotnet new dapper-solution -n MyProject \
--authMode {header|cookie|both} \ # default: both
--dbServer / --dbPort / --dbName / --dbUser \
--jwtIssuer / --jwtAudience / --jwtExpirationMinutes \
--companyName / --companyEmail / --apiDocName \
--googleClientId / --googleClientSecret \
--runtimePrefix / --environmentName \
--includeUsers / --includeProducts / --includeAuth \ # toggles de features
--includeDocker / --includeTests / --includeCI # toggles de extras
Estructura generada
MyProject/
├── MyProject.Api/ ← Web host: controllers, middlewares, DI wiring
│ ├── Configuration/ ← ApiGroups, response conventions
│ ├── Controllers/ ← AuthController, UserController, RolesController, ProductsController
│ ├── HostedServices/ ← TokenCleanupService
│ ├── Middlewares/ ← GlobalException, CorrelationId, SecurityHeaders, Validation
│ ├── Modules/ ← IServiceModule + uno por feature (auto-registro DI)
│ ├── Security/ ← HasPermission, AuditAttribute, CsrfProtection, AuthCookies
│ ├── Program.cs
│ └── appsettings.json
│
├── MyProject.Application/ ← Reglas de negocio, sin dependencias de infraestructura
│ ├── Common/ ← ApiResponse, DomainException, PagedResult, ErrorCatalog
│ ├── DTOs/ ← Requests + Responses
│ ├── Interfaces/ ← Contratos de repos (IUsersRepository, etc.)
│ ├── Notifications/ ← IEmailSender
│ ├── Profiles/ ← AutoMapper
│ ├── Security/ ← Permissions catálogo, JwtSettings, LockoutSettings, ITokenRevocationStore
│ ├── Services/ ← AuthService, UserService, RoleService, AccountService, ProductService
│ └── Validators/ ← FluentValidation por DTO
│
├── MyProject.Domain/ ← Entidades puras
│ ├── Common/BaseEntity.cs
│ ├── User.cs, Role.cs, Permission.cs, Product.cs
│ ├── RefreshToken.cs, RevokedToken.cs, LoginAttempt.cs
│ ├── PasswordResetToken.cs, EmailVerificationToken.cs
│ └── AuditEntry.cs
│
├── MyProject.Infrastructure/ ← Implementaciones de IO
│ ├── Data/DbConnectionFactory.cs
│ ├── Notifications/LogEmailSender.cs
│ ├── Repositories/ ← Dapper, todos heredan RepositoryBase o se conectan directo
│ └── Security/ ← JwtTokenGenerator, TokenRevocationStore
│
├── MyProject.Tests/ ← Tests de integración con Testcontainers
│
├── Database/ ← 9 scripts SQL (0001-0009) — convención manual, sin runner
├── postman/ ← Postman collection lista para importar
├── docs/ ← Documentación por tema
├── .github/workflows/ci.yml ← CI: build + test + (opcional) push docker
├── Directory.Packages.props ← Central package management
├── .editorconfig
└── MyProject.slnx
Endpoints (resumen)
# Auth (rate-limited 5 req/min, audit log)
POST /api/v1/auth/login ← Anónimo
POST /api/v1/auth/refresh ← Anónimo (acepta cookie o body)
POST /api/v1/auth/logout ← Autenticado, mata JWT actual + revoca refresh tokens
POST /api/v1/auth/forgot-password ← Anónimo, envía email con token (no revela existencia)
POST /api/v1/auth/reset-password ← Anónimo, aplica nuevo password + revoca todas las sesiones
POST /api/v1/auth/verify-email ← Anónimo
POST /api/v1/auth/resend-verification ← Anónimo
GET /api/v1/auth/google-login ← Inicia OAuth flow (si Google está configurado)
GET /api/v1/auth/google-response ← Callback
# Users
POST /api/v1/users ← Anónimo (register, dispara email verification)
GET /api/v1/users ← [HasPermission(users.read)] paginado
GET /api/v1/users/{id} ← [HasPermission(users.read)]
PUT /api/v1/users/{id}/role ← [HasPermission(users.write)] (audit)
# Roles (administración dinámica)
GET /api/v1/roles ← [HasPermission(roles.read)]
GET /api/v1/roles/permissions ← Catálogo del sistema (para UI con checkboxes)
GET /api/v1/roles/{id} ← [HasPermission(roles.read)]
POST /api/v1/roles ← [HasPermission(roles.manage)] (audit)
PUT /api/v1/roles/{id} ← [HasPermission(roles.manage)] (audit)
DELETE /api/v1/roles/{id} ← [HasPermission(roles.manage)] (audit)
# Products
GET /api/v1/products ← [HasPermission(products.read)] paginado
GET /api/v1/products/{id} ← [HasPermission(products.read)]
POST /api/v1/products ← [HasPermission(products.write)]
# Operaciones
GET /health/live ← Liveness probe
GET /health/ready ← Readiness (incluye ping a MySQL)
GET /metrics ← Prometheus scrape
Requisitos
- .NET SDK 10
- MySQL 8.x
- Docker (opcional)
Licencia
MIT — ver LICENSE.
Autor
Missael Del Jesus Ching Mendez · missael.ching@gamasis.mx
Changelog
Ver CHANGELOG.md.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.