자습서: TypeScript 및 Webpack을 사용하여 ASP.NET Core SignalR 시작

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

바스티앙 수그네즈

이 자습서에서는 ASP.NET Core SignalR 웹앱에서 Webpack을 사용하여 TypeScript로 작성된 클라이언트를 묶고 빌드하는 방법을 보여 줍니다. WebPack을 사용하면 개발자가 웹 앱의 클라이언트 쪽 리소스를 번들링 및 빌드할 수 있습니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • ASP.NET Core SignalR 앱 만들기
  • SignalR 서버 구성
  • WebPack을 사용하여 빌드 파이프라인 구성
  • SignalR TypeScript 클라이언트 구성
  • 클라이언트와 서버 간 통신 사용

샘플 코드 보기 및 다운로드(다운로드 방법)

필수 조건

ASP.NET Core 웹앱 만들기

기본적으로 Visual Studio는 설치 디렉터리에 있는 npm의 버전을 사용합니다. PATH 환경 변수에서 npm을 찾도록 Visual Studio를 구성하려면 다음을 수행합니다.

Visual Studio를 시작합니다. 시작 창에서 코드를 사용하지 않고 계속을 선택합니다.

  1. 도구>옵션>프로젝트 및 솔루션>웹 패키지 관리>외부 웹 도구로 이동합니다.

  2. $(PATH) 목록에서 항목을 선택합니다. 위쪽 화살표를 선택하여 이 항목을 목록의 두 번째 위치로 이동한 후 확인을 선택합니다.

    Visual Studio 구성.

새 ASP.NET Core 웹앱을 만들려면 다음을 수행합니다.

  1. 파일>새로 만들기>프로젝트 메뉴 옵션에서 ASP.NET Core 빈 템플릿을 선택합니다. 다음을 선택합니다.
  2. 프로젝트 이름을 SignalRWebpack으로 지정하고 만들기를 선택합니다.
  3. 프레임워크 드롭다운에서 .NET 8.0(장기 지원)선택합니다. 만들기를 실행합니다.

프로젝트에 Microsoft.TypeScript.MSBuild NuGet 패키지를 추가합니다.

  1. 솔루션 탐색기에서 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리를 선택합니다. 찾아보기 탭에서 Microsoft.TypeScript.MSBuild를 검색한 다음, 오른쪽에 있는 설치를 선택하여 패키지를 설치합니다.

Visual Studio가 솔루션 탐색기종속성 노드에 NuGet 패키지를 추가하여 프로젝트의 TypeScript 컴파일을 사용 설정합니다.

서버 구성

이 섹션에서는 SignalR 메시지를 보내고 받도록 ASP.NET Core 웹앱을 구성합니다.

  1. Program.cs에서 AddSignalR을 호출합니다.

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddSignalR();
    
  2. 다시, Program.cs에서 UseDefaultFilesUseStaticFiles를 호출합니다.

    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    위의 코드를 사용하면 서버에서 파일을 찾아서 제공할 수 index.html 있습니다. 이 파일은 사용자가 웹앱의 전체 URL을 입력하든 아니면 루트 URL을 입력하든 관계없이 제공됩니다.

  3. 프로젝트 루트 SignalRWebpack/에서 SignalR 허브 클래스에 대해 Hubs라는 새 디렉터리를 만듭니다.

  4. 다음 코드를 사용하여 새 파일 Hubs/ChatHub.cs를 만듭니다.

    using Microsoft.AspNetCore.SignalR;
    
    namespace SignalRWebpack.Hubs;
    
    public class ChatHub : Hub
    {
        public async Task NewMessage(long username, string message) =>
            await Clients.All.SendAsync("messageReceived", username, message);
    }
    

    앞의 코드는 서버가 메시지를 수신한 후 수신된 메시지를 모든 연결된 사용자에게 브로드캐스트합니다. 모든 메시지를 수신하기 위해 일반 on 메서드를 포함할 필요는 없습니다. 메시지 이름 뒤에 명명한 메서드로 충분합니다.

    이 예에서는 다음이 적용됩니다.

    • TypeScript 클라이언트는 로 식별된 newMessage메시지를 보냅니다.
    • C# NewMessage 메서드에는 클라이언트가 보낸 데이터가 필요합니다.
    • Clients.All에서 SendAsync가 호출됩니다.
    • 수신된 메시지를 허브에 연결된 모든 클라이언트에 보냅니다.
  5. ChatHub 참조를 확인하기 위해 Program.cs의 맨 위에 다음 using 문을 추가합니다.

    using SignalRWebpack.Hubs;
    
  6. Program.cs에서 ChatHub 허브에 /hub 경로를 매핑합니다. Hello World!를 표시하는 코드를 다음 코드로 바꿉니다.

    app.MapHub<ChatHub>("/hub");
    

클라이언트 구성

