Actualización de ASP.NET Framework a ASP.NET Core

Motivos para actualizar a la versión más reciente de .NET

ASP.NET Core es el marco web moderno para .NET. Aunque ASP.NET Core tiene muchas similitudes con ASP.NET en .NET Framework, se trata de un nuevo marco completamente reescrito. Las aplicaciones ASP.NET actualizadas a ASP.NET Core pueden beneficiarse de un mejor rendimiento y del acceso a las últimas características y funcionalidades de desarrollo web.

Enfoques de actualización de ASP.NET Framework

La mayoría de las aplicaciones ASP.NET Framework no triviales deben considerar el uso del enfoque de actualización incremental. Para obtener más información, consulte Actualización incremental de ASP.NET a ASP.NET Core.

Para obtener aplicaciones de ASP.NET MVC y de API web, vea Más información sobre cómo actualizar desde ASP.NET MVC y API web a ASP.NET Core MVC). Para obtener aplicaciones de ASP.NET Framework Web Forms Apps, vea Más información sobre cómo actualizar de ASP.NET Web Forms a ASP.NET Core).

Patrones de aplicación web confiable

Consulte El patrón de aplicación web confiable para .NETen los vídeos de YouTube y el artículo para obtener instrucciones sobre cómo crear una aplicación moderna, confiable, que se puede probar, rentable y escalable de ASP.NET Core, ya sea desde cero o refactorizando una aplicación existente.

Diferencias de descodificación de URI entre ASP.NET y ASP.NET Core

ASP.NET Core tiene las siguientes diferencias de descodificación de URI con respecto a ASP.NET Framework:

ASCII Encoded ASP.NET Core ASP.NET Framework
\ %5C \ /
/ %2F %2F /

Al descodificar %2F en ASP.NET Core:

  • Toda la ruta de acceso se quita, excepto %2F porque convertirlo en / cambiaría la estructura de la ruta de acceso. No se puede descodificar hasta que la ruta de acceso se divida en segmentos.

Para generar el valor de HttpRequest.Url, use new Uri(this.AspNetCoreHttpRequest.GetEncodedUrl()); para evitar que Uri malinterprete los valores.

Migración de secretos de usuario de ASP.NET Framework a ASP.NET Core

Consulte este problema de GitHub.

Este artículo sirve de guía de referencia para migrar aplicaciones de ASP.NET a ASP.NET Core.

Visual Studio tiene herramientas para ayudar a migrar aplicaciones ASP.NET a ASP.NET Core. Para obtener más información, consulte Migrating from ASP.NET to ASP.NET Core in Visual Studio (Migración de ASP.NET a ASP.NET Core en Visual Studio).

El Asistente para actualización de .NET es una herramienta de línea de comandos que puede ayudar a migrar ASP.NET a ASP.NET Core. Para obtener más información, consulte Información general sobre el Asistente para actualización de .NET y Actualización de una aplicación de ASP.NET MVC a .NET 6 con el Asistente para actualización de .NET.

Vea el libro electrónico Migración de aplicaciones existentes de ASP.NET a .NET Core para obtener una guía de migración completa.

Requisitos previos

.NET Core SDK 2.2 o posterior

Versiones de .NET Framework de destino

Los proyectos de ASP.NET Core proporcionan a los desarrolladores la flexibilidad de usar la versión .NET Core, .NET Framework o ambas. Vea Selección entre .NET Core y .NET Framework para aplicaciones de servidor para determinar qué plataforma de destino es más adecuada.

Cuando se usa la versión .NET Framework, es necesario que los proyectos hagan referencia a paquetes de NuGet individuales.

Cuando se usa .NET Core, se pueden eliminar numerosas referencias explícitas del paquete gracias al metapaquete de ASP.NET Core. Instale el metapaquete Microsoft.AspNetCore.App en el proyecto:

<ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

