ASP.NET Core의 태그 도우미 구성 요소

작성자: Scott Addie, Fiyaz Bin Hasan

태그 도우미 구성 요소는 서버 쪽 코드에서 HTML 요소를 조건부로 수정하거나 추가할 수 있도록 하는 태그 도우미입니다. 이 기능은 ASP.NET Core 2.0 이상에서 사용할 수 있습니다.

ASP.NET Core에는 두 개의 기본 제공 태그 도우미 구성 요소, 즉 headbody가 포함되어 있습니다. 이 구성 요소는 Microsoft.AspNetCore.Mvc.Razor.TagHelpers 네임스페이스에 있으며 MVC 및 Razor Pages에서 모두 사용할 수 있습니다. 태그 도우미 구성 요소에는 앱 _ViewImports.cshtml등록이 필요하지 않습니다.

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

사용 사례

태그 도우미 구성 요소의 두 가지 일반적인 사용 사례는 다음과 같습니다.

  1. <link><head>에 주입합니다.
  2. <script><body>에 주입합니다.

다음 섹션에서는 이 사용 사례를 설명합니다.

HTML 헤드 요소에 주입

HTML <head> 요소 내에서 CSS 파일은 일반적으로 HTML <link> 요소로 가져옵니다. 다음 코드는 head 태그 도우미 구성 요소를 사용하여 <head> 요소에 <link> 요소를 주입합니다.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace RazorPagesSample.TagHelpers
{
    public class AddressStyleTagHelperComponent : TagHelperComponent
    {
        private readonly string _style = 
            @"<link rel=""stylesheet"" href=""/css/address.css"" />";

        public override int Order => 1;

        public override Task ProcessAsync(TagHelperContext context,
                                          TagHelperOutput output)
        {
            if (string.Equals(context.TagName, "head", 
                              StringComparison.OrdinalIgnoreCase))
            {
                output.PostContent.AppendHtml(_style);
            }

            return Task.CompletedTask;
        }
    }
}

위의 코드에서

  • AddressStyleTagHelperComponentTagHelperComponent를 구현합니다. 추상화:
    • TagHelperContext를 사용한 클래스 초기화를 허용합니다.
    • 태그 도우미 구성 요소를 사용하여 HTML 요소를 추가하거나 수정할 수 있게 합니다.
  • Order 속성은 구성 요소가 렌더링되는 순서를 정의합니다. 앱에서 태그 도우미 구성 요소가 여러 번 사용되는 경우 Order가 필요합니다.
  • ProcessAsync는 실행 컨텍스트의 TagName 속성 값을 head와 비교합니다. 비교가 true로 평가되면 _style 필드의 내용이 HTML <head> 요소에 주입됩니다.

HTML 본문 요소에 주입

body 태그 도우미 구성 요소는 <body> 요소에 <script> 요소를 주입할 수 있습니다. 다음 코드에서는 이 기술을 보여 줍니다.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace RazorPagesSample.TagHelpers
{
    public class AddressScriptTagHelperComponent : TagHelperComponent
    {
        public override int Order => 2;
        
        public override async Task ProcessAsync(TagHelperContext context,
                                                TagHelperOutput output)
        {
            if (string.Equals(context.TagName, "body",
                              StringComparison.OrdinalIgnoreCase))
            {
                var script = await File.ReadAllTextAsync(
                    "TagHelpers/Templates/AddressToolTipScript.html");
                output.PostContent.AppendHtml(script);
            }
        }
    }
}

별도의 HTML 파일이 <script> 요소를 저장하는 데 사용됩니다. HTML 파일은 코드를 더 깔끔하고 유지 관리가 가능하도록 만듭니다. 앞의 TagHelpers/Templates/AddressToolTipScript.html 코드는 해당 내용을 읽고 태그 도우미 출력과 함께 추가합니다. 파일에는 AddressToolTipScript.html 다음 태그가 포함됩니다.

<script>
$("address[printable]").hover(function() {
    $(this).attr({
        "data-toggle": "tooltip",
        "data-placement": "right",
        "title": "Home of Microsoft!"
    });
});
</script>

위의 코드는 부트스트랩 도구 설명 위젯printable 특성을 포함하는 <address> 요소에 바인드합니다. 요소 위에 마우스 포인터를 놓으면 효과가 표시됩니다.

구성 요소 등록

태그 도우미 구성 요소를 앱의 태그 도우미 구성 요소 컬렉션에 추가해야 합니다. 컬렉션에 추가하는 세 가지 방법이 있습니다.

서비스 컨테이너를 통한 등록

태그 도우미 구성 요소 클래스가 ITagHelperComponentManager로 관리되지 않는 경우 DI(종속성 주입) 시스템에 등록되어야 합니다. 다음 Startup.ConfigureServices 코드는 임시 수명이 있는 AddressStyleTagHelperComponentAddressScriptTagHelperComponent 클래스를 등록합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddTransient<ITagHelperComponent, 
        AddressScriptTagHelperComponent>();
    services.AddTransient<ITagHelperComponent, 
        AddressStyleTagHelperComponent>();
}

Razor 파일을 통한 등록

태그 도우미 구성 요소가 DI에 등록되어 있지 않으면 Razor Pages 페이지 또는 MVC 보기에서 등록할 수 있습니다. 이 기술은 Razor 파일에서 삽입된 태그 및 구성 요소 실행 순서를 제어하는 데 사용됩니다.

ITagHelperComponentManager는 태그 도우미 구성 요소를 추가하거나 앱에서 제거하는 데 사용됩니다. 다음 코드에서는 AddressTagHelperComponent를 사용한 이 기술을 보여 줍니다.