이 섹션에서는 Webpack을 사용하여 TypeScript를 JavaScript로 변환하고 HTML 및 CSS를 비롯한 클라이언트 쪽 리소스를 번들로 묶는 Node.js 프로젝트를 만듭니다.

  1. 프로젝트 루트에서 다음 명령을 실행하여 파일을 만듭니다 package.json .

    npm init -y
    
  2. 강조 표시된 속성을 package.json 파일에 추가하고 파일 변경 내용을 저장합니다.

    {
      "name": "SignalRWebpack",
      "version": "1.0.0",
      "private": true,
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    

    private 속성을 true로 설정하면 다음 단계에서 패키지 설치 경고가 나타나지 않습니다.

  3. 필요한 npm 패키지를 설치합니다. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli
    

    -E 옵션은 유의적 버전 관리 범위 연산자를 package.json에 쓰는 npm의 기본 동작을 비활성화합니다. 예를 들어 "webpack": "^5.76.1" 대신 "webpack": "5.76.1"을 사용합니다. 이 옵션은 최신 패키지 버전으로 의도하지 않은 업그레이드를 방지합니다.

    자세한 내용은 npm-install 설명서를 참조하세요.

  4. package.json 파일의 scripts 속성을 다음 코드로 바꿉니다.

    "scripts": {
      "build": "webpack --mode=development --watch",
      "release": "webpack --mode=production",
      "publish": "npm run release && dotnet publish -c Release"
    },
    

    다음 스크립트가 정의됩니다.

    • build: 개발 모드에서 클라이언트 쪽 리소스를 번들로 묶고 파일 변경 내용을 감시합니다. 파일 감시자는 프로젝트 파일이 변경될 때마다 번들이 다시 생성되도록 합니다. mode 옵션은 프로덕션 최적화(예: 트리 셰이킹 및 축소)를 비활성화합니다. 개발 시에는 build만 사용합니다.
    • release: 프로덕션 모드에서 클라이언트 쪽 리소스를 번들로 묶습니다.
    • publish: 프로덕션 모드에서 release 스크립트를 실행하여 클라이언트 쪽 리소스를 번들링합니다. 이는 .NET CLI의 publish 명령을 호출하여 앱을 게시합니다.
  5. 다음 코드를 사용하여 프로젝트 루트에 이름이 지정된 webpack.config.js 파일을 만듭니다.

    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const { CleanWebpackPlugin } = require("clean-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
        entry: "./src/index.ts",
        output: {
            path: path.resolve(__dirname, "wwwroot"),
            filename: "[name].[chunkhash].js",
            publicPath: "/",
        },
        resolve: {
            extensions: [".js", ".ts"],
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    use: "ts-loader",
                },
                {
                    test: /\.css$/,
                    use: [MiniCssExtractPlugin.loader, "css-loader"],
                },
            ],
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: "./src/index.html",
            }),
            new MiniCssExtractPlugin({
                filename: "css/[name].[chunkhash].css",
            }),
        ],
    };
    

    앞의 파일은 Webpack 컴파일 프로세스를 구성합니다.

    • 속성의 output 기본값을 재정의 합니다 dist. 대신 번들은 디렉터리에서 wwwroot 내보내집니다.
    • 배열에는 resolve.extensions 클라이언트 JavaScript를 가져오는 것이 SignalR 포함됩니다.js.
  6. 클라이언트 코드에 대한 프로젝트 루트SignalRWebpack/에 명명된 src 새 디렉터리를 만듭니다.

  7. src 샘플 프로젝트에서 프로젝트 루트로 디렉터리 및 해당 내용을 복사합니다. src 디렉터리에는 다음 파일이 포함됩니다.

    • index.html은 홈페이지의 전형적인 마크업을 정의합니다.

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <title>ASP.NET Core SignalR with TypeScript and Webpack</title>
        </head>
        <body>
          <div id="divMessages" class="messages"></div>
          <div class="input-zone">
            <label id="lblMessage" for="tbMessage">Message:</label>
            <input id="tbMessage" class="input-zone-input" type="text" />
            <button id="btnSend">Send</button>
          </div>
        </body>
      </html>
      
    • css/main.css는 홈페이지에 대한 CSS 스타일을 제공합니다.

      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }
      
      html,
      body {
        margin: 0;
        padding: 0;
      }
      
      .input-zone {
        align-items: center;
        display: flex;
        flex-direction: row;
        margin: 10px;
      }
      
      .input-zone-input {
        flex: 1;
        margin-right: 10px;
      }
      
      .message-author {
        font-weight: bold;
      }
      
      .messages {
        border: 1px solid #000;
        margin: 10px;
        max-height: 300px;
        min-height: 300px;
        overflow-y: auto;
        padding: 5px;
      }
      
    • tsconfig.jsonECMAScript 5 호환 JavaScript를 생성하도록 TypeScript 컴파일러를 구성합니다.

      {
        "compilerOptions": {
          "target": "es5"
        }
      }
      
    • index.ts:

      import * as signalR from "@microsoft/signalr";
      import "./css/main.css";
      
      const divMessages: HTMLDivElement = document.querySelector("#divMessages");
      const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
      const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
      const username = new Date().getTime();
      
      const connection = new signalR.HubConnectionBuilder()
          .withUrl("/hub")
          .build();
      
      connection.on("messageReceived", (username: string, message: string) => {
        const m = document.createElement("div");
      
        m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`;
      
        divMessages.appendChild(m);
        divMessages.scrollTop = divMessages.scrollHeight;
      });
      
      connection.start().catch((err) => document.write(err));
      
      tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.key === "Enter") {
          send();
        }
      });
      
      btnSend.addEventListener("click", send);
      
      function send() {
        connection.send("newMessage", username, tbMessage.value)
          .then(() => (tbMessage.value = ""));
      }
      

      앞의 코드는 DOM 요소 참조를 조회하여 두 가지 이벤트 핸들러를 연결합니다.

      • keyup: 사용자가 tbMessage 텍스트 상자에 입력하고 Enter 키를 눌러 send 함수를 호출할 때 발생합니다.
      • click: 사용자가 보내기 단추를 선택하고 호출 send 함수를 호출할 때 발생합니다.

      HubConnectionBuilder 클래스는 서버 연결을 구성하기 위한 새 빌더를 생성합니다. withUrl 함수는 허브 URL을 구성합니다.

      SignalR은 클라이언트 및 서버 간 메시지 교환을 활성화합니다. 각 메시지는 특정 이름을 가집니다. 예를 들어 messageReceived라는 이름을 가진 메시지가 메시지 영역에 새 메시지를 표시하는 로직을 실행할 수 있습니다. 특정 메시지 수신 대기는 on 함수를 통해 수행할 수 있습니다. 임의 개수의 메시지 이름을 수신 대기할 수 있습니다. 또한 수신 메시지의 작성자 이름 및 내용 등을 메시지에 파라미터로 전달할 수도 있습니다. 클라이언트가 메시지를 수신한 후 innerHTML 특성의 작성자 이름 및 메시지 내용을 사용하여 새 div 요소가 생성됩니다. 이 요소가 주 div 요소에 추가되어 메시지를 표시합니다.

      WebSockets 연결을 통해 메시지를 보내려면 send 메서드를 호출해야 합니다. 이 메서드의 첫 번째 매개 변수는 메시지의 이름입니다. 메시지의 데이터는 다른 매개 변수를 통해 지정합니다. 이 예제에서는 newMessage로 식별되는 메시지가 서버로 전송됩니다. 메시지는 사용자 이름 및 텍스트 상자에 입력된 사용자 입력으로 구성됩니다. 전송이 완료되면 텍스트 상자의 값이 지워집니다.

  8. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i @microsoft/signalr @types/node
    

    이전 명령은 다음을 설치합니다.

    • 클라이언트에서 서버로 메시지를 전송할 수 있게 해주는 SignalR TypeScript 클라이언트.
    • Node.js 형식에 대한 컴파일 시간 검사를 가능하게 하는 Node.js에 대한 TypeScript 형식 정의.

앱 테스트

다음 단계를 통해 앱이 작동하는지 확인합니다.

  1. 모드에서 Webpack을 release 실행합니다. 패키지 관리자 콘솔 창을 사용하여 프로젝트 루트에서 다음 명령을 실행합니다.

    npm run release
    

    이 명령은 앱을 실행할 때 제공되는 클라이언트 쪽 자산을 생성합니다. 자산은 폴더에 wwwroot 배치됩니다.

    Webpack은 다음 작업을 완료했습니다.

    • 디렉터리의 콘텐츠를 wwwroot 제거했습니다.
    • Transpilation이라는 프로세스에서 TypeScript를 JavaScript로 변환했습니다.
    • 축소라고 하는 프로세스에서 파일 크기를 줄이기 위해 생성된 JavaScript를 망가트했습니다.
    • 처리된 JavaScript, CSS 및 HTML 파일을 디렉터리로 srcwwwroot 복사했습니다.
    • 파일에 다음 요소를 삽입했습니다 wwwroot/index.html .
      • wwwroot/main.<hash>.css 파일을 참조하는 <link> 태그입니다. 이 태그는 </head> 태그를 닫기 전에 즉시 배치합니다.
      • 최소화된 wwwroot/main.<hash>.js 파일을 참조하는 <script> 태그입니다. 이 태그는 닫 </title> 는 태그 바로 후에 배치됩니다.
  2. 디버그>디버그하지 않고 시작을 선택하여 디버거를 연결하지 않고 브라우저에서 앱을 시작합니다. wwwroot/index.html 파일은 https://localhost:<port>에서 제공됩니다.

    컴파일 오류가 있는 경우 솔루션을 닫고 다시 열어 보세요.

  3. 다른 브라우저 인스턴스(모든 브라우저)를 열고 주소 표시줄에 URL을 붙여 넣습니다.

  4. 브라우저를 선택하고 메시지 텍스트 상자에 내용을 입력하고 보내기 단추를 클릭합니다. 고유한 사용자 이름과 메시지는 두 페이지 모두에 즉시 표시됩니다.

두 브라우저 창에 표시되는 메시지

다음 단계

추가 리소스

이 자습서에서는 ASP.NET Core SignalR 웹앱에서 Webpack을 사용하여 TypeScript로 작성된 클라이언트를 묶고 빌드하는 방법을 보여 줍니다. WebPack을 사용하면 개발자가 웹 앱의 클라이언트 쪽 리소스를 번들링 및 빌드할 수 있습니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • ASP.NET Core SignalR 앱 만들기
  • SignalR 서버 구성
  • WebPack을 사용하여 빌드 파이프라인 구성
  • SignalR TypeScript 클라이언트 구성
  • 클라이언트와 서버 간 통신 사용

샘플 코드 보기 및 다운로드(다운로드 방법)

필수 조건

ASP.NET Core 웹앱 만들기

기본적으로 Visual Studio는 설치 디렉터리에 있는 npm의 버전을 사용합니다. PATH 환경 변수에서 npm을 찾도록 Visual Studio를 구성하려면 다음을 수행합니다.

Visual Studio를 시작합니다. 시작 창에서 코드를 사용하지 않고 계속을 선택합니다.

  1. 도구>옵션>프로젝트 및 솔루션>웹 패키지 관리>외부 웹 도구로 이동합니다.

  2. $(PATH) 목록에서 항목을 선택합니다. 위쪽 화살표를 선택하여 이 항목을 목록의 두 번째 위치로 이동한 후 확인을 선택합니다.

    Visual Studio 구성.

새 ASP.NET Core 웹앱을 만들려면 다음을 수행합니다.

  1. 파일>새로 만들기>프로젝트 메뉴 옵션에서 ASP.NET Core 빈 템플릿을 선택합니다. 다음을 선택합니다.
  2. 프로젝트 이름을 SignalRWebpack으로 지정하고 만들기를 선택합니다.
  3. 프레임워크 드롭다운에서 .NET 7.0 (Standard Term Support)를 선택합니다. 만들기를 실행합니다.

프로젝트에 Microsoft.TypeScript.MSBuild NuGet 패키지를 추가합니다.

  1. 솔루션 탐색기에서 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리를 선택합니다. 찾아보기 탭에서 Microsoft.TypeScript.MSBuild를 검색한 다음, 오른쪽에 있는 설치를 선택하여 패키지를 설치합니다.

Visual Studio가 솔루션 탐색기종속성 노드에 NuGet 패키지를 추가하여 프로젝트의 TypeScript 컴파일을 사용 설정합니다.

서버 구성

이 섹션에서는 SignalR 메시지를 보내고 받도록 ASP.NET Core 웹앱을 구성합니다.

  1. Program.cs에서 AddSignalR을 호출합니다.

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddSignalR();
    
  2. 다시, Program.cs에서 UseDefaultFilesUseStaticFiles를 호출합니다.

    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    위의 코드를 사용하면 서버에서 파일을 찾아서 제공할 수 index.html 있습니다. 이 파일은 사용자가 웹앱의 전체 URL을 입력하든 아니면 루트 URL을 입력하든 관계없이 제공됩니다.

  3. 프로젝트 루트 SignalRWebpack/에서 SignalR 허브 클래스에 대해 Hubs라는 새 디렉터리를 만듭니다.

  4. 다음 코드를 사용하여 새 파일 Hubs/ChatHub.cs를 만듭니다.

    using Microsoft.AspNetCore.SignalR;
    
    namespace SignalRWebpack.Hubs;
    
    public class ChatHub : Hub
    {
        public async Task NewMessage(long username, string message) =>
            await Clients.All.SendAsync("messageReceived", username, message);
    }
    

    앞의 코드는 서버가 메시지를 수신한 후 수신된 메시지를 모든 연결된 사용자에게 브로드캐스트합니다. 모든 메시지를 수신하기 위해 일반 on 메서드를 포함할 필요는 없습니다. 메시지 이름 뒤에 명명한 메서드로 충분합니다.

    이 예에서는 다음이 적용됩니다.

    • TypeScript 클라이언트는 로 식별된 newMessage메시지를 보냅니다.
    • C# NewMessage 메서드에는 클라이언트가 보낸 데이터가 필요합니다.
    • Clients.All에서 SendAsync가 호출됩니다.
    • 수신된 메시지를 허브에 연결된 모든 클라이언트에 보냅니다.
  5. ChatHub 참조를 확인하기 위해 Program.cs의 맨 위에 다음 using 문을 추가합니다.

    using SignalRWebpack.Hubs;
    
  6. Program.cs에서 ChatHub 허브에 /hub 경로를 매핑합니다. Hello World!를 표시하는 코드를 다음 코드로 바꿉니다.

    app.MapHub<ChatHub>("/hub");
    

클라이언트 구성

이 섹션에서는 Webpack을 사용하여 TypeScript를 JavaScript로 변환하고 HTML 및 CSS를 비롯한 클라이언트 쪽 리소스를 번들로 묶는 Node.js 프로젝트를 만듭니다.

  1. 프로젝트 루트에서 다음 명령을 실행하여 파일을 만듭니다 package.json .

    npm init -y
    
  2. 강조 표시된 속성을 package.json 파일에 추가하고 파일 변경 내용을 저장합니다.

    {
      "name": "SignalRWebpack",
      "version": "1.0.0",
      "private": true,
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    

    private 속성을 true로 설정하면 다음 단계에서 패키지 설치 경고가 나타나지 않습니다.

  3. 필요한 npm 패키지를 설치합니다. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli
    

    -E 옵션은 유의적 버전 관리 범위 연산자를 package.json에 쓰는 npm의 기본 동작을 비활성화합니다. 예를 들어 "webpack": "^5.76.1" 대신 "webpack": "5.76.1"을 사용합니다. 이 옵션은 최신 패키지 버전으로 의도하지 않은 업그레이드를 방지합니다.

    자세한 내용은 npm-install 설명서를 참조하세요.

  4. package.json 파일의 scripts 속성을 다음 코드로 바꿉니다.

    "scripts": {
      "build": "webpack --mode=development --watch",
      "release": "webpack --mode=production",
      "publish": "npm run release && dotnet publish -c Release"
    },
    

    다음 스크립트가 정의됩니다.

    • build: 개발 모드에서 클라이언트 쪽 리소스를 번들로 묶고 파일 변경 내용을 감시합니다. 파일 감시자는 프로젝트 파일이 변경될 때마다 번들이 다시 생성되도록 합니다. mode 옵션은 프로덕션 최적화(예: 트리 셰이킹 및 축소)를 비활성화합니다. 개발 시에는 build만 사용합니다.
    • release: 프로덕션 모드에서 클라이언트 쪽 리소스를 번들로 묶습니다.
    • publish: 프로덕션 모드에서 release 스크립트를 실행하여 클라이언트 쪽 리소스를 번들링합니다. 이는 .NET CLI의 publish 명령을 호출하여 앱을 게시합니다.
  5. 다음 코드를 사용하여 프로젝트 루트에 이름이 지정된 webpack.config.js 파일을 만듭니다.

    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const { CleanWebpackPlugin } = require("clean-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
        entry: "./src/index.ts",
        output: {
            path: path.resolve(__dirname, "wwwroot"),
            filename: "[name].[chunkhash].js",
            publicPath: "/",
        },
        resolve: {
            extensions: [".js", ".ts"],
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    use: "ts-loader",
                },
                {
                    test: /\.css$/,
                    use: [MiniCssExtractPlugin.loader, "css-loader"],
                },
            ],
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: "./src/index.html",
            }),
            new MiniCssExtractPlugin({
                filename: "css/[name].[chunkhash].css",
            }),
        ],
    };
    

    앞의 파일은 Webpack 컴파일 프로세스를 구성합니다.

    • 속성의 output 기본값을 재정의 합니다 dist. 대신 번들은 디렉터리에서 wwwroot 내보내집니다.
    • 배열에는 resolve.extensions 클라이언트 JavaScript를 가져오는 것이 SignalR 포함됩니다.js.
  6. src 샘플 프로젝트에서 프로젝트 루트로 디렉터리 및 해당 내용을 복사합니다. src 디렉터리에는 다음 파일이 포함됩니다.

    • index.html은 홈페이지의 전형적인 마크업을 정의합니다.

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <title>ASP.NET Core SignalR with TypeScript and Webpack</title>
        </head>
        <body>
          <div id="divMessages" class="messages"></div>
          <div class="input-zone">
            <label id="lblMessage" for="tbMessage">Message:</label>
            <input id="tbMessage" class="input-zone-input" type="text" />
            <button id="btnSend">Send</button>
          </div>
        </body>
      </html>
      
    • css/main.css는 홈페이지에 대한 CSS 스타일을 제공합니다.

      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }
      
      html,
      body {
        margin: 0;
        padding: 0;
      }
      
      .input-zone {
        align-items: center;
        display: flex;
        flex-direction: row;
        margin: 10px;
      }
      
      .input-zone-input {
        flex: 1;
        margin-right: 10px;
      }
      
      .message-author {
        font-weight: bold;
      }
      
      .messages {
        border: 1px solid #000;
        margin: 10px;
        max-height: 300px;
        min-height: 300px;
        overflow-y: auto;
        padding: 5px;
      }
      
    • tsconfig.jsonECMAScript 5 호환 JavaScript를 생성하도록 TypeScript 컴파일러를 구성합니다.

      {
        "compilerOptions": {
          "target": "es5"
        }
      }
      
    • index.ts:

      import * as signalR from "@microsoft/signalr";
      import "./css/main.css";
      
      const divMessages: HTMLDivElement = document.querySelector("#divMessages");
      const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
      const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
      const username = new Date().getTime();
      
      const connection = new signalR.HubConnectionBuilder()
          .withUrl("/hub")
          .build();
      
      connection.on("messageReceived", (username: string, message: string) => {
        const m = document.createElement("div");
      
        m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`;
      
        divMessages.appendChild(m);
        divMessages.scrollTop = divMessages.scrollHeight;
      });
      
      connection.start().catch((err) => document.write(err));
      
      tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.key === "Enter") {
          send();
        }
      });
      
      btnSend.addEventListener("click", send);
      
      function send() {
        connection.send("newMessage", username, tbMessage.value)
          .then(() => (tbMessage.value = ""));
      }
      

      앞의 코드는 DOM 요소 참조를 조회하여 두 가지 이벤트 핸들러를 연결합니다.

      • keyup: 사용자가 tbMessage 텍스트 상자에 입력하고 Enter 키를 눌러 send 함수를 호출할 때 발생합니다.
      • click: 사용자가 보내기 단추를 선택하고 호출 send 함수를 호출할 때 발생합니다.

      HubConnectionBuilder 클래스는 서버 연결을 구성하기 위한 새 빌더를 생성합니다. withUrl 함수는 허브 URL을 구성합니다.

      SignalR은 클라이언트 및 서버 간 메시지 교환을 활성화합니다. 각 메시지는 특정 이름을 가집니다. 예를 들어 messageReceived라는 이름을 가진 메시지가 메시지 영역에 새 메시지를 표시하는 로직을 실행할 수 있습니다. 특정 메시지 수신 대기는 on 함수를 통해 수행할 수 있습니다. 임의 개수의 메시지 이름을 수신 대기할 수 있습니다. 또한 수신 메시지의 작성자 이름 및 내용 등을 메시지에 파라미터로 전달할 수도 있습니다. 클라이언트가 메시지를 수신한 후 innerHTML 특성의 작성자 이름 및 메시지 내용을 사용하여 새 div 요소가 생성됩니다. 이 요소가 주 div 요소에 추가되어 메시지를 표시합니다.

      WebSockets 연결을 통해 메시지를 보내려면 send 메서드를 호출해야 합니다. 이 메서드의 첫 번째 매개 변수는 메시지의 이름입니다. 메시지의 데이터는 다른 매개 변수를 통해 지정합니다. 이 예제에서는 newMessage로 식별되는 메시지가 서버로 전송됩니다. 메시지는 사용자 이름 및 텍스트 상자에 입력된 사용자 입력으로 구성됩니다. 전송이 완료되면 텍스트 상자의 값이 지워집니다.

  7. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i @microsoft/signalr @types/node
    

    이전 명령은 다음을 설치합니다.

    • 클라이언트에서 서버로 메시지를 전송할 수 있게 해주는 SignalR TypeScript 클라이언트.
    • Node.js 형식에 대한 컴파일 시간 검사를 가능하게 하는 Node.js에 대한 TypeScript 형식 정의.

