ASP.NET Core 8.0의 기타 유용한 기능들

  • 48 minutes to read

이번 강의에서는 ASP.NET Core의 유용한 기능이면서 자주 사용되는 기능인 강력한 형식의 환경 설정 및 파일 업로드 그리고 로깅에 대한 내용을 살펴보겠습니다.

1. 소개

이번 강의에서는 ASP.NET Core에서 제공하는 유용한 기능 몇 가지를 소개하겠습니다.

2. 강력한 형식의 환경 설정

ASP.NET Core로 오면서 데이터베이스 연결 문자열처럼 자주 사용되는 정보는 프로젝트 루트에 있는 appsettings.json 파일에서 주로 관리합니다. 이러한 JSON 파일의 정보는 문자열 정보로 취급하는데 JSON의 데이터를 C#의 클래스 형태로 받아서 사용할 수 있습니다. 이러한 기능을 강력한 형식(Strongly Typed)의 환경 설정(Configuration Setting)이라고 부릅니다.

(1)

// appsettings.json
{
  "AppKeys": {
    "AzureStorageAccount": "",
    "AzureStorageAccessKey": "",
    "AzureStorageContainer": "",
    "SendGridKey":  ""
  },

  "ConnectionStrings": {
    "DefaultConnection": 
      "Server=(localdb)\\mssqllocaldb;Database=Hawaso;Trusted_Connection=True;"
  },

  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },

  "AllowedHosts": "*"
}

(2)

// /Settings/AppKeyConfig.cs
namespace Hawaso.Settings
{
    public class AppKeyConfig
    {
        public string AzureStorageContainer { get; set; }
        public string AzureStorageAccount { get; set; }
        public string AzureStorageAccessKey { get; set; }
        public string SendGridKey { get; set; }
    }
}

(3)

