Migración de ASP.NET Web Forms a Blazor
Sugerencia
Este contenido es un extracto del libro electrónico "Blazor for ASP.NET Web Forms Developers for Azure" (Blazor para desarrolladores de ASP.NET Web Forms), disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.
La migración de una base de código desde ASP.NET Web Forms a Blazor es una tarea para la que se necesita tiempo y planificación. En este capítulo se describe el proceso. Algo que puede facilitar la transición es asegurarse de que la aplicación se adhiera a una arquitectura de n niveles, donde el modelo de la aplicación (en este caso, Web Forms) es independiente de la lógica de negocios. Esta separación lógica de los niveles permite identificar con claridad lo que se debe trasladar a .NET Core y Blazor.
En este ejemplo, se usa la aplicación eShop disponible en GitHub. eShop es un servicio de catálogo que proporciona funciones CRUD mediante la entrada y validación de formularios.
¿Por qué se debería migrar una aplicación funcional a Blazor? En muchos casos no es necesario. ASP.NET Web Forms seguirán contando con soporte técnico durante muchos años. Pero muchas de las características que proporciona Blazor solo se admiten en una aplicación migrada. Entre esas características se incluyen las siguientes:
- Mejoras de rendimiento en el marco como
Span<T>
- Capacidad de ejecutarse como WebAssembly
- Compatibilidad multiplataforma para Linux y macOS
- Implementación local de la aplicación o implementación de un marco compartido sin afectar a otras aplicaciones
Si estas u otras características nuevas son lo suficientemente atractivas, es posible que la migración de la aplicación tenga su utilidad. La migración puede adoptar distintas formas; puede ser toda la aplicación o solo determinados puntos de conexión en los que se necesiten los cambios. En última instancia, la decisión de realizar la migración se basa en los problemas empresariales que el desarrollador tenga que resolver.
Diferencias entre el hospedaje del lado servidor y del lado cliente
Como se ha descrito en el capítulo sobre modelos de hospedaje, una aplicación Blazor se puede hospedar de dos maneras diferentes: en el lado servidor y en el lado cliente. En el modelo del lado servidor se usan conexiones SignalR de ASP.NET Core para administrar las actualizaciones del DOM mientras se ejecuta código real en el servidor. El modelo del lado cliente se ejecuta como WebAssembly dentro de un explorador y no necesita conexión a ningún servidor. Hay varias diferencias que pueden afectar a la mejor opción para una aplicación específica:
- En la actualidad, la ejecución como WebAssembly no admite todas las características (como el subprocesamiento).
- La comunicación activa entre el cliente y el servidor puede producir problemas de latencia en el modo del lado servidor
- Para el acceso a bases de datos y a servicios internos o protegidos se necesita un servicio independiente con hospedaje en el lado cliente.
En el momento de escribir este documento, el modelo del lado servidor es el que más se parece al de Web Forms. La mayor parte de este capítulo se centra en el modelo de hospedaje del lado servidor, ya que está listo para la producción.
Creación de un nuevo proyecto
Este paso de migración inicial consiste en crear un proyecto. Este tipo de proyecto se basa en los proyectos de estilo SDK de .NET y simplifica gran parte del texto reutilizable que se ha usado en los formatos de proyecto anteriores. Para obtener más información, vea el capítulo sobre la estructura del proyecto.
Una vez que se ha creado el proyecto, instale las bibliotecas que se han usado en el proyecto anterior. En proyectos de Web Forms anteriores, es posible que haya usado el archivo packages.config para enumerar los paquetes NuGet necesarios. En el nuevo proyecto de estilo SDK, packages.config se ha reemplazado por elementos <PackageReference>
en el archivo del proyecto. Una ventaja de este enfoque es que todas las dependencias se instalan de forma transitiva. Solo se muestran las dependencias de nivel superior que le interesan.
Muchas de las dependencias que se usan están disponibles para .NET, como Entity Framework 6 y log4net. Si no hay ninguna versión de .NET o .NET Standard disponible, a menudo se puede usar la de .NET Framework. Puede que tu experiencia sea diferente. Cualquier API que se use y no esté disponible en .NET provocará un error en tiempo de ejecución. Visual Studio le informa de estos paquetes. Aparece un icono amarillo en el nodo Referencias del proyecto en el Explorador de soluciones.
En el proyecto eShop basado en Blazor, puede ver los paquetes que están instalados. Anteriormente, en el archivo packages.config se enumeraban todos los paquetes usados en el proyecto, lo que genera un archivo de casi 50 líneas de longitud. Un fragmento de packages.config es este:
<?xml version="1.0" encoding="utf-8"?>
<packages>
...
<package id="Microsoft.ApplicationInsights.Agent.Intercept" version="2.4.0" targetFramework="net472" />
<package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.9.1" targetFramework="net472" />
<package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.9.1" targetFramework="net472" />
<package id="Microsoft.ApplicationInsights.Web" version="2.9.1" targetFramework="net472" />
<package id="Microsoft.ApplicationInsights.WindowsServer" version="2.9.1" targetFramework="net472" />
<package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.9.1" targetFramework="net472" />
<package id="Microsoft.AspNet.FriendlyUrls" version="1.0.2" targetFramework="net472" />
<package id="Microsoft.AspNet.FriendlyUrls.Core" version="1.0.2" targetFramework="net472" />
<package id="Microsoft.AspNet.ScriptManager.MSAjax" version="5.0.0" targetFramework="net472" />
<package id="Microsoft.AspNet.ScriptManager.WebForms" version="5.0.0" targetFramework="net472" />
...
<package id="System.Memory" version="4.5.1" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.0" targetFramework="net472" />
<package id="System.Threading.Channels" version="4.5.0" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.1" targetFramework="net472" />
<package id="WebGrease" version="1.6.0" targetFramework="net472" />
</packages>
El elemento <packages>
incluye todas las dependencias necesarias. Es difícil identificar cuál de estos paquetes se incluyen porque son necesarios. Algunos elementos <package>
solo se muestran para satisfacer las necesidades de las dependencias que necesita.
En el proyecto Blazor se enumeran las dependencias que necesita dentro de un elemento <ItemGroup>
en el archivo del proyecto:
<ItemGroup>
<PackageReference Include="Autofac" Version="4.9.3" />
<PackageReference Include="EntityFramework" Version="6.4.4" />
<PackageReference Include="log4net" Version="2.0.12" />
<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="2.2.12" />
</ItemGroup>
Un paquete NuGet que simplifica el trabajo de los desarrolladores de Web Forms es el paquete de compatibilidad de Windows. Aunque .NET es multiplataforma, algunas características solo están disponibles en Windows. Las características específicas de Windows se ponen a disposición mediante la instalación del paquete de compatibilidad. Entre los ejemplos de estas características se incluyen el Registro, WMI y los servicios de directorio. El paquete agrega aproximadamente 20 000 API y activa muchos servicios con los que es posible que ya esté familiarizado. En el proyecto eShop no se necesita el paquete de compatibilidad; pero si en los proyectos se usan características específicas de Windows, el paquete facilita el trabajo de migración.
Habilitación del proceso de inicio
El proceso de inicio de Blazor ha cambiado con respecto a Web Forms y sigue una configuración similar a la de otros servicios de ASP.NET Core. Cuando se hospedan en el lado servidor, los componentes de Razor se ejecutan como parte de una aplicación normal de ASP.NET Core. Cuando se hospedan en el explorador con WebAssembly, los componentes de Razor utilizan un modelo de hospedaje similar. La diferencia es que los componentes se ejecutan como un servicio independiente de cualquiera de los procesos de back-end. En cualquier caso, el inicio es similar.
El archivo Global.asax.cs es la página de inicio predeterminada para los proyectos de Web Forms. En el proyecto eShop, este archivo configura el contenedor de inversión de control (IoC) y controla los distintos eventos de ciclo de vida de la aplicación o solicitud. Algunos de estos eventos se controlan con middleware (como Application_BeginRequest
). Otros eventos necesitan la invalidación de servicios concretos por medio de la inserción de dependencias (DI).
Por ejemplo, el archivo Global.asax.cs para eShop contiene el código siguiente:
public class Global : HttpApplication, IContainerProviderAccessor
{
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static IContainerProvider _containerProvider;
IContainer container;
public IContainerProvider ContainerProvider
{
get { return _containerProvider; }
}
protected void Application_Start(object sender, EventArgs e)
{
// Code that runs on app startup
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ConfigureContainer();
ConfigDataBase();
}
/// <summary>
/// Track the machine name and the start time for the session inside the current session
/// </summary>
protected void Session_Start(Object sender, EventArgs e)
{
HttpContext.Current.Session["MachineName"] = Environment.MachineName;
HttpContext.Current.Session["SessionStartTime"] = DateTime.Now;
}
/// <summary>
/// https://autofaccn.readthedocs.io/en/latest/integration/webforms.html
/// </summary>
private void ConfigureContainer()
{
var builder = new ContainerBuilder();
var mockData = bool.Parse(ConfigurationManager.AppSettings["UseMockData"]);
builder.RegisterModule(new ApplicationModule(mockData));
container = builder.Build();
_containerProvider = new ContainerProvider(container);
}
private void ConfigDataBase()
{
var mockData = bool.Parse(ConfigurationManager.AppSettings["UseMockData"]);
if (!mockData)
{
Database.SetInitializer<CatalogDBContext>(container.Resolve<CatalogDBInitializer>());
}
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
//set the property to our new object
LogicalThreadContext.Properties["activityid"] = new ActivityIdHelper();
LogicalThreadContext.Properties["requestinfo"] = new WebRequestInfo();
_log.Debug("Application_BeginRequest");
}
}
El archivo anterior se convierte en el archivo Program.cs en Blazor del lado del servidor:
using eShopOnBlazor.Models;
using eShopOnBlazor.Models.Infrastructure;
using eShopOnBlazor.Services;
using log4net;
using System.Data.Entity;
using eShopOnBlazor;
var builder = WebApplication.CreateBuilder(args);
// add services
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
if (builder.Configuration.GetValue<bool>("UseMockData"))
{
builder.Services.AddSingleton<ICatalogService, CatalogServiceMock>();
}
else
{
builder.Services.AddScoped<ICatalogService, CatalogService>();
builder.Services.AddScoped<IDatabaseInitializer<CatalogDBContext>, CatalogDBInitializer>();
builder.Services.AddSingleton<CatalogItemHiLoGenerator>();
builder.Services.AddScoped(_ => new CatalogDBContext(builder.Configuration.GetConnectionString("CatalogDBContext")));
}
var app = builder.Build();
new LoggerFactory().AddLog4Net("log4Net.xml");
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// Middleware for Application_BeginRequest
app.Use((ctx, next) =>
{
LogicalThreadContext.Properties["activityid"] = new ActivityIdHelper(ctx);
LogicalThreadContext.Properties["requestinfo"] = new WebRequestInfo(ctx);
return next();
});
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
ConfigDataBase(app);
void ConfigDataBase(IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.CreateScope())
{
var initializer = scope.ServiceProvider.GetService<IDatabaseInitializer<CatalogDBContext>>();
if (initializer != null)
{
Database.SetInitializer(initializer);
}
}
}
app.Run();
Un cambio importante que se aprecia con respecto a Web Forms es la importancia de la inserción de dependencias (ID). La inserción de dependencias ha sido un principio básico del diseño de ASP.NET Core. Admite la personalización de casi todos los aspectos del marco ASP.NET Core. Incluso hay un proveedor de servicios integrado que se puede usar para muchos escenarios. Si se necesita más personalización, se puede admitir con muchos proyectos de la comunidad. Por ejemplo, puede sacar adelante su inversión en bibliotecas de inserción de dependencias de terceros.
En la aplicación eShop original, hay una configuración para la administración de sesiones. Como en Blazor del lado servidor se usa SignalR de ASP.NET Core para la comunicación, no se admite el estado de sesión, ya que las conexiones pueden producirse independientemente de un contexto HTTP. Una aplicación que usa el estado de sesión se tiene que rediseñar antes de poder ejecutarse como una aplicación Blazor.
Para obtener más información sobre el inicio de la aplicación, vea Inicio de la aplicación.
Migración de módulos y controladores HTTP a middleware
Los módulos y controladores HTTP son patrones comunes en Web Forms para controlar la canalización de solicitudes HTTP. Las clases que implementan IHttpModule
o IHttpHandler
se pueden registrar y procesar solicitudes entrantes. En Web Forms, los módulos y controladores se configuran en el archivo web.config. En gran medida, Web Forms también se basa en el control de eventos del ciclo de vida de la aplicación. En su lugar, ASP.NET Core usa middleware. El middleware se registra en el método Configure
de la clase Startup
. El orden de ejecución de middleware se establece según el orden de registro.
En la sección Habilitación del proceso de inicio, Web Forms ha generado un evento de ciclo de vida como el método Application_BeginRequest
. Este evento no está disponible en ASP.NET Core. Una manera de lograr este comportamiento consiste en implementar el middleware como se ha visto en el ejemplo del archivo Startup.cs. Este middleware realiza la misma lógica y, después, transfiere el control al siguiente controlador en la canalización de middleware.
Para obtener más información sobre la migración de módulos y controladores, vea Migración de controladores y módulos HTTP a middleware de ASP.NET Core.
Migración de archivos estáticos
Para ofrecer archivos estáticos (por ejemplo, HTML, CSS, imágenes y JavaScript), el middleware debe exponer los archivos. La llamada al método UseStaticFiles
permite proporcionar archivos estáticos desde la ruta de acceso raíz web. El directorio raíz web predeterminado es wwwroot, pero se puede personalizar. Como se incluye en el archivo Program.cs:
...
app.UseStaticFiles();
...
El proyecto eShop permite el acceso a archivos estáticos básicos. Hay muchas personalizaciones disponibles para el acceso a los archivos estáticos. Para obtener información sobre cómo habilitar archivos predeterminados o un explorador de archivos, vea Archivos estáticos en ASP.NET Core.
Migración de la configuración de agrupación y minificación del entorno de ejecución
La agrupación y la minificación son técnicas de optimización del rendimiento para reducir el número y el tamaño de las solicitudes de servidor para recuperar determinados tipos de archivo. Normalmente, JavaScript y CSS se someten a algún tipo de agrupación o minificación antes de enviarse al cliente. En ASP.NET Web Forms, estas optimizaciones se controlan en tiempo de ejecución. Las convenciones de optimización se definen como un archivo App_Start/BundleConfig.cs. En ASP.NET Core se adopta un enfoque más declarativo. Los archivos que se van a minificar se enumeran en un archivo, junto con la configuración de minificación específica.
Para obtener más información sobre la agrupación y la minificación, vea Agrupación y minificación de recursos estáticos en ASP.NET Core.
Migración de páginas ASPX
Una página de una aplicación de Web Forms es un archivo con la extensión .aspx. Una página de Web Forms a menudo se puede asignar a un componente en Blazor. Un componente de Razor se crea en un archivo con la extensión .razor. Para el proyecto eShop, se convierten cinco páginas en una página de Razor.
Por ejemplo, la vista de detalles consta de tres archivos en el proyecto de Web Forms: Details.aspx, Details.aspx.cs y Details.aspx.designer.cs. Al realizar la conversión a Blazor, el código subyacente y el marcado se combinan en Details.razor. La compilación de Razor (equivalente al contenido de los archivos .designer.cs) se almacena en el directorio obj y, de forma predeterminada, no es visible en el Explorador de soluciones. La página de Web Forms consta del marcado siguiente:
<%@ Page Title="Details" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Details.aspx.cs" Inherits="eShopLegacyWebForms.Catalog.Details" %>
<asp:Content ID="Details" ContentPlaceHolderID="MainContent" runat="server">
<h2 class="esh-body-title">Details</h2>
<div class="container">
<div class="row">
<asp:Image runat="server" CssClass="col-md-6 esh-picture" ImageUrl='<%#"/Pics/" + product.PictureFileName%>' />
<dl class="col-md-6 dl-horizontal">
<dt>Name
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.Name%>' />
</dd>
<dt>Description
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.Description%>' />
</dd>
<dt>Brand
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.CatalogBrand.Brand%>' />
</dd>
<dt>Type
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.CatalogType.Type%>' />
</dd>
<dt>Price
</dt>
<dd>
<asp:Label CssClass="esh-price" runat="server" Text='<%#product.Price%>' />
</dd>
<dt>Picture name
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.PictureFileName%>' />
</dd>
<dt>Stock
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.AvailableStock%>' />
</dd>
<dt>Restock
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.RestockThreshold%>' />
</dd>
<dt>Max stock
</dt>
<dd>
<asp:Label runat="server" Text='<%#product.MaxStockThreshold%>' />
</dd>
</dl>
</div>
<div class="form-actions no-color esh-link-list">
<a runat="server" href='<%# GetRouteUrl("EditProductRoute", new {id =product.Id}) %>' class="esh-link-item">Edit
</a>
|
<a runat="server" href="~" class="esh-link-item">Back to list
</a>
</div>
</div>
</asp:Content>
El código subyacente del marcado anterior incluye el código siguiente:
using eShopLegacyWebForms.Models;
using eShopLegacyWebForms.Services;
using log4net;
using System;
using System.Web.UI;
namespace eShopLegacyWebForms.Catalog
{
public partial class Details : System.Web.UI.Page
{
private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
protected CatalogItem product;
public ICatalogService CatalogService { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
var productId = Convert.ToInt32(Page.RouteData.Values["id"]);
_log.Info($"Now loading... /Catalog/Details.aspx?id={productId}");
product = CatalogService.FindCatalogItem(productId);
this.DataBind();
}
}
}
Cuando se convierte a Blazor, la página de Web Forms se convierte al código siguiente:
@page "/Catalog/Details/{id:int}"
@inject ICatalogService CatalogService
@inject ILogger<Details> Logger
<h2 class="esh-body-title">Details</h2>
<div class="container">
<div class="row">
<img class="col-md-6 esh-picture" src="@($"/Pics/{_item.PictureFileName}")">
<dl class="col-md-6 dl-horizontal">
<dt>
Name
</dt>
<dd>
@_item.Name
</dd>
<dt>
Description
</dt>
<dd>
@_item.Description
</dd>
<dt>
Brand
</dt>
<dd>
@_item.CatalogBrand.Brand
</dd>
<dt>
Type
</dt>
<dd>
@_item.CatalogType.Type
</dd>
<dt>
Price
</dt>
<dd>
@_item.Price
</dd>
<dt>
Picture name
</dt>
<dd>
@_item.PictureFileName
</dd>
<dt>
Stock
</dt>
<dd>
@_item.AvailableStock
</dd>
<dt>
Restock
</dt>
<dd>
@_item.RestockThreshold
</dd>
<dt>
Max stock
</dt>
<dd>
@_item.MaxStockThreshold
</dd>
</dl>
</div>
<div class="form-actions no-color esh-link-list">
<a href="@($"/Catalog/Edit/{_item.Id}")" class="esh-link-item">
Edit
</a>
|
<a href="/" class="esh-link-item">
Back to list
</a>
</div>
</div>
@code {
private CatalogItem _item;
[Parameter]
public int Id { get; set; }
protected override void OnInitialized()
{
Logger.LogInformation("Now loading... /Catalog/Details/{Id}", Id);
_item = CatalogService.FindCatalogItem(Id);
}
}
Observe que el código y el marcado están en el mismo archivo. Los servicios necesarios se hacen accesibles con el atributo @inject
. Según la directiva @page
, se puede acceder a esta página desde la ruta Catalog/Details/{id}
. El valor del marcador de posición {id}
de la ruta se ha restringido a un entero. Como se describe en la sección Enrutamiento, a diferencia de Web Forms, un componente de Razor indica explícitamente su ruta y los parámetros que se incluyen. Es posible que muchos controles de Web Forms no tengan homólogos exactos en Blazor. A menudo hay un fragmento de código HTML equivalente que tendrá el mismo propósito. Por ejemplo, el control <asp:Label />
se puede reemplazar por un elemento <label>
HTML.
Validación de modelos en Blazor
Si el código de Web Forms incluye validación, puede transferir gran parte de lo que ya tiene sin apenas cambios. Una ventaja de realizar la ejecución en Blazor es que se puede ejecutar la misma lógica de validación sin necesidad de JavaScript personalizado. Las anotaciones de datos permiten la validación sencilla del modelo.
Por ejemplo, la página Create.aspx tiene un formulario de entrada de datos con validación. Un fragmento de ejemplo podría tener este aspecto:
<div class="form-group">
<label class="control-label col-md-2">Name</label>
<div class="col-md-3">
<asp:TextBox ID="Name" runat="server" CssClass="form-control"></asp:TextBox>
<asp:RequiredFieldValidator runat="server" ControlToValidate="Name" Display="Dynamic"
CssClass="field-validation-valid text-danger" ErrorMessage="The Name field is required." />
</div>
</div>
En Blazor, el marcado equivalente se proporciona en un archivo Create.razor:
<EditForm Model="_item" OnValidSubmit="@...">
<DataAnnotationsValidator />
<div class="form-group">
<label class="control-label col-md-2">Name</label>
<div class="col-md-3">
<InputText class="form-control" @bind-Value="_item.Name" />
<ValidationMessage For="(() => _item.Name)" />
</div>
</div>
...
</EditForm>
El contexto EditForm
incluye compatibilidad con la validación y se puede encapsular en torno a una entrada. Las anotaciones de datos son una manera común de agregar validación. Esta compatibilidad de validación se puede agregar por medio del componente DataAnnotationsValidator
. Para obtener más información sobre este mecanismo, vea Formularios y validación de Blazor de ASP.NET Core.
Migración de la configuración
En un proyecto de Web Forms, los datos de configuración se almacenan normalmente en el archivo web.config. A los datos de configuración se accede con ConfigurationManager
. Los servicios normalmente tenían que analizar objetos. Con .NET Framework 4.7.2, se agregó capacidad de composición a la configuración por medio de ConfigurationBuilders
. Estos generadores permitían a los desarrolladores agregar varios orígenes para la configuración que, después, se componía en tiempo de ejecución para recuperar los valores necesarios.
En ASP.NET Core se presentó un sistema de configuración flexible que le permite definir el origen (u orígenes) de configuración que usa la aplicación y la implementación. La infraestructura de ConfigurationBuilder
que puede usar en la aplicación de Web Forms se modelaba en función de los conceptos usados en el sistema de configuración de ASP.NET Core.
En el fragmento de código siguiente se muestra cómo el proyecto eShop de Web Forms usa web.config para almacenar los valores de configuración:
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="CatalogDBContext" connectionString="Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb; Integrated Security=True; MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="UseMockData" value="true" />
<add key="UseCustomizationData" value="false" />
</appSettings>
</configuration>
Es habitual que los secretos, como las cadenas de conexión de base de datos, se almacenen en web.config. Los secretos se conservan inevitablemente en ubicaciones no seguras, como el control de código fuente. Con Blazor en ASP.NET Core, la configuración basada en XML anterior se reemplaza por el código JSON siguiente:
{
"ConnectionStrings": {
"CatalogDBContext": "Data Source=(localdb)\\MSSQLLocalDB; Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb; Integrated Security=True; MultipleActiveResultSets=True;"
},
"UseMockData": true,
"UseCustomizationData": false
}
JSON es el formato de configuración predeterminado; pero, ASP.NET Core admite muchos otros formatos, incluido XML. También hay varios formatos admitidos por la comunidad.
Puede acceder a valores de configuración desde el generador en Program.cs:
if (builder.Configuration.GetValue<bool>("UseMockData"))
{
builder.Services.AddSingleton<ICatalogService, CatalogServiceMock>();
}
else
{
builder.Services.AddScoped<ICatalogService, CatalogService>();
builder.Services.AddScoped<IDatabaseInitializer<CatalogDBContext>, CatalogDBInitializer>();
builder.Services.AddSingleton<CatalogItemHiLoGenerator>();
builder.Services.AddScoped(_ => new CatalogDBContext(builder.Configuration.GetConnectionString("CatalogDBContext")));
}
De forma predeterminada, las variables de entorno, los archivos JSON (appsettings.json yappsettings.{Entorno}.json) y las opciones de línea de comandos se registran como orígenes de configuración válidos en el objeto de configuración. Se puede acceder a los orígenes de configuración por medio de Configuration[key]
. Una técnica más avanzada consiste en enlazar los datos de configuración a los objetos mediante el patrón de opciones. Para obtener más información sobre la configuración y el patrón de opciones, vea Configuración en ASP.NET Core y Patrón de opciones en ASP.NET Core, respectivamente.
Migración del acceso a datos
El acceso a datos es un aspecto importante de cualquier aplicación. El proyecto eShop almacena información de catálogo en una base de datos y recupera los datos con Entity Framework (EF) 6. Como EF 6 es compatible con .NET 5, se puede seguir usando en el proyecto.
Los siguientes cambios relacionados con EF han sido necesarios para eShop:
- En .NET Framework, el objeto
DbContext
acepta una cadena con el formato name=ConnectionString y usa la cadena de conexión deConfigurationManager.AppSettings[ConnectionString]
para conectarse. En .NET Core esto no se admite. Se debe proporcionar la cadena de conexión. - Se ha accedido a la base de datos de forma sincrónica. Aunque esto funcione, la escalabilidad puede verse afectada. Esta lógica se debe trasladar a un patrón asincrónico.
Aunque no hay la misma compatibilidad nativa para el enlace de conjuntos de datos, Blazor proporciona flexibilidad y capacidad con su compatibilidad con C# en una página de Razor. Por ejemplo, puede realizar cálculos y mostrar el resultado. Para obtener más información sobre los patrones de datos en Blazor, vea el capítulo Acceso a datos.
Cambios de arquitectura
Por último, hay algunas diferencias arquitectónicas importantes que tener en cuenta a la hora de realizar la migración a Blazor. Muchos de estos cambios se aplican a cualquier elemento basado en .NET Core o en ASP.NET Core.
Como Blazor se basa en .NET Core, existen consideraciones a la hora de garantizar la compatibilidad en .NET Core. Algunos de los cambios principales incluyen la eliminación de las características siguientes:
- Varios dominios de aplicación
- Comunicación remota
- Seguridad de acceso del código (CAS)
- Transparencia de seguridad
Para obtener más información sobre las técnicas de identificación de los cambios necesarios para admitir la ejecución en .NET Core, vea Portabilidad del código de .NET Framework a .NET Core.
ASP.NET Core es una versión "reimaginada" de ASP.NET y tiene algunos cambios que es posible que no parezcan inicialmente obvios. Los cambios principales son los siguientes:
- No existe un contexto de sincronización, lo que significa que no hay
HttpContext.Current
,Thread.CurrentPrincipal
, ni otros descriptores de acceso estáticos - No se crean instantáneas
- No hay una cola de solicitudes
Muchas operaciones de ASP.NET Core son asincrónicas, lo que permite una descarga más sencilla de las tareas enlazadas a E/S. Es importante no realizar bloqueos mediante Task.Wait()
o Task.GetResult()
, lo que puede agotar rápidamente los recursos del grupo de subprocesos.
Conclusión de la migración
Llegados a este punto, ha visto muchos ejemplos de lo que se necesita para trasladar un proyecto de Web Forms a Blazor. Para obtener un ejemplo completo, consulte el proyecto eShopOnBlazor.