앱 테스트

다음 단계를 통해 앱이 작동하는지 확인합니다.

  1. 모드에서 Webpack을 release 실행합니다. 패키지 관리자 콘솔 창을 사용하여 프로젝트 루트에서 다음 명령을 실행합니다.

    npm run release
    

    이 명령은 앱을 실행할 때 제공되는 클라이언트 쪽 자산을 생성합니다. 자산은 폴더에 wwwroot 배치됩니다.

    Webpack은 다음 작업을 완료했습니다.

    • 디렉터리의 콘텐츠를 wwwroot 제거했습니다.
    • Transpilation이라는 프로세스에서 TypeScript를 JavaScript로 변환했습니다.
    • 축소라고 하는 프로세스에서 파일 크기를 줄이기 위해 생성된 JavaScript를 망가트했습니다.
    • 처리된 JavaScript, CSS 및 HTML 파일을 디렉터리로 srcwwwroot 복사했습니다.
    • 파일에 다음 요소를 삽입했습니다 wwwroot/index.html .
      • wwwroot/main.<hash>.css 파일을 참조하는 <link> 태그입니다. 이 태그는 </head> 태그를 닫기 전에 즉시 배치합니다.
      • 최소화된 wwwroot/main.<hash>.js 파일을 참조하는 <script> 태그입니다. 이 태그는 닫 </title> 는 태그 바로 후에 배치됩니다.
  2. 디버그>디버그하지 않고 시작을 선택하여 디버거를 연결하지 않고 브라우저에서 앱을 시작합니다. wwwroot/index.html 파일은 https://localhost:<port>에서 제공됩니다.

    컴파일 오류가 있는 경우 솔루션을 닫고 다시 열어 보세요.

  3. 다른 브라우저 인스턴스(모든 브라우저)를 열고 주소 표시줄에 URL을 붙여 넣습니다.

  4. 브라우저를 선택하고 메시지 텍스트 상자에 내용을 입력하고 보내기 단추를 클릭합니다. 고유한 사용자 이름과 메시지는 두 페이지 모두에 즉시 표시됩니다.

두 브라우저 창에 표시되는 메시지

다음 단계

추가 리소스