Cuando se usa el metapaquete, con la aplicación no se implementa ningún paquete al que se hace referencia en el metapaquete. El almacén de tiempo de ejecución de .NET Core incluye estos activos que están compilados previamente para mejorar el rendimiento. Consulte Metapaquete Microsoft.AspNetCore.App para ASP.NET Core para más información.

Diferencias en la estructura de proyecto

El formato de archivo .csproj se ha simplificado en ASP.NET Core. Estos son algunos cambios importantes:

  • La inclusión explícita de archivos no es necesaria para que se consideren parte del proyecto. Esto reduce el riesgo de conflictos al fusionar XML cuando se trabaja en equipos grandes.

  • No hay referencias de GUID a otros proyectos, lo cual mejora la legibilidad del archivo.

  • El archivo se puede editar sin descargarlo en Visual Studio:

    Edit CSPROJ context menu option in Visual Studio 2017]

Reemplazo del archivo Global.asax

ASP.NET Core introdujo un nuevo mecanismo para arrancar una aplicación. El punto de entrada para las aplicaciones ASP.NET es el archivo Global.asax. En el archivo Global.asax se controlan tareas como la configuración de enrutamiento y los registros de filtro y de área.

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

Este enfoque acopla la aplicación y el servidor en el que está implementada de forma que interfiere con la implementación. En un esfuerzo por desacoplar, se introdujo OWIN para ofrecer una forma más limpia de usar varios marcos de trabajo de manera conjunta. OWIN proporciona una canalización para agregar solo los módulos necesarios. El entorno de hospedaje toma una función de inicio para configurar servicios y la canalización de solicitud de la aplicación. Startup registra un conjunto de middleware en la aplicación. Para cada solicitud, la aplicación llama a cada uno de los componentes de middleware con el puntero principal de una lista vinculada a un conjunto de controladores existente. Cada componente de middleware puede agregar uno o varios controladores a la canalización de control de la solicitud. Esto se consigue mediante la devolución de una referencia al controlador que ahora es el primero de la lista. Cada controlador se encarga de recordar e invocar el controlador siguiente en la lista. Con ASP.NET Core, el punto de entrada a una aplicación es Startup y ya no se tiene dependencia de Global.asax. Cuando utilice OWIN con .NET Framework, use algo parecido a lo siguiente como canalización:

using Owin;
using System.Web.Http;

namespace WebApi
{
    // Note: By default all requests go through this OWIN pipeline. Alternatively you can turn this off by adding an appSetting owin:AutomaticAppStartup with value “false”. 
    // With this turned off you can still have OWIN apps listening on specific routes by adding routes in global.asax file using MapOwinPath or MapOwinRoute extensions on RouteTable.Routes
    public class Startup
    {
        // Invoked once at startup to configure your application.
        public void Configuration(IAppBuilder builder)
        {
            HttpConfiguration config = new HttpConfiguration();
            config.Routes.MapHttpRoute("Default", "{controller}/{customerID}", new { controller = "Customer", customerID = RouteParameter.Optional });

            config.Formatters.XmlFormatter.UseXmlSerializer = true;
            config.Formatters.Remove(config.Formatters.JsonFormatter);
            // config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;

            builder.UseWebApi(config);
        }
    }
}

Esto configura las rutas predeterminadas y tiene como valor predeterminado XmlSerialization a través de Json. Agregue otro middleware a esta canalización según sea necesario (carga de servicios, opciones de configuración, archivos estáticos, etcétera).

ASP.NET Core usa un enfoque similar, pero no depende de OWIN para controlar la entrada. Alternativamente, se usa el método Program.csMain (similar a las aplicaciones de consola), y Startup se carga a través de ahí.

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace WebApplication2
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

Startup debe incluir un método Configure. En Configure, agregue el middleware necesario a la canalización. En el ejemplo siguiente (de la plantilla de sitio web predeterminada), los métodos de extensión configuran la canalización con compatibilidad para:

  • Páginas de error
  • Seguridad de transporte estricta de HTTP
  • Redireccionamiento HTTP a HTTPS
  • ASP.NET Core MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseMvc();
}

