Search
Duplicate

ASP.NET Core/ 웹 개발 및 배포 환경 구축/ 3. ASP.Net Core + Blazor WebAssembly

프로젝트 생성

Visual Studio에서 새 프로젝트 만들기를 하고 Blazor WebAssembly 앱을 선택한다.
추가 정보에서 인증 유형은 개별 계정을 선택하고, ASP.NET Core 호스팅을 선택한다. (HTTPS에 대한 구성을 선택하면 알아서 https를 지원하고, 프로그레시브 웹 어플리케이션을 선택하면 PWA 도 빌드할 수 있다.)
ASP.Net Core 호스팅을 선택하면 아래와 같이 Client, Server로 나뉜 프로젝트를 만들어 준다.
이렇게 구성된 프로젝트를 빌드한 후에 프로젝트 폴더를 열어보면, Server 프로젝트가 Client(Blazor앱)을 DLL로 갖고 있음을 알 수 있다. 이는 사용자가 서버에 접속하면 서버는 클라이언트(Blazor)를 을 사용자에게 보내주는 식으로 되어 있고, 클라이언트는 자신을 감싸는 서버에 의존되어 동작한다는 것을 의미한다.
사용자가 클라이언트에 접속하면 클라이언트가 내부적으로 서버에 다시 요청하는 방식과는 다른 것이다.
여튼 이 프로젝트를 실행하면 바로 다음과 같은 브라우저 결과가 뜬다. 참고로 우측 상단의 Log In 버튼은 프로젝트 생성시에 인증 유형을 선택하지 않으면 만들어지지 않는다.
여기서 Fetch Data를 선택하면 아래와 같은 에러 화면이 뜬다. 이는 ASP Core의 기본 DB가 MS-SQL로 잡혀 있기 때문이다. 우선 이것을 바꾸자.

프로젝트 DB 설정 + 사용자 관리

우선 HeidiSQL을 통해 프로젝트에서 사용할 데이터베이스를 생성한다. (여기서는 testblazor)
다음으로 Server 프로젝트의 NuGet 패키지 관리자 메뉴에 들어가서 .NET의 MySQL EntityFramework인 Pomelo.EntityFrameworkCore.MySql를 찾아서 설치한다. (당연하지만 DB는 서버에서 접속하므로 Client 프로젝트에서는 받을 필요 없다)
패키지를 설치했으면 Server 프로젝트의 appsetting.json을 열어서 ConnectionString을 MariaDB용으로 변경한다.
"ConnectionStrings": { "DefaultConnection": "Server=(DB가 있는 IP주소); port=3306; database=(database이름); user=(사용자이름); password=(password); 1. `Persist Security Info=False; Connect Timeout=300" },
JSON
복사
다음으로 Program.cs에서 SqlServer로 되어 있던 것을 MySQL용으로 바꾼다.
// 최초로 자동 세팅되는 SqlServer 버전 //builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); // MariaDB 버전 builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
JSON
복사
소스를 수정한 후에 코드 상에서 DB를 연결하기 위해 migration을 업데이트해줘야 한다. 우선 기존에 SqlServer용으로 만들어져 있던 Migration 폴더를 통채로 지운다.
다음으로 도구 → NuGet 패키지 관리자 → 패키지 관리자 콘솔을 띄우고
다음의 명령을 입력한다.
Add-Migration DBInit
Shell
복사
이러면 migration 폴더를 다시 생성한다.
이 다음에 다시 패키지 관리자 콘솔에서 다음의 명령을 입력하면 DB에 필요한 테이블을 자동으로 생성한다.
Update-Database
Shell
복사
이때 자동으로 생성되는 테이블은 DbContext를 상속받는 클래스들이다. 실제 테이블 생성하는 코드는 Migrations 폴더의 DBInit.cs 파일을 보면 확인할 수 있음. 프로그램 개발 중에 DB 스키마가 바뀌면 Update-Database를 다시 해주면 된다.
생성이 끝난 후에 DB를 다시 확인해 보면 사용자 계정 관리를 위한 테이블들이 자동 생성되어 있음을 확인할 수 있다. —바로 안 보이면 새로고침
이제 다시 프로젝트를 실행하면 아까 에러 나던 Fetch Data에 에러가 없어졌음을 알 수 있다. 현재 계정이 없기 때문에 자동으로 Log In 화면으로 넘어간다.
기본적인 계정 등록, 인증 등의 기능이 갖춰져 있기 때문에 사용자 관리 기능을 따로 구현할 필요가 없다. 사용자 관리 기능이 핵심인 프로젝트는 거의 없을 것이므로 기본으로 제공되는 기능에 프로젝트에 따라 필요한 추가 정보를 이용하여 구현하는 것을 추천 —직접 구현하려면 은근히 신경 써야 할 것이 많다. 바퀴를 다시 만들지 마라.