이 자습서에서는 ASP.NET Core SignalR 웹앱에서 Webpack을 사용하여 TypeScript로 작성된 클라이언트를 묶고 빌드하는 방법을 보여 줍니다. WebPack을 사용하면 개발자가 웹 앱의 클라이언트 쪽 리소스를 번들링 및 빌드할 수 있습니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • ASP.NET Core SignalR 앱 만들기
  • SignalR 서버 구성
  • WebPack을 사용하여 빌드 파이프라인 구성
  • SignalR TypeScript 클라이언트 구성
  • 클라이언트와 서버 간 통신 사용

샘플 코드 보기 및 다운로드(다운로드 방법)

필수 조건

ASP.NET Core 웹앱 만들기

기본적으로 Visual Studio는 설치 디렉터리에 있는 npm의 버전을 사용합니다. PATH 환경 변수에서 npm을 찾도록 Visual Studio를 구성하려면 다음을 수행합니다.

  1. Visual Studio를 시작합니다. 시작 창에서 코드를 사용하지 않고 계속을 선택합니다.

  2. 도구>옵션>프로젝트 및 솔루션>웹 패키지 관리>외부 웹 도구로 이동합니다.

  3. $(PATH) 목록에서 항목을 선택합니다. 위쪽 화살표를 선택하여 이 항목을 목록의 두 번째 위치로 이동한 후 확인을 선택합니다.

    Visual Studio 구성.

새 ASP.NET Core 웹앱을 만들려면 다음을 수행합니다.

  1. 파일>새로 만들기>프로젝트 메뉴 옵션에서 ASP.NET Core 빈 템플릿을 선택합니다. 다음을 선택합니다.
  2. 프로젝트 이름을 SignalRWebpack으로 지정하고 만들기를 선택합니다.
  3. 프레임워크 드롭다운에서 .NET 6.0 (Long Term Support)를 선택합니다. 만들기를 실행합니다.

프로젝트에 Microsoft.TypeScript.MSBuild NuGet 패키지를 추가합니다.

  1. 솔루션 탐색기에서 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리를 선택합니다. 찾아보기 탭에서 Microsoft.TypeScript.MSBuild를 검색한 다음, 오른쪽에 있는 설치를 선택하여 패키지를 설치합니다.

Visual Studio가 솔루션 탐색기종속성 노드에 NuGet 패키지를 추가하여 프로젝트의 TypeScript 컴파일을 사용 설정합니다.

서버 구성

이 섹션에서는 SignalR 메시지를 보내고 받도록 ASP.NET Core 웹앱을 구성합니다.

  1. Program.cs에서 AddSignalR을 호출합니다.

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddSignalR();
    
  2. 다시, Program.cs에서 UseDefaultFilesUseStaticFiles를 호출합니다.

    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    위의 코드를 사용하면 서버에서 파일을 찾아서 제공할 수 index.html 있습니다. 이 파일은 사용자가 웹앱의 전체 URL을 입력하든 아니면 루트 URL을 입력하든 관계없이 제공됩니다.

  3. 프로젝트 루트 SignalRWebpack/에서 SignalR 허브 클래스에 대해 Hubs라는 새 디렉터리를 만듭니다.

  4. 다음 코드를 사용하여 새 파일 Hubs/ChatHub.cs를 만듭니다.

    using Microsoft.AspNetCore.SignalR;
    
    namespace SignalRWebpack.Hubs;
    
    public class ChatHub : Hub
    {
        public async Task NewMessage(long username, string message) =>
            await Clients.All.SendAsync("messageReceived", username, message);
    }
    

    앞의 코드는 서버가 메시지를 수신한 후 수신된 메시지를 모든 연결된 사용자에게 브로드캐스트합니다. 모든 메시지를 수신하기 위해 일반 on 메서드를 포함할 필요는 없습니다. 메시지 이름 뒤에 명명한 메서드로 충분합니다.

    이 예에서 TypeScript 클라이언트는 newMessage로 식별된 메시지를 보냅니다. C# NewMessage 메서드에는 클라이언트가 보낸 데이터가 필요합니다. Clients.All에서 SendAsync가 호출됩니다. 수신된 메시지를 허브에 연결된 모든 클라이언트에 보냅니다.

  5. ChatHub 참조를 확인하기 위해 Program.cs의 맨 위에 다음 using 문을 추가합니다.

    using SignalRWebpack.Hubs;
    
  6. Program.cs에서 ChatHub 허브에 /hub 경로를 매핑합니다. Hello World!를 표시하는 코드를 다음 코드로 바꿉니다.

    app.MapHub<ChatHub>("/hub");
    

클라이언트 구성

이 섹션에서는 Webpack을 사용하여 TypeScript를 JavaScript로 변환하고 HTML 및 CSS를 비롯한 클라이언트 쪽 리소스를 번들로 묶는 Node.js 프로젝트를 만듭니다.

  1. 프로젝트 루트에서 다음 명령을 실행하여 파일을 만듭니다 package.json .

    npm init -y
    
  2. 강조 표시된 속성을 package.json 파일에 추가하고 파일 변경 내용을 저장합니다.

    {
      "name": "SignalRWebpack",
      "version": "1.0.0",
      "private": true,
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    

    private 속성을 true로 설정하면 다음 단계에서 패키지 설치 경고가 나타나지 않습니다.

  3. 필요한 npm 패키지를 설치합니다. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin ts-loader typescript webpack webpack-cli
    

    -E 옵션은 유의적 버전 관리 범위 연산자를 package.json에 쓰는 npm의 기본 동작을 비활성화합니다. 예를 들어 "webpack": "^5.70.0" 대신 "webpack": "5.70.0"을 사용합니다. 이 옵션은 최신 패키지 버전으로 의도하지 않은 업그레이드를 방지합니다.

    자세한 내용은 npm-install 설명서를 참조하세요.

  4. package.json 파일의 scripts 속성을 다음 코드로 바꿉니다.

    "scripts": {
      "build": "webpack --mode=development --watch",
      "release": "webpack --mode=production",
      "publish": "npm run release && dotnet publish -c Release"
    },
    

    다음 스크립트가 정의됩니다.

    • build: 개발 모드에서 클라이언트 쪽 리소스를 번들로 묶고 파일 변경 내용을 감시합니다. 파일 감시자는 프로젝트 파일이 변경될 때마다 번들이 다시 생성되도록 합니다. mode 옵션은 프로덕션 최적화(예: 트리 셰이킹 및 축소)를 비활성화합니다. 개발 시에는 build만 사용합니다.
    • release: 프로덕션 모드에서 클라이언트 쪽 리소스를 번들로 묶습니다.
    • publish: 프로덕션 모드에서 release 스크립트를 실행하여 클라이언트 쪽 리소스를 번들링합니다. 이는 .NET CLI의 publish 명령을 호출하여 앱을 게시합니다.
  5. 다음 코드를 사용하여 프로젝트 루트에 이름이 지정된 webpack.config.js 파일을 만듭니다.

    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const { CleanWebpackPlugin } = require("clean-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
      entry: "./src/index.ts",
      output: {
        path: path.resolve(__dirname, "wwwroot"),
        filename: "[name].[chunkhash].js",
        publicPath: "/",
      },
      resolve: {
        extensions: [".js", ".ts"],
      },
      module: {
        rules: [
          {
            test: /\.ts$/,
            use: "ts-loader",
          },
          {
            test: /\.css$/,
            use: [MiniCssExtractPlugin.loader, "css-loader"],
          },
        ],
      },
      plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
          template: "./src/index.html",
        }),
        new MiniCssExtractPlugin({
          filename: "css/[name].[chunkhash].css",
        }),
      ],
    };
    

    앞의 파일은 Webpack 컴파일 프로세스를 구성합니다.

    • 속성의 output 기본값을 재정의 합니다 dist. 대신 번들은 디렉터리에서 wwwroot 내보내집니다.
    • 배열에는 resolve.extensions 클라이언트 JavaScript를 가져오는 것이 SignalR 포함됩니다.js.
  6. src 샘플 프로젝트에서 프로젝트 루트로 디렉터리 및 해당 내용을 복사합니다. src 디렉터리에는 다음 파일이 포함됩니다.

    • index.html은 홈페이지의 전형적인 마크업을 정의합니다.

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <title>ASP.NET Core SignalR with TypeScript and Webpack</title>
        </head>
        <body>
          <div id="divMessages" class="messages"></div>
          <div class="input-zone">
            <label id="lblMessage" for="tbMessage">Message:</label>
            <input id="tbMessage" class="input-zone-input" type="text" />
            <button id="btnSend">Send</button>
          </div>
        </body>
      </html>
      
    • css/main.css는 홈페이지에 대한 CSS 스타일을 제공합니다.

      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }
      
      html,
      body {
        margin: 0;
        padding: 0;
      }
      
      .input-zone {
        align-items: center;
        display: flex;
        flex-direction: row;
        margin: 10px;
      }
      
      .input-zone-input {
        flex: 1;
        margin-right: 10px;
      }
      
      .message-author {
        font-weight: bold;
      }
      
      .messages {
        border: 1px solid #000;
        margin: 10px;
        max-height: 300px;
        min-height: 300px;
        overflow-y: auto;
        padding: 5px;
      }
      
    • tsconfig.jsonECMAScript 5 호환 JavaScript를 생성하도록 TypeScript 컴파일러를 구성합니다.

      {
        "compilerOptions": {
          "target": "es5"
        }
      }
      
    • index.ts:

      import * as signalR from "@microsoft/signalr";
      import "./css/main.css";
      
      const divMessages: HTMLDivElement = document.querySelector("#divMessages");
      const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
      const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
      const username = new Date().getTime();
      
      const connection = new signalR.HubConnectionBuilder()
          .withUrl("/hub")
          .build();
      
      connection.on("messageReceived", (username: string, message: string) => {
        const m = document.createElement("div");
      
        m.innerHTML = `<div class="message-author">${username}</div><div>${message}</div>`;
      
        divMessages.appendChild(m);
        divMessages.scrollTop = divMessages.scrollHeight;
      });
      
      connection.start().catch((err) => document.write(err));
      
      tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.key === "Enter") {
          send();
        }
      });
      
      btnSend.addEventListener("click", send);
      
      function send() {
        connection.send("newMessage", username, tbMessage.value)
          .then(() => (tbMessage.value = ""));
      }
      

    앞의 코드는 DOM 요소 참조를 조회하여 두 가지 이벤트 핸들러를 연결합니다.

    • keyup: 사용자가 tbMessage 텍스트 상자에 입력하고 Enter 키를 눌러 send 함수를 호출할 때 발생합니다.
    • click: 사용자가 보내기 단추를 선택하고 호출 send 함수를 호출할 때 발생합니다.

    HubConnectionBuilder 클래스는 서버 연결을 구성하기 위한 새 빌더를 생성합니다. withUrl 함수는 허브 URL을 구성합니다.

    SignalR은 클라이언트 및 서버 간 메시지 교환을 활성화합니다. 각 메시지는 특정 이름을 가집니다. 예를 들어 messageReceived라는 이름을 가진 메시지가 메시지 영역에 새 메시지를 표시하는 로직을 실행할 수 있습니다. 특정 메시지 수신 대기는 on 함수를 통해 수행할 수 있습니다. 임의 개수의 메시지 이름을 수신 대기할 수 있습니다. 또한 수신 메시지의 작성자 이름 및 내용 등을 메시지에 파라미터로 전달할 수도 있습니다. 클라이언트가 메시지를 수신한 후 innerHTML 특성의 작성자 이름 및 메시지 내용을 사용하여 새 div 요소가 생성됩니다. 이 요소가 주 div 요소에 추가되어 메시지를 표시합니다.

    WebSockets 연결을 통해 메시지를 보내려면 send 메서드를 호출해야 합니다. 이 메서드의 첫 번째 매개 변수는 메시지의 이름입니다. 메시지의 데이터는 다른 매개 변수를 통해 지정합니다. 이 예제에서는 newMessage로 식별되는 메시지가 서버로 전송됩니다. 메시지는 사용자 이름 및 텍스트 상자에 입력된 사용자 입력으로 구성됩니다. 전송이 완료되면 텍스트 상자의 값이 지워집니다.

  7. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i @microsoft/signalr @types/node
    

    이전 명령은 다음을 설치합니다.

    • 클라이언트에서 서버로 메시지를 전송할 수 있게 해주는 SignalR TypeScript 클라이언트.
    • Node.js 형식에 대한 컴파일 시간 검사를 가능하게 하는 Node.js에 대한 TypeScript 형식 정의.