El host y la aplicación se han desacoplado, lo que proporciona la flexibilidad de pasar a una plataforma diferente en el futuro.

Nota

Para acceder a referencias más detalladas sobre el inicio de ASP.NET Core y middleware, consulte Inicio de la aplicación en ASP.NET Core.

Almacenamiento de valores de configuración

ASP.NET admite el almacenamiento de valores de configuración. Estos valores de configuración se usan, por ejemplo, para admitir el entorno donde se implementan las aplicaciones. Antes se solían almacenar todos los pares de clave y valor personalizados en la sección <appSettings> del archivo Web.config:

<appSettings>
  <add key="UserName" value="User" />
  <add key="Password" value="Password" />
</appSettings>

Las aplicaciones leen esta configuración mediante la colección ConfigurationManager.AppSettings en el espacio de nombres System.Configuration:

string userName = System.Web.Configuration.ConfigurationManager.AppSettings["UserName"];
string password = System.Web.Configuration.ConfigurationManager.AppSettings["Password"];

ASP.NET Core puede almacenar datos de configuración para la aplicación en cualquier archivo y cargarlos como parte del arranque de middleware. El archivo predeterminado que se usa en las plantillas de proyecto es appsettings.json :

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "AppConfiguration": {
    "UserName": "UserName",
    "Password": "Password"
  }
}

La carga de este archivo en una instancia de IConfiguration dentro de la aplicación se realiza en Startup.cs:

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

La aplicación lee de Configuration para obtener la configuración:

string userName = Configuration.GetSection("AppConfiguration")["UserName"];
string password = Configuration.GetSection("AppConfiguration")["Password"];

Existen extensiones de este enfoque para lograr que el proceso sea más sólido, como el uso de inserción de dependencias para cargar un servicio con estos valores. El enfoque de la inserción de dependencias proporciona un conjunto fuertemente tipado de objetos de configuración.

// Assume AppConfiguration is a class representing a strongly-typed version of AppConfiguration section
services.Configure<AppConfiguration>(Configuration.GetSection("AppConfiguration"));

Nota

Para acceder a referencias más detalladas sobre la configuración de ASP.NET Core, consulte Configuración en ASP.NET Core.

Inserción de dependencias nativa

Un objetivo importante al compilar aplicaciones grandes y escalables es lograr el acoplamiento flexible de los componentes y los servicios. La inserción de dependencias es una técnica popular para lograrlo y se trata de un componente nativo de ASP.NET Core.

En las aplicaciones ASP.NET, los desarrolladores se sirven de una biblioteca de terceros para implementar la inserción de dependencias. Una de estas bibliotecas es Unity, suministrada por Microsoft Patterns & Practices.

Un ejemplo de configuración de la inserción de dependencias con Unity consiste en implementar IDependencyResolver que encapsula un UnityContainer:

using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;

public class UnityResolver : IDependencyResolver
{
    protected IUnityContainer container;

    public UnityResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException("container");
        }
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch (ResolutionFailedException)
        {
            return new List<object>();
        }
    }

    public IDependencyScope BeginScope()
    {
        var child = container.CreateChildContainer();
        return new UnityResolver(child);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        container.Dispose();
    }
}

Cree una instancia de UnityContainer, registre el servicio y establezca la resolución de dependencias de HttpConfiguration en la nueva instancia de UnityResolver para el contenedor:

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
    config.DependencyResolver = new UnityResolver(container);

    // Other Web API configuration not shown.
}

Inserte IProductRepository cuando sea necesario:

public class ProductsController : ApiController
{
    private IProductRepository _repository;

    public ProductsController(IProductRepository repository)  
    {
        _repository = repository;
    }

    // Other controller methods not shown.
}