테스트 프로젝트

자동 빌드를 할 때 테스트 프로젝트가 있으면 검증이 수월하기 때문에 테스트 프로젝트를 구성한다. 순수 로직인 서버(back-end)와 UI가 포함된 클라이언트(front-end)는 테스트 프로젝트를 구성할 때도 약간 차이가 있다.

서버용 테스트 프로젝트

서버 테스트를 위해 새 프로젝트를 추가하고 xUnit 프로젝트를 추가한다.
추가한 테스트 프로젝트에 Server 프로젝트 참조를 추가한다. (프로젝트 → 종속성 → 서버 프로젝트 추가)
추가한 프로젝트에 서버의 기본 Controller를 테스트 하기 위한 아래 코드를 추가한다. —Controller를 테스트하려면 Logger가 필요한데, 그것은 아래와 같이 NullLoggerFactory를 통해 테스트용 Logger를 만들 수 있다.
using BlazorApp.Server.Controllers; using BlazorApp.Shared; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System.Collections.Generic; using System.Linq; using Xunit; namespace BlazorApp.Server.Test { public class WeatherForecastControllerTest { WeatherForecastController controller; public WeatherForecastControllerTest() { ILoggerFactory loggerFactory = new NullLoggerFactory(); this.controller = new WeatherForecastController(loggerFactory.CreateLogger<WeatherForecastController>()); } [Fact] public void GetTest() { List<WeatherForecast> weatherForecasts = this.controller.Get().ToList(); Assert.Equal(5, weatherForecasts.Count); } } }
C#
복사
테스트 탐색기에서 테스트가 통과하는지 확인하면 끝.

블레이저용 테스트 프로젝트