// Startup.cs 
public void ConfigureServices(IServiceCollection services)
{
    // <AppKeyConfig Binding>
    services.Configure<AppKeyConfig>(Configuration.GetSection("AppKeys"));
    // </AppKeyConfig Binding>

(4)

// /Controllers/AppKeysController.cs
using Hawaso.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace Hawaso.Controllers
{
    public class AppKeysController : Controller
    {
        private readonly AppKeyConfig appKeyConfig;

        public AppKeysController(IOptions<AppKeyConfig> appKeyConfig)
        {
            this.appKeyConfig = appKeyConfig.Value;
        }

        public IActionResult Index()
        {
            ViewData["AzureStorageAccount"] = appKeyConfig.AzureStorageAccount;
            return View();
        }
    }
}

(5)

// /Views/AppKeys/Index.cshtml
@ViewData["AzureStorageAccount"]

(6) 출력: (appsettings.json 파일에 저장된 값 출력) Acount1234567890 </추가>

3. [실습] ASP.NET Core에서 강력한 형식의 환경 설정

소개

ASP.NET Core 웹 응용프로그램에서 JSON 파일에 들어 있는 데이터를 강력한 형식인 클래스 기반의 개체로 접근해서 사용하는 방식을 강력한 형식의 환경 설정이라고 하는데 이 방법을 살펴보겠습니다.

따라하기 1: 강력한 형식의 환경 설정 구성

(1) Visual Studio를 열고 C:\ASP.NET\DotNetNote 프로젝트를 실행합니다.

(2) 강력한 형식의 환경 설정을 하려면 NuGet 패키지 관리자로 다음 패키지를 추가해야 하는데 웹 응용프로그램 템플릿에는 이미 추가되어 있는 상태입니다. project.json 파일을 열고 다음 패키지가 추가되어 있는지 확인만 합니다. "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0"

(3) DotNetNote 웹 프로젝트에 Settings 이름으로 폴더를 생성합니다. JSON 파일과 CS 파일을 프로젝트 루트에 놓아도 되겠지만, 따로 사용자 정의 환경 설정 파일을 연습하기 위해 Settings 폴더로 구분합니다.

(4) Settings 폴더에 <추가 > 새 항목> 메뉴를 사용하여 JSON 파일을 다음과 같이 생성합니다.

// /Settings/DotNetNoteSettings.json
{
    "DotNetNoteSettings": {
      "SiteName": "DotNetNote",
      "SiteAdmin":  "Admin"
    }
}

(5) Settings 폴더에 모델 클래스 또는 POCO(Plain Old CLR Object) 클래스라 불리는 단순 클래스 파일을 다음과 같이 생성합니다. 위에 있는 JSON 파일이 이 클래스의 인스턴스에 담길 것입니다.

// /Settings/DotNetNoteSettings.cs
namespace DotNetNote.Settings
{
    // POCO(Plain Old CLR Object) 클래스
    public class DotNetNoteSettings
    {
        public string SiteName { get; set; } = "DotNetNote";
        public string SiteUrl { get; set; } = "http://www.dotnetnote.com";
        public string SiteAdmin { get; set; } = "Admin"; // 관리자 아이디 지정
    }
}

(6) 프로젝트 루트에 있는 Startup.cs 파일을 열고, 네임스페이스 선언부에 다음 코드를 한 줄 추가하면 DotNetNoteSettings 클래스를 사용할 수 있습니다.

// Startup.cs 파일에 코드 한 줄 추가
using DotNetNote.Models;
using DotNetNote.Services;
using DotNetNote.Settings;
using Microsoft.AspNetCore.Builder;

(7) Startup.cs 파일의 생성자에는 ConfigurationBuilder 클래스를 사용하여 appsettings.json에 들어 있는 기본 설정 데이터를 읽어오는 코드가 포함되어 있습니다. 이 코드에 AddJsonFile 메서드를 사용하여 Settings 폴더의 JSON 파일을 읽어 오는 코드를 추가합니다.

// Startup.cs 파일의 생성자
public Startup(IWebHostEnvironment env)
{
    // [!] Configuration 
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        // [!] Configuration : Strongly Typed Configuration Setting
        //    추가 환경 설정 파일 지정
        .AddJsonFile($"Settings\\DotNetNoteSettings.json", optional: true) 
        .AddEnvironmentVariables();

    Configuration = builder.Build();
}

(8) Startup.cs 파일의 ConfigureServices() 메서드에 다음 코드를 삽입하여 JSON 파일의 데이터를 POCO 클래스인 DotNetNoteSettings에 주입합니다.

// Startup.cs 파일의 ConfigureServices() 메서드 일부 
public void ConfigureServices(IServiceCollection services)
{

    // [!] Configuration: JSON 파일의 데이터를 POCO 클래스에 주입
    services.Configure<DotNetNoteSettings>(
        Configuration.GetSection("DotNetNoteSettings"));

    services.AddMvc();
}

따라하기 2: 강력한 형식의 환경 설정 사용

(1) 강력한 형식의 환경 설정을 사용해 보기 위해서 새로운 컨트롤러 클래스를 생성합니다. Controllers 폴더에 StronglyTypedConfigurationController.cs 이름으로 MVC 컨트롤러 클래스를 생성합니다. 기본 생성된 코드는 다음과 같습니다.

// StronglyTypedConfigurationController.cs
using Microsoft.AspNetCore.Mvc;
 
namespace DotNetNote.Controllers
{
    public class StronglyTypedConfigurationController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

(2) StronglyTypedConfigurationController.cs 컨트롤러 클래스에서 IOptions<T> 형태로 생성자 매개변수를 받아서 강력한 형식으로 구성 정보를 가져다 사용할 수 있습니다. StronglyTypedConfigurationController.cs 파일의 내용을 다음과 같이 작성합니다. 코드의 Index 액션 메서드에서는 _dnnSettings 개체에 점을 찍으면 자동으로 SiteName과 SiteUrl 정보를 인텔리센스로 제공받을 수 있습니다.

// StronglyTypedConfigurationController.cs 파일의 생성자에 DotNetNoteSettings 클래스 주입
using DotNetNote.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
 
namespace DotNetNote.Controllers
{
    public class StronglyTypedConfigurationController : Controller
    {
        // 강력한 형식의 클래스의 인스턴스 생성
        private DotNetNoteSettings _dnnSettings;
        public StronglyTypedConfigurationController(
            IOptions<DotNetNoteSettings> options)
        {
            _dnnSettings = options.Value;
        }
 
        public IActionResult Index()
        {
            // 뷰 페이지로 전송
            ViewData["SiteName"] = _dnnSettings.SiteName;
            ViewBag.SiteUrl = _dnnSettings.SiteUrl;
 
            return View();
        }
    }
}

(3) 컨트롤러에서 전송한 환경 설정 정보 두 개를 뷰 페이지에서 테스트해보겠습니다. Views 폴더에 StronglyTypedConfiguration 이름으로 폴더를 생성하고 Index.cshtml 이름으로 뷰 페이지를 생성하고 다음과 같이 코드를 작성합니다.

// Views/StronglyTypedConfiguration/Index.cshtml
@{ 
    Layout = null;
}
 
@{
    ViewData["Title"] = ViewData["SiteName"];
}
 
@ViewBag.SiteName
<text>(</text>@ViewBag.SiteUrl@(")") 사이트에 오신 걸 환영합니다.

(4) DotNetNote 프로젝트를 실행 후 웹 브라우저 주소 창에 /StronglyTypedConfiguration/Index 경로를 요청하면 다음과 같이 실행됩니다. 강력한 형식의 환경 설정을 사용하면 JSON 파일의 데이터를 C# 개체에 담아서 사용할 수 있어 이 값을 뷰 페이지의 특정 영역에 출력할 수 있습니다.

그림 31 1ㅡ강력한 형식의 환경 설정을 뷰 페이지에서 사용

마무리

웹 사이트 전체에서 공통적으로 사용되는 데이터는 JSON 파일에 넣어 놓고 관리합니다. 이때 매번 JSON 파일의 데이터를 읽어서 사용할 수도 있고, 이번 실습처럼 C# 개체에 담아 놓고 사용할 수도 있습니다.

4. 파일 업로드

ASP.NET Core에서는 컴퓨터의 파일을 웹 응용프로그램이 서비스되는 서버 또는 Azure Blob 저장소 같은 클라우드 스토리지 서비스 또는 Entity Framework Core 등을 사용하여 SQL 데이터베이스에 직접 업로드하는 기능을 제공합니다.

4.1. .NET에서 파일 업로드

4.1.1. 서버: 로컬 또는 원격 서버의 폴더

4.1.2. 데이터베이스 컬럼: byte[]

4.1.3. BLOB: Azure Blob 저장소 등

4.2. 서버(로컬/원격)에 직접 업로드

일반적으로 닷넷 실행 코드가 게시되어 실행되는 wwwroot 폴더에 files, uploads, photos 등과 같은 폴더를 만들고 쓰기 권한을 주고 해당 폴더에 파일들을 업로드하는 방식을 사용할 수 있습니다. 이 방법이 가장 편하고 시작하기에는 좋습니다.

4.3. 데이터베이스 컬럼에 byte 배열로 업로드

게시된 소스 코드와 데이터베이스의 딱 2개의 관심사만 가지고 앱을 운영하고자할 때에는 데이터베이스에 모든 데이터와 파일을 몰아 넣을 수 있습니다. 하지만, 이 방법은 데이터베이스를 엄청 괴롭히고 대용량 파일 처리에는 적합하지 않습니다. 강의에서는 이 방법을 추천하지도 않고 전혀 사용하지 않습니다.

4.4. BLOB 저장소에 업로드

현대적인 앱은 앱 실행 소스, 데이터베이스, 파일과 이메일 등의 서비스를 엄격히 구분해야 합니다. 데이터베이스처럼 어딘가에 중앙에 데이터베이스를 구성하듯 파일들도 Azure Blob 저장소를 구성하고 이곳에 모아 놓고 추후 CDN까지 확장해 가면 어떠한 경우의 파일 처리에 대한 내용을 모두 감당할 수 있습니다. Blob에 대한 업로드 및 내려받기에 대한 간단한 샘플 코드는 다음 링크를 참고하세요. https://github.com/VisualAcademy/DotNetNoteChat/blob/master/DotNetNote/Services/BlobStorageManager.cs

4.5. 마무리

강의에서 파일 처리에 대한 시작은 파일 업로드는 대부분 로컬 서버의 wwwroot 폴더에 파일을 업로드하고 이곳에서 다운로드합니다. 하지만, 실제 운영하는 소스는 모두 Azure Blob Storage를 사용하여 하나 이상의 저장소 컨테이너를 만들고 이곳에 파일을 업로드 및 이곳으로부터 다운로드가 되도록 코드가 작성되어야 합니다. 참고로, 데이터베이스와 마찬가지로 Azure Blob Storage와 같은 서비스들은 C#, Java, Python, Node 등의 대부분의 모든 프로그래밍 언어에 대한 API를 제공합니다. 참고로, 강의에서도 로컬에 업로드 후 클라우드 저장소에 게시하는 목적으로 진행되는 강의는 모두 Blob 저장소를 사용하여 진행이됩니다.

5. [실습] ASP.NET Core에서 파일 업로드 기능 구현

5.1. 소개

ASP.NET Core MVC에서 파일 업로드 기능을 구현하는 방식을 살펴보겠습니다.

5.2. 따라하기

(1) DotNetNote 프로젝트에서 FileDemoController.cs 파일로 컨트롤러를 생성하고 다음과 같이 코드를 작성합니다. 생성자에 IWebHostEnvironment 인터페이스를 매개변수로 전달 받아 웹 프로젝트의 wwwroot 폴더에 대한 물리적인 경로를 받을 수 있도록 설정합니다. FileUploadDemo 액션 메서드를 하나 만들고, 이에 대한 HttpGet과 HttpPost 메서드를 구분합니다. 전체 소스는 다음과 같습니다.

// Controllers/FileDemoController.cs 
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
 
namespace DotNetNote.Controllers
{
    public class FileDemoController : Controller
    {
        private IWebHostEnvironment _environment;
 
        public FileDemoController(IWebHostEnvironment environment)
        {
            _environment = environment;
        }
 
        /// <summary>
        /// 파일 업로드 폼 
        /// </summary>
        [HttpGet]
        public IActionResult FileUploadDemo()
        {
            return View();
        }
 
        /// <summary>
        /// 파일 업로드 처리
        /// </summary>
        [HttpPost]
        public async Task<IActionResult> FileUploadDemo(
            ICollection<IFormFile> files)
        {
            var uploadFolder = Path.Combine(_environment.WebRootPath, "files");
 
            foreach (var file in files)
            {
                if (file.Length > 0)
                {
                    var fileName = Path.GetFileName(
                        ContentDispositionHeaderValue.Parse(
                            file.ContentDisposition).FileName.Trim('"'));
 
                    using (var fileStream = new FileStream(
                        Path.Combine(uploadFolder, fileName), FileMode.Create))
                    {
                        await file.CopyToAsync(fileStream);
                    }
                }
            }
 
            return View();
        }
 
        /// <summary>
        /// 파일 다운로드 처리 데모
        /// </summary>
        public FileResult FileDownloadDemo(string fileName = "Test.txt")
        {
            byte[] fileBytes = System.IO.File.ReadAllBytes(
                Path.Combine(
                    _environment.WebRootPath, "files") + "\\" + fileName);
                        
            return File(fileBytes, "application/octet-stream", fileName);
        }
    }
}

(2) wwwroot 폴더에 files 폴더를 생성합니다.

(3) Views 폴더에 컨트롤러 이름으로 FileDemo 폴더를 생성합니다. 이곳에 액션 메서드 이름으로 FileUploadDemo.cshtml 뷰 페이지를 생성하고, 다음과 같이 코드를 작성합니다. 파일 업로드 폼을 구성하기 위한 폼 페이지를 다음과 같이 작성합니다. 파일을 업로드하기 위해서는 반드시 enctype 속성을 “multipart/form-data”로 설정해야 합니다. file 필드를 하나만 두는 간단한 폼으로 구성했습니다.

// Views/FileDemo/FileUploadDemo.cshtml
@{ 
    Layout = null;
}
 
<form method="post" asp-controller="FileDemo" asp-action="FileUploadDemo" 
      enctype="multipart/form-data">
    <input type="file" name="files" multiple />
    <input type="submit" value="업로드" />
</form>

(4) 웹 프로젝트를 실행하고 /FileDemo/FileuploadDemo로 경로를 요청하면 다음과 같이 파일 업로드 폼이 나타난다. 파일을 첨부하고 <업로드> 버튼을 클릭하면 프로젝트의 wwwroot/files 폴더로 업로드가 진행됩니다.

그림 31 2 파일업로드 폼 실행

(5) files 폴더에서 파일이 정상적으로 업로드 되었음을 확인할 수 있습니다. 그림 31 3 업로드된 파일 확인

(6) FileDownloadDemo 액션 메서드는 매개변수로 fileName을 받습니다. fileName에 files 폴더에 업로드된 파일을 요청하면 다음과 같이 해당 파일을 내려받습니다.

그림 31 4 파일 다운로드

5.3. 마무리

이번 실습에서는 ASP.NET Core에서 파일을 업로드하고 내려받는 가장 기본이 되는 코드를 살펴보았습니다. 이 내용은 뒤에서 게시판 만들기 프로젝트에서 다시 한 번 사용할 것입니다.

5.4. ASP.NET Core에서 파일 업로드

5.4.1. 모델 바인딩을 사용하여 작은 파일 업로딩

5.4.2. 스트리밍을 사용하여 큰 파일 업로딩

https://learn.microsoft.com/ko-kr/aspnet/core/mvc/models/file-uploads

6. 로깅

로그(Log), 로깅(Logging)은 무언가 정보를 남긴다는 의미다. ASP.NET Core에서는 ILogger 개체를 사용하여 기본 제공 로깅 기능을 사용할 수 있습니다.

6.1. ASP.NET Core 기본 내장 로깅 기능

로깅을 사용하면 개발 당시의 문제점을 쉽게 찾아낼 수 있습니다. ASP.NET Core에 자체 내장되어 있는 로깅 시스템을 사용하면 비즈니스 로직에 집중할 수 있습니다.

6.2. 로깅하는 법

로깅을 적용하고자 하는 컨트롤러의 생성자에 ILogger<T>의 인스턴스를 생성합니다.

private ILogger<HomeController> _logger;
 
public HomeController(ILogger<HomeController> logger)
{
    _logger = logger;
}

이렇게 생성된 로그 개체로 로깅이 필요한 영역에서 LogInformation() 메서드 같이 미리 준비된 메서드를 호출하면 디버그 창 및 콘솔 창에 로깅 정보가 출력됩니다. Index 액션 메서드에서 로그를 기록하는 코드 샘플은 다음과 같습니다.

public IActionResult Index()
{
    _logger.LogInformation("Index 액션 실행시간: {time}", DateTime.Now);
    return View();
}

7. [실습] 페이지 실행 시간을 로그로 기록하기

7.1. 소개

ASP.NET Core에 내장된 로깅 관련 기능을 사용해서 특정 페이지 실행 시 시간을 로그로 살펴보겠습니다.

7.2. 따라하기

(1) DotNetNote 프로젝트의 Controllers 폴더에 LoggingDemoController.cs 이름으로 컨트롤러를 생성하고 다음과 같이 코드를 작성합니다.

// Controllers/LoggingDemoController.cs 
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
 
namespace DotNetNote.Controllers
{
    public class LoggingDemoController : Controller
    {
        private ILogger<LoggingDemoController> _logger;
 
        public LoggingDemoController(ILogger<LoggingDemoController> logger)
        {
            _logger = logger; // 생성자 주입 방식으로 ILogger 개체 사용
        }
 
        public IActionResult Index()
        {
            // Index 페이지 실행 시 로그의 Info 범주에 문자열과 시간 출력
            _logger.LogInformation("Index View {time}", DateTime.Now);
            return View();
        }
 
        public IActionResult About()
        {
            // About 페이지 실행 시 로그의 Info 범주에 문자열과 시간 출력
            _logger.LogInformation("About View {time}", DateTime.Now);
 
            return View();
        }
 
    }
}

(2) 간단히 실행을 테스트할 목적으로 Views 폴더에 LoggingDemo 폴더를 생성하고 Index 뷰 페이지를 다음과 같이 작성합니다.

// Views/LoggingDemo/Index.cshtml 
@{ 
    Layout = null;
}
 
<h3>LoggingDemo 컨트롤러의 Index 뷰 페이지</h3>

(3) 역시 테스트 목적으로 About 뷰 페이지를 작성합니다.

// Views/LoggingDemo/About.cshtml 
@{ 
    Layout = null;
}
 
<h3>LoggingDemo 컨트롤러의 About 뷰 페이지</h3>

(4) Visual Studio에서 기본 제공 IIS Express가 아닌 Kestrel 웹 서버를 실행합니다. 다음과 같이 현재 프로젝트를 선택하고 [F5]를 눌러 웹 프로젝트를 실행합니다. 이렇게 하면 웹 브라우저와 함께 콘솔 창이 함께 실행됩니다. 그림 31 5 IIS Express가 아닌 Kestrel 웹 서버로 실행

(5) /LoggingDemo/Index 페이지를 요청하면 다음과 같이 실행됩니다. Index 경로는 생략해도 되기에 /LoggingDemo 경로만 요청해도 똑같이 표현됩니다. 그림 31 6 LoggingDemo 컨트롤러의 Index 뷰 페이지

(6) 같이 실행된 콘솔 창을 잘 살펴보면 다음과 같이 “Index View {시간}” 형태로 info 범주에 로그가 남는 걸 볼 수 있습니다. 그림 31 7 Index 뷰 페이지 실행 정보

(7) /LoggingDemo/About 경로를 요청하여 About 뷰 페이지를 실행합니다. 그림 31 8 LoggingDemo 컨트롤러의 About 뷰 페이지

(8) About 뷰 페이지가 실행되는 순간 콘솔 창에 다음과 같이 실행 시간이 출력됩니다. 그림 31 9 About 뷰 페이지 실행 정보

7.3. 마무리

간단한 실습으로 기본 내장된 로깅 기능을 살펴보았습니다. 만약 더 많은 정보를 확인하려면 Serilog 같은 외부 확장 로깅 패키지를 사용하면 됩니다.

8. 로깅 미들웨어와 써드파티 로깅

8.1. 기본 프로바이더

  • Console
  • Debug
  • EventSource
  • EventLog
  • TraceSource
  • Azure App Service

8.2. 로그 레벨(Log Levels)

  • Trace
  • Debug
  • Information
  • Warning
  • Error
  • Critical

8.3. ILogger의 기타 옵션

  • Log Category: 로그 그룹핑
  • Log Event ID
  • 포맷 스트링

8.4. Third-Party 로깅: Serilog

데이터베이스 기반이 아닌 텍스트 기반의 로깅을 구현할 때에는 Serilog가 유용합니다.

8.4.1. Serilog 어셈블리 참조

프로젝트에 Serilog 관련 다음 3개의 어셈블리를 참조합니다.

  • Serilog
  • Serilog.Extensions.Logging
  • Serilog.Sinks.File

[그림] Serilog 관련 4개 패키지 추가

다음 코드는 DotNetNote 프로젝트의 자료가 최신입니다.

// Startup.cs 파일에 Serilog 설정: 프로젝트 루트의 Logs에 텍스트 파일로 저장
// ## 8.4. Serilog
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "DnnLogs-{Date}.txt"))
    .CreateLogger();
loggerFactory.AddSerilog();
// Logs 폴더에 로그 파일 저장
// ## 8.4. Serilog
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.File(Path.Combine(env.ContentRootPath, "DnsLogs-{Date}.txt"))
    .CreateLogger();
loggerFactory.AddSerilog();
// 홈페이지에서 로깅 설정 코드 샘플
public class HomeController : Controller
{
    private ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogInformation("Starting Index.cshtml");

        return View();
    }
…
// 게시판 리스트 페이지에 로깅 설정 코드 샘플 
private ILogger<DotNetNoteController> _logger; // 기본 제공 로깅

        public DotNetNoteController(
            ILogger<DotNetNoteController> logger
            )
        {
            _logger = logger;
        }


        /// <summary>
        /// 게시판 리스트 페이지
        /// </summary>
        public IActionResult Index()
        {
            // 로깅
            _logger.LogInformation("게시판 리스트 페이지 로딩"); ``` 


## 9. ASP.NET Core에서 인-메모리 캐싱 사용하기

ASP.NET Core에서 인-메모리 캐싱 사용하기

캐싱 소개

캐싱은 콘텐츠를 생성하는 데 필요한 작업을 줄임으로써 앱의 성능을 크게 향상시킬 수 있습니다. 캐싱은 자주 변경되지 않는 데이터를 처리하는데 아주 유용합니다. 캐싱을 사용하면 원본 데이터를 더 빨리 읽어올 수 있는 데이터 복사본을 만들 수 있습니다. 단, 개발할 때에는 캐시 된 데이터에 의존하지 않도록 앱을 작성하고 테스트해야 합니다.

ASP.NET Core는 여러 가지 캐시를 지원하는데, 가장 간단한 캐시는 웹 서버의 메모리에 저장되는 인-메모리 캐시인 IMemoryCache를 사용합니다. 여러 서버로 구성된 서버 팜에서 실행되는 앱은 메모리 캐시를 사용할 때 세션이 고정적인지 확인해야 합니다. 

IMemoryCache는 캐시 우선 순위를 제공합니다.
메모리 캐시에는 모든 개체를 저장할 수 있습니다. 

IMemoryCache 사용

ASP.NET Core에서 인-메모리 캐시를 사용하려면 Startup.cs 파일의 ConfigureServices() 메서드에 services.AddMemoryCache() 미들웨어를 등록해야 합니다. 
IMemoryCache 인터페이스는 “Microsoft.Extensions.Caching.Memory” NuGet 패키지가 필요합니다.

services.AddMemoryCache();
services.AddMvc();

특정 컨트롤러에서 인-메모리 캐시를 사용하려면 생성자 매개 변수 주입 방식으로 IMemoryCache 개체를 삽입한 후 필드로 참조해서 사용합니다.
```csharp
public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }
```csharp

다음 코드는 TryGetValue 메서드를 사용해 현재 시간이 캐시에 있는지 확인합니다. 항목이 캐시에 없으면 Set 메서드에 의해서 캐시에 추가됩니다.
```csharp
public IActionResult CacheTryGetValueSet()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Set cache options.
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            // Keep in cache for this time, reset time if accessed.
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Save data in cache.
        _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
    }

    return View("Cache", cacheEntry);
}

