Ejemplo didáctico de web dinámicas con .NET 10 y ASP.NET Core MVC y Razor Pages.
Una aplicación web de comercio electrónico de segunda mano con características avanzadas de seguridad, Railway Oriented Programming y gestión de usuarios con ASP.NET Core Identity.
WalaDaw es un marketplace moderno desarrollado con .NET 10 ASP.NET Core que implementa una aplicación web completa para una tienda en línea, usando tanto Razor Pages como Blazor Server. El proyecto está diseñado con una arquitectura en capas con una aproximación híbrida de presentación que permite la coexistencia de renderizado tradicional (SSR) con componentes reactivos en tiempo real.
🏪 Gestión de Productos y Categorías : CRUD completo con validaciones
🛒 Carrito de Compras : Persistencia SQLite con control de concurrencia
👥 Gestión de Usuarios : Autenticación con ASP.NET Core Identity (Cookies)
💾 Persistencia con SQLite In-Memory : Base de datos volátil para testing rápido
🔐 Seguridad : Claims, roles, CSRF y protección de rutas
📡 Blazor Server + SignalR : Componentes interactivos y notificaciones en tiempo real
📊 Panel de Administración : Dashboard con estadísticas en tiempo real
🧪 Testing : Unit tests con NUnit, bUnit para Blazor, y E2E con Playwright
🎨 Interfaz Híbrida : Razor Pages + Blazor Server + AJAX (共存 de tres enfoques)
Categoría
Funcionalidades
Gestión
Productos (CRUD), Categorías, Usuarios, Valoraciones, Favoritos
Compras
Carrito persistente, Checkout, Facturas PDF, Historial de pedidos
Autenticación
Registro, Login, Roles (ADMIN, USER, MODERADOR), Claims, Password hashing
Tiempo Real
SignalR, Notificaciones live, Dashboard admin con estadísticas en vivo
Testing
Unit tests (NUnit), Componentes (bUnit), E2E (Playwright)
UI/UX
Razor Pages, Blazor Server, AJAX, Bootstrap 5.3, I18n/L10n
Tecnología
Versión
Propósito
.NET
10
Plataforma principal y runtime
C#
14
Lenguaje de programación
ASP.NET Core MVC
10
Framework web con patrón MVC
Razor Pages
10
Motor de vistas del lado servidor
Blazor Server
10
Componentes interactivos en tiempo real
SignalR
10
Comunicación bidireccional para reactividad
EF Core
10
ORM con SQLite In-Memory
SQLite In-Memory
-
Base de datos volátil para desarrollo/testing
ASP.NET Core Identity
10
Sistema de autenticación y autorización
NUnit
4.x
Framework de testing unitario
bUnit
2.x
Testing de componentes Blazor
Playwright
1.x
Testing E2E en navegador
Bootstrap
5.3
Framework CSS responsive
CSharpFunctionalExtensions
2.x
Railway Oriented Programming (ROP)
OutputCache
10
Caché de respuestas HTML
InMemoryCache
10
Caché de objetos en memoria
Serilog
8.x
Logging estructurado
# Clonar repositorio
git clone https://github.com/joseluisgs/TiendaDawWeb-NetCore.git
cd TiendaDawWeb-NetCore
# Restaurar dependencias
dotnet restore
# Ejecutar aplicación (Normal)
dotnet run --project TiendaDawWeb.Mvc
# Ejecutar con Hot Reload (Recomendado para desarrollo)
dotnet watch --project TiendaDawWeb.Mvc
Nota: La aplicación usa SQLite In-Memory, por lo que los datos se pierden al detener la aplicación. Para desarrollo, usa dotnet watch para mantener los datos mientras editas.
Para ejecutar la aplicación en contenedores Docker:
# Construir y ejecutar todos los servicios
docker-compose up -d --build
# Ver logs de la aplicación
docker-compose logs -f
# Detener servicios
docker-compose down
# Detener y eliminar volúmenes
docker-compose down -v
Servicios incluidos:
TiendaDawWeb.Mvc (puerto 5000): Aplicación MVC tradicional
TiendaDawWeb.RazorPages (puerto 5002): Aplicación Razor Pages
Ver más: Docker y Contenedores
🧪 Estrategia de Testing Total
WalaDaw implementa una pirámide de pruebas profesional para garantizar la máxima calidad:
Nivel
Tipo
Propósito
Herramienta
1
Unit Tests
Validación de servicios, lógica de negocio
NUnit
2
Integration Tests
Transacciones SQLite, Repositories
NUnit + SQLite
3
Component Tests
Testeo reactivo de componentes Blazor
bUnit v2.x
4
E2E Tests
Simulación de navegación real en navegador
Playwright + C#
# 🚀 Ejecutar TODOS los tests
dotnet test
# Solo tests unitarios y de integración (.NET)
dotnet test --filter " FullyQualifiedName!~E2E"
# Solo tests E2E (requiere aplicación corriendo)
cd TiendaDawWeb.Tests.E2E && dotnet test
Tests Unitarios y de Componentes
Escenario
Comando
Todos los tests
dotnet test
Solo unitarios
dotnet test --filter "Category=Unit"
Solo integración
dotnet test --filter "Category=Integration"
Solo componentes Blazor
dotnet test --filter "Category=Component"
# Ejecutar tests E2E
cd TiendaDawWeb.Tests.E2E && dotnet test
# Con video y screenshots
dotnet test --project TiendaDawWeb.Tests.E2E -v normal
Ver más: Unit Testing con NUnit y bUnit , E2E Testing con Playwright
# Ejecutar todos los tests con coverage
dotnet test --collect:" XPlat Code Coverage"
# Ver reporte de coverage
open coverage/index.html
Ver más: Code Coverage
Tipo de Test
Parallelización
Base de Datos
Unit Tests
✅ Paralelo
Sin dependencia
Integration
✅ Paralelo
SQLite In-Memory
Components
✅ Paralelo
Sin dependencia
E2E Tests
❌ No paralelo
Requiere app corriendo
Para una comprensión profunda de la arquitectura y las tecnologías utilizadas, consulta los documentos en la carpeta doc/ :
Fundamentos y Configuración
#
Documento
Descripción
04
MVC Controllers
Enfoque tradicional MVC, routing, binding
05
Razor Pages
Desarrollo web con Razor Pages y Tag Helpers
Interfaz de Usuario (Razor & Blazor)
Validación y Manejo de Errores
#
Documento
Descripción
16
Exception Handling
Middleware de seguridad, ModelState vs Result
Optimización y Rendimiento
#
Documento
Descripción
24
Docker
Contenedores y configuración de producción
25
Logging
Logging estructurado, correlación de peticiones
26
Infrastructure
Clean Architecture, DI, Extension Methods
27
CI/CD con GitHub Actions
Pipelines, automatización, GitHub CLI
⚒️ Diagrama de Clases del Dominio
classDiagram
direction TB
%% ENUMS
class ProductCategory {
<<enumeration>>
SMARTPHONES
LAPTOPS
AUDIO
GAMING
ACCESSORIES
}
class UserRole {
<<enumeration>>
USER
ADMIN
MODERATOR
}
%% CLASES PRINCIPALES
class User {
+long Id
+string Nombre
+string Apellidos
+string Email
+string PasswordHash
+string Rol
+string? Avatar
+DateTime FechaAlta
+bool IsDeleted
+DateTime? DeletedAt
+string? DeletedBy
+byte[]? RowVersion
}
class Product {
+long Id
+string Nombre
+string Descripcion
+decimal Precio
+string? Imagen
+long? CategoriaId
+long UserId
+bool Reservado
+bool IsDeleted
+DateTime? DeletedAt
+string? DeletedBy
+DateTime CreatedAt
+DateTime UpdatedAt
+double RatingPromedio
+string ImagenOrDefault
}
class Purchase {
+long Id
+long UserId
+DateTime FechaCompra
+decimal Total
+PurchaseEstado Estado
+bool IsDeleted
+DateTime? DeletedAt
+byte[]? RowVersion
}
class PurchaseItem {
+long Id
+long PurchaseId
+long ProductId
+string ProductNombre
+decimal ProductPrecio
+int Cantidad
+decimal Subtotal
}
class CarritoItem {
+long Id
+long UserId
+long ProductId
+decimal Precio
+int Cantidad
+DateTime CreatedAt
+byte[]? RowVersion
}
class Favorite {
+long Id
+long UserId
+long ProductId
+DateTime CreatedAt
}
class Rating {
+long Id
+long UserId
+long ProductId
+int Puntuacion
+string? Comentario
+DateTime CreatedAt
}
%% RELACIONES
User "1" --> "1" UserRole : Rol
User "1" -- "*" Product : Propietario
User "1" -- "*" Purchase : Compras
User "1" -- "*" CarritoItem : Carrito
User "1" -- "*" Favorite : Favoritos
User "1" -- "*" Rating : Valoraciones
Product "*" --> "1" ProductCategory : Categoria
Product "1" -- "*" CarritoItem : EnCarrito
Product "1" -- "*" Favorite : Favorito
Product "1" -- "*" Rating : Valoraciones
Product "*" --> "1" User : Vendedor
Purchase "1" -- "*" PurchaseItem : Items
Purchase "1" --> "1" User : Comprador
Purchase "*" --> "1" PurchaseEstado : Estado
CarritoItem "*" --> "1" User : Usuario
CarritoItem "*" --> "1" Product : Producto
Favorite "*" --> "1" User : Usuario
Favorite "*" --> "1" Product : Producto
Rating "*" --> "1" User : Usuario
Rating "*" --> "1" Product : Producto
Loading
📂 Estructura del Proyecto
TiendaDawWeb-NetCore/
├── TiendaDawWeb.slnx # Solución global de .NET
├── docker-compose.yml # Orquestación por defecto
├── docker-compose.local.yml # Desarrollo local
├── docker-compose.prod.yml # Producción
├── .env.example # Variables de entorno de ejemplo
│
├── TiendaDawWeb.Mvc/ # Proyecto Principal MVC
│ ├── Program.cs # Configuración de Pipeline, DI
│ ├── Controllers/ # Controladores MVC
│ │ ├── AccountController.cs # Auth (Login, Register)
│ │ ├── ProductsController.cs # Gestión de productos
│ │ ├── CartController.cs # Carrito de compras
│ │ └── Admin/ # Panel de administración
│ ├── Views/ # Vistas Razor
│ ├── wwwroot/ # Recursos estáticos
│ ├── appsettings.json # Configuración
│ └── Dockerfile # Contenedor MVC
│
├── TiendaDawWeb.RazorPages/ # Proyecto Razor Pages
│ ├── Pages/ # Páginas Razor
│ └── Program.cs # Configuración específica
│
├── TiendaDawWeb.Shared/ # Lógica Compartida
│ ├── Models/ # Modelos de dominio
│ │ ├── User.cs # Entidad Usuario
│ │ └── Product.cs # Entidad Producto
│ ├── Services/ # Interfaces de Servicios
│ │ └── IProductService.cs # Contrato productos
│ ├── Mappers/ # Mapeadores
│ └── Extensions/ # Extensiones DI
│
├── TiendaDawWeb.Shared.Blazor/ # Componentes Blazor
│ ├── Components/ # Componentes reutilizables
│ │ ├── Admin/ # Dashboard admin
│ │ └── Ratings/ # Sistema de valoraciones
│ └── Services/ # State Container
│
├── TiendaDawWeb.Tests/ # Pruebas Unitarias
│ ├── Unit/ # Tests unitarios
│ ├── Integration/ # Tests de integración
│ └── Components/ # Tests Blazor (bUnit)
│
├── TiendaDawWeb.Tests.E2E/ # Pruebas E2E (Playwright)
│ ├── Auth/ # Tests de login/registro
│ ├── Products/ # Tests de productos
│ └── E2ETestBase.cs # Clase base con video
│
├── doc/ # Documentación técnica
└── README.md # Este archivo
Descripción de Carpetas Principales
Carpeta
Propósito
Contenido
Controllers
Entry points HTTP MVC
Account, Products, Cart, Ratings, Admin
Views
Vistas Razor Pages
Shared, Account, Products, Cart
Components
Componentes Blazor Server
Admin/StatsWidget, Ratings/*
Services
Lógica de negocio (Contratos)
IProductService, IUserService, ICartService
Models
Modelos de dominio
User, Product, Purchase, CarritoItem, Rating
Shared
Lógica compartida entre proyectos
Models, Services, Mappers
Tests
Unit, Integration, Components
NUnit, bUnit
Tests.E2E
End-to-End
Playwright con C#
🏗️ Arquitectura Híbrida MVC/Razor/Blazor
El proyecto implementa una arquitectura híbrida que combina el enfoque tradicional de MVC/Razor Pages con la reactividad moderna de Blazor Server.
Principio
Implementación
Core en el centro
Modelos (User, Product) sin dependencias externas
Inversión de dependencias
Interfaces en Shared, implementaciones en el proyecto principal
Separación de responsabilidades
Controllers → Services → Repositories → Data
Interfaz híbrida
MVC + Razor Pages + Blazor Server coexisten
Single Language Stack
C# en backend y frontend (Blazor Server)
graph TB
subgraph "🌍 Capa de Presentación - Web"
MVC["ASP.NET Core MVC<br/>Controladores + Vistas"]
RP["Razor Pages<br/>Modelo de Páginas"]
BLZR["Blazor Server<br/>Componentes Reactivos"]
end
subgraph "🔵 Capa de Controladores"
CTRL["Controllers<br/>Account, Products<br/>Cart, Ratings"]
FILT["Filters<br/>Auth, Validation<br/>Exception"]
end
subgraph "🟠 Capa de Negocio - Servicios"
SVC["Services<br/>Product, User, Cart"]
ROP["Result~T,E><br/>Railway Oriented"]
CCC["Cross-Cutting<br/>Cache, Validation"]
end
subgraph "🔷 Capa Shared - Core"
MOD["Models<br/>User, Product"]
INT["Interfaces<br/>IServices"]
end
subgraph "🔴 Capa de Datos - Persistencia"
EF["EF Core<br/>SQLite"]
ID["Identity<br/>Cookies"]
DB[("SQLite<br/>tienda.db")]
end
MVC & RP & BLZR ==> CTRL
CTRL ==> FILT
CTRL ==> SVC
SVC ==> ROP
SVC ==> CCC
SVC ==> MOD
SVC ==> EF
SVC ==> ID
EF & ID ==> DB
style BLZR fill:#512bd4,color:#fff
style ROP fill:#e91e63,color:#fff
Loading
Estructura de Dependencias
graph TB
subgraph "🌍 Presentación"
MVC["MVC + Views"]
RP["Razor Pages"]
BLZR["Blazor Server"]
end
subgraph "🔵 Controllers"
CTRL["Controllers"]
FILT["Filters"]
end
subgraph "🟠 Servicios"
SVC["Services"]
end
subgraph "🟡 Shared Core"
MOD["Models"]
INT["Interfaces"]
end
subgraph "🔴 Infraestructura"
EF["EF Core SQLite"]
ID["Identity"]
CACHE["InMemory"]
LOG["Serilog"]
end
MVC & RP & BLZR ==> CTRL
CTRL ==> FILT
FILT ==> SVC
SVC ==> INT
SVC ==> MOD
SVC ==> EF & ID & CACHE & LOG
Loading
Ventajas de Esta Arquitectura
Ventaja
Descripción
Flexibilidad
Tres enfoques de UI coexisten
Testabilidad
Core sin dependencias → fácil mocking
Mantenibilidad
Cambios en UI no afectan lógica de negocio
🛤️ Railway Oriented Programming (ROP)
El proyecto implementa Railway Oriented Programming (ROP) usando CSharpFunctionalExtensions.
Concepto
Descripción
Ejemplo
Result
Wrapper que encapsula éxito o fallo
Result<T, TError>
Success
Camino happy path con valor
Result.Success(value)
Failure
Camino de error con mensaje
Result.Failure(error)
Bind
Encadena operaciones que retornan Result
result.Bind()
Map
Transforma el valor en éxito
result.Map(value => newValue)
Match
Maneja ambos casos
result.Match(onSuccess, onFailure)
Anatomía del Patrón (Two-Track Model)
flowchart TB
subgraph ROP["RAILWAY ORIENTED PROGRAMMING"]
INP["INPUT"]
R1["RAIL 1 (SUCCESS)"]
OUT_H["OUTPUT (Happy)"]
R2["RAIL 2 (FAILURE)"]
OUT_E["OUTPUT (Error)"]
INP --> R1
R1 --> OUT_H
R1 -.->|"SWITCH"| R2
R2 --> OUT_E
end
style INP fill:#27ae60,color:#fff
style R1 fill:#27ae60,color:#fff
style OUT_H fill:#27ae60,color:#fff
style R2 fill:#e74c3c,color:#fff
style OUT_E fill:#e74c3c,color:#fff
Loading
Beneficio
Descripción
Sin Exceptions
Los errores son valores
Composabilidad
Encadenar operaciones de forma segura
Tipado Seguro
El tipo de error está en la firma
Legibilidad
Flujo lineal en lugar de if-else anidados
public async Task < Result < Product , DomainError > > CreateAsync ( CreateProductDto dto )
{
return await Validate ( dto )
. Bind ( ValidateStockAsync )
. Bind ( CreateProductAsync )
. Map ( p => _mapper . Map < Product > ( p ) ) ;
}
// Controller usage
var result = await _productService . CreateAsync ( dto ) ;
return result . Match (
onSuccess : product => RedirectToAction ( "Index" ) ,
onFailure : error => View ( "Error" , error ) ) ;
Comparación: ROP vs Try-Catch
Aspecto
Try-Catch tradicional
ROP
Errores
Exceptions
Valores
Flujo
Saltos inesperados
Lineal
Tipado
Exception genérica
Error tipado específico
Composición
Difícil
natural con Bind/Map
🗄️ Persistencia con SQLite
Aspecto
Descripción
Base de Datos
SQLite (relacional)
ORM
Entity Framework Core 10
Patrón
Code First con migraciones
Testing
SQLite In-Memory para tests rápidos
Concurrencia
Optimistic Concurrency (RowVersion)
✅ Cookies Authentication : ASP.NET Core Identity con cookies
✅ Role-Based Authorization : ADMIN, USER, MODERADOR roles
✅ Claims-Based : Información adicional en tokens
✅ CSRF Protection : Anti-Forgery Tokens
✅ Password Hashing : BCrypt con salt aleatorio
✅ Soft Delete : Eliminación lógica (IsDeleted)
✅ Concurrency Control : RowVersion
✅ Rate Limiting : Protección contra DDoS
✅ Security Headers : X-Content-Type-Options
Endpoint
Método
Auth
Descripción
/Account/Register
GET
No
Formulario de registro
/Account/Register
POST
No
Registrar nuevo usuario
/Account/Login
GET
No
Formulario de login
/Account/Login
POST
No
Iniciar sesión (Cookie)
/Account/Logout
POST
Sí
Cerrar sesión
/Account/Profile
GET
Sí
Ver perfil propio
Endpoint
Método
Auth
Descripción
/Products
GET
No
Listar productos
/Products/{id:long}
GET
No
Ver producto
/Products/Create
GET
Sí
Formulario crear producto
/Products/Create
POST
Sí
Crear producto
/Products/{id}/Edit
GET
Sí*
Formulario editar producto
Endpoint
Método
Auth
Descripción
/Cart
GET
Sí
Ver carrito
/Cart/Add/{productId}
POST
Sí
Añadir producto al carrito
/Cart/Remove/{itemId}
POST
Sí
Eliminar item
/Cart/Checkout
POST
Sí
Finalizar compra
Componentes Blazor Server (SignalR integrado)
Blazor Server usa SignalR internamente para comunicación bidireccional. Solo existen 3 componentes Blazor en el proyecto:
Componente
Auth
Descripción
<RatingSummary />
No
Muestra promedio de valoraciones
<RatingSection />
Sí
Formulario para añadir valoraciones
<AdminStatsWidget />
ADMIN
Panel de admin con auto-refresh (15s)
Nota : El carrito es MVC tradicional (CartController), no Blazor.
Este proyecto es un ejemplo educativo con fines didácticos.
Codificado con 💖 por José Luis González Sánchez
Este repositorio está licenciado bajo Creative Commons . Por favor si compartes, usas o modificas este proyecto cita a su autor.