팁
이 콘텐츠는 Blazor 또는 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공되는 for ASP NET Web Forms Developers for Azure eBook에서 발췌한 것입니다.
ASP.NET Web Forms에서 Blazor로 코드베이스를 마이그레이션하는 작업은 계획이 필요한 시간이 많이 걸리는 작업입니다. 이 장에서는 프로세스를 간략하게 설명합니다. 앱이 ‘N 계층’ 아키텍처를 준수하도록 하면 쉽게 전환할 수 있으므로 앱 모델(이 경우 Web Forms)은 비즈니스 논리와는 별개입니다. 계층의 이 논리적 분리를 통해 .NET Core 및 Blazor로 이동하는 데 필요한 항목이 분명해집니다.
이 예제에서는 GitHub에서 사용할 수 있는 eShop 앱이 사용됩니다. eShop은 양식 항목 및 유효성 검사를 통해 CRUD 기능을 제공하는 카탈로그 서비스입니다.
작업 앱을 Blazor로 마이그레이션해야 하는 이유는 무엇인가요? 많은 시간이 필요하지 않습니다. ASP.NET Web Forms는 수년 동안 계속 지원될 예정입니다. 그러나 Blazor가 제공하는 많은 기능은 마이그레이션된 앱에서만 지원됩니다. 해당 기능은 다음을 포함합니다.
-
Span<T>와 같은 프레임워크의 성능 향상 - WebAssembly로 실행하는 기능
- Linux 및 macOS에 대한 플랫폼 간 지원
- 다른 앱에 영향을 주지 않고 앱 로컬 배포 또는 공유 프레임워크 배포
해당 기능이나 다른 새로운 기능이 충분히 매력적인 경우 앱을 마이그레이션할 때 가치가 있을 수 있습니다. 마이그레이션은 여러 가지 셰이프를 사용할 수 있습니다. 전체 앱이거나 단지 변경 내용이 필요한 특정 엔드포인트일 수 있습니다. 마이그레이션하도록 결정하는 것은 궁극적으로 개발자가 해결할 비즈니스 문제에 따라 달라집니다.
서버 쪽 및 클라이언트 쪽 호스팅
호스팅 모델 장에 설명된 대로 Blazor 앱은 서버 쪽 및 클라이언트 쪽의 두 가지 방법으로 호스트될 수 있습니다. 서버 쪽 모델에서는 ASP.NET Core SignalR 연결을 사용하여 서버에서 실제 코드를 실행하는 동안 DOM 업데이트를 관리합니다. 클라이언트 쪽 모델은 브라우저 내에서 WebAssembly로 실행되며 서버 연결이 필요하지 않습니다. 특정 앱에 가장 적합한 것에 영향을 줄 수 있는 많은 차이점이 있습니다.
- WebAssembly로 실행하면 현재는 일부 기능(예: 스레딩)이 지원되지 않습니다.
- 클라이언트와 서버 간 과도한 통신으로 인해 서버 쪽 모드에서 대기 시간 문제가 발생할 수 있습니다.
- 데이터베이스와 내부 또는 보호된 서비스에 대한 액세스에는 클라이언트 쪽 호스팅을 사용하는 별도의 서비스가 필요합니다.
작성 시점에 서버 쪽 모델은 Web Forms와 더 많이 유사합니다. 이 장의 대부분은 프로덕션이 준비되었을 때 서버 쪽 호스팅 모델에 초점을 맞춥니다.
새 프로젝트 만들기
이 초기 마이그레이션 단계는 새 프로젝트를 만드는 것입니다. 이 프로젝트 형식은 .NET의 SDK 스타일 프로젝트를 기반으로 하며 이전 프로젝트 형식에서 사용된 많은 상용구를 간소화합니다. 자세한 내용은 프로젝트 구조에 관한 장을 참조하세요.
프로젝트가 생성된 후 이전 프로젝트에서 사용된 라이브러리를 설치합니다. 이전 Web Forms 프로젝트에서는 packages.config 파일을 사용하여 필요한 NuGet 패키지를 나열했을 수 있습니다. 새 SDK 스타일 프로젝트에서 packages.config는 프로젝트 파일의 <PackageReference> 요소로 바뀌었습니다. 이 접근 방식의 이점은 모든 종속성이 타동적으로 설치된다는 것입니다. 관심 있는 최상위 종속성만 나열합니다.
사용 중인 많은 종속성은 Entity Framework 6 및 log4net을 포함하여 .NET에 사용할 수 있습니다. 사용할 수 있는 .NET 또는 .NET Standard 버전이 없는 경우에는 대개 .NET Framework 버전을 사용할 수 있습니다. 진행 정도는 달라질 수 있습니다. .NET에서 사용할 수 없는 API를 사용하면 런타임 오류가 발생합니다. Visual Studio에서 해당 패키지에 관해 알려 줍니다. 솔루션 탐색기에서 프로젝트의 참조 노드에 노란색 아이콘이 표시됩니다.
Blazor 기반 eShop 프로젝트에서 설치된 패키지를 확인할 수 있습니다. 이전에 packages.config 파일에는 프로젝트에 사용되는 모든 패키지가 나열되어 파일 길이가 거의 50줄이 되었습니다. packages.config 조각은 다음과 같습니다.
<?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>
<packages> 요소에는 필요한 모든 종속성이 포함됩니다. 해당 패키지가 필요하기 때문에 그 중 포함된 패키지를 식별하기가 어렵습니다. 일부 <package> 요소는 단순히 필요한 종속성의 요구 사항을 충족하기 위해 나열됩니다.
Blazor 프로젝트에는 프로젝트 파일의 <ItemGroup> 요소 내에서 필요한 종속성이 나열됩니다.
<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>
Web Forms 개발자의 일상생활을 간단하게 만드는 하나의 NuGet 패키지는 Windows 호환 기능 팩입니다. .NET은 플랫폼 간 기능이지만 일부 기능은 Windows에서만 사용할 수 있습니다. Windows 관련 기능은 호환 기능 팩을 설치하여 사용할 수 있습니다. 해당 기능의 예로는 레지스트리, WMI, 디렉터리 서비스가 있습니다. 해당 패키지는 약 20,000개의 API를 추가하고 이미 친숙할 수 있는 많은 서비스를 활성화합니다. eShop 프로젝트에는 호환 기능 팩이 필요하지 않습니다. 그러나 프로젝트에서 Windows 관련 기능을 사용하는 경우 패키지는 마이그레이션 작업에 도움이 됩니다.
시작 프로세스 사용
Blazor의 시작 프로세스는 Web Forms에서 변경되었으며 다른 ASP.NET Core 서비스의 유사한 설정을 따릅니다. 서버 쪽에서 호스트되는 경우 Razor 구성 요소는 일반 ASP.NET Core 앱의 일부로 실행됩니다. 브라우저에서 WebAssembly를 사용하여 호스트되는 경우 Razor 구성 요소는 유사한 호스팅 모델을 사용합니다. 차이점은 구성 요소가 백 엔드 프로세스에서 별도 서비스로 실행되는 것입니다. 어느 쪽이든 시작은 유사합니다.
Global.asax.cs 파일은 Web Forms 프로젝트의 기본 시작 페이지입니다. eShop 프로젝트에서 해당 파일은 IoC(Inversion of Control) 컨테이너를 구성하고 앱 또는 요청의 다양한 수명 주기 이벤트를 처리합니다. 해당 이벤트 중 일부는 미들웨어(예: Application_BeginRequest)를 사용하여 처리됩니다. 다른 이벤트의 경우 DI(종속성 주입)를 통해 특정 서비스를 재정의해야 합니다.
예를 들어 eShop의 Global.asax.cs 파일에는 다음 코드가 포함됩니다.
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");
}
}
이전 파일이 서버 쪽 의 Blazor 파일이 됩니다.
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();
Web Forms에서 확인할 수 있는 한 가지 중요한 변경 내용은 DI(종속성 주입)의 중요성입니다. DI는 ASP.NET Core 디자인의 가이드 원칙이었습니다. DI에서는 ASP.NET Core 프레임워크의 거의 모든 측면에 대한 사용자 지정을 지원합니다. 많은 시나리오에 사용할 수 있는 기본 제공 서비스 공급자도 있습니다. 더 많은 사용자 지정이 필요한 경우 많은 커뮤니티 프로젝트에서 지원될 수 있습니다. 예를 들어 타사 DI 라이브러리 투자를 이월할 수 있습니다.
원래 eShop 앱에는 세션 관리를 위한 몇 가지 구성이 있습니다. 서버 쪽 Blazor는 통신에 ASP.NET Core SignalR을 사용하기 때문에 연결이 HTTP 컨텍스트와 독립적으로 발생할 수 있는 동안에는 세션 상태가 지원되지 않습니다. 세션 상태를 사용하는 앱은 Blazor 앱으로 실행하기 전에 다시 설계해야 합니다.
앱 시작에 관한 자세한 내용은 앱 시작을 참조하세요.
HTTP 모듈 및 처리기를 미들웨어로 마이그레이션
HTTP 모듈 및 처리기는 HTTP 요청 파이프라인을 제어하기 위한 Web Forms의 일반적인 패턴입니다.
IHttpModule 또는 IHttpHandler를 구현하는 클래스는 등록되며 들어오는 요청을 처리할 수 있습니다. Web Forms는 web.config 파일에서 모듈 및 처리기를 구성합니다. 또한 Web Forms는 앱 수명 주기 이벤트 처리에 따라 크게 달라집니다. ASP.NET Core는 미들웨어를 대신 사용합니다. 미들웨어는 Configure 클래스의 Startup 메서드에 등록됩니다. 미들웨어 실행 순서는 등록 순서로 결정됩니다.
시작 프로세스 사용 섹션에서 Web Forms는 수명 주기 이벤트를 Application_BeginRequest 메서드로 발생시켰습니다. 해당 이벤트는 ASP.NET Core에서 사용할 수 없습니다. 이 동작을 수행하는 한 가지 방법은 Startup.cs 파일 예제에 표시된 대로 미들웨어를 구현하는 것입니다. 해당 미들웨어는 동일한 논리를 수행한 다음, 미들웨어 파이프라인의 다음 처리기로 제어를 전달합니다.
모듈 및 처리기를 마이그레이션하는 방법에 관한 자세한 내용은 HTTP 처리기 및 모듈을 ASP.NET Core 미들웨어로 마이그레이션을 참조하세요.
정적 파일 마이그레이션
정적 파일(예: HTML, CSS, 이미지, JavaScript)을 제공하려면 미들웨어가 파일을 공개해야 합니다.
UseStaticFiles 메서드를 호출하면 웹 루트 경로에서 정적 파일을 제공할 수 있습니다. 기본 웹 루트 디렉터리는 wwwroot이지만 사용자 지정할 수 있습니다.
Program.cs 파일에 포함되어 있습니다.
...
app.UseStaticFiles();
...
eShop 프로젝트에서는 기본 정적 파일 액세스를 사용할 수 있습니다. 정적 파일 액세스에 사용할 수 있는 많은 사용자 지정이 있습니다. 기본 파일 또는 파일 브라우저 사용에 관한 자세한 내용은 ASP.NET Core의 정적 파일을 참조하세요.
런타임 묶음 및 축소 설정 마이그레이션
묶음 및 축소는 특정 파일 형식을 검색하는 서버 요청의 수와 크기를 줄이기 위한 성능 최적화 기술입니다. JavaScript 및 CSS는 대개 클라이언트에 전송되기 전에 몇 가지 형태의 묶음 또는 축소를 수행합니다. ASP.NET Web Forms에서는 이러한 최적화가 런타임에 처리됩니다. 최적화 규칙은 App_Start/BundleConfig.cs 파일에서 정의됩니다. ASP.NET Core에는 더 선언적인 접근 방식이 채택됩니다. 파일에는 특정 축소 설정과 함께 축소되는 파일이 나열됩니다.
묶음 및 축소에 관한 자세한 내용은 ASP.NET Core에서 정적 자산 묶음 및 축소를 참조하세요.
ASPX 페이지 마이그레이션
Web Forms 앱의 페이지는 .aspx 확장명을 사용하는 파일입니다. Web Forms 페이지는 일반적으로 Blazor 구성 요소에 매핑될 수 있습니다. Razor 구성 요소는 .razor 확장명을 사용하는 파일에서 작성됩니다. eShop 프로젝트의 경우 5개 페이지가 Razor 페이지로 변환됩니다.
예를 들어 세부 정보 뷰는 Web Forms 프로젝트의 세 개 파일인 Details.aspx, Details.aspx.cs, Details.aspx.designer.cs로 구성됩니다. Blazor로 변환할 때 코드 숨김 및 태그가 Details.razor로 결합됩니다. Razor 컴파일(.designer.cs 파일의 콘텐츠와 동일)은 obj 디렉터리에 저장되며 기본적으로 솔루션 탐색기에서 볼 수 없습니다. Web Forms 페이지는 다음 태그로 구성됩니다.
<%@ 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>
이전 태그의 코드 숨김에는 다음 코드가 포함됩니다.
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();
}
}
}
Blazor로 변환되는 경우 Web Forms 페이지는 다음 코드로 변환됩니다.
@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);
}
}
코드와 태그는 동일한 파일에 있습니다. 모든 필수 서비스는 @inject 특성을 사용하여 액세스 가능하도록 설정합니다.
@page 지시문에 따라 이 페이지는 Catalog/Details/{id} 경로에서 액세스할 수 있습니다. 경로의 {id} 자리 표시자 값은 정수로 제한되었습니다.
라우팅 섹션에 설명된 대로 Razor 구성 요소는 Web Forms와 달리 해당 경로 및 포함된 모든 매개 변수를 명시적으로 나타냅니다. 많은 Web Forms 컨트롤은 Blazor에서 정확히 일치하는 항목이 없을 수 있습니다. 보통은 동일한 용도로 제공될 동일한 HTML 조각이 있습니다. 예를 들어 <asp:Label /> 컨트롤은 HTML <label> 요소로 바뀔 수 있습니다.
Blazor의 모델 유효성 검사
Web Forms 코드에 유효성 검사가 포함된 경우 거의 변경하지 않고도 사용하는 많은 항목을 전송할 수 있습니다. Blazor에서 실행하는 경우의 이점은 사용자 지정 JavaScript가 없어도 동일한 유효성 검사 논리를 실행할 수 있다는 것입니다. 데이터 주석을 사용하면 모델의 유효성을 쉽게 검사할 수 있습니다.
예를 들어 Create.aspx 페이지에는 유효성 검사가 포함된 데이터 입력 양식이 있습니다. 예제 조각은 다음과 같이 표시됩니다.
<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>
Blazor에서 동일한 태그는 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>
EditForm 컨텍스트는 유효성 검사 지원을 포함하며 입력을 래핑할 수 있습니다. 데이터 주석은 유효성 검사를 추가하는 일반적인 방법입니다. 해당 유효성 검사 지원은 DataAnnotationsValidator 구성 요소를 통해 추가할 수 있습니다. 이 메커니즘에 관한 자세한 내용은 ASP.NET Core Blazor 양식 및 유효성 검사를 참조하세요.
구성 마이그레이션
Web Forms 프로젝트에서 구성 데이터는 가장 일반적으로 web.config 파일에 저장됩니다. 구성 데이터는 ConfigurationManager를 사용하여 액세스합니다. 개체를 구문 분석하려면 종종 서비스가 필요했습니다. .NET Framework 4.7.2에서는 ConfigurationBuilders를 통해 작성 가능성이 구성에 추가되었습니다. 해당 작성기를 통해 개발자는 런타임에 작성된 구성의 여러 소스를 추가하여 필요한 값을 가져올 수 있습니다.
ASP.NET Core에는 구성 소스 또는 앱 및 배포에 사용되는 소스를 정의할 수 있는 유연한 구성 시스템이 도입되었습니다. Web Forms 앱에서 사용할 수 있는 ConfigurationBuilder 인프라는 ASP.NET Core 구성 시스템에서 사용되는 개념에 따라 모델링되었습니다.
다음 조각에서는 Web Forms eShop 프로젝트가 web.config를 사용하여 구성 값을 저장하는 방법을 보여 줍니다.
<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>
데이터베이스 연결 문자열과 같은 비밀은 web.config 내에 저장되는 것이 일반적입니다. 비밀은 불가피하게 소스 제어와 같은 안전하지 않은 위치에서 유지됩니다. ASP.NET Core에서 Blazor를 사용하면 이전 XML 기반 구성이 다음 JSON으로 바뀝니다.
{
"ConnectionStrings": {
"CatalogDBContext": "Data Source=(localdb)\\MSSQLLocalDB; Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb; Integrated Security=True; MultipleActiveResultSets=True;"
},
"UseMockData": true,
"UseCustomizationData": false
}
JSON은 기본 구성 형식입니다. 그러나 ASP.NET Core는 XML을 비롯한 다른 많은 형식을 지원합니다. 또한 여러 가지 커뮤니티 지원 형식이 있습니다.
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")));
}
기본적으로 환경 변수, JSON 파일(appsettings.json 및 appsettings.{Environment}.json) 및 명령줄 옵션은 구성 개체에 유효한 구성 소스로 등록됩니다. 구성 소스는 Configuration[key]를 통해 액세스할 수 있습니다. 고급 기술은 옵션 패턴을 사용하여 구성 데이터를 개체에 바인딩하는 것입니다. 구성 및 옵션 패턴에 관한 자세한 내용은 각각 ASP.NET Core의 구성 및 ASP.NET Core의 옵션 패턴을 참조하세요.
데이터 액세스 마이그레이션
데이터 액세스는 모든 앱의 중요한 측면입니다. eShop 프로젝트는 카탈로그 정보를 데이터베이스에 저장하고 EF(Entity Framework) 6을 사용하여 데이터를 검색합니다. EF 6은 .NET 5에서 지원되기 때문에 프로젝트에서 계속 사용할 수 있습니다.
eShop에는 다음 EF 관련 변경이 필요했습니다.
- .NET Framework에서
DbContext개체는 name=ConnectionString 형식의 문자열을 수락하고ConfigurationManager.AppSettings[ConnectionString]의 연결 문자열을 사용하여 연결합니다. .NET Core에서는 이 기능이 지원되지 않습니다. 연결 문자열을 제공해야 합니다. - 동기 방식으로 데이터베이스에 액세스했습니다. 이 방법은 효과가 있지만 스케일링 성능이 저하될 수 있습니다. 이 논리는 비동기 패턴으로 이동해야 합니다.
데이터 세트 바인딩에 대한 동일한 기본 지원은 없지만 Blazor는 Razor 페이지에서 C# 지원을 통해 유연성과 성능을 제공합니다. 예를 들어 계산하고 결과를 표시할 수 있습니다. Blazor의 데이터 패턴에 관한 자세한 내용은 데이터 액세스 장을 참조하세요.
아키텍처 자체가 변경
마지막으로, Blazor로 마이그레이션할 때 고려해야 할 몇 가지 중요한 아키텍처 차이점이 있습니다. 이와 같은 많은 변경 내용은 .NET Core 또는 ASP.NET Core를 기반으로 하는 항목에 적용할 수 있습니다.
Blazor는 .NET Core를 기반으로 빌드되기 때문에 .NET Core에 대한 지원을 보장하기 위해 고려해야 할 사항이 있습니다. 일부 주요 변경 내용에는 다음 기능의 제거가 포함됩니다.
- 여러 AppDomain
- 원격 통신
- CAS(코드 액세스 보안)
- 보안 투명도
.NET Core에서 실행을 지원하는 데 필요한 변경 내용을 식별하는 기술에 관한 자세한 내용은 .NET Framework에서 .NET Core로 코드 이동을 참조하세요.
ASP.NET Core는 ASP.NET의 이미지로 재설치되는 버전이며 처음에는 분명하지 않은 것 같은 몇 가지 변경 내용이 있습니다. 주요 변경 내용은 다음과 같습니다.
- 동기화 컨텍스트가 없습니다. 즉,
HttpContext.Current,Thread.CurrentPrincipal또는 기타 정적 접근자가 없음 - 섀도 복사 없음
- 요청 큐 없음
ASP.NET Core의 많은 작업은 비동기 작업이므로 I/O 바인딩된 작업을 더 쉽게 오프로드할 수 있습니다. 스레드 풀 리소스를 빠르게 모두 사용할 수 있는 Task.Wait() 또는 Task.GetResult()를 사용하여 차단하지 않는 것이 중요합니다.
마이그레이션 결론
이제 Web Forms 프로젝트를 Blazor로 이동하는 데 사용하는 항목에 관한 많은 예제를 살펴보았습니다. 전체 예제를 보려면 eShopOnBlazor 프로젝트를 참조하세요.
.NET