앱 테스트

다음 단계를 통해 앱이 작동하는지 확인합니다.

  1. 모드에서 Webpack을 release 실행합니다. 패키지 관리자 콘솔 창을 사용하여 프로젝트 루트에서 다음 명령을 실행합니다. 프로젝트 루트가 아닌 경우 명령을 입력하기 전에 cd SignalRWebpack을 입력합니다.

    npm run release
    

    이 명령은 앱을 실행할 때 제공되는 클라이언트 쪽 자산을 생성합니다. 자산은 폴더에 wwwroot 배치됩니다.

    Webpack은 다음 작업을 완료했습니다.

    • 디렉터리의 콘텐츠를 wwwroot 제거했습니다.
    • Transpilation이라는 프로세스에서 TypeScript를 JavaScript로 변환했습니다.
    • 축소라고 하는 프로세스에서 파일 크기를 줄이기 위해 생성된 JavaScript를 망가트했습니다.
    • 처리된 JavaScript, CSS 및 HTML 파일을 디렉터리로 srcwwwroot 복사했습니다.
    • 파일에 다음 요소를 삽입했습니다 wwwroot/index.html .
      • wwwroot/main.<hash>.css 파일을 참조하는 <link> 태그입니다. 이 태그는 </head> 태그를 닫기 전에 즉시 배치합니다.
      • 최소화된 wwwroot/main.<hash>.js 파일을 참조하는 <script> 태그입니다. 이 태그는 닫 </title> 는 태그 바로 후에 배치됩니다.
  2. 디버그>디버그하지 않고 시작을 선택하여 디버거를 연결하지 않고 브라우저에서 앱을 시작합니다. wwwroot/index.html 파일은 https://localhost:<port>에서 제공됩니다.

    컴파일 오류가 발생하면 솔루션을 닫았다가 다시 열어 보세요.

  3. 다른 브라우저 인스턴스(모든 브라우저)를 열고 주소 표시줄에 URL을 붙여 넣습니다.

  4. 브라우저를 선택하고 메시지 텍스트 상자에 내용을 입력하고 보내기 단추를 클릭합니다. 고유한 사용자 이름과 메시지는 두 페이지 모두에 즉시 표시됩니다.

두 브라우저 창에 표시되는 메시지

다음 단계

추가 리소스

이 자습서에서는 ASP.NET Core SignalR 웹앱에서 Webpack을 사용하여 TypeScript로 작성된 클라이언트를 묶고 빌드하는 방법을 보여 줍니다. WebPack을 사용하면 개발자가 웹 앱의 클라이언트 쪽 리소스를 번들링 및 빌드할 수 있습니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • 시작 ASP.NET Core SignalR 앱 스캐폴드
  • SignalR TypeScript 클라이언트 구성
  • WebPack을 사용하여 빌드 파이프라인 구성
  • SignalR 서버 구성
  • 클라이언트 및 서버 간 통신 활성화

샘플 코드 보기 및 다운로드(다운로드 방법)

필수 조건

ASP.NET Core 웹앱 만들기

PATH 환경 변수에서 npm을 찾도록 Visual Studio를 구성합니다. 기본적으로 Visual Studio는 설치 디렉터리에 있는 npm의 버전을 사용합니다. Visual Studio에서 다음 지침을 따릅니다.

  1. Visual Studio를 시작합니다. 시작 창에서 코드를 사용하지 않고 계속을 선택합니다.

  2. 도구>옵션>프로젝트 및 솔루션>웹 패키지 관리>외부 웹 도구로 이동합니다.

  3. 목록에서 $(PATH) 항목을 선택합니다. 위쪽 화살표를 선택하여 이 항목을 목록의 두 번째 위치로 이동한 후 확인을 선택합니다.

    Visual Studio 구성.

Visual Studio의 구성이 완료되었습니다.

  1. 파일>새로 만들기>프로젝트 메뉴 옵션을 사용하고 ASP.NET Core 웹 애플리케이션 템플릿을 선택합니다. 다음을 선택합니다.
  2. 프로젝트 이름을 *SignalRWebPac``으로 지정하고 만들기를 선택합니다.
  3. 대상 프레임워크 드롭다운에서 .NET Core를 선택하고 프레임워크 선택기 드롭다운에서 ASP.NET Core 3.1을 선택합니다. 템플릿을 선택하고 만들기를 선택합니다.

프로젝트에 Microsoft.TypeScript.MSBuild 패키지를 추가:

  1. 솔루션 탐색기(오른쪽 창)에서 프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리를 선택합니다. 찾아보기 탭에서 Microsoft.TypeScript.MSBuild를 검색한 다음 오른쪽에 있는 설치를 클릭하여 패키지를 설치합니다.

Visual Studio가 솔루션 탐색기종속성 노드에 NuGet 패키지를 추가하여 프로젝트의 TypeScript 컴파일을 사용 설정합니다.

WebPack 및 TypeScript 구성

