Compatibilidad de ASP.NET Core con AOT nativo

ASP.NET Core 8.0 ofrece compatibilidad con .NET ahead-of-time (AOT) nativo.

Razones para usar AOT nativo con ASP.NET Core

La publicación e implementación de una aplicación de AOT nativo proporciona las siguientes ventajas:

  • Superficie de disco minimizada: al publicar con AOT nativo, se genera un único archivo ejecutable que contiene solo el código de las dependencias externas que se necesitan para respaldar el programa. El tamaño reducido de archivo ejecutable puede dar lugar a:
    • Imágenes de contenedor más pequeñas; por ejemplo, en escenarios de implementación en contenedores.
    • Tiempo de implementación reducido de las imágenes más pequeñas.
  • Menor tiempo de inicio: las aplicaciones de AOT nativo logran unos tiempos de inicio reducidos, lo que significa que:
    • La aplicación está lista para atender las solicitudes más rápidamente.
    • La implementación mejora cuando los orquestadores de contenedores necesitan administrar la transición de una versión de la aplicación a otra.
  • Menor demanda de memoria: las aplicaciones de AOT nativo pueden tener una menor demanda de memoria en función del trabajo que la aplicación realice. El menor consumo de memoria puede dar lugar a una mayor densidad de implementación y a una mejor escalabilidad.

Hemos ejecutado la aplicación de plantilla en nuestro laboratorio de pruebas comparativas para cotejar el rendimiento de una aplicación publicada con AOT, de una aplicación en tiempo de ejecución recortada y de una aplicación en tiempo de ejecución sin recortar. En el siguiente gráfico se muestran los resultados de la prueba comparativa:

Chart showing comparison of application size, memory use, and startup time metrics of an AOT published app, a runtime app that is trimmed, and an untrimmed runtime app.

En el gráfico anterior se muestra que AOT nativo tiene un tamaño de aplicación, un uso de memoria y un tiempo de inicio más reducidos.

ASP.NET Core y compatibilidad con AOT nativo

No todas las características de ASP.NET Core son actualmente compatibles con AOT nativo. En la tabla siguiente se resume la compatibilidad de las características de ASP.NET Core con AOT nativo:

Característica Compatibilidad total Se admite parcialmente No compatible
gRPC Compatibilidad total
API mínimas Compatibilidad parcial
MVC No compatible
Blazor Server No compatible
SignalR No compatible
Autenticación JWT Compatibilidad total
Otra autenticación No compatible
CORS Compatibilidad total
HealthChecks Compatibilidad total
HttpLogging Compatibilidad total
Localización Compatibilidad total
OutputCaching Compatibilidad total
RateLimiting Compatibilidad total
RequestDecompression Compatibilidad total
ResponseCaching Compatibilidad total
ResponseCompression Compatibilidad total
Rewrite Compatibilidad total
Sesión No compatible
Spa No compatible
StaticFiles Compatibilidad total
WebSockets Compatibilidad total

Para obtener más información sobre las limitaciones, consulte:

Al cambiar a un modelo de implementación de AOT nativo, es importante probar una aplicación exhaustivamente. La aplicación de AOT implementada debe probarse para comprobar que la funcionalidad no ha cambiado respecto a la aplicación sin recortar y con compilación JIT. Al compilar la aplicación, examine y corrija las advertencias de AOT. Es posible que una aplicación que emite advertencias de AOT durante la publicación no funcione correctamente. Si no se emite ninguna advertencia de AOT en el momento de la publicación, la aplicación AOT publicada debe funcionar igual que la aplicación compilada sin intentar y JIT.

Publicación de AOT nativo

AOT nativo se habilita con la propiedad de MSBuild PublishAot: En el siguiente ejemplo se muestra cómo habilitar AOT nativo en un archivo de proyecto:

<PropertyGroup>
  <PublishAot>true</PublishAot>
</PropertyGroup>

Esta configuración habilita la compilación de AOT nativo durante la publicación y el análisis dinámico del uso de código en el transcurso de la compilación y la edición. Un proyecto que usa la publicación de AOT nativo utiliza la compilación JIT cuando se ejecuta de forma local. Una aplicación AOT presenta las siguientes diferencias con respecto a una aplicación compilada por JIT:

  • Las características que no son compatibles con AOT nativo se deshabilitan y producen excepciones en tiempo de ejecución.
  • Un analizador de origen está habilitado para resaltar el código que no es compatible con AOT nativo. En el momento de publicación, se analizará la aplicación completa, incluidos los paquetes NuGet, con fines de compatibilidad.

El análisis de AOT nativo abarca todo el código de la aplicación y las bibliotecas de las que esta depende. Revise las advertencias de AOT nativo y tome medidas correctivas. Se recomienda publicar las aplicaciones con frecuencia para detectar problemas lo antes posible en el ciclo de vida de desarrollo.

En .NET 8, AOT nativo es compatible con los siguientes tipos de aplicación ASP.NET Core:

Plantilla de API web (AOT nativa)

La plantilla ASP.NET Core Web API (AOT nativa) (nombre corto webapiaot) crea un proyecto con AOT habilitado. La plantilla difiere de la plantilla de proyecto Web API de las siguientes maneras:

  • Usa solo API mínimas, ya que MVC aún no es compatible con AOT nativo.
  • Usa la API CreateSlimBuilder() para garantizar que solo las características esenciales están habilitadas de forma predeterminada, lo que reduce al mínimo el tamaño implementado de la aplicación.
  • Está configurada para escuchar solo HTTP, ya que en las implementaciones nativas de nube el tráfico HTTPS suele estar controlado por un servicio de entrada.
  • No incluye un perfil de inicio para ejecutarse en IIS o IIS Express.
  • Crea un archivo .http configurado con solicitudes HTTP de ejemplo que se pueden enviar a los puntos de conexión de la aplicación.
  • Incluye una API Todo de ejemplo en lugar del ejemplo de previsión meteorológica.
  • Agrega PublishAot al archivo de proyecto, tal y como hemos visto anteriormente en este artículo.
  • Habilita los generadores de código fuente del serializador JSON. Los generadores de código fuente se usan para generar código de serialización en tiempo de compilación, lo cual es necesario para la compilación AOT nativa.

Cambios para admitir la generación de orígenes

En el siguiente ejemplo se muestra el código agregado al archivo Program.cs para admitir la generación de orígenes de serialización JSON:

using MyFirstAotWebApi;
+using System.Text.Json.Serialization;

-var builder = WebApplication.CreateBuilder();
+var builder = WebApplication.CreateSlimBuilder(args);

+builder.Services.ConfigureHttpJsonOptions(options =>
+{
+  options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
+});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
    sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
        ? Results.Ok(todo)
        : Results.NotFound());

app.Run();

+[JsonSerializable(typeof(Todo[]))]
+internal partial class AppJsonSerializerContext : JsonSerializerContext
+{
+
+}

Sin este código agregado, System.Text.Json usa la reflexión para serializar y deserializar JSON, La reflexión no se admite en AOT nativo.

Para más información, consulte:

Cambios a launchSettings.json

El archivo launchSettings.json creado por la plantilla de API web (AOT nativo) tiene la sección iisSettings y el perfil IIS Express quitados:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
-  "iisSettings": {
-     "windowsAuthentication": false,
-     "anonymousAuthentication": true,
-     "iisExpress": {
-       "applicationUrl": "http://localhost:11152",
-       "sslPort": 0
-     }
-   },
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "todos",
      "applicationUrl": "http://localhost:5102",
        "environmentVariables": {
          "ASPNETCORE_ENVIRONMENT": "Development"
        }
      },
-     "IIS Express": {
-       "commandName": "IISExpress",
-       "launchBrowser": true,
-       "launchUrl": "todos",
-      "environmentVariables": {
-       "ASPNETCORE_ENVIRONMENT": "Development"
-      }
-    }
  }
}

El método CreateSlimBuilder

La plantilla usa el método CreateSlimBuilder() en vez del método CreateBuilder().

using System.Text.Json.Serialization;
using MyFirstAotWebApi;

var builder = WebApplication.CreateSlimBuilder(args);
builder.Logging.AddConsole();

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
    sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
        ? Results.Ok(todo)
        : Results.NotFound());

app.Run();

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}

El método CreateSlimBuilder inicializa WebApplicationBuilder con las características de ASP.NET Core mínimas necesarias para ejecutar una aplicación.

Como hemos señalado anteriormente, el método CreateSlimBuilder no incluye compatibilidad con HTTPS ni HTTP/3. Normalmente, estos protocolos no son necesarios para las aplicaciones que se ejecutan detrás de un proxy de terminación TLS. Por ejemplo, vea Terminación TLS y TLS de un extremo a otro con Application Gateway. HTTPS se puede habilitar llamando a builder.WebHost.UseKestrelHttpsConfiguration HTTP/3 se puede habilitar llamando a builder.WebHost.UseQuic.

CreateSlimBuilder frente a CreateBuilder

El método CreateSlimBuilder no admite las siguientes características compatibles con el método CreateBuilder:

El método CreateSlimBuilder incluye las siguientes características necesarias para una experiencia de desarrollo eficaz:

  • Configuración del archivo JSON para appsettings.json y appsettings.{EnvironmentName}.json
  • Configuración de secretos de usuario
  • Registro de consolas
  • Configuración del registro

Para obtener un generador que omita las características anteriores, vea El método CreateEmptyBuilder.

Incluir las características mínimas es beneficioso para el recorte, así como para AOT. Para obtener más información, vea Recorte de implementaciones autocontenidas y ejecutables.

Para obtener información más detallada, consulte Comparación WebApplication.CreateBuilder con CreateSlimBuilder

Generadores de origen

Dado que el código sin usar se recorta durante la publicación para AOT nativo, la aplicación no puede usar la reflexión sin enlazar en tiempo de ejecución. Los generadores de código fuente sirven para generar código y evitan tener que recurrir a la reflexión. En algunos casos, los generadores de código fuente generan código optimizado para AOT, incluso cuando no es necesario un generador.

Para ver el código fuente generado, agregue la propiedad EmitCompilerGeneratedFiles al archivo .csproj de una aplicación, como se muestra en el siguiente ejemplo:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <!-- Other properties omitted for brevity -->
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  </PropertyGroup>

</Project>

Ejecute el comando dotnet build para ver el código generado. La salida incluye un directorio obj/Debug/net8.0/generated/ que contiene todos los archivos generados del proyecto.

El comando dotnet publish también compila los archivos de origen y genera archivos que se compilan. Asimismo, dotnet publish pasa los ensamblados generados a un compilador de IL nativo. El compilador de IL genera el ejecutable nativo. El archivo ejecutable nativo contiene el código de máquina nativo.

Bibliotecas y AOT nativo

Muchas de las bibliotecas populares que se usan en los proyectos de ASP.NET Core actualmente tienen algunos problemas de compatibilidad cuando se utilizan en un proyecto que tiene como destino AOT nativo, por ejemplo:

  • Uso de la reflexión para inspeccionar y detectar tipos.
  • Carga condicional de bibliotecas en tiempo de ejecución.
  • Generar código sobre la marcha para implementar la funcionalidad.

Las bibliotecas que usan estas características dinámicas se deben actualizar para poder trabajar con AOT nativo. Se pueden actualizar usando herramientas como generadores de origen de Roslyn.

A los creadores de bibliotecas que quieran admitir AOT nativo se les recomienda lo siguiente:

API mínimas y JScargas de ON

El marco de API mínimo está optimizado para recibir y devolver JScargas ON mediante System.Text.Json. System.Text.Json:

Todos los tipos que se transmiten como parte del cuerpo HTTP o que se devuelven de delegados de solicitud en aplicaciones de API mínimas deben configurarse en un JsonSerializerContext que se registra a través de la inserción de dependencias de ASP.NET Core:

using System.Text.Json.Serialization;
using MyFirstAotWebApi;

var builder = WebApplication.CreateSlimBuilder(args);
builder.Logging.AddConsole();

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");
todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
    sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
        ? Results.Ok(todo)
        : Results.NotFound());

app.Run();

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}

En el código resaltado anterior:

Parámetro del delegado que no está enlazado al cuerpo y no es necesario serializable. Por ejemplo, un parámetro de cadena de consulta que es un tipo de objeto enriquecido e implementa IParsable<T>.

public class Todo
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public DateOnly? DueBy { get; set; }
    public bool IsComplete { get; set; }
}

static class TodoGenerator
{
    private static readonly (string[] Prefixes, string[] Suffixes)[] _parts = new[]
        {
            (new[] { "Walk the", "Feed the" }, new[] { "dog", "cat", "goat" }),
            (new[] { "Do the", "Put away the" }, new[] { "groceries", "dishes", "laundry" }),
            (new[] { "Clean the" }, new[] { "bathroom", "pool", "blinds", "car" })
        };
    // Remaining code omitted for brevity.

Problemas conocidos

Consulte este problema de GitHub para notificar o revisar los problemas de compatibilidad con AOT nativo en ASP.NET Core.

Consulte también