← maurobernal.com.ar

Etiqueta: dotnet

  • C# 12: Primary Constructors, Collection Expressions y el código que debería haber existido siempre

    C# 12 llegó con .NET 8 y fue la versión que más cambió mi forma de escribir código en el día a día. Los Primary Constructors me sacaron dos tercios del boilerplate de mis servicios. Las Collection Expressions me hicieron olvidar cuándo usar new List<> vs new[] vs Array.Empty(). Repaso todo lo que realmente uso.

    Primary Constructors: adiós al boilerplate de inyección de dependencias

    Antes de C# 12, cada servicio que necesitaba inyección de dependencias requería declarar campos privados, un constructor y asignaciones manuales. Con Primary Constructors, todo eso desaparece.

    // ❌ Antes (C# 11 y anteriores): mucho código repetitivo
    public class PedidoService
    {
        private readonly IRepositorio _repo;
        private readonly ILogger<PedidoService> _logger;
        private readonly IEmailService _email;
    
        public PedidoService(
            IRepositorio repo,
            ILogger<PedidoService> logger,
            IEmailService email)
        {
            _repo   = repo;
            _logger = logger;
            _email  = email;
        }
    
        public async Task ProcesarAsync(Pedido pedido)
        {
            _logger.LogInformation("Procesando pedido {Id}", pedido.Id);
            await _repo.GuardarAsync(pedido);
            await _email.NotificarAsync(pedido.ClienteEmail);
        }
    }
    
    // ✅ C# 12: Primary Constructor — el compilador genera el backing field
    public class PedidoService(
        IRepositorio repo,
        ILogger<PedidoService> logger,
        IEmailService email)
    {
        public async Task ProcesarAsync(Pedido pedido)
        {
            logger.LogInformation("Procesando pedido {Id}", pedido.Id);
            await repo.GuardarAsync(pedido);
            await email.NotificarAsync(pedido.ClienteEmail);
        }
    }

    Los parámetros del primary constructor son accesibles en todo el cuerpo de la clase. No son campos públicos — son parámetros capturados. Si necesitás exponerlos como propiedades, lo hacés explícitamente:

    // Primary constructor en record (siempre existió)
    public record Producto(string Nombre, decimal Precio, int Stock);
    
    // Primary constructor en clase con propiedad explícita
    public class Configuracion(string connectionString, int timeout)
    {
        public string ConnectionString { get; } = connectionString;
        public int TimeoutSeconds { get; } = timeout > 0 ? timeout : 30;
    }

    Collection Expressions: una sintaxis para todos los tipos de colección

    Antes de C# 12, la forma de inicializar una colección dependía de su tipo. Un array era new[] { 1, 2, 3 }, una lista era new List<int> { 1, 2, 3 }, un span era algo diferente. C# 12 unifica todo con [].

    // ✅ C# 12: misma sintaxis para todos los tipos
    int[]          array   = [1, 2, 3];
    List<int>      lista   = [4, 5, 6];
    Span<int>      span    = [7, 8, 9];
    IEnumerable<int> seq  = [10, 11, 12];
    ReadOnlySpan<int> ros  = [13, 14, 15];
    
    // Colección vacía — sin ambigüedades
    List<string> vacia = [];   // antes: new List<string>() o Array.Empty<string>()
    
    // Spread operator (..) para combinar colecciones
    int[] primeros = [1, 2, 3];
    int[] segundos = [4, 5, 6];
    int[] todos    = [..primeros, ..segundos, 7, 8];  // [1,2,3,4,5,6,7,8]
    
    // Muy útil en APIs: combinar listas de validación
    var reglas = [..reglasBase, ..reglasEspecificas, nuevaRegla];

    Using aliases para cualquier tipo

    Antes de C# 12, using solo podía crear alias para tipos nombrados. Ahora puede crear alias para cualquier tipo: tuplas, arrays, punteros, tipos genéricos.

    // C# 12: aliases para tipos complejos
    using Coordenadas   = (double Latitud, double Longitud);
    using MatrizInt     = int[][];
    using DiccionarioId = System.Collections.Generic.Dictionary<int, string>;
    using Callback      = System.Action<string, int>;
    
    // Uso en el código — mucho más legible
    Coordenadas ubicacion = (Latitud: -34.6037, Longitud: -58.3816);
    DiccionarioId usuarios = new() { { 1, "Mauro" }, { 2, "Juan" } };

    Inline Arrays: buffers de alto rendimiento sin unsafe

    Los Inline Arrays son structs con un tamaño fijo definido en compilación. Se almacenan completamente en el stack, sin asignaciones en el heap. Útiles para código de sistema o cuando necesitás buffers de tamaño conocido con rendimiento máximo.

    "[System.Runtime.CompilerServices.InlineArray(8)]
    public struct Buffer8<T>
    {
        private T _element; // El compilador genera 8 slots contiguos en memoria
    }
    
    // Uso: se comporta como un array pero sin allocations
    Buffer8<int> buffer;
    buffer[0] = 100;
    buffer[7] = 800;
    // Todo en el stack — el GC no ve nada de esto

    Lambdas con parámetros opcionales y params

    // C# 12: parámetros opcionales en lambdas
    var saludar = (string nombre, string prefijo = "Hola") => $"{prefijo}, {nombre}!";
    Console.WriteLine(saludar("Mauro"));          // "Hola, Mauro!"
    Console.WriteLine(saludar("Mauro", "Buenas")); // "Buenas, Mauro!"
    
    // params en lambdas
    var sumar = (params int[] numeros) => numeros.Sum();
    Console.WriteLine(sumar(1, 2, 3, 4, 5));  // 15

    El impacto en el código real

    En un proyecto de tamaño mediano que migré a C# 12, los Primary Constructors redujeron el código de los servicios en aproximadamente un 30%. No es solo cosmético: menos código es menos superficie de error. Las Collection Expressions eliminaron la pregunta «¿new List<> o Array.Empty<>?» de los code reviews. El spread operator simplificó docenas de patrones de concatenación de listas que antes requerían AddRange o LINQ Concat.


    ← De .NET 8 a .NET 10: qué cambió, qué mejoró y por qué conviene actualizar ya | Serie .NET 8 → .NET 10 | Próximo: C# 13: params modernos, Lock de primera clase y Task.WhenEach →

  • De .NET 8 a .NET 10: qué cambió, qué mejoró y por qué conviene actualizar ya

    Trabajo con .NET desde los tiempos de .NET Framework 4.5. Cada nueva versión traía algo interesante pero también algo que romper. El salto de .NET 8 a .NET 10 es diferente: es una consolidación. Rendimiento extremo, sintaxis más limpia, herramientas que finalmente hacen lo que uno espera. Esta serie cubre todo lo que vale la pena conocer.

    La política de versiones de .NET: qué es LTS y por qué importa

    Microsoft lanza una versión mayor de .NET cada noviembre. Las versiones pares son LTS (Long Term Support) — soporte por 3 años. Las impares son STS (Standard Term Support) — soporte por 18 meses. En producción, siempre apunto a LTS.

    VersiónTipoC#Fin de soporteEstado
    .NET 8LTSC# 12Nov 2026✅ En soporte
    .NET 9STSC# 13May 2026⚠️ Fin pronto
    .NET 10LTSC# 14Nov 2028✅ Actual recomendado

    Lo más importante de cada versión en un vistazo

    .NET 8 / C# 12 (noviembre 2023)

    • Primary constructors en clases y structs
    • Collection expressions: [1, 2, 3] para arrays, listas y spans
    • Spread operator .. para combinar colecciones
    • Inline arrays de alto rendimiento
    • Using aliases para cualquier tipo (incluso tuplas y punteros)
    • Lambdas con parámetros opcionales y params

    .NET 9 / C# 13 (noviembre 2024)

    • params ReadOnlySpan<T>: cero asignaciones en heap para métodos variádicos
    • System.Threading.Lock: tipo de primera clase para exclusión mutua
    • Task.WhenEach: procesar tareas a medida que terminan
    • LINQ: CountBy(), AggregateBy(), Index()
    • Nuevos miembros partial en clases parciales
    • OpenAPI built-in en Minimal APIs

    .NET 10 / C# 14 (noviembre 2025)

    • Palabra clave field: acceso al backing field autogenerado
    • Extension members: propiedades y miembros estáticos de extensión
    • Stack allocation inteligente para arrays pequeños
    • Native AOT mejorado: binarios más pequeños, arranque más rápido
    • dotnet run script.cs: ejecutar archivos sin proyecto
    • Mejoras profundas en el GC

    ¿Por qué actualizar desde .NET 8 a .NET 10 ahora?

    Si estás en .NET 8 LTS, no hay urgencia — tiene soporte hasta noviembre 2026. Pero .NET 10 ofrece mejoras de rendimiento concretas que se traducen en menor uso de memoria, menor latencia y binarios más chicos. En mis APIs en producción, la migración de .NET 8 a .NET 10 redujo el uso de memoria de los pods en Kubernetes entre un 15 y un 25%.

    # Verificar versión instalada
    dotnet --version
    
    # Instalar .NET 10 SDK
    # Linux/WSL:
    wget https://dot.net/v1/dotnet-install.sh
    bash dotnet-install.sh --channel 10.0
    
    # Ver SDKs disponibles
    dotnet --list-sdks
    
    # Cambiar un proyecto a .NET 10
    # En el .csproj:
    # <TargetFramework>net10.0</TargetFramework>

    El contexto: .NET unificado, multiplataforma y de alto rendimiento

    Desde .NET 5, Microsoft unificó el ecosistema: ya no hay .NET Framework, .NET Core y Xamarin como caminos separados. Todo es .NET. Corre en Windows, Linux, macOS, Android, iOS, WASM. Un solo SDK, una sola librería estándar, un solo modelo de deployment.

    Para los que venimos del mundo de .NET Framework y ASP.NET MVC clásico, este es el contexto del que partimos. Para los que arrancaron directamente con .NET Core: la dirección es la misma, pero el ritmo de mejoras se aceleró. Esta serie cubre exactamente qué cambió y cómo aprovecharlo.


    Serie .NET 8 → .NET 10 | Próximo: C# 12: Primary Constructors, Collection Expressions y el código que debería haber existido siempre →

  • error MSB3030: No se pudo copiar el archivo

    Desde hace tiempo que vengo trabajando con Net MAUI para el desarrollo de aplicaciones móviles. Pero algunos paquetes de Google Firebase han empezado a presentar inconvenientes, sobre todo cuando trabajas con iOs. Por ello decidí empezar a probar la versión Net 8.0 preview con VS2022 preview. Algo tan simple puede volverse un dolor de cabeza…

    C:\Program Files\dotnet\sdk\8.0.100-rc.1.23455.8\Microsoft.Common.CurrentVersion.targets(4879,5): error MSB3030: No se
    pudo copiar el archivo "D:\NUGET\xamarin.firebase.ios.cloudfirestore\8.10.0.3\lib\net6.0-ios15.4\Firebase.CloudFirestor
    e.resources\grpcpp.xcframework\ios-arm64_x86_64-simulator\grpcpp.framework\PrivateHeaders\src\core\ext\upb-generated\en
    voy\config\filter\network\http_connection_manager\v2\http_connection_manager.upb.h" porque no se encontró. [D:\source\r
    epos\globalassistgroup\metroin.app-trabajador\metroin.app-trabajador\metroin.app-trabajador.csproj::TargetFramework=net
    8.0-ios]
    Error Could not find a part of the path 'C:\Users\vivek.nuget\packages\xamarin.firebase.ios.core\8.10.0.1\lib\xamarinios10\Firebase.Core.resources\GoogleUtilitiesComponents.xcframework\ios-arm64_i386_x86_64-simulator\GoogleUtilitiesComponents.framework\PrivateHeaders\GULCCComponentContainerInternal.h'.

    ¡

    ¿Porqué ocurre?

    Al parecer por el largo de la ruta que se genera dinámicamente, la cuál no es soportado en Windows.

    Solución:

    Acortar la ruta. Para ello podemos modificar dos paramétros

    • Ruta de los paquetes nugets
    • Habilitar que Windows maneje rutas largas

    Ruta de paquetes nugets

    Debemos editar las variables de entorno y agregar dos nuevas entradas

    Largo de rutas en windows

    Debes crear un registro en Regedit

    Windows Registry Editor Version 5.00
    
    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem]
    "LongPathsEnabled"=dword:00000001

    Maximum Path Length Limitation – Win32 apps | Microsoft Learn

    ¿Me cuentas si te funcionó?

  • Failed to execute operation: No such file or directory

    ¿Porque un systemctl enable myservice.service da este error?

    Hoy es unos de esos días donde algo tan simple deja de funcionar. Te pongo en contexto: Un worker service creado en .NET 7.0 encargado de interactuar con un servicio de telefónia Asteriks y una API de .NET

    Este servicio funciona correctamente en el servidor linux que cuenta con CentOS y una distro de «FreePBX». Por lo que se decidió pasar a servicio la ejecución del mismo.

    Para ello, de acuerdo a la documentación de .NET tan solo con el siguiente archivo de texto es suficiente:

    Host ASP.NET Core on Linux with Nginx | Microsoft Learn

    [Unit]
    Description=Example .NET Web API App running on Linux
    After=network.target

    [Service]
    WorkingDirectory=/var/ami
    ExecStart=/usr/bin/dotnet /var/ami/WorkerServiceAMI.dll
    Restart=always

    Restart service after 10 seconds if the dotnet service crashes:

    RestartSec=10

    KillSignal=SIGINT
    SyslogIdentifier=dotnet-ami
    User=root
    Environment=ASPNETCORE_ENVIRONMENT=Production
    Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

    [Install]
    WantedBy=multi-user.target

    Una vez generado el mismo tan solo al ejecutar systemctl enable myservice.service debería funcionar pero recibía constantemente este error:

    Failed to execute operation: No such file or directory

    Luego de romperme la cabeza y no encontrar la solución en Google me puse analizar el porque no encontraba el archivo. Y entendí lo siguiente: Systemctl es parte del administrador de servicios del sistema, por lo tanto busca los archivos de forma predeterminada en /etc/systemd/system o /usr/lib/systemd/system.

    Solución:

    Copia el archivo dentro de esa ubicación y asunto arreglado.

    Espero que te sirva este dato.

  • Consultas Linq en Query Syntax y Method Syntax en .net con c#

    LINQ : Language Integrated Query – Wikipedia, la enciclopedia libre

    Como su nombre lo dice, es lenguaje para realizar consultas integradas, y es provisto y administrado por .NET

    Aprende a usarlo, te permitirá recorrer cualquier colección (Enumerables, Dictioraries, Lists, etc)

    También cada resultado proveniente de una consulta con el ORM de EF (Entity Framework ) te devolverá un IQueriable (Un tipo de colleción), y su manipulación, sin dudas los harás mediante LINQ.

    Para que puedas aprender en el siguiente video vemos varias consultas para que puedas realizar, y sobre todo para que puedas comparar los dos métodos disponibles que tienes:

    Method Sintax & Query Sintax

    Sin mas nada que decir, aquí está el video:

    Consultas Linq en Query Syntax y Method Syntax en .net con c# – YouTube

  • A connection was successfully established with the server, but then an error occurred during the pre-login handshake

    Trabajas con Docker, MSSQL y recibes este error

    Para ser mas claro el error que me estuvo volviendo loco fue el siguiente:

    A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 35 – An internal exception was caught

    Es difícil de entender y descifrar así te cuento un poco como era mi escenario

    Escenario:

    Persistencia: MS SQL Server 2018 Express Edition

    Aplicación Web: Net 6.0

    Contenedor: Docker

    Al principio pensé que era una cuestión de conexión al motor, por lo que suele tener las siguientes precauciones:

    En mi imagen base suele habilitar algunas herramientas para facilitar las pruebas: Por ejemplo iputils

    FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
    
    WORKDIR /app
    #si el motor es SQL 2018
    RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf 
    EXPOSE 80
    EXPOSE 443
    USER root
    RUN apt-get update
    RUN apt-get install -y iputils-ping
    RUN apt-get install -y nano
    RUN apt-get install -y tzdata
    RUN apt-get install -y locales
    ENV LANG es_AR.UTF-8  
    ENV LANGUAGE es_AR:es  
    ENV LC_ALL es_AR.UTF-8  
    ENV TZ America/Mendoza

    Por otro lado en mi aplicación web bajo NET 6.0 suele habilitar la migración por párametros y habilitarlas al lanzamiento de la misma:

    Mi Program.cs quería así

    ....
    bool applymigrations = Configuration.GetValue<bool>("Migrations:ApplyMigrations");
    string cone_rw6 = Configuration.GetValue<string>("ConnectionStrings:RW6Connection");
    string cone_beje = Configuration.GetValue<string>("ConnectionStrings:BejermanConnection");
    
    Console.WriteLine($"BD: Migraciones {applymigrations}, Conection: {cone_rw6} ");
    Console.WriteLine($"BD: Migraciones {applymigrations}, Conection: {cone_beje} ");
    
    var scope = app.Services.CreateScope();
    if (applymigrations) await Migrations(scope.ServiceProvider);
    
    Console.WriteLine("******************Finalizado configuración de middlewares *******************");
    
    /* ========  Run  =======*/
    app.Run();

    Y mi función de Migraciones así

    
    
    async Task Migrations(IServiceProvider services)
    {
        //Cadena de conexión de variable externa
    
    
    
        var context_identity = services.GetRequiredService<AppIdentityContext>();
        var context_appdb = services.GetRequiredService<AppDbContext>();
        var conn_appidentity = context_identity.Database.GetDbConnection();
        var conn_appdb = context_appdb.Database.GetDbConnection();
    
        Console.WriteLine($"Conexión Actual Identity: {conn_appdb.ToString()}  {Environment.NewLine}  {conn_appdb.ConnectionString}");
        Console.WriteLine($"Conexión Actual AppDB: {conn_appdb.ToString()}  {Environment.NewLine}  {conn_appdb.ConnectionString}");
        Console.WriteLine("****************** Probando acceso  *******************");
        try
        {
            Console.WriteLine("Base Disponible de Identity:" + context_identity.Database.CanConnect());
            Console.WriteLine("Base Disponible de AppDb:" + context_appdb.Database.CanConnect());
        }
        catch (Exception ex)
        {
     Console.WriteLine($"------ !!! ERROR connectando: {ex.Message}");
        }
    
        Console.WriteLine("****************** Aplica Migraciones:  *******************");
        Console.WriteLine(applymigrations);
    
        if (applymigrations)
    
        {
            //Aplico la migracion
            Console.WriteLine("******************Aplicando migración*******************");
            Console.WriteLine("Migrando Identity:");
            context_identity.Database.Migrate();
            Console.WriteLine("Migrando Context:");
            context_appdb.Database.Migrate();
    
            Console.WriteLine("******************Cargando Usuarios:*******************");
            var userManager = services.GetRequiredService<UserManager<MyAUser_Models>>();
            var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
    
            await AppIdentity_StartUp.CargarUsuarios(userManager, roleManager);
        }
    }

    Pero a pesar de todo esto no había caso. La aplicación se conectaba, pero luego la misma se caía.

    Por lo que me puse a analizar el error completo:

    Microsoft.Data.SqlClient.SqlException (0x80131904): 
    A connection was successfully established with the server, 
    but then an error occurred during the pre-login handshake. 
    (provider: TCP Provider, error: 35 - An internal exception was caught)
     ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
     ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.

    Y venía de la mano de SSL. Ya sabiendo por donde orientar mi búsqueda, fue mas sencillo llegar al causar y a la solución:

    Para OpenSSL, la mínima versión con la que opera la imagen de .NET es TLS v1.2 en adelante, mientras que el MS SQL Server 2018 express edition, solo soporta hasta TLS v1.0

    Solo era cuestión de probar, si la teoría era correcta. Para ello ingresé a la imagen y modifique la configuración del openssl para correr en una versión inferior:

    #docker exec -it <name> sh

    #cat /etc/ssl/openssl.cnf

    .......
    [system_default_sect] 
    MinProtocol = TLSv1.2 
    CipherString = DEFAULT@SECLEVEL=2

    Por lo que procedí a modificar mi imagen, y no tener que cambiarlo manualmente en la implementación:

    Para ello agregué

    RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf

    Mi primer parte del dockerfile quedó así

    #################################################################################################
    FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
    
    WORKDIR /app
    #si el motor es SQL 2018
    RUN sed -i 's/TLSv1.2/TLSv1/g' /etc/ssl/openssl.cnf 
    EXPOSE 80
    EXPOSE 443
    USER root
    RUN apt-get update
    RUN apt-get install -y iputils-ping
    RUN apt-get install -y nano
    RUN apt-get install -y tzdata
    RUN apt-get install -y locales
    ENV LANG es_AR.UTF-8  
    ENV LANGUAGE es_AR:es  
    ENV LC_ALL es_AR.UTF-8  
    ENV TZ America/Mendoza

  • API rest con Net 5.0 y MongoDB

    API rest con Net 5.0 y MongoDB

    Desde que salío Net 5.0 (Nov 2020) empezamos a migrar todos nuestros proyectos a esta nueva versión. Muy similar a Net Core, pero con algunos cambios, sobre todo haciendo foco en Microservicios. Por otro lado con el crecimiento de popularidad de las bases No SQL decide armar esta tutorial con el producto mas conocidos: Mongo DB.

    Esta base no relacional presenta ciertas diferencias con respecto a sus antecesoras relacionales como MS SQL Server. Por lo que en los siguientes videos te vamos a mostar

    • Como instalar desde 0 tu motor MongoDB
    • Crear la Base de Datos
    • Crear la colección
    • Realizar las primeras consultas por consola
    • Realizar las primeras consultas por Compass

    En esta segunda parte ya nos enfocamos en trabajar con Net 5.0 y crear nuestro servicio web

    • Creamos el proyecto
    • Armamos nuestro modelo
    • Incorporamos Swagger y MongoDB Driver
    • Creamos la estructura para empezar a trabajar

    En esta tercer parte ya estamos listo para nuestro servicio

    • Creamos el CRUD (Create, Recovery, Update y Delete)
    • Creamos el controlador
    • Probamos nuestro API Rest

    En el siguiente video estaremos viendo los siguientes temas

    • Crear filtros avanzados
    • Usando Docker

  • The version of SQL in use does not support datatype ‘datetime2’

    Actualmente estoy trabajando en un proyecto de integrar información de un motor MYSQL 5.0 a MSSQL 2005. Para ello el integrador esta en VB NET 2010 con DotNet 4.0.

    El mejor framework para el diagramado y la realización de consultas fue usar LINQ TO ENTITIES.

    Luego de un par de dolores de cabeza(*) pude llevar mi primera prueba al servidor de producción.

    Al ejecutarlo obtuve mi primer error «The versión of SQL in use does not support datatype ‘datetime2′» .

    En este momento recordé que mi servidor de desarrollo era un MSSQL Server 2008, y el de producción un MSSQL 2005. (L)

    Antes de poder ponerme a pensar que hacer decidí googlearlo y encontré una solución simple pero efectiva.

    Consiste en buscar el .emdx dentro del projecto de Visual Studio. Modificarlo con un editor de XML, o similar. Y buscar el siguiente código.

    ProviderManifestToken=«2008« y reemplazarlo por ProviderManifestToken=«2005«

    Algo simple pero efectivo… Al final funcionó…

    (*) Si al ejecutar su aplicación se cierra sin más aviso, no se olviden de instalar el MYSQL Connector con el que estuvieron trabajando en el servidor de producción.

Tags

tsql (27)mssql (26)sql (20)devops (20)dotnet (18)docker (15)performance (14)contenedores (11)dotnet10 (10)linux (9)csharp (8)microservicios (7)angular (7)angular21 (7)sql server (6)issabel (6)docker-compose (6)typescript (6)mysql (5).NET (5)