다음 단계에서는 TypeScript에서 JavaScript로 변환 및 클라이언트 쪽 리소스의 번들링을 구성합니다.

  1. 프로젝트 루트에서 다음 명령을 실행하여 파일을 만듭니다 package.json .

    npm init -y
    
  2. 강조 표시된 속성을 package.json 파일에 추가하고 파일 변경 내용을 저장합니다.

    {
      "name": "SignalRWebPack",
      "version": "1.0.0",
      "private": true,
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    

    private 속성을 true로 설정하면 다음 단계에서 패키지 설치 경고가 나타나지 않습니다.

  3. 필요한 npm 패키지를 설치합니다. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i -D -E clean-webpack-plugin@3.0.0 css-loader@3.4.2 html-webpack-plugin@3.2.0 mini-css-extract-plugin@0.9.0 ts-loader@6.2.1 typescript@3.7.5 webpack@4.41.5 webpack-cli@3.3.10
    

    참고할 몇몇 명령 세부 정보:

    • 버전 번호는 각 패키지 이름에 대해 @ 부호 뒤에 옵니다. npm은 해당 특정 패키지 버전을 설치합니다.
    • -E 옵션은 유의적 버전 관리 범위 연산자를 *packagejson에 쓰는 npm의 기본 동작을 비활성화합니다. 예를 들어 "webpack": "^4.41.5" 대신 "webpack": "4.41.5"을 사용합니다. 이 옵션은 최신 패키지 버전으로 의도하지 않은 업그레이드를 방지합니다.

    자세한 내용은 npm-install 문서를 참조하세요.

  4. 파일의 scriptspackage.json 속성을 다음 코드로 바꿉다.

    "scripts": {
      "build": "webpack --mode=development --watch",
      "release": "webpack --mode=production",
      "publish": "npm run release && dotnet publish -c Release"
    },
    

    스크립트에 대한 간략한 설명:

    • build: 개발 모드에서 클라이언트 쪽 리소스를 번들로 묶고 파일 변경 내용을 감시합니다. 파일 감시자는 프로젝트 파일이 변경될 때마다 번들이 다시 생성되도록 합니다. mode 옵션은 프로덕션 최적화(예: 트리 셰이킹 및 축소)를 비활성화합니다. 개발 시에는 build만 사용합니다.
    • release: 프로덕션 모드에서 클라이언트 쪽 리소스를 번들로 묶습니다.
    • publish: 프로덕션 모드에서 release 스크립트를 실행하여 클라이언트 쪽 리소스를 번들링합니다. 이는 .NET Core CLI의 publish 명령을 호출하여 앱을 게시합니다.
  5. 프로젝트 루트에 다음 코드를 포함한 webpack.config.js라는 파일을 만듭니다.

    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const { CleanWebpackPlugin } = require("clean-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    module.exports = {
        entry: "./src/index.ts",
        output: {
            path: path.resolve(__dirname, "wwwroot"),
            filename: "[name].[chunkhash].js",
            publicPath: "/"
        },
        resolve: {
            extensions: [".js", ".ts"]
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    use: "ts-loader"
                },
                {
                    test: /\.css$/,
                    use: [MiniCssExtractPlugin.loader, "css-loader"]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: "./src/index.html"
            }),
            new MiniCssExtractPlugin({
                filename: "css/[name].[chunkhash].css"
            })
        ]
    };
    

    앞의 파일은 WebPack 컴파일을 구성합니다. 참고할 일부 구성 세부 정보:

    • 속성의 output 기본값을 재정의 합니다 dist. 대신 번들은 디렉터리에서 wwwroot 내보내집니다.
    • 배열에는 resolve.extensions 클라이언트 JavaScript를 가져오는 것이 SignalR 포함됩니다.js.
  6. 프로젝트 루트에 새 src 디렉터리를 만들어 프로젝트의 클라이언트 쪽 자산을 저장합니다.

  7. 다음 태그를 사용하여 만듭니 src/index.html 다.

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>ASP.NET Core SignalR</title>
    </head>
    <body>
        <div id="divMessages" class="messages">
        </div>
        <div class="input-zone">
            <label id="lblMessage" for="tbMessage">Message:</label>
            <input id="tbMessage" class="input-zone-input" type="text" />
            <button id="btnSend">Send</button>
        </div>
    </body>
    </html>
    

    앞의 HTML은 홈 페이지의 상용구 마크업을 정의합니다.

  8. src/css 디렉터리를 생성합니다. 그 목적은 프로젝트의 파일을 저장하는 것입니다 .css .

  9. 다음 CSS를 사용하여 만듭니 src/css/main.css 다.

    *, *::before, *::after {
        box-sizing: border-box;
    }
    
    html, body {
        margin: 0;
        padding: 0;
    }
    
    .input-zone {
        align-items: center;
        display: flex;
        flex-direction: row;
        margin: 10px;
    }
    
    .input-zone-input {
        flex: 1;
        margin-right: 10px;
    }
    
    .message-author {
        font-weight: bold;
    }
    
    .messages {
        border: 1px solid #000;
        margin: 10px;
        max-height: 300px;
        min-height: 300px;
        overflow-y: auto;
        padding: 5px;
    }
    

    위의 main.css 파일은 앱의 스타일을 지정합니다.

  10. 다음 JSON으로 src/tsconfig.json을(를) 만듭니다.

    {
      "compilerOptions": {
        "target": "es5"
      }
    }
    

    이 코드는 ECMAScript 5 호환 JavaScript를 생성하도록 TypeScript 컴파일러를 구성합니다.

  11. 다음 코드로 src/index.ts를 만듭니다.

    import "./css/main.css";
    
    const divMessages: HTMLDivElement = document.querySelector("#divMessages");
    const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
    const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
    const username = new Date().getTime();
    
    tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.key === "Enter") {
            send();
        }
    });
    
    btnSend.addEventListener("click", send);
    
    function send() {
    }
    

    이 TypeScript는 DOM 요소 참조를 조회하여 두 가지 이벤트 핸들러를 연결합니다.

    • keyup: 사용자가 텍스트 상자에 입력 tbMessage할 때 이 이벤트가 발생합니다. send 함수는 사용자가 Enter 키를 누를 때 호출됩니다.
    • click: 이 이벤트는 사용자가 보내기 단추를 선택할 때 발생합니다. send 함수가 호출됩니다.

앱 구성

  1. Startup.Configure에서 UseDefaultFiles(IApplicationBuilder)UseStaticFiles(IApplicationBuilder) 호출을 추가합니다.

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        
        app.UseRouting();
        app.UseDefaultFiles();
        app.UseStaticFiles();
        
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapHub<ChatHub>("/hub");
        });
            
    }
    

    위의 코드를 사용하면 서버에서 파일을 찾아서 제공할 수 index.html 있습니다. 이 파일은 사용자가 웹앱의 전체 URL을 입력하든 아니면 루트 URL을 입력하든 관계없이 제공됩니다.

  2. Startup.Configure의 끝에서 /hub 경로를 ChatHub 허브에 매핑합니다. Hello World!를 표시하는 코드를 다음 줄로 바꿉니다.

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>("/hub");
    });
    
  3. Startup.ConfigureServices에서 AddSignalR을 호출합니다.

    services.AddSignalR();
    
  4. 프로젝트 루트 SignalRWebPack/Hubs라는 새 디렉터리를 만들어 SignalR 허브를 저장합니다.

  5. 다음 코드를 사용하여 허브 Hubs/ChatHub.cs 를 만듭니다.

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRWebPack.Hubs
    {
        public class ChatHub : Hub
        {
        }
    }
    
  6. 파일 맨 위에 Startup.cs 다음 using 문을 추가하여 참조를 확인합니다ChatHub.

    using SignalRWebPack.Hubs;
    

클라이언트 및 서버 통신 활성화

현재 앱에는 메시지를 전송하기 위한 기본 양식이 표시되어 있지만 아직 작동하지 않습니다. 서버는 특정 경로를 수신 대기하지만 보낸 메시지를 사용하여 아무 작업도 하지 않습니다.

  1. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm i @microsoft/signalr @types/node
    

    이전 명령은 다음을 설치합니다.

    • 클라이언트에서 서버로 메시지를 전송할 수 있게 해주는 SignalR TypeScript 클라이언트.
    • Node.js 형식에 대한 컴파일 시간 검사를 가능하게 하는 Node.js에 대한 TypeScript 형식 정의.
  2. src/index.ts 파일에 강조 표시된 코드를 추가합니다.

    import "./css/main.css";
    import * as signalR from "@microsoft/signalr";
    
    const divMessages: HTMLDivElement = document.querySelector("#divMessages");
    const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
    const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
    const username = new Date().getTime();
    
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/hub")
        .build();
    
    connection.on("messageReceived", (username: string, message: string) => {
        let m = document.createElement("div");
    
        m.innerHTML =
            `<div class="message-author">${username}</div><div>${message}</div>`;
    
        divMessages.appendChild(m);
        divMessages.scrollTop = divMessages.scrollHeight;
    });
    
    connection.start().catch(err => document.write(err));
    
    tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.key === "Enter") {
            send();
        }
    });
    
    btnSend.addEventListener("click", send);
    
    function send() {
    }
    

    이 코드는 서버에서 오는 메시지의 수신을 지원합니다. HubConnectionBuilder 클래스는 서버 연결을 구성하기 위한 새 빌더를 생성합니다. withUrl 함수는 허브 URL을 구성합니다.

    SignalR은 클라이언트 및 서버 간 메시지 교환을 활성화합니다. 각 메시지는 특정 이름을 가집니다. 예를 들어 messageReceived라는 이름을 가진 메시지가 메시지 영역에 새 메시지를 표시하는 로직을 실행할 수 있습니다. 특정 메시지 수신 대기는 on 함수를 통해 수행할 수 있습니다. 임의 개수의 메시지 이름을 수신 대기할 수 있습니다. 또한 수신 메시지의 작성자 이름 및 내용 등을 메시지에 파라미터로 전달할 수도 있습니다. 클라이언트가 메시지를 수신한 후 innerHTML 특성의 작성자 이름 및 메시지 내용을 사용하여 새 div 요소가 생성됩니다. 이 요소가 주 div 요소에 추가되어 메시지를 표시합니다.

  3. 이제 클라이언트가 메시지를 수신할 수 있으므로, 메시지를 보내도록 구성합니다. src/index.ts 파일에 강조 표시된 코드를 추가합니다.

    import "./css/main.css";
    import * as signalR from "@microsoft/signalr";
    
    const divMessages: HTMLDivElement = document.querySelector("#divMessages");
    const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
    const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
    const username = new Date().getTime();
    
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/hub")
        .build();
    
    connection.on("messageReceived", (username: string, message: string) => {
        let messages = document.createElement("div");
    
        messages.innerHTML =
            `<div class="message-author">${username}</div><div>${message}</div>`;
    
        divMessages.appendChild(messages);
        divMessages.scrollTop = divMessages.scrollHeight;
    });
    
    connection.start().catch(err => document.write(err));
    
    tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.key === "Enter") {
            send();
        }
    });
    
    btnSend.addEventListener("click", send);
    
    function send() {
        connection.send("newMessage", username, tbMessage.value)
            .then(() => tbMessage.value = "");
    }
    

    WebSockets 연결을 통해 메시지를 보내려면 send 메서드를 호출해야 합니다. 이 메서드의 첫 번째 매개 변수는 메시지의 이름입니다. 메시지의 데이터는 다른 매개 변수를 통해 지정합니다. 이 예제에서는 newMessage로 식별되는 메시지가 서버로 전송됩니다. 메시지는 사용자 이름 및 텍스트 상자에 입력된 사용자 입력으로 구성됩니다. 전송이 완료되면 텍스트 상자의 값이 지워집니다.

  4. ChatHub 클래스에 NewMessage 메서드를 추가합니다.

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRWebPack.Hubs
    {
        public class ChatHub : Hub
        {
            public async Task NewMessage(long username, string message)
            {
                await Clients.All.SendAsync("messageReceived", username, message);
            }
        }
    }
    

    앞의 코드는 서버가 메시지를 수신한 후 수신된 메시지를 모든 연결된 사용자에게 브로드캐스트합니다. 모든 메시지를 수신하기 위해 일반 on 메서드를 포함할 필요는 없습니다. 메시지 이름 뒤에 명명한 메서드로 충분합니다.

    이 예에서 TypeScript 클라이언트는 newMessage로 식별된 메시지를 보냅니다. C# NewMessage 메서드에는 클라이언트가 보낸 데이터가 필요합니다. Clients.All에서 SendAsync가 호출됩니다. 수신된 메시지를 허브에 연결된 모든 클라이언트에 보냅니다.