@using RazorPagesSample.TagHelpers;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager manager;

@{
    string markup;

    if (Model.IsWeekend)
    {
        markup = "<em class='text-warning'>Office closed today!</em>";
    }
    else
    {
        markup = "<em class='text-info'>Office open today!</em>";
    }

    manager.Components.Add(new AddressTagHelperComponent(markup, 1));
}

위의 코드에서

  • @inject 지시문은 ITagHelperComponentManager의 인스턴스를 제공합니다. 이 인스턴스는 Razor 파일의 다운스트림에 액세스하기 위해 manager라는 변수에 할당됩니다.
  • AddressTagHelperComponent 인스턴스가 앱의 태그 도우미 구성 요소 컬렉션에 추가됩니다.

AddressTagHelperComponentmarkuporder 매개 변수를 허용하는 생성자를 수용하도록 수정됩니다.

private readonly string _markup;

public override int Order { get; }

public AddressTagHelperComponent(string markup = "", int order = 1)
{
    _markup = markup;
    Order = order;
}

제공된 markup 매개 변수는 다음과 같이 ProcessAsync에 사용됩니다.

public override async Task ProcessAsync(TagHelperContext context,
                                        TagHelperOutput output)
{
    if (string.Equals(context.TagName, "address",
            StringComparison.OrdinalIgnoreCase) &&
        output.Attributes.ContainsName("printable"))
    {
        TagHelperContent childContent = await output.GetChildContentAsync();
        string content = childContent.GetContent();
        output.Content.SetHtmlContent(
            $"<div>{content}<br>{_markup}</div>{_printableButton}");
    }
}

페이지 모델 또는 컨트롤러를 통한 등록

태그 도우미 구성 요소가 DI에 등록되어 있지 않으면 Razor Pages 페이지 모델 또는 MVC 컨트롤러에서 등록할 수 있습니다. 이 기술은 Razor 파일에서 C# 논리를 분리하는 데 유용합니다.

생성자 주입은 ITagHelperComponentManager의 인스턴스에 액세스하는 데 사용됩니다. 태그 도우미 구성 요소를 인스턴스의 태그 도우미 구성 요소 컬렉션에 추가합니다. 다음 Razor Pages 페이지 모델은 AddressTagHelperComponent를 사용하여 이 기술을 보여줍니다.

using System;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesSample.TagHelpers;

public class IndexModel : PageModel
{
    private readonly ITagHelperComponentManager _tagHelperComponentManager;

    public bool IsWeekend
    {
        get
        {
            var dayOfWeek = DateTime.Now.DayOfWeek;

            return dayOfWeek == DayOfWeek.Saturday ||
                   dayOfWeek == DayOfWeek.Sunday;
        }
    }

    public IndexModel(ITagHelperComponentManager tagHelperComponentManager)
    {
        _tagHelperComponentManager = tagHelperComponentManager;
    }

    public void OnGet()
    {
        string markup;

        if (IsWeekend)
        {
            markup = "<em class='text-warning'>Office closed today!</em>";
        }
        else
        {
            markup = "<em class='text-info'>Office open today!</em>";
        }

        _tagHelperComponentManager.Components.Add(
            new AddressTagHelperComponent(markup, 1));
    }
}

위의 코드에서

  • 생성자 주입은 ITagHelperComponentManager의 인스턴스에 액세스하는 데 사용됩니다.
  • AddressTagHelperComponent 인스턴스가 앱의 태그 도우미 구성 요소 컬렉션에 추가됩니다.

구성 요소 만들기

사용자 지정 태그 도우미 구성 요소를 만들려면 다음을 수행합니다.

다음 코드는 <address> HTML 요소를 대상으로 하는 사용자 지정 태그 도우미 구성 요소를 만듭니다.

using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;

namespace RazorPagesSample.TagHelpers
{
    [HtmlTargetElement("address")]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public class AddressTagHelperComponentTagHelper : TagHelperComponentTagHelper
    {
        public AddressTagHelperComponentTagHelper(
            ITagHelperComponentManager componentManager, 
            ILoggerFactory loggerFactory) : base(componentManager, loggerFactory)
        {
        }
    }
}

다음과 같이 사용자 지정 address 태그 도우미 구성 요소를 사용하여 HTML 태그를 주입합니다.

public class AddressTagHelperComponent : TagHelperComponent
{
    private readonly string _printableButton =
        "<button type='button' class='btn btn-info' onclick=\"window.open(" +
        "'https://binged.it/2AXRRYw')\">" +
        "<span class='glyphicon glyphicon-road' aria-hidden='true'></span>" +
        "</button>";

    public override int Order => 3;

    public override async Task ProcessAsync(TagHelperContext context,
                                            TagHelperOutput output)
    {
        if (string.Equals(context.TagName, "address",
                StringComparison.OrdinalIgnoreCase) &&
            output.Attributes.ContainsName("printable"))
        {
            var content = await output.GetChildContentAsync();
            output.Content.SetHtmlContent(
                $"<div>{content.GetContent()}</div>{_printableButton}");
        }
    }
}

위의 ProcessAsync 메서드는 SetHtmlContent에 제공된 HTML을 일치하는 <address> 요소에 주입합니다. 주입은 다음과 같은 경우에 발생합니다.

  • 실행 컨텍스트의 TagName 속성 값이 address와 같습니다.
  • 해당 <address> 요소에 printable 특성이 있습니다.

예를 들어 if 문은 다음 <address> 요소를 처리할 때 true로 평가됩니다.

<address printable>
    One Microsoft Way<br />
    Redmond, WA 98052-6399<br />
    <abbr title="Phone">P:</abbr>
    425.555.0100
</address>

추가 리소스