클라이언트 프로젝트를 테스트하기 위해 서버와 마찬가지로 새 프로젝트 추가에서 xUnit 프로젝트를 추가하고, 클라이언트 프로젝트를 참조에 추가한다.
UI 테스트를 위해 추가로 bUnit 패키지를 설치해야 한다. 클라이언트 테스트 프로젝트의 NuGet 패키지 관리를 통해 bnit.web 패키지를 설치한다.
추가한 프로젝트에 클라이언트의 counter를 테스트 하기 위해 아래의 코드를 추가한다. —bUnit의 TestContext를 이용해서 UI 구성요소를 찾고 UI 요소에 event를 call한다.
using BlazorApp.Client.Pages; using Bunit; using Xunit; namespace BlazorApp.Client.Test { public class CounterTest { [Fact] public void IncrementCountTest() { using var ctx = new TestContext(); var cut = ctx.RenderComponent<Counter>(); var paraElm = cut.Find("p"); // Act cut.Find("button").Click(); var paraElmText = paraElm.TextContent; // Assert paraElmText.MarkupMatches("Current count: 1"); } } }
C#
복사
만일 테스트 메서드가 많은 경우 TestContext를 만드는게 귀찮으니 아예 아래 코드처럼 class가 TestContext를 상속 받는 식으로 구현해도 된다.
using BlazorApp.Client.Pages; using Bunit; using Xunit; public class CounterTest : TestContext { [Fact] public void IncrementCountTest() { var cut = RenderComponent<Counter>(); var paraElm = cut.Find("p"); // Act cut.Find("button").Click(); var paraElmText = paraElm.TextContent; // Assert paraElmText.MarkupMatches("Current count: 1"); } }
C#
복사
테스트 탐색기에서 테스트가 통과하면 끝.

Docker 빌드

마지막으로 프로젝트를 도커로 실행하기 위해 Server 프로젝트에 Docker 지원을 추가한다. —플랫폼은 원하는 것을 알맞게 선택.
추가를 하면 자동적으로 Docker 관련 빌드를 시작하고 Server 프로젝트에 DockerFile이 생성된다.
Docker Build를 위한 이 파일은 다음과 같은 내용을 담고 있다.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["Server/BlazorApp.Server.csproj", "Server/"] COPY ["Shared/BlazorApp.Shared.csproj", "Shared/"] COPY ["Client/BlazorApp.Client.csproj", "Client/"] RUN dotnet restore "Server/BlazorApp.Server.csproj" COPY . . WORKDIR "/src/Server" RUN dotnet build "BlazorApp.Server.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "BlazorApp.Server.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "BlazorApp.Server.dll"]
Docker
복사
빌드를 실행하면 Visual Studio에서 알아서 Docker Image를 빌드하고, Container를 실행하는 것까지 해준다.
이때 출력창을 열어 확인해 보면 아래와 같은 커맨드로 Docker 빌드를 실행하고 있음을 알 수 있는데, Debug와 Release의 커맨드가 약간 다르다. —Debug용에는 Debug를 위한 옵션이 추가됨.
이 커맨드는 이후 Jenkins에서 Docker Build 할 때 사용해야 하므로 잘 기억해 두자.
[Debug] // docker image 빌드 docker build -f "D:\Dev\SuyeongPark\TestBlazor\Server\Dockerfile" --force-rm -t blazorappserver:dev --target base --label "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=BlazorApp.Server" "D:\Dev\SuyeongPark\TestBlazor" // 기존 docker container 삭제 docker rm -f 9581ab77dd89ae03d1eb4ebe3ccbdae9d4cdbefa38f1656809380e698ffc8e0a // docker debug용 container 실행 docker run -dt -v "C:\Users\Suyeongpark\vsdbg\vs2017u5:/remote_debugger:rw" -v "C:\Users\Suyeongpark\AppData\Roaming\Microsoft\UserSecrets:/root/.microsoft/usersecrets:ro" -v "C:\Users\Suyeongpark\AppData\Roaming\ASP.NET\Https:/root/.aspnet/https:ro" -v "D:\Dev\SuyeongPark\TestBlazor\Server:/app" -v "D:\Dev\SuyeongPark\TestBlazor:/src/" -v "C:\Users\Suyeongpark\.nuget\packages\:/root/.nuget/fallbackpackages" -e "ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS=true" -e "ASPNETCORE_ENVIRONMENT=Development" -e "ASPNETCORE_URLS=https://+:443;http://+:80" -e "DOTNET_USE_POLLING_FILE_WATCHER=1" -e "NUGET_PACKAGES=/root/.nuget/fallbackpackages" -e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages" -P --name BlazorApp.Server --entrypoint tail blazorappserver:dev -f /dev/null [Release] // docker image 빌드 docker build -f "D:\Dev\SuyeongPark\TestBlazor\Server\Dockerfile" --force-rm -t blazorappserver --label "com.microsoft.created-by=visual-studio" --label "com.microsoft.visual-studio.project-name=BlazorApp.Server" "D:\Dev\SuyeongPark\TestBlazor" // 기존 docker container 삭제 rm -f fd82aea1dae4bfac58a469e4930818b0b8735a0e2091c828ad164e6d8180063b // docker release용 container 실행 docker run -dt -v "C:\Users\Suyeongpark\vsdbg\vs2017u5:/remote_debugger:rw" -v "C:\Users\Suyeongpark\AppData\Roaming\Microsoft\UserSecrets:/root/.microsoft/usersecrets:ro" -v "C:\Users\Suyeongpark\AppData\Roaming\ASP.NET\Https:/root/.aspnet/https:ro" -e "ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS=true" -e "ASPNETCORE_ENVIRONMENT=Development" -e "ASPNETCORE_URLS=https://+:443;http://+:80" -P --name BlazorApp.Server --entrypoint tail blazorappserver -f /dev/null
Shell
복사
Debug, Release 빌드를 마친 후에 Docker Desktop을 보면 아래와 같이 이미지가 만들어져 있음을 확인할 수 있다. 이때 Tag가 dev인 것은 Debug 빌드인 것이고, latest는 Release 빌드를 뜻한다.
컨테이너가 잘 돌고 있는지도 확인한다. 아래의 port를 이용해서 브라우저에 localhost:<portnum>을 입력하면 실행되는 프로젝트를 확인할 수 있다. —Debug는 프로젝트가 실행 중일 때만 접속 되고, Release는 프로젝트 종료 후에도 접속 가능하다.

시리즈

이 글은 아래와 같은 시리즈로 이루어짐

참조 자료