앱 테스트

다음 단계를 사용하여 앱이 작동하는지 확인합니다.

  1. WebPack을 release 모드로 실행합니다. 패키지 관리자 콘솔 창을 사용하여 프로젝트 루트에서 다음 명령을 실행합니다. 프로젝트 루트가 아닌 경우 명령을 입력하기 전에 cd SignalRWebPack을 입력합니다.

    npm run release
    

    이 명령은 앱을 실행할 때 제공되는 클라이언트 쪽 자산을 생성합니다. 자산은 폴더에 wwwroot 배치됩니다.

    Webpack은 다음 작업을 완료했습니다.

    • 디렉터리의 콘텐츠를 wwwroot 제거했습니다.
    • Transpilation이라는 프로세스에서 TypeScript를 JavaScript로 변환했습니다.
    • 축소라고 하는 프로세스에서 파일 크기를 줄이기 위해 생성된 JavaScript를 망가트했습니다.
    • 처리된 JavaScript, CSS 및 HTML 파일을 디렉터리로 srcwwwroot 복사했습니다.
    • 파일에 다음 요소를 삽입했습니다 wwwroot/index.html .
      • wwwroot/main.<hash>.css 파일을 참조하는 <link> 태그입니다. 이 태그는 </head> 태그를 닫기 전에 즉시 배치합니다.
      • 최소화된 wwwroot/main.<hash>.js 파일을 참조하는 <script> 태그입니다. 이 태그는 닫 </title> 는 태그 바로 후에 배치됩니다.
  2. 디버그>디버그하지 않고 시작을 선택하여 디버거를 연결하지 않고 브라우저에서 앱을 시작합니다. wwwroot/index.html 파일은 http://localhost:<port_number>에서 제공됩니다.

    컴파일 오류가 발생하면 솔루션을 닫았다가 다시 열어 보세요.

  3. 다른 브라우저 인스턴스(임의 브라우저)를 엽니다. URL을 주소 표시줄에 붙여넣습니다.

  4. 브라우저를 선택하고 메시지 텍스트 상자에 내용을 입력하고 보내기 단추를 클릭합니다. 고유한 사용자 이름과 메시지는 두 페이지 모두에 즉시 표시됩니다.

두 브라우저 창에 표시되는 메시지

추가 리소스

이 자습서에서는 ASP.NET Core SignalR 웹앱에서 Webpack을 사용하여 TypeScript로 작성된 클라이언트를 묶고 빌드하는 방법을 보여 줍니다. WebPack을 사용하면 개발자가 웹 앱의 클라이언트 쪽 리소스를 번들링 및 빌드할 수 있습니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • 시작 ASP.NET Core SignalR 앱 스캐폴드
  • SignalR TypeScript 클라이언트 구성
  • WebPack을 사용하여 빌드 파이프라인 구성
  • SignalR 서버 구성
  • 클라이언트 및 서버 간 통신 활성화

샘플 코드 보기 및 다운로드(다운로드 방법)

필수 조건

ASP.NET Core 웹앱 만들기

PATH 환경 변수에서 npm을 찾도록 Visual Studio를 구성합니다. 기본적으로 Visual Studio는 설치 디렉터리에 있는 npm의 버전을 사용합니다. Visual Studio에서 다음 지침을 따릅니다.

  1. 도구>옵션>프로젝트 및 솔루션>웹 패키지 관리>외부 웹 도구로 이동합니다.

  2. 목록에서 $(PATH) 항목을 선택합니다. 위쪽 화살표를 선택하여 이 항목을 목록의 두 번째 위치로 이동합니다.

    Visual Studio 구성

