← maurobernal.com.ar

Guía práctica: cómo migré mis proyectos de .NET 8 a .NET 10 sin romper producción

Migrar un proyecto de .NET 8 a .NET 10 es el tipo de tarea que parece riesgosa pero en la práctica es incremental y reversible. Lo hice en varios proyectos de producción sin downtime. El proceso siempre es el mismo: actualizar el TFM, correr los tests, adoptar las nuevas features de a poco.

Paso 1: Actualizar el Target Framework

# Verificar SDK instalado
dotnet --list-sdks

# Instalar .NET 10 SDK si no está
# Linux/WSL:
wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh
bash dotnet-install.sh --channel 10.0

# En cada .csproj de la solución:
# <TargetFramework>net10.0</TargetFramework>

# Para actualizar todos los .csproj de una solución en Linux:
find . -name "*.csproj" -exec sed -i 's/net8.0/net10.0/g; s/net9.0/net10.0/g' {} \;

# Restaurar y compilar
dotnet restore
dotnet build

Paso 2: Actualizar paquetes NuGet

# Ver paquetes desactualizados
dotnet list package --outdated

# Actualizar todos los paquetes de Microsoft.* a versiones compatibles con .NET 10
dotnet add package Microsoft.AspNetCore.OpenApi
dotnet add package Microsoft.Extensions.Http.Resilience

# Paquetes que pueden desinstalarse en .NET 10:
# - Swashbuckle.AspNetCore (reemplazado por OpenAPI nativo)
# - Microsoft.AspNetCore.Mvc.NewtonsoftJson (System.Text.Json mejorado)

# Remover Swashbuckle si usabas OpenAPI:
dotnet remove package Swashbuckle.AspNetCore

Paso 3: Adoptar las nuevas features de C# 14

No hay que cambiar nada para que el proyecto compile. Las nuevas features son opt-in. La estrategia es adoptarlas gradualmente donde aporten más valor.

// Prioridad 1: field — máximo impacto con mínimo riesgo
// Buscar propiedades con backing fields privados y migrarlas

// Antes:
private string _nombre = string.Empty;
public string Nombre
{
    get => _nombre;
    set => _nombre = value?.Trim() ?? throw new ArgumentNullException();
}

// Después:
public string Nombre
{
    get => field;
    set => field = value?.Trim() ?? throw new ArgumentNullException();
}

// Prioridad 2: Primary Constructors en servicios con DI
// Especialmente útil en proyectos con muchos servicios inyectados

// Prioridad 3: params ReadOnlySpan en métodos variádicos de uso frecuente
// Buscar: params int[], params string[], params object[]
// Reemplazar por: params ReadOnlySpan<T> donde sea posible

// Prioridad 4: Lock para thread safety
// Buscar: private readonly object _lockObj = new object();
// Reemplazar por: private readonly Lock _lockObj = new();

Paso 4: Migrar a OpenAPI nativo (si usabas Swashbuckle)

// Program.cs — reemplazar Swashbuckle por OpenAPI nativo

// ❌ Antes con Swashbuckle:
// builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", ...); });
// app.UseSwagger();
// app.UseSwaggerUI();

// ✅ .NET 9/10 nativo:
builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, ct) =>
    {
        document.Info = new()
        {
            Title = "Mi API",
            Version = "v1",
            Description = "API de gestión interna"
        };
        return Task.CompletedTask;
    });
});

// En el pipeline:
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    // Opcional: seguir usando Swagger UI solo para dev
    app.UseSwaggerUI(options =>
        options.SwaggerEndpoint("/openapi/v1.json", "Mi API v1"));
}

Checklist de migración completo

PasoAcciónImpacto
1Cambiar TFM a net10.0 en todos los .csprojObligatorio
2dotnet restore && dotnet buildObligatorio
3Ejecutar suite de tests completaObligatorio
4Actualizar paquetes Microsoft.* a versión 10.xRecomendado
5Adoptar field en propiedades con backing fieldGradual
6Primary Constructors en servicios con DIGradual
7Reemplazar object _lock por Lock _lockGradual
8Migrar OpenAPI de Swashbuckle a nativoOpcional
9Adoptar params ReadOnlySpan<T> en hot pathsOpcional
10Evaluar Native AOT para Workers/APIs simplesOpcional avanzado

Errores comunes y cómo resolverlos

// ERROR 1: Paquete no compatible con net10.0
// "Package X is not compatible with net10.0"
// Solución: actualizar a la versión más reciente del paquete
// Si no hay versión compatible, usar <TargetFrameworks>net8.0;net10.0</TargetFrameworks>

// ERROR 2: Reflection en Native AOT
// "System.InvalidOperationException: Type ... is not reflection-enabled"
// Solución: agregar source generator o usar [DynamicDependency]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(MiClase))]
static void MetodoConReflection() { ... }

// ERROR 3: field en C# 14 — feature no disponible
// "error CS8652: The feature 'field keyword' is currently in Preview"
// Solución: habilitar preview features en .csproj
// <LangVersion>preview</LangVersion>
// (o esperar a que salga de preview en la versión final de .NET 10)

// ERROR 4: Cambios breaking en serialización JSON
// System.Text.Json en .NET 10 es más estricto con algunos tipos
// Solución: revisar JsonSerializerOptions y ajustar si es necesario

Mi experiencia: cuánto tardó cada proyecto

  • API Minimal con 15 endpoints: 30 minutos (cambiar TFM, actualizar paquetes, correr tests)
  • Worker Service con threading complejo: 2 horas (migración de locks + tests de concurrencia)
  • Aplicación ASP.NET MVC grande (80 controllers): medio día (TFM + paquetes + resolver warnings)
  • Adopción gradual de C# 14 features: 2-3 semanas en paralelo con desarrollo normal

La migración del TFM en sí es rápida. Lo que toma tiempo es la adopción de las nuevas features, y eso es completamente optativo. Podés migrar a .NET 10 hoy y adoptar field o Primary Constructors a tu ritmo, archivo por archivo, cuando tocás esa parte del código.


← dotnet run script.cs y las nuevas herramientas de .NET 10: OpenAPI, HttpClient y más | Fin de la Serie .NET 8 → .NET 10

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.