현재 시간과 캐시 된 시간이 표시됩니다.

@model DateTime?

<div>
    <h2>Actions</h2>
    <ul>
        <li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
        <li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
        <li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
    </ul>
</div>

<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>

캐시 된 DateTime 값은 제한 시간동안 요청이 있을때 캐시에 남아 있게 됩니다. 메모리 부족으로 삭제되지 않습니다.

다음 그림은 현재 시간과 캐시된 시간을 보여줍니다.

다음 코드는 GetOrCreate 및 GetOrCreateAsync를 사용하여 데이터를 캐시합니다.

public IActionResult CacheGetOrCreate()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(3);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

public async Task<IActionResult> CacheGetOrCreateAsync()
{
    var cacheEntry = await
        _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(3);
        return Task.FromResult(DateTime.Now);
    });

    return View("Cache", cacheEntry);
}

다음 코드는 Get 을 호출 하여 캐시 된 시간을 가져옵니다.

```csharp

public IActionResult CacheGet()
{
    var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
    return View("Cache", cacheEntry);
}

캐시 메소드에 대한 설명 은 IMemoryCache 메소드 및 CacheExtensions 메소드 를 참조하십시오 . MemoryCacheEntryOptions 사용하기 다음 샘플은 : 절대 만료 시간을 설정합니다. 이것은 항목을 캐시 할 수있는 최대 시간이며 슬라이딩 만료가 계속 갱신 될 때 항목이 너무 오래 지 않게 방지합니다. 슬라이딩 만료 시간을 설정합니다. 이 캐시 된 항목에 액세스하는 요청은 슬라이딩 만료 클럭을 재설정합니다. 캐시 우선 순위를로 설정합니다 CacheItemPriority.NeverRemove. 항목이 캐시에서 제거 된 후에 호출 되는 PostEvictionDelegate 를 설정합니다 . 콜백은 캐시에서 항목을 제거하는 코드와 다른 스레드에서 실행됩니다. 기음# 부

public IActionResult CreateCallbackEntry()
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        // Pin to cache.
        .SetPriority(CacheItemPriority.NeverRemove)
        // Add eviction callback
        .RegisterPostEvictionCallback(callback: EvictionCallback, state: this);

    _cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);

    return RedirectToAction("GetCallbackEntry");
}

public IActionResult GetCallbackEntry()
{
    return View("Callback", new CallbackViewModel
    {
        CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
        Message = _cache.Get<string>(CacheKeys.CallbackMessage)
    });
}

public IActionResult RemoveCallbackEntry()
{
    _cache.Remove(CacheKeys.CallbackEntry);
    return RedirectToAction("GetCallbackEntry");
}

private static void EvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}

캐시 의존성 다음 샘플에서는 종속 항목이 만료 될 때 캐시 항목을 만료시키는 방법을 보여줍니다. CancellationChangeToken캐시 된 항목에 A 가 추가됩니다. 시 Cancel온라고 CancellationTokenSource, 모두 캐시 항목이 추출됩니다. 기음# 부

public IActionResult CreateDependentEntries()
{
    var cts = new CancellationTokenSource();
    _cache.Set(CacheKeys.DependentCTS, cts);

    using (var entry = _cache.CreateEntry(CacheKeys.Parent))
    {
        // expire this entry if the dependant entry expires.
        entry.Value = DateTime.Now;
        entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);

        _cache.Set(CacheKeys.Child,
            DateTime.Now,
            new CancellationChangeToken(cts.Token));
    }

    return RedirectToAction("GetDependentEntries");
}

public IActionResult GetDependentEntries()
{
    return View("Dependent", new DependentViewModel
    {
        ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
        ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
        Message = _cache.Get<string>(CacheKeys.DependentMessage)
    });
}

public IActionResult RemoveChildEntry()
{
    _cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
    return RedirectToAction("GetDependentEntries");
}

private static void DependentEvictionCallback(object key, object value,
    EvictionReason reason, object state)
{
    var message = $"Parent entry was evicted. Reason: {reason}.";
    ((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}

를 사용 CancellationTokenSource하면 여러 캐시 항목을 그룹으로 축출 할 수 있습니다. using위 코드 의 패턴을 사용하면 using블록 내에 만들어진 캐시 항목 이 트리거 및 만료 설정을 상속받습니다. 추가 메모

콜백을 사용하여 캐시 항목을 다시 채우는 경우 : 콜백이 완료되지 않았기 때문에 여러 요청에서 캐시 된 키 값을 비워 둘 수 있습니다. 이로 인해 캐시 된 항목이 여러 스레드에서 다시 채워질 수 있습니다. 하나의 캐시 항목을 사용하여 다른 항목을 만들면 하위 항목은 상위 항목의 만료 토큰과 시간 기반 만료 설정을 복사합니다. 하위 항목을 수동으로 제거하거나 업데이트하면 하위 항목이 만료되지 않습니다. 기타 리소스

분산 캐시로 작업하기 응답 캐싱 미들웨어

10. 닷넷 스탠다드에서 인-메모리 캐싱 사용하기

10.1. MemoryCache 클래스

Microsoft.Extensions.Caching.Memory.dll 어셈블리에 존재하는 MemoryCache 클래스를 사용하면 닷넷 스탠다드 기반에서 In-Memory 캐싱을 적용할 수 있습니다.

using Microsoft.Extensions.Caching.Memory;

public class ElecFeeRepository
{
    private readonly IDbConnection db;
    private readonly MemoryCache _cache;

    public ElecFeeRepository(string connectionString)
    {
        db = new SqlConnection(connectionString);
        _cache = new MemoryCache(new MemoryCacheOptions());
    }

    #region 전기 요금 계산기 리스트 캐싱
    public List<ElecFee> GetElecFeeListCache()
    {
        string sql = "SELECT * FROM ELECFEE ORDER BY CONVERT(INT, ID) ASC";

        List<ElecFee> fees;
        if (!_cache.TryGetValue("GetElecFee", out fees))
        {
            // 캐시에 개체 값을 담기
            fees = db.Query<ElecFee>(sql).ToList();

            // 키시에 실행 결괏값을 담기
            _cache.Set("GetElecFee", fees, (new MemoryCacheEntryOptions())
                .SetAbsoluteExpiration(TimeSpan.FromMinutes(10)));
        }

        return fees;
    }
    #endregion

10.2. MemoryCache.Remove(“CacheKey”)

11. 뷰 컴포넌트

ASP.NET Core에서 새롭게 도입된 뷰 컴포넌트는 UI와 데이터를 묶어서 뷰 페이지의 원하는 곳에서 출력해주는 기능을 제공합니다.

소개

ASP.NET Core에서 뷰 컴포넌트(View Component)란 UI와 Data를 묶어서 뷰 페이지에 출력해 주는 컴포넌트를 말합니다. 기존 웹 폼의 서버 컨트롤, MVC의 부분 뷰(Partial View)와 비슷한 개념입니다. 뷰 컴포넌트는 한 번 만들어 놓고, 여러 개의 뷰 페이지에서 호출해서 사용할 수 있습니다.

  • 재 사용
  • 특정 기능을 하나의 조각으로 묶어서 관리

그림 32 1 뷰 컴포넌트

뷰 컴포넌트는 동적인 메뉴, 로그인 패널, 쇼핑 카트, 최근 글 리스트 등 부분 페이지를 구현하는데 유용합니다.

코드: 뷰 컴포넌트 호출

<div>
    @await Component.InvokeAsync(“NoticeWidget”)
</div>

참고:

뷰 컴포넌트, 부분 뷰, 태그 헬퍼 중 어떤 것 사용? 특별히 어떤 것이 더 좋다라는 것은 의미가 없을 것 같습니다. 뷰 컴포넌트, 부분 뷰, 태그 헬퍼의 3가지로 동일한 기능을 구현할 수 있습니다.

13. ViewComponent 클래스

모든 뷰 컴포넌트는 ViewComponent 클래스로부터 상속을 받습니다. MVC 컨트롤러 클래스와 비슷하게 뷰 컴포넌트는 ViewComponent 접미사로 끝난다.

14. 뷰 컴포넌트 경로

뷰 페이지에서 호출되는 뷰 컴포넌트는 다음 경로에서 찾습니다. 기본 뷰 이름은 Default.cshtml 파일입니다.

  • Views/<컨트롤러 이름>/Components/<뷰 컴포넌트 이름>/<뷰 이름>
  • Views/Shared/Components/<뷰 컴포넌트 이름>/<뷰 이름>

참고: 자식 액션의 경로와 뷰 컴포넌트의 경로 자식 액션 . Views/Shared/{뷰이름} 뷰 컴포넌트 . Views/Shared/Components/{컴포넌트이름}/{뷰이름} . Views/<컨트롤러이름>/Components/{컴포넌트이름}/{뷰이름}

15. 뷰 컴포넌트 호출

@Component.InvokeAsync(“뷰 컴포넌트 이름”, <익명 형식의 매개변수 리스트>)

16. 컨트롤러에서 직접 뷰 컴포넌트 호출

뷰 컴포넌트는 이름처럼 뷰에서 호출하여 사용하는 기능이지만, 컨트롤러에서 ViewComponent() 메서드를 통해 직접 호출할 수도 있습니다.

17. 뷰 컴포넌트의 폴더 구조

ASP.NET Core에서 뷰 컴포넌트를 사용하려면 프로젝트 루트에 ViewComponents 폴더를 만들고 이곳에 ViewComponent 접미사를 사용하여 클래스 파일을 만듭니다. 이 뷰 컴포넌트에 대한 UI 페이지는 Shared 폴더에 Components 폴더를 만들고, 뷰 컴포넌트 이름으로 폴더를 만든 다음, 기본값으로 Default.cshtml 페이지를 만들어 이곳에 UI를 구성합니다. 다음은 Copyright 이름의 뷰 컴포넌트를 생성한 후의 프로젝트 구조입니다. 그림 32 2 뷰 컴포넌트의 폴더 구조

[실습] ASP.NET Core에서 뷰 컴포넌트 만들기

1. 소개

ASP.NET Core에서 Razor 구문을 담고 있는 UI와 데이터를 묶어서 간단한 조각(부분) 페이지를 만들어 낼 수 있는 단위를 뷰 컴포넌트라고 합니다. 최소한의 절차로 뷰 컴포넌트를 만드는 과정을 실습해보겠습니다.

2. 따라하기 1: 초간단 뷰 컴포넌트 만들기

(1) Visual Studio를 열고 C:\ASP.NET\DotNetNote 프로젝트를 실행합니다.

(2) DotNetNote 프로젝트의 루트에 ViewComponents 이름으로 폴더가 있는지 확인합니다. ASP.NET Core에서는 이 폴더에 뷰 컴포넌트 클래스를 넣어 놓는다. ViewComponents 폴더에 CopyrightViewComponent.cs 이름으로 클래스를 생성하고, 다음과 같이 ViewComponent 클래스로부터 상속받아 Invoke() 메서드를 구현합니다.

// /ViewComponents/CopyrightViewComponent.cs
using Microsoft.AspNetCore.Mvc;
using System;

namespace DotNetNote.ViewComponents
{
    /// <summary>
    /// Copyright 뷰 컴포넌트
    /// </summary>
    public class CopyrightViewComponent : ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            // 초 단위로 짝수일 때와 홀수일 때 서로 다른 뷰 출력
            string viewName = "Default";
            if (DateTime.Now.Second % 2 == 0)
            {
                viewName = "Details";
            }

            return View(viewName);
        }
    }
}

(3) Views/Shared 폴더에 Components 폴더가 있는지 확인하고 만약 새롭게 프로젝트를 만들어서 연습한다면 Components 폴더를 새롭게 생성합니다. 이곳에 뷰 컴포넌트 이름인 Copyright 이름으로 폴더를 생성하고, Default.cshtml로 뷰 페이지를 만든 다음 다음과 같이 코드를 작성합니다.

// /Views/Shared/Components/Copyright/Default.cshtml
<div class="text-center">
    Copyright &copy; @DateTime.Now.Year all right reserved.
</div>

뷰 컴포넌트의 뷰 페이지의 기본 이름은 Default.cshtml입니다.

(4) 같은 경로에 Details.cshtml 뷰 페이지를 추가한 후 다음과 같이 코드를 작성합니다. 뷰 컴포넌트에서 서로 다른 뷰를 호출하는 걸 테스트하기 위해서 코드만 조금 바꾸었습니다.

// /Views/Shared/Components/Copyright/Details.cshtml
<div class="text-center">
    Copyright &copy; @DateTime.Now.Year <em>DotNetNote</em> all right reserved.
</div>

(5) 뷰 컴포넌트를 테스트할 페이지를 작성합니다. Controllers 폴더에ViewComponentDemoController.cs 파일로 컨트롤러를 추가하고 다음과 같이 코드를 입력합니다.

// /Controllers/ViewComponentDemoController.cs
using Microsoft.AspNetCore.Mvc;

namespace DotNetNote.Controllers
{
    public class ViewComponentDemoController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// CopyrightViewComponent 출력 데모
        /// </summary>
        /// <returns></returns>
        public IActionResult CopyrightDemo()
        {
            return View();
        }
    }
}

(6) Views 폴더에 컨트롤러 이름인 ViewComponentDemo로 폴더를 생성하고 CopyrightDemo.cshtml 이름으로 뷰 페이지를 작성 후 다음과 같이 뷰 컴포넌트를 호출하는 코드를 작성합니다. InvokeAsync 메서드가 비동기 메서드이므로 @await를 붙여서 호출해야 합니다.

// /Views/ViewComponentDemo/CopyrightDemo.cshtml
@{
    Layout = null;
}
<h3>Copyright 뷰 컴포넌트 호출</h3>
@await Component.InvokeAsync("Copyright")

(7) 현재 프로젝트를 시작 프로젝트로 설정하고, [Ctrl]+[F5]로 실행합니다. 웹 브라우저 경로에 /ViewComponentDemo/CopyrightDemo 경로를 요청하면 다음과 같이 출력됩니다. 그림 32 3 Copyright 뷰 컴포넌트 호출

단순히 Copyright 문자열을 출력시키는 뷰 컴포넌트를 만들어 보았습니다. 간단한 예제를 살펴보았으니 다음에는 컬렉션 형태의 데이터를 받아서 UI를 꾸며주는 뷰 컴포넌트로 확장해보겠습니다.

전체 소스는 다음과 같습니다.

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\ViewComponents\CopyrightViewComponent.cs

namespace DotNetNote.ViewComponents;

/// <summary>
/// Copyright 뷰 컴포넌트
/// </summary>
public class CopyrightViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        // 초 단위로 짝수일 때와 홀수일 때 서로 다른 뷰 출력
        string viewName = "Default";
        if (DateTime.Now.Second % 2 == 0)
        {
            viewName = "Details";
        }

        return View(viewName);
    }
}

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\Views\Shared\Components\Copyright\Default.cshtml

<div class="text-center">
    Copyright &copy; @DateTime.Now.Year all rights reserved.
</div>

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\Views\Shared\Components\Copyright\Details.cshtml

<div class="text-center">
    Copyright &copy; @DateTime.Now.Year <em>DotNetNote</em> all right reserved.
</div>

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\Views\Shared_Layout.cshtml

@await Component.InvokeAsync("Copyright")

따라하기 2: 데이터와 UI를 포함한 뷰 컴포넌트 만들기 1

(1) 이번에는 데이터를 사용하는 뷰 컴포넌트를 만듭니다. DotNetNote 프로젝트에 Models 폴더에 Tech.cs 파일로 모델 클래스인 Tech 클래스를 다음과 같이 작성합니다. 이 클래스의 내용은 Web API를 학습할 때 한 번 더 사용할 예정입니다.

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote.Models\Tech\Tech.cs

// /Models/Tech.cs
namespace DotNetNote.Models;

/// <summary>
/// Teches 테이블과 일대일로 연결되는 클래스
/// </summary>
public class Tech
{
    public int Id { get; set; }
    public string Title { get; set; }
}

(2) 계속해서 새로운 뷰 컴포넌트를 만들어보겠습니다. DotNetNote 프로젝트의 ViewComponents 폴더에 TechListViewComponent.cs 이름으로 클래스 파일을 생성하고 다음과 같이 코드를 작성합니다. 뷰 페이지에 컬렉션 형태의 데이터를 던져주는 코드가 추가되었습니다. 이 부분 코드는 실제 데이터베이스를 사용해도 되지만, 뷰 컴포넌트 학습에 초점을 맞춰 List<T> 형태의 인 메모리 데이터베이스를 사용하였습니다.

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\ViewComponents\TechListViewComponent.cs

// /ViewComponents/TechListViewComponent.cs
namespace DotNetNote.ViewComponents;

public class TechListViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        var techLists = new List<Tech>()
        {
            new() { Id = 1, Title = "ASP.NET Core" },
            new Tech { Id = 2, Title = "Bootstrap" },
            new() { Id = 3, Title = "C#" },
            new Tech { Id = 4, Title = "Dapper" },
            new() { Id = 5, Title = "Azure" },
            new Tech { Id = 6, Title = "jQuery" },
            new() { Id = 7, Title = "Angular" }
        };

        return View(techLists);
    }
}

(3) Views 폴더의 Shared 폴더의 Components 폴더에 TechList 폴더를 생성합니다. Default.cshtml 뷰 페이지를 생성하고 다음과 같이 코드를 작성합니다. 컬렉션 형태의 데이터를 받아서 반복 구문을 통해 UI를 만들어낸다.

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\Views\Shared\Components\TechList\Default.cshtml

// /Views/Shared/Components/TechList/Default.cshtml 
@model List<DotNetNote.Models.Tech>

<ul>
    @foreach (var t in Model)
    {
        <li>@t.Title</li>
    }
</ul>

(4) TechList 뷰 컴포넌트를 테스트해보겠습니다. ViewComponentDemo 컨트롤러에 다음과 같이 TechListDemo 액션 메서드를 추가합니다.

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\Controllers\ViewComponentDemoController.cs

namespace DotNetNote.Controllers;

public class ViewComponentDemoController : Controller
{
    public IActionResult Index() => View();

    /// <summary>
    /// CopyrightViewComponent 출력 데모
    /// </summary>
    public IActionResult CopyrightDemo() => View();

    /// <summary>
    /// TechListViewComponent 사용 데모
    /// </summary>
    public IActionResult TechListDemo() => View();


    /// <summary>
    /// SiteListViewComponent 사용 데모
    /// </summary>
    public IActionResult SiteListDemo() => View();


    /// <summary>
    /// 최근 댓글 리스트 출력 데모
    /// </summary>
    public IActionResult RecentlyCommentListDemo() => View();
}
// /Controllers/ViewComponentDemoController.cs에 액션 메서드 추가
/// <summary>
/// TechListViewComponent 사용 데모
/// </summary>
/// <returns></returns>
public IActionResult TechListDemo()
{
    return View();
}

(5) 마지막으로 TechListDemo.cshtml 뷰 페이지를 작성합니다. 뷰 컴포넌트를 호출하는 코드를 사용하여 TechList와 Copyright 뷰 컴포넌트를 호출합니다.

C:\DotNetNote6\DotNetNote\DotNetNote\DotNetNote\Views\ViewComponentDemo\TechListDemo.cshtml

// ~/Views/ViewComponentDemo/TechListDemo.cshtml
@{ 
    Layout = null;
}

<h3>현재 사이트에서 사용된 기술 리스트</h3>

@await Component.InvokeAsync("TechList")


@await Component.InvokeAsync("Copyright")

(6) 현재 프로젝트를 시작 프로젝트로 설정 후 Ctrl+>F5로 실행합니다. 웹 브라우저에 /ViewComponentDemo/TechListDemo 경로를 요청하면 다음과 같이 출력됩니다. List<T> 형태로 저장된 문자열 데이터가 UI 코드와 섞여서 출력됩니다. 그림 32 4 TechList 뷰 컴포넌트 실행

따라하기 3: 데이터와 UI를 포함한 뷰 컴포넌트 만들기 2

(1) 이번에는 즐겨찾기 사이트 또는 추천 사이트 리스트를 만들기 위한 뷰 컴포넌트를 만들어 보겠습니다. DotNetNote 프로젝트의 Models 폴더에 Site.cs 클래스 파일을 생성하고, 모델 클래스인 Site 클래스를 다음과 같이 작성합니다. 이 뷰 컴포넌트의 내용은 메인 페이지를 꾸밀 때 사용될 것입니다.

// /Models/Site.cs
namespace DotNetNote.Models
{
    /// <summary>
    /// Site 모델 클래스 === Sites 테이블
    /// </summary>
    public class Site
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Url { get; set; }
        public string Description { get; set; }
    }
}

(2) 계속해서 새로운 뷰 컴포넌트를 만들어 보겠습니다. DotNetNote 프로젝트의 ViewComponents 폴더에 SiteListViewComponent.cs 이름으로 클래스 파일을 생성하고 다음과 같이 코드를 작성합니다. TechList 뷰 컴포넌트와 마찬가지로 뷰 페이지에 컬렉션 형태의 데이터를 던져 주는 코드가 추가되었습니다.

// /ViewComponents/SiteListViewComponent.cs
using DotNetNote.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
 
namespace DotNetNote.ViewComponents
{
    public class SiteListViewComponent : ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            var siteLists = new List<Site>() {
                new Site { Id = 1, Title = "길벗출판사",
                    Url = "http://www.gilbut.co.kr",
                    Description = "ASP.NET Core 서적 출간" },
                new Site { Id = 2, Title = "데브렉",
                    Url = "http://www.devlec.com",
                    Description = "DotNetNote 사이트 제작 관련 동영상 강의 제공" },
                new Site { Id = 3, Title = "Taeyo.NET",
                    Url = "http://www.taeyo.net",
                    Description = "ASP.NET Core 강좌 제공" },
                new Site { Id = 4, Title = "ASP.NET Korea User Group",
                    Url = "https://www.facebook.com/groups/AspxKorea/",
                    Description = "ASP.NET 한국 사용자 그룹" },
                new Site { Id = 5, Title = "닷넷코리아",
                    Url = "http://www.dotnetkorea.com",
                    Description = "박용준 MVP 개인 홈페이지" },
                new Site { Id = 6, Title = "VisualAcademy",
                    Url = "https://www.youtube.com/user/visualacademy",
                    Description = "박용준 MVP 개인 유튜브 채널" },
                new Site { Id = 7, Title = "ASP.NET",
                    Url = "http://www.asp.net",
                    Description = "ASP.NET 공식 사이트" }
            };
 
            return View(siteLists);
        }
    }
}

(3) Views 폴더의 Shared 폴더의 Components 폴더에 SiteList 폴더를 생성합니다. 이 폴더에 Default.cshtml 뷰 페이지를 생성하고 다음과 같이 코드를 작성합니다. 컬렉션 형태의 데이터를 받아서 반복 구문을 통해 UI를 만들어 낸다.

// /Views/Shared/Components/SiteList/Default.cshtml 
@model List<DotNetNote.Models.Site>
 
<ul>
    @foreach (var t in Model)
    {
        <li><a href="@t.Url" target="_blank">@t.Title (@t.Description)</a></li>
    }
</ul>

(4) SiteList 폴더의 Default.cshtml 뷰 페이지와 같은 뷰 페이지를 같은 폴더에 Index.cshtml 파일로 생성합니다. 뷰 컴포넌트 호출 시 기본값이 아닌 다른 이름으로 호출하는 걸 테스트해보기 위함입니다.

// /Views/Shared/SiteList/Index.cshtml
@model List<DotNetNote.Models.Site>
 
<ul>
    @foreach (var t in Model)
    {
        <li><a href="@t.Url" target="_blank">@t.Title (@t.Description)</a></li>
    }
</ul>

(5) SiteList 뷰 컴포넌트를 테스트하기 위해 ViewComponentDemo 컨트롤러에 다음과 같이 SiteListDemo 액션 메서드를 추가합니다.

// /Controllers/ViewComponentDemoController.cs에 액션 메서드 추가
/// <summary>
/// SiteListViewComponent 사용 데모
/// </summary>
public IActionResult SiteListDemo()
{
    return View();
}

(6) 마지막으로 SiteListDemo.cshtml 뷰 페이지를 작성합니다. 뷰 컴포넌트를 호출하는 코드를 사용하여 TechList와 Copyright 뷰 컴포넌트를 호출합니다.

// /Views/ViewComponentDemo/SiteListDemo.cshtml
@{ 
    Layout = null;
}
 
<h3>현재 사이트와 관련된 추천 사이트</h3>
 
@await Component.InvokeAsync("SiteList", "Index")

(7) 현재 프로젝트를 시작 프로젝트로 설정 후 [Ctrl]+[F5]로 실행합니다. 웹 브라우저에 /ViewComponentDemo/SiteListDemo 경로를 요청하면 다음과 같이 출력됩니다. List<T> 형태로 저장된 문자열 데이터가 UI 코드와 섞여서 출력됩니다. 그림 32 5 SiteList 뷰 컴포넌트 실행

마무리

뷰 컴포넌트는 메인 페이지와 같이 한 페이지에 여러 개의 내용이 출력되는 경우, 하나 하나를 모듈 단위로 쪼개서 관리하고자 할 때 사용합니다. 뷰 컴포넌트로 조각 기능을 만들어서 원하는 곳에서 호출해서 사용할 수 있습니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com