Visual Studio 구성이 완료되었습니다. 이제 프로젝트를 만들어야 합니다.

  1. 파일>새로 만들기>프로젝트 메뉴 옵션을 사용하고 ASP.NET Core 웹 애플리케이션 템플릿을 선택합니다.
  2. 프로젝트 이름을 *SignalRWebPack`으로 지정하고 만들기를 선택합니다.
  3. 대상 프레임워크 드롭다운에서 .NET Core를 선택하고, 프레임워크 선택기 드롭다운에서 ASP.NET Core 2.2를 선택합니다. 템플릿을 선택하고 만들기를 선택합니다.

WebPack 및 TypeScript 구성

다음 단계에서는 TypeScript에서 JavaScript로 변환 및 클라이언트 쪽 리소스의 번들링을 구성합니다.

  1. 프로젝트 루트에서 다음 명령을 실행하여 파일을 만듭니다 package.json .

    npm init -y
    
  2. 강조 표시된 속성을 파일에 추가합니다.package.json

    {
      "name": "SignalRWebPack",
      "version": "1.0.0",
      "private": true,
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
    

    private 속성을 true로 설정하면 다음 단계에서 패키지 설치 경고가 나타나지 않습니다.

  3. 필요한 npm 패키지를 설치합니다. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm install -D -E clean-webpack-plugin@1.0.1 css-loader@2.1.0 html-webpack-plugin@4.0.0-beta.5 mini-css-extract-plugin@0.5.0 ts-loader@5.3.3 typescript@3.3.3 webpack@4.29.3 webpack-cli@3.2.3
    

    참고할 몇몇 명령 세부 정보:

    • 버전 번호는 각 패키지 이름에 대해 @ 부호 뒤에 옵니다. npm은 해당 특정 패키지 버전을 설치합니다.
    • -E 옵션은 유의적 버전 관리 범위 연산자를 *packagejson에 쓰는 npm의 기본 동작을 비활성화합니다. 예를 들어 "webpack": "^4.29.3" 대신 "webpack": "4.29.3"을 사용합니다. 이 옵션은 최신 패키지 버전으로 의도하지 않은 업그레이드를 방지합니다.

    자세한 내용은 npm-install 문서를 참조하세요.

  4. 파일의 scriptspackage.json 속성을 다음 코드로 바꿉다.

    "scripts": {
      "build": "webpack --mode=development --watch",
      "release": "webpack --mode=production",
      "publish": "npm run release && dotnet publish -c Release"
    },
    

    스크립트에 대한 간략한 설명:

    • build: 개발 모드에서 클라이언트 쪽 리소스를 번들로 묶고 파일 변경 내용을 감시합니다. 파일 감시자는 프로젝트 파일이 변경될 때마다 번들이 다시 생성되도록 합니다. mode 옵션은 프로덕션 최적화(예: 트리 셰이킹 및 축소)를 비활성화합니다. 개발 시에는 build만 사용합니다.
    • release: 프로덕션 모드에서 클라이언트 쪽 리소스를 번들로 묶습니다.
    • publish: 프로덕션 모드에서 release 스크립트를 실행하여 클라이언트 쪽 리소스를 번들링합니다. 이는 .NET Core CLI의 publish 명령을 호출하여 앱을 게시합니다.
  5. 프로젝트 루트에 다음 코드를 포함한 *webpack.config.js라는 파일을 생성합니다.

    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    const CleanWebpackPlugin = require("clean-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
    module.exports = {
        entry: "./src/index.ts",
        output: {
            path: path.resolve(__dirname, "wwwroot"),
            filename: "[name].[chunkhash].js",
            publicPath: "/"
        },
        resolve: {
            extensions: [".js", ".ts"]
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    use: "ts-loader"
                },
                {
                    test: /\.css$/,
                    use: [MiniCssExtractPlugin.loader, "css-loader"]
                }
            ]
        },
        plugins: [
            new CleanWebpackPlugin(["wwwroot/*"]),
            new HtmlWebpackPlugin({
                template: "./src/index.html"
            }),
            new MiniCssExtractPlugin({
                filename: "css/[name].[chunkhash].css"
            })
        ]
    };
    

    앞의 파일은 WebPack 컴파일을 구성합니다. 참고할 일부 구성 세부 정보:

    • 속성의 output 기본값을 재정의 합니다 dist. 대신 번들은 디렉터리에서 wwwroot 내보내집니다.
    • 배열에는 resolve.extensions 클라이언트 JavaScript를 가져오는 것이 SignalR 포함됩니다.js.
  6. 프로젝트 루트에 새 src 디렉터리를 만들어 프로젝트의 클라이언트 쪽 자산을 저장합니다.

  7. 다음 태그를 사용하여 만듭니 src/index.html 다.

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>ASP.NET Core SignalR</title>
    </head>
    <body>
        <div id="divMessages" class="messages">
        </div>
        <div class="input-zone">
            <label id="lblMessage" for="tbMessage">Message:</label>
            <input id="tbMessage" class="input-zone-input" type="text" />
            <button id="btnSend">Send</button>
        </div>
    </body>
    </html>
    

    앞의 HTML은 홈 페이지의 상용구 마크업을 정의합니다.

  8. src/css 디렉터리를 생성합니다. 그 목적은 프로젝트의 파일을 저장하는 것입니다 .css .

  9. 다음 태그를 포함한 src/css/main.css를 생성합니다.

    *, *::before, *::after {
        box-sizing: border-box;
    }
    
    html, body {
        margin: 0;
        padding: 0;
    }
    
    .input-zone {
        align-items: center;
        display: flex;
        flex-direction: row;
        margin: 10px;
    }
    
    .input-zone-input {
        flex: 1;
        margin-right: 10px;
    }
    
    .message-author {
        font-weight: bold;
    }
    
    .messages {
        border: 1px solid #000;
        margin: 10px;
        max-height: 300px;
        min-height: 300px;
        overflow-y: auto;
        padding: 5px;
    }
    

    위의 main.css 파일은 앱의 스타일을 지정합니다.

  10. 다음 JSON으로 src/tsconfig.json을(를) 만듭니다.

    {
      "compilerOptions": {
        "target": "es5"
      }
    }
    

    이 코드는 ECMAScript 5 호환 JavaScript를 생성하도록 TypeScript 컴파일러를 구성합니다.

  11. 다음 코드로 src/index.ts를 만듭니다.

    import "./css/main.css";
    
    const divMessages: HTMLDivElement = document.querySelector("#divMessages");
    const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
    const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
    const username = new Date().getTime();
    
    tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.keyCode === 13) {
            send();
        }
    });
    
    btnSend.addEventListener("click", send);
    
    function send() {
    }
    

    이 TypeScript는 DOM 요소 참조를 조회하여 두 가지 이벤트 핸들러를 연결합니다.

    • keyup: 사용자가 텍스트 상자에 입력 tbMessage 할 때 이 이벤트가 발생합니다. send 함수는 사용자가 Enter 키를 누를 때 호출됩니다.
    • click: 이 이벤트는 사용자가 보내기 단추를 선택할 때 발생합니다. send 함수가 호출됩니다.

ASP.NET Core 앱 구성

  1. Startup.Configure 메서드에 제공된 코드는 Hello World!를 표시합니다. app.Run 메서드 호출을 UseDefaultFiles(IApplicationBuilder)UseStaticFiles(IApplicationBuilder) 호출로 바꿉니다.

    app.UseDefaultFiles();
    app.UseStaticFiles();
    

    위의 코드를 사용하면 사용자가 웹앱의 전체 URL 또는 루트 URL을 입력하는지 여부에 관계없이 서버에서 파일을 찾아서 제공할 index.html 수 있습니다.

  2. AddSignalR에서 Startup.ConfigureServices를 호출합니다. 그러면 SignalR 서비스가 프로젝트에 추가됩니다.

    services.AddSignalR();
    
  3. /hub 경로를 ChatHub 허브에 매핑합니다. Startup.Configure 끝에 다음 줄을 추가합니다.

    app.UseSignalR(options =>
    {
        options.MapHub<ChatHub>("/hub");
    });
    
  4. 프로젝트 루트에 Hubs라는 새 디렉터리를 생성합니다. 그 목적은 다음 단계에서 생성되는 SignalR 허브를 저장하는 것입니다.

  5. 다음 코드를 사용하여 허브 Hubs/ChatHub.cs 를 만듭니다.

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRWebPack.Hubs
    {
        public class ChatHub : Hub
        {
        }
    }
    
  6. ChatHub 참조를 확인하기 위해 Startup.cs 파일의 맨 위에 다음 코드를 추가합니다.

    using SignalRWebPack.Hubs;
    

클라이언트 및 서버 통신 활성화

앱은 현재 메시지를 보낼 간단한 양식을 표시합니다. 그러한 시도에서는 아무 작업도 수행되지 않습니다. 서버는 특정 경로를 수신 대기하지만 보낸 메시지를 사용하여 아무 작업도 하지 않습니다.

  1. 프로젝트 루트에서 다음 명령을 실행합니다.

    npm install @aspnet/signalr
    

    이 명령은 클라이언트에서 서버로 메시지를 전송할 수 있게 해주는 SignalR TypeScript 클라이언트를 설치합니다.

  2. src/index.ts 파일에 강조 표시된 코드를 추가합니다.

    import "./css/main.css";
    import * as signalR from "@aspnet/signalr";
    
    const divMessages: HTMLDivElement = document.querySelector("#divMessages");
    const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
    const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
    const username = new Date().getTime();
    
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/hub")
        .build();
    
    connection.on("messageReceived", (username: string, message: string) => {
        let m = document.createElement("div");
    
        m.innerHTML =
            `<div class="message-author">${username}</div><div>${message}</div>`;
    
        divMessages.appendChild(m);
        divMessages.scrollTop = divMessages.scrollHeight;
    });
    
    connection.start().catch(err => document.write(err));
    
    tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.keyCode === 13) {
            send();
        }
    });
    
    btnSend.addEventListener("click", send);
    
    function send() {
    }
    

    이 코드는 서버에서 오는 메시지의 수신을 지원합니다. HubConnectionBuilder 클래스는 서버 연결을 구성하기 위한 새 빌더를 생성합니다. withUrl 함수는 허브 URL을 구성합니다.

    SignalR은 클라이언트 및 서버 간 메시지 교환을 활성화합니다. 각 메시지는 특정 이름을 가집니다. 예를 들어 messageReceived라는 이름을 가진 메시지가 메시지 영역에 새 메시지를 표시하는 로직을 실행할 수 있습니다. 특정 메시지 수신 대기는 on 함수를 통해 수행할 수 있습니다. 임의 개수의 메시지 이름을 수신 대기할 수 있습니다. 또한 수신 메시지의 작성자 이름 및 내용 등을 메시지에 파라미터로 전달할 수도 있습니다. 클라이언트가 메시지를 수신한 후 innerHTML 특성의 작성자 이름 및 메시지 내용을 사용하여 새 div 요소가 생성됩니다. 메시지를 표시하는 주 div 요소에 새 메시지가 추가됩니다.

  3. 이제 클라이언트가 메시지를 수신할 수 있으므로, 메시지를 보내도록 구성합니다. src/index.ts 파일에 강조 표시된 코드를 추가합니다.

    import "./css/main.css";
    import * as signalR from "@aspnet/signalr";
    
    const divMessages: HTMLDivElement = document.querySelector("#divMessages");
    const tbMessage: HTMLInputElement = document.querySelector("#tbMessage");
    const btnSend: HTMLButtonElement = document.querySelector("#btnSend");
    const username = new Date().getTime();
    
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/hub")
        .build();
    
    connection.on("messageReceived", (username: string, message: string) => {
        let messageContainer = document.createElement("div");
    
        messageContainer.innerHTML =
            `<div class="message-author">${username}</div><div>${message}</div>`;
    
        divMessages.appendChild(messageContainer);
        divMessages.scrollTop = divMessages.scrollHeight;
    });
    
    connection.start().catch(err => document.write(err));
    
    tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
        if (e.keyCode === 13) {
            send();
        }
    });
    
    btnSend.addEventListener("click", send);
    
    function send() {
        connection.send("newMessage", username, tbMessage.value)
                  .then(() => tbMessage.value = "");
    }
    

    WebSockets 연결을 통해 메시지를 보내려면 send 메서드를 호출해야 합니다. 이 메서드의 첫 번째 매개 변수는 메시지의 이름입니다. 메시지의 데이터는 다른 매개 변수를 통해 지정합니다. 이 예제에서는 newMessage로 식별되는 메시지가 서버로 전송됩니다. 메시지는 사용자 이름 및 텍스트 상자에 입력된 사용자 입력으로 구성됩니다. 전송이 완료되면 텍스트 상자의 값이 지워집니다.

  4. ChatHub 클래스에 NewMessage 메서드를 추가합니다.

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRWebPack.Hubs
    {
        public class ChatHub : Hub
        {
            public async Task NewMessage(long username, string message)
            {
                await Clients.All.SendAsync("messageReceived", username, message);
            }
        }
    }
    

    앞의 코드는 서버가 메시지를 수신한 후 수신된 메시지를 모든 연결된 사용자에게 브로드캐스트합니다. 모든 메시지를 수신하기 위해 일반 on 메서드를 포함할 필요는 없습니다. 메시지 이름 뒤에 명명한 메서드로 충분합니다.

    이 예에서 TypeScript 클라이언트는 newMessage로 식별된 메시지를 보냅니다. C# NewMessage 메서드에는 클라이언트가 보낸 데이터가 필요합니다. Clients.All에서 SendAsync가 호출됩니다. 수신된 메시지를 허브에 연결된 모든 클라이언트에 보냅니다.

앱 테스트

다음 단계를 사용하여 앱이 작동하는지 확인합니다.

  1. WebPack을 release 모드로 실행합니다. 패키지 관리자 콘솔 창을 사용하여 프로젝트 루트에서 다음 명령을 실행합니다. 프로젝트 루트가 아닌 경우 명령을 입력하기 전에 cd SignalRWebPack을 입력합니다.

    npm run release
    

    이 명령은 앱을 실행할 때 제공되는 클라이언트 쪽 자산을 생성합니다. 자산은 폴더에 wwwroot 배치됩니다.

    Webpack은 다음 작업을 완료했습니다.

    • 디렉터리의 콘텐츠를 wwwroot 제거했습니다.
    • Transpilation이라는 프로세스에서 TypeScript를 JavaScript로 변환했습니다.
    • 축소라고 하는 프로세스에서 파일 크기를 줄이기 위해 생성된 JavaScript를 망가트했습니다.
    • 처리된 JavaScript, CSS 및 HTML 파일을 디렉터리로 srcwwwroot 복사했습니다.
    • 파일에 다음 요소를 삽입했습니다 wwwroot/index.html .
      • wwwroot/main.<hash>.css 파일을 참조하는 <link> 태그입니다. 이 태그는 </head> 태그를 닫기 전에 즉시 배치합니다.
      • 최소화된 wwwroot/main.<hash>.js 파일을 참조하는 <script> 태그입니다. 이 태그는 닫 </title> 는 태그 바로 후에 배치됩니다.
  2. 디버그>디버그하지 않고 시작을 선택하여 디버거를 연결하지 않고 브라우저에서 앱을 시작합니다. wwwroot/index.html 파일은 http://localhost:<port_number>에서 제공됩니다.

  3. 다른 브라우저 인스턴스(임의 브라우저)를 엽니다. URL을 주소 표시줄에 붙여넣습니다.

  4. 브라우저를 선택하고 메시지 텍스트 상자에 내용을 입력하고 보내기 단추를 클릭합니다. 고유한 사용자 이름과 메시지는 두 페이지 모두에 즉시 표시됩니다.

두 브라우저 창에 표시되는 메시지

추가 리소스