코다람쥐 2022. 4. 25. 12:43

1. Blazor Server 흐름분석

Blazor Server프로젝트를 생성하면 위와같은 폴더와 파일들이 생성이되는데 이를 실행하면 아래의 웹 페이지가 출력이된다.

 

코드의 흐름을 보면 다음의 순서와 같다.

 

Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

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

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

먼저 메인함수안에서 Startup.cs파일을 실행시킨다.

 

Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using HelloBlazorServer.Data;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace HelloBlazorServer
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}

Startup에서 코드 흐름을 따라가다(코드 전부를 이해할 필요는 없다) 마지막에 "endpoints.MapFallbackToPage("/_Host");"에 의미는 _Host.cshtml을 참조한다는 뜻이다.

 

_Host.cshtml

@page "/"
@namespace HelloBlazorServer.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>HelloBlazorServer</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
</head>
<body>
    <app>
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </app>

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

코드 중간에  <app> <component type="typeof(App)" render-mode="ServerPrerendered" /> </app>이 입력되어 있다.  여기서 typeof(App)가 의미하는 바는 App.razor파일을 참조한다는 뜻이다.

 

참고로, <app>은 정식 html태그가 아니고 따로 정의해준 태그이다.

 

App.razor

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

세 번째 줄에 "@typeof(MainLayout)"이라고 적혀있는데 MainLaout.razor파일을 참조한다는 뜻이다.

 

MainLayout.razor

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

<div class="sidebar">가 무슨 뜻인지는 정확히 몰라도 사이드 메뉴를 정해주는 역할이라는 것을 추론할 수 있다.

그리고 다음줄에 <NavMenu />라고 적혀있는데 NavMenu.razor파일을 참조한다는 뜻이다.

 

NavMenu.razor

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">HelloBlazorServer</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

sidebar

html을 분석해보면 sidebar에 있는 메뉴들을 정의해준다는 것을 어느정도 짐작할 수 있다.

특이한점은 @code라는 부분에서 C#코드가 나오는 것을 알 수 있는데, Razor Page에서는 V와 C가 두 개의 파일로 묶여있다고 했었다. 그런데 Blazor에서는 Razor Page보다 한술 더 떠서 V와 C가 하나의 파일안에 모두 작성되었다고 생각하면 된다.

 

다시 html로 돌아와서 사이드메뉴에는 Home, Counter, Fetch data의 총 3개의 메뉴가 있다.

각각의 기능들을 정의하는 .razor파일들이 Pages 어딘가에 정의 되어있다. 이 파일들은 각각 href="", href="counter", href="fetchdata"라는 html코드를 통해 접근하게되는데. 이 중에 href="counter"를 분석해보기 위해서 Counter.razor파일을 참조해보자.

 

Counter.razor

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

맨 위에 @page "/counter"이라고 정의되어 있는데 href="counter"에서의 counter의 의미는 @page "/counter"의 counter를 뜻한다. 그렇다면 href="fetchdata"는 Pages폴더 어딘가에 정의되어있는 razor파일에 @page "/fetchdata"를 참조한다는 것을 짐작할 수 있다.

 

웹 페이지로 가서 Counter사이드 메뉴를 누르면 Click me 버튼이 있는 화면으로 바뀌는데, Click me 버튼을 누르면 Current count: 0의 값이 1로 증가한다. 이러한 기능은 Counter.razor파일안에 @code부분에서 정의되어있는데 다시 한 번 V와 C의 기능이 하나의 파일에 묶여있음을 확인 할 수 있다.