Dado que la inserción de dependencia forma parte de ASP.NET Core, puede agregar el servicio en el método ConfigureServices de Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    // Add application services.
    services.AddTransient<IProductRepository, ProductRepository>();
}

El repositorio se puede insertar en cualquier lugar, como ocurría con Unity.

Nota

Para más información sobre la inserción de dependencias, vea Inserción de dependencias.

Proporcionar archivos estáticos

Una parte importante del desarrollo web es la capacidad de trabajar con activos estáticos de cliente. Los ejemplos más comunes de archivos estáticos son HTML, CSS, JavaScript e imágenes. Estos archivos deben guardarse en la ubicación de publicación de la aplicación (o la red de entrega de contenido) y es necesario hacer referencia a ellos para que una solicitud los pueda cargar. Este proceso ha cambiado en ASP.NET Core.

En ASP.NET, los archivos estáticos se almacenan en directorios distintos y se hace referencia a ellos en las vistas.

En ASP.NET Core, los archivos estáticos se almacenan en la "raíz web" ( <raíz del contenido>/wwwroot), a menos que se configuren de otra manera. Los archivos se cargan en la canalización de solicitud mediante la invocación del método de extensión UseStaticFiles desde Startup.Configure:

Nota

Si el destino es .NET Framework, debe instalarse el paquete de NuGet Microsoft.AspNetCore.StaticFiles.

Por ejemplo, el explorador puede acceder a un recurso de imagen en la carpeta wwwroot/images en una ubicación como http://<app>/images/<imageFileName>.

Nota

Para acceder a referencias más detalladas sobre cómo trabajar con archivos estáticos en ASP.NET Core, consulte Archivos estáticos.

cookies de varios valores

cookies de varios valores no se admiten en ASP.NET Core. Cree una cookie por valor.

Las autenticaciones cookie no se comprimen en ASP.NET Core.

Por motivos de seguridad, las autenticaciones cookie no se comprimen en ASP.NET Core. Cuando se usan las autenticaciones cookie, los desarrolladores deben minimizar el número de información de notificaciones que se incluye solo para sus necesidades.

Migración parcial de aplicaciones

Un enfoque para la migración parcial de aplicaciones consiste en crear una subaplicación de IIS y solo trasladar determinadas rutas de ASP.NET 4.x a ASP.NET Core, al tiempo que se conserva la estructura de la dirección URL de la aplicación. Por ejemplo, considere la estructura de dirección URL de la aplicación en el archivo applicationHost.config:

<sites>
    <site name="Default Web Site" id="1" serverAutoStart="true">
        <application path="/">
            <virtualDirectory path="/" physicalPath="D:\sites\MainSite\" />
        </application>
        <application path="/api" applicationPool="DefaultAppPool">
            <virtualDirectory path="/" physicalPath="D:\sites\netcoreapi" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:" />
            <binding protocol="https" bindingInformation="*:443:" sslFlags="0" />
        </bindings>
    </site>
	...
</sites>

Estructura de directorios:

.
├── MainSite
│   ├── ...
│   └── Web.config
└── NetCoreApi
    ├── ...
    └── web.config

[BIND] y formateadores de entrada

Las versiones anteriores de ASP.NET usaban el atributo [Bind] para protegerse frente a ataques de publicación excesiva. Los formateadores de entrada funcionan de manera diferente en ASP.NET Core. El atributo [Bind] ya no está diseñado para evitar la publicación excesiva cuando se usa con formateadores de entrada para analizar JSON o XML. Estos atributos afectan al enlace de modelos cuando el origen de datos es un formulario de datos publicado con el tipo de contenido x-www-form-urlencoded.

En el caso de las aplicaciones que envían información de JSON a los controladores y usan formateadores de entrada JSON para analizar los datos, se recomienda reemplazar el atributo [Bind] por un modelo de vista que coincida con las propiedades definidas por el atributo [Bind].

Recursos adicionales