<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ASP.NET Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<atom:link href="https://lycos7560.com/tag/asp-net/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>생각의 흐름을 타고 다니며 만드는 블로그</description>
	<lastBuildDate>Fri, 08 Aug 2025 07:45:28 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://lycos7560.com/wp-content/uploads/2022/11/cropped-cropped-cropped-log-1-150x150-1-80x80.png</url>
	<title>ASP.NET Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Fri, 08 Aug 2025 07:45:23 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[2FA]]></category>
		<category><![CDATA[Account]]></category>
		<category><![CDATA[AccountManagement]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[appsettings]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Backend]]></category>
		<category><![CDATA[Blazor]]></category>
		<category><![CDATA[Callback]]></category>
		<category><![CDATA[CD]]></category>
		<category><![CDATA[Challenge]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[CICD]]></category>
		<category><![CDATA[Claim]]></category>
		<category><![CDATA[ClaimsPrincipal]]></category>
		<category><![CDATA[ClientSide]]></category>
		<category><![CDATA[CloudComputing]]></category>
		<category><![CDATA[CloudNative]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Cookie]]></category>
		<category><![CDATA[Core]]></category>
		<category><![CDATA[Cryptography]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[DataTransferObject]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[Debugging]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[DTO]]></category>
		<category><![CDATA[EmailConfirmation]]></category>
		<category><![CDATA[EntityFramework]]></category>
		<category><![CDATA[ErrorHandling]]></category>
		<category><![CDATA[Exception]]></category>
		<category><![CDATA[ExternalLogin]]></category>
		<category><![CDATA[ExternalProvider]]></category>
		<category><![CDATA[Frontend]]></category>
		<category><![CDATA[FullStack]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[GitLab]]></category>
		<category><![CDATA[GoogleAuth]]></category>
		<category><![CDATA[GoogleCloud]]></category>
		<category><![CDATA[GoogleLogin]]></category>
		<category><![CDATA[HTTP302]]></category>
		<category><![CDATA[HttpGet]]></category>
		<category><![CDATA[HttpPost]]></category>
		<category><![CDATA[HttpRequest]]></category>
		<category><![CDATA[HttpResponse]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[Identity]]></category>
		<category><![CDATA[IIS]]></category>
		<category><![CDATA[JSONWebToken]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Kestrel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Log]]></category>
		<category><![CDATA[Logging]]></category>
		<category><![CDATA[Login]]></category>
		<category><![CDATA[Logout]]></category>
		<category><![CDATA[Microservices]]></category>
		<category><![CDATA[MicrosoftIdentity]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[NoSQL]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[OAuth2.0]]></category>
		<category><![CDATA[OpenID]]></category>
		<category><![CDATA[OpenIDConnect]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[password]]></category>
		<category><![CDATA[Passwordless]]></category>
		<category><![CDATA[path]]></category>
		<category><![CDATA[pipeline]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[Profile]]></category>
		<category><![CDATA[QueryString]]></category>
		<category><![CDATA[Redirect]]></category>
		<category><![CDATA[RedirectResponse]]></category>
		<category><![CDATA[Register]]></category>
		<category><![CDATA[ResetPassword]]></category>
		<category><![CDATA[RESTfulAPI]]></category>
		<category><![CDATA[Route]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SecurityToken]]></category>
		<category><![CDATA[ServerSide]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[SignInManager]]></category>
		<category><![CDATA[SignUp]]></category>
		<category><![CDATA[SinglePageApplication]]></category>
		<category><![CDATA[SocialLogin]]></category>
		<category><![CDATA[SPA]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[stage]]></category>
		<category><![CDATA[Startup]]></category>
		<category><![CDATA[StateManagement]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Token]]></category>
		<category><![CDATA[TryCatch]]></category>
		<category><![CDATA[TwoFactor]]></category>
		<category><![CDATA[URI]]></category>
		<category><![CDATA[url]]></category>
		<category><![CDATA[UserDB]]></category>
		<category><![CDATA[UserManagement]]></category>
		<category><![CDATA[UserManager]]></category>
		<category><![CDATA[UserProfile]]></category>
		<category><![CDATA[UserStore]]></category>
		<category><![CDATA[VisualStudio]]></category>
		<category><![CDATA[VSCode]]></category>
		<category><![CDATA[WebAPI]]></category>
		<category><![CDATA[WebApp]]></category>
		<category><![CDATA[WebApplication]]></category>
		<category><![CDATA[WebDevelopment]]></category>
		<category><![CDATA[WebSecurity]]></category>
		<category><![CDATA[WebServer]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40245</guid>

					<description><![CDATA[<p>🔥 ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석 1️⃣ 사용자가 &#8220;Google로 로그인&#8221; 버튼을 클릭 Navigation.NavigateTo(..., forceLoad: true) Blazor의 내부 라우팅이 아닌, 브라우저가 직접 해당 URL(api/auth/Challenge/Google...)로 GET 요청보낸다. 2️⃣ 백엔드의 인증 시작 (AuthController &#8211; Challenge) 3️⃣ 외부 공급자 인증 (Google) 사용자는 Google 로그인 페이지로 이동 4️⃣ 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback) [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/">ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</h2>


				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-cbc30617      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core-identity를-활용한-구글-로그인oauth-전체-흐름-분석" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-사용자가-google로-로그인-버튼을-클릭" class="uagb-toc-link__trigger">1&#x20e3; 사용자가 &quot;Google로 로그인&quot; 버튼을 클릭</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-백엔드의-인증-시작-authcontroller-challenge" class="uagb-toc-link__trigger">2&#x20e3; 백엔드의 인증 시작 (AuthController &#8211; Challenge)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-외부-공급자-인증-google" class="uagb-toc-link__trigger">3&#x20e3; 외부 공급자 인증 (Google)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-백엔드의-콜백-처리-및-로그인회원가입-authcontroller-callback" class="uagb-toc-link__trigger">4&#x20e3; 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-완료" class="uagb-toc-link__trigger">5&#x20e3; 완료</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#6-authcontrollercs" class="uagb-toc-link__trigger">6&#x20e3; AuthController.cs</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1159" height="1920" src="https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid.jpg" alt="" class="wp-image-40248" srcset="https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid.jpg 1159w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-181x300.jpg 181w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-768x1272.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-927x1536.jpg 927w" sizes="(max-width: 1159px) 100vw, 1159px" /></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 사용자가 &#8220;Google로 로그인&#8221; 버튼을 클릭</h3>



<p><code>Navigation.NavigateTo(..., forceLoad: true)</code></p>



<p>Blazor의 내부 라우팅이 아닌, 브라우저가 직접 해당 URL(<code>api/auth/Challenge/Google...</code>)로 <strong>GET 요청</strong>보낸다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
private void LoginWithGoogle()
{
    var returnUrl = "/"; // 로그인 성공 후 돌아올 주소
    var googleLoginUrl = $"/api/auth/Challenge/Google?returnUrl={Uri.EscapeDataString(returnUrl)}";

    // 이 주소로 브라우저가 페이지를 새로고침하며 이동
    Navigation.NavigateTo(googleLoginUrl, forceLoad: true);
}</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 백엔드의 인증 시작 (AuthController &#8211; Challenge)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AuthController.cs
[HttpGet("Challenge/{provider}")]
public IActionResult Challenge(string provider, string? returnUrl = null)
{
    // ...
    var redirectUrl = Url.Action("Callback", "Auth", ...); // 1. Google이 인증 후 돌아올 우리 API 주소(Callback)를 지정
    var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); // 2. 인증 요청에 필요한 속성 구성

    return Challenge(properties, provider); // 3. Google 로그인 페이지로 리디렉션하는 응답 생성
}</pre>



<ul class="wp-block-list">
<li><code>AuthController.cs</code>의 <code>Challenge</code> 메서드가 실행됩니다. <br><code>provider</code> 매개변수에는 &#8220;Google&#8221;이 전달</li>



<li><code>_signInManager</code>는 ASP.NET Core Identity의 핵심 기능으로, 외부 로그인 과정을 도와줌</li>



<li><code>return Challenge(properties, provider);</code>는 브라우저에게 <strong>HTTP 302 Redirect</strong> 응답을 보냄<br>이 응답 헤더에는 사용자가 이동해야 할 <strong>Google의 로그인 페이지 주소</strong>가 포함</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 외부 공급자 인증 (Google)</h3>



<p><strong>사용자는 Google 로그인 페이지로 이동</strong></p>



<ul class="wp-block-list">
<li>사용자는 자신의 Google 계정으로 로그인하고, 우리 애플리케이션이 요청하는 정보(이메일, 프로필 등)에 대한 접근 권한을 허용</li>



<li>인증이 성공적으로 완료되면, Google은 2단계에서 백엔드가 지정했던 <strong>콜백 URL</strong>(<code>api/Auth/Callback</code>)로 사용자를 다시 리디렉션<br>이때 인증 코드 또는 토큰과 함께 사용자를 보냄</li>
</ul>



<ol start="1" class="wp-block-list"></ol>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback)</h3>



<p><strong>Google이 사용자를 우리 백엔드의 <code>Callback</code> 엔드포인트로 돌려보냄</strong></p>



<p><code>AuthController.cs</code>의 <code>Callback</code> (라우팅 경로: <code>externalLogin</code>) 메서드가 실행</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AuthController.cs
[HttpGet("externalLogin")]
public async Task&lt;IActionResult> Callback(...)
{
    // 1. Google이 보내준 정보로 외부 로그인 정보를 가져옴
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null) { /* 에러 처리 */ }

    // 2. 이 외부 정보(공급자, 키)로 우리 DB에 연결된 사용자가 있는지 확인하고 로그인 시도
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, ...);

    if (result.Succeeded)
    {
        // 3a. 성공! 이미 가입하고 연동된 사용자. 로그인 처리 후 returnUrl로 리디렉션
        return LocalRedirect(returnUrl);
    }
    else
    {
        // 3b. 실패. 신규 사용자이거나, 이메일은 같지만 연동되지 않은 사용자.
        return await HandleUserCreationOrLinking(info, returnUrl);
    }
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>HandleUserCreationOrLinking</code> 메서드는 다음 두 가지 시나리오를 처리</p>



<ul class="wp-block-list">
<li><strong>시나리오 A: 이메일은 존재하지만 소셜 연동이 안 된 경우 (<code>LinkExistingUser</code>)</strong>
<ul class="wp-block-list">
<li>Google에서 받은 이메일로 우리 DB에서 사용자를 찾습니다. (<code>_userManager.FindByEmailAsync</code>)</li>



<li>기존 사용자가 있다면, 해당 계정에 이 Google 로그인 정보를 추가로 연결합니다. (<code>_userManager.AddLoginAsync</code>)</li>



<li>연결 후, 사용자를 로그인시키고 <code>returnUrl</code>로 리디렉션합니다.</li>
</ul>
</li>



<li><strong>시나리오 B: 완전히 새로운 사용자인 경우 (<code>CreateNewUserAsync</code>)</strong>
<ul class="wp-block-list">
<li>Google에서 받은 이메일과 이름으로 새로운 <code>ApplicationUser</code> 객체를 만듭니다. 이때 <code>EmailConfirmed</code>는 <code>true</code>로 설정합니다. (소셜 로그인은 이미 이메일이 인증된 것으로 간주)</li>



<li><code>_userManager.CreateAsync(user)</code>로 DB에 새 사용자를 저장합니다.</li>



<li>새로 생성된 사용자 계정에 Google 로그인 정보를 연결합니다. (<code>_userManager.AddLoginAsync</code>)</li>



<li>새 사용자를 로그인시키고 <code>returnUrl</code>로 리디렉션합니다.</li>
</ul>
</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5&#x20e3; 완료</h3>



<p>로그인 또는 회원가입 및 연동이 모두 성공적으로 끝나면, 백엔드는 최종적으로 <code>LocalRedirect(returnUrl)</code>을 통해 사용자를 맨 처음 <code>Login.razor</code>에서 지정했던 주소(<code>/</code>)로 리디렉션합니다. </p>



<p>이제 사용자는 로그인된 상태로 메인 페이지를 보게 됩니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">6&#x20e3; AuthController.cs</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.IdentityModel.Tokens;
using Neco.BaseTemplate.Shared.Data;
using Neco.BaseTemplete.Data;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;

namespace Neco.BaseTemplete.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        private readonly UserManager&lt;ApplicationUser> _userManager;
        private readonly SignInManager&lt;ApplicationUser> _signInManager;
        private readonly IUserStore&lt;ApplicationUser> _userStore;
        private readonly IEmailSender&lt;ApplicationUser> _emailSender;
        private readonly ILogger&lt;AuthController> _logger;

        public AuthController(
            UserManager&lt;ApplicationUser> userManager,
            SignInManager&lt;ApplicationUser> signInManager,
            IUserStore&lt;ApplicationUser> userStore,
            IEmailSender&lt;ApplicationUser> emailSender,
            ILogger&lt;AuthController> logger)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _userStore = userStore;
            _emailSender = emailSender;
            _logger = logger;
        }

        #region Basic Authentication

        /// &lt;summary>
        /// Handles user login.
        /// &lt;/summary>
        /// &lt;param name="model">Login request DTO (email, password, RememberMe option)&lt;/param>
        /// &lt;returns>The login result&lt;/returns>
        [HttpPost("login")]
        public async Task&lt;IActionResult> Login([FromBody] LoginUserRequestDTO model)
        {
            try
            {
                var result = await _signInManager.PasswordSignInAsync(
                    model.Email,
                    model.Password,
                    model.RememberMe,
                    lockoutOnFailure: true);

                if (result.Succeeded)
                {
                    _logger.LogInformation("User logged in: {Email}", model.Email);
                    return Ok(new { Success = true });
                }

                if (result.RequiresTwoFactor)
                {
                    return Ok(new { RequiresTwoFactor = true });
                }

                if (result.IsLockedOut)
                {
                    _logger.LogWarning("User account locked out: {Email}", model.Email);
                    return BadRequest("Account locked out");
                }

                return BadRequest("Invalid login attempt");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Login error for {Email}", model.Email);
                return StatusCode(500, "An error occurred");
            }
        }

        /// &lt;summary>
        /// Handles new user registration.
        /// &lt;/summary>
        /// &lt;param name="model">Registration request DTO&lt;/param>
        /// &lt;returns>The registration result&lt;/returns>
        [HttpPost("register")]
        public async Task&lt;IActionResult> Register([FromBody] CreateUserRequestDTO model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Fail_RequestInvalid,
                    Errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
                });
            }

            var user = CreateUser();
            await _userStore.SetUserNameAsync(user, model.Email, CancellationToken.None);
            var emailStore = GetEmailStore();
            await emailStore.SetEmailAsync(user, model.Email, CancellationToken.None);

            var result = await _userManager.CreateAsync(user, model.Password);

            if (!result.Succeeded)
            {
                _logger.LogInformation("User registration failed: {Email}", model.Email);

                // Handle duplicate email error
                if (result.Errors.Any(e => e.Code == "DuplicateUserName"))
                {
                    return BadRequest(new RegisterUserResponseDTO
                    {
                        Status = RegisterUserResponseDTO.ResponseStatus.Fail_DuplicateEmail,
                    });
                }

                // Handle general Identity errors
                return BadRequest(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Fail_General,
                    Errors = result.Errors.Select(e => e.Description)
                });
            }

            _logger.LogInformation("User created a new account with a password.");

            // Send email confirmation link
            try
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

                // NOTE: For security, it's recommended to configure a fixed base URL
                // from your application's settings rather than relying on the request host.
                var baseUrl = model.BaseUrl ?? $"{Request.Scheme}://{Request.Host.Value}";
                var callbackUrl = $"{baseUrl}/Account/ConfirmEmail?userId={userId}&amp;code={code}";

                await _emailSender.SendConfirmationLinkAsync(user, model.Email, HtmlEncoder.Default.Encode(callbackUrl));
            }
            catch (Exception)
            {
                // Account created successfully but email sending failed.
                return Ok(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Success_EmailWarning,
                    Message = "The account was created, but the verification email sending failed."
                });
            }

            return Ok(new RegisterUserResponseDTO
            {
                Status = RegisterUserResponseDTO.ResponseStatus.Success_NeedConfirmation
            });
        }

        /// &lt;summary>
        /// Resends the email confirmation link.
        /// &lt;/summary>
        /// &lt;param name="model">Email confirmation request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPost("emailConfirm")]
        public async Task&lt;IActionResult> ResendEmailConfirm([FromBody] RequestEmailConfirmDTO model)
        {
            if (!ModelState.IsValid)
            {
                // Always return a success response for security (to avoid user enumeration).
                return Ok(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                    Message = "The confirmation email has been sent successfully."
                });
            }

            try
            {
                var user = await _userManager.FindByEmailAsync(model.Email);

                // Even if the user doesn't exist or is already confirmed, return a success response for security.
                if (user == null || user.EmailConfirmed)
                {
                    return Ok(new ResponseEmailConfirmDTO
                    {
                        Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                        Message = "The confirmation email has been sent successfully."
                    });
                }

                // Generate and send the email confirmation link
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

                // NOTE: It is recommended to use a pre-configured base URL.
                var baseUrl = model.BaseUrl ?? $"{Request.Scheme}://{Request.Host.Value}";
                var callbackUrl = $"{baseUrl}/Account/ConfirmEmail?userId={userId}&amp;code={code}";

                await _emailSender.SendConfirmationLinkAsync(user, model.Email, HtmlEncoder.Default.Encode(callbackUrl));

                return Ok(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                    Message = "The confirmation email has been sent successfully."
                });
            }
            catch (Exception)
            {
                // Mail server or other exception occurred.
                return BadRequest(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Fail,
                    Message = $"An error occurred while processing your request. (Server Error)"
                });
            }
        }

        /// &lt;summary>
        /// Handles password reset requests.
        /// &lt;/summary>
        /// &lt;param name="model">Password reset request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPost("resetPassword")]
        public async Task&lt;IActionResult> ResetPassword([FromBody] ResetPasswordRequestDTO model)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return BadRequest("User not found");
            }

            var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
            var callbackUrl = $"{model.BaseUrl}/Account/ResetPassword?email={model.Email}&amp;token={resetToken}";

            await _emailSender.SendPasswordResetLinkAsync(user, model.Email, callbackUrl);

            return Ok("Password reset link sent");
        }

        /// &lt;summary>
        /// Handles user logout.
        /// &lt;/summary>
        /// &lt;returns>The logout result&lt;/returns>
        [HttpPost("logout")]
        public async Task&lt;IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return Ok("Logged out successfully");
        }

        /// &lt;summary>
        /// Handles the generation of a JWT token.
        /// &lt;/summary>
        /// &lt;param name="model">Login request DTO (email, password)&lt;/param>
        /// &lt;returns>Token information&lt;/returns>
        [HttpPost("token")]
        public async Task&lt;IActionResult> GenerateToken([FromBody] LoginUserRequestDTO model)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
            {
                return Unauthorized("Invalid credentials");
            }

            var claims = new[]
            {
                new Claim(ClaimTypes.Name, user.UserName),
                new Claim(ClaimTypes.Email, user.Email)
            };

            // WARNING: Do not hard-code the key in production.
            // Move this to a secure configuration file (e.g., appsettings.json)
            // and use a strong, randomly generated key.
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("A_Very_Strong_And_Secret_Key_For_JWT_Auth"));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: "yourdomain.com",
                audience: "yourdomain.com",
                claims: claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

            return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
        }

        #endregion

        #region External Authentication (OAuth)

        /// &lt;summary>
        /// Initiates a challenge request to an external authentication provider (e.g., Google).
        /// &lt;/summary>
        /// &lt;param name="provider">The name of the authentication provider (e.g., "Google")&lt;/param>
        /// &lt;param name="returnUrl">The URL to redirect to after successful authentication&lt;/param>
        /// &lt;returns>A challenge result to the external provider&lt;/returns>
        [HttpGet("Challenge/{provider}")]
        public IActionResult Challenge(string provider, string? returnUrl = null)
        {
            _logger.LogInformation("Starting {Provider} login", provider);

            if (string.IsNullOrWhiteSpace(provider))
            {
                return BadRequest(new { error = "Provider not specified." });
            }

            // Normalize and validate returnUrl
            returnUrl = NormalizeReturnUrl(returnUrl);

            // Set the callback URL
            var redirectUrl = Url.Action("Callback", "Auth", new { returnUrl }, Request.Scheme);

            // Configure authentication properties
            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);

            return Challenge(properties, provider);
        }

        /// &lt;summary>
        /// Handles the callback from an external authentication provider.
        /// &lt;/summary>
        /// &lt;param name="returnUrl">The URL to redirect to after successful authentication&lt;/param>
        /// &lt;param name="remoteError">Error message from the external provider&lt;/param>
        /// &lt;returns>An appropriate response based on the authentication result&lt;/returns>
        [HttpGet("externalLogin")]
        public async Task&lt;IActionResult> Callback(string? returnUrl = null, string? remoteError = null)
        {
            try
            {
                returnUrl = NormalizeReturnUrl(returnUrl);

                if (!string.IsNullOrEmpty(remoteError))
                {
                    _logger.LogError("External provider error: {RemoteError}", remoteError);
                    return Redirect($"/login?error={Uri.EscapeDataString(remoteError)}");
                }

                var info = await _signInManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    _logger.LogError("Could not retrieve external login info");
                    return Redirect("/login?error=external_login_failed");
                }

                // Attempt to sign in an existing user
                var result = await _signInManager.ExternalLoginSignInAsync(
                    info.LoginProvider,
                    info.ProviderKey,
                    isPersistent: false,
                    bypassTwoFactor: true);

                if (result.Succeeded)
                {
                    var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
                    _logger.LogInformation("User {Email} successfully logged in", user?.Email);
                    return LocalRedirect(returnUrl);
                }

                if (result.IsLockedOut)
                {
                    _logger.LogWarning("Attempt to log in with a locked-out account");
                    return Redirect("/login?error=locked_out");
                }

                // New user or linking an existing user
                return await HandleUserCreationOrLinking(info, returnUrl);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while processing external login");
                return Redirect("/login?error=server_error");
            }
        }

        #endregion

        #region Helper Methods

        /// &lt;summary>
        /// Normalizes and validates the returnUrl.
        /// &lt;/summary>
        private string NormalizeReturnUrl(string? returnUrl)
        {
            if (string.IsNullOrWhiteSpace(returnUrl))
                return "/";

            if (Uri.TryCreate(returnUrl, UriKind.Absolute, out var uri))
                returnUrl = uri.PathAndQuery;

            return Url.IsLocalUrl(returnUrl) ? returnUrl : "/";
        }

        /// &lt;summary>
        /// Handles new user creation or linking an existing user to an external login.
        /// &lt;/summary>
        private async Task&lt;IActionResult> HandleUserCreationOrLinking(ExternalLoginInfo info, string returnUrl)
        {
            var email = info.Principal.FindFirstValue(ClaimTypes.Email);
            if (string.IsNullOrEmpty(email))
            {
                _logger.LogError("Missing email information from external provider {Provider}", info.LoginProvider);
                return Redirect("/login?error=email_required");
            }

            var existingUser = await _userManager.FindByEmailAsync(email);
            return existingUser != null
                ? await LinkExistingUser(existingUser, info, returnUrl)
                : await CreateNewUserAsync(info, returnUrl);
        }

        /// &lt;summary>
        /// Links external login information to an existing user.
        /// &lt;/summary>
        private async Task&lt;IActionResult> LinkExistingUser(ApplicationUser user, ExternalLoginInfo info, string returnUrl)
        {
            var addLoginResult = await _userManager.AddLoginAsync(user, info);
            if (addLoginResult.Succeeded)
            {
                _logger.LogInformation("Successfully linked {Provider} login to existing user {Email}", info.LoginProvider, user.Email);
                await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
                return LocalRedirect(returnUrl);
            }

            _logger.LogError("Failed to link external login to existing user: {Errors}",
                string.Join(", ", addLoginResult.Errors.Select(e => e.Description)));
            return Redirect("/login?error=link_failed");
        }

        /// &lt;summary>
        /// Creates a new user based on external login information.
        /// &lt;/summary>
        private async Task&lt;IActionResult> CreateNewUserAsync(ExternalLoginInfo info, string returnUrl)
        {
            var email = info.Principal.FindFirstValue(ClaimTypes.Email);
            if (string.IsNullOrEmpty(email))
            {
                _logger.LogError("Missing email information from external provider");
                return BadRequest(new { error = "Email information was not provided." });
            }

            var userName = email.Split('@')[0];
            var user = new ApplicationUser
            {
                UserName = email,
                Email = email,
                NickName = userName,
                EmailConfirmed = true // Email is already confirmed via external provider
            };

            var createResult = await _userManager.CreateAsync(user);
            if (!createResult.Succeeded)
            {
                _logger.LogError("User creation failed: {Errors}",
                    string.Join(", ", createResult.Errors.Select(e => e.Description)));

                // Attempt to create an alternative user name on a naming conflict
                if (createResult.Errors.Any(e => e.Code == "DuplicateUserName"))
                {
                    user.UserName = $"{userName}_{Guid.NewGuid().ToString("N").Substring(0, 4)}";
                    createResult = await _userManager.CreateAsync(user);

                    if (!createResult.Succeeded)
                    {
                        return BadRequest(new
                        {
                            error = "Account creation failed",
                            details = createResult.Errors.Select(e => e.Description)
                        });
                    }
                }
                else
                {
                    return BadRequest(new
                    {
                        error = "Account creation failed",
                        details = createResult.Errors.Select(e => e.Description)
                    });
                }
            }

            // Link external login information
            var addLoginResult = await _userManager.AddLoginAsync(user, info);
            if (!addLoginResult.Succeeded)
            {
                _logger.LogError("Failed to add login information: {Errors}",
                    string.Join(", ", addLoginResult.Errors.Select(e => e.Description)));

                // Rollback: Delete the created user
                await _userManager.DeleteAsync(user);

                return BadRequest(new
                {
                    error = "Failed to link external login information",
                    details = addLoginResult.Errors.Select(e => e.Description)
                });
            }

            _logger.LogInformation("New user created and logged in successfully: {Email}", email);
            await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);

            return LocalRedirect(returnUrl);
        }

        private ApplicationUser CreateUser()
        {
            try
            {
                return Activator.CreateInstance&lt;ApplicationUser>();
            }
            catch
            {
                throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
                    $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor.");
            }
        }

        private IUserEmailStore&lt;ApplicationUser> GetEmailStore()
        {
            if (!_userManager.SupportsUserEmail)
            {
                throw new NotSupportedException("The default UI requires a user store with email support.");
            }
            return (IUserEmailStore&lt;ApplicationUser>)_userStore;
        }

        #endregion

        #region Profile Management

        /// &lt;summary>
        /// Handles user profile updates.
        /// &lt;/summary>
        /// &lt;param name="model">Update request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPut("updateProfile")]
        public async Task&lt;IActionResult> UpdateProfile([FromBody] UpdateUserProfileDTO model)
        {
            var user = await _userManager.FindByIdAsync(model.Email);
            if (user == null)
            {
                return NotFound("User not found");
            }

            user.NickName = model.Nickname;
            user.PhoneNumber = model.PhoneNumber;

            var result = await _userManager.UpdateAsync(user);
            if (!result.Succeeded)
            {
                return BadRequest(result.Errors.Select(e => e.Description));
            }

            return Ok("Profile updated successfully");
        }

        /// &lt;summary>
        /// Handles user deletion.
        /// &lt;/summary>
        /// &lt;param name="userId">The ID of the user to delete&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpDelete("deleteUser/{userId}")]
        public async Task&lt;IActionResult> DeleteUser(string userId)
        {
            var user = await _userManager.FindByIdAsync(userId);
            if (user == null)
            {
                return NotFound("User not found");
            }

            var result = await _userManager.DeleteAsync(user);
            if (!result.Succeeded)
            {
                return BadRequest(result.Errors.Select(e => e.Description));
            }

            return Ok("User deleted successfully");
        }

        #endregion
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private void LoginWithGoogle()
{
    try
    {
        var returnUrl = "/";
        var googleLoginUrl = $"/api/auth/Challenge/Google?returnUrl={Uri.EscapeDataString(returnUrl)}";

        // Redirect in the current window instead of a popup
        // This is a placeholder for a client-side navigation method, e.g., in Blazor
        // Navigation.NavigateTo(googleLoginUrl, forceLoad: true);
    }
    catch (Exception ex)
    {
        // Error handling
        Console.WriteLine($"Google login error: {ex.Message}");
    }
}
</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/">ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core Route Constraints</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Mon, 28 Jul 2025 04:02:09 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET Development]]></category>
		<category><![CDATA[Action Method]]></category>
		<category><![CDATA[alpha]]></category>
		<category><![CDATA[Alphabetic]]></category>
		<category><![CDATA[alphanum]]></category>
		<category><![CDATA[Alphanumeric]]></category>
		<category><![CDATA[API Development]]></category>
		<category><![CDATA[API 개발]]></category>
		<category><![CDATA[app.UseEndpoints()]]></category>
		<category><![CDATA[app.UseRouting()]]></category>
		<category><![CDATA[Application Routing]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Attribute Routing]]></category>
		<category><![CDATA[Backend Development]]></category>
		<category><![CDATA[bool]]></category>
		<category><![CDATA[Boolean]]></category>
		<category><![CDATA[Catch-all parameter]]></category>
		<category><![CDATA[Conditional Routing]]></category>
		<category><![CDATA[Constraint]]></category>
		<category><![CDATA[Constraint Mapping]]></category>
		<category><![CDATA[Constraint Pattern]]></category>
		<category><![CDATA[ConstraintMap]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Convention-based Routing]]></category>
		<category><![CDATA[custom]]></category>
		<category><![CDATA[datetime]]></category>
		<category><![CDATA[decimal]]></category>
		<category><![CDATA[Default Value]]></category>
		<category><![CDATA[double]]></category>
		<category><![CDATA[Dynamic Path]]></category>
		<category><![CDATA[Endpoint]]></category>
		<category><![CDATA[Endpoint Routing]]></category>
		<category><![CDATA[Explicit Routing]]></category>
		<category><![CDATA[Extensibility]]></category>
		<category><![CDATA[float]]></category>
		<category><![CDATA[guid]]></category>
		<category><![CDATA[HTTP Methods]]></category>
		<category><![CDATA[HTTP Request]]></category>
		<category><![CDATA[HTTP 메서드]]></category>
		<category><![CDATA[HTTP 요청]]></category>
		<category><![CDATA[HttpContext]]></category>
		<category><![CDATA[IEndpointRouteBuilder]]></category>
		<category><![CDATA[Implicit Routing]]></category>
		<category><![CDATA[int]]></category>
		<category><![CDATA[Integer]]></category>
		<category><![CDATA[IRouteConstraint]]></category>
		<category><![CDATA[Lambda Handler]]></category>
		<category><![CDATA[Length]]></category>
		<category><![CDATA[length()]]></category>
		<category><![CDATA[long]]></category>
		<category><![CDATA[MapGet()]]></category>
		<category><![CDATA[MapPost()]]></category>
		<category><![CDATA[max()]]></category>
		<category><![CDATA[Maximum]]></category>
		<category><![CDATA[Maximum Length]]></category>
		<category><![CDATA[maxlength()]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[min()]]></category>
		<category><![CDATA[Minimal API]]></category>
		<category><![CDATA[Minimum]]></category>
		<category><![CDATA[Minimum Length]]></category>
		<category><![CDATA[minlength()]]></category>
		<category><![CDATA[MVC Pattern]]></category>
		<category><![CDATA[MVC 패턴]]></category>
		<category><![CDATA[Optional Parameter]]></category>
		<category><![CDATA[Parameter Binding]]></category>
		<category><![CDATA[Path Constraint]]></category>
		<category><![CDATA[Path Matching]]></category>
		<category><![CDATA[Placeholder]]></category>
		<category><![CDATA[range()]]></category>
		<category><![CDATA[Razor Pages]]></category>
		<category><![CDATA[Regex]]></category>
		<category><![CDATA[Request Processing]]></category>
		<category><![CDATA[Request Routing]]></category>
		<category><![CDATA[required]]></category>
		<category><![CDATA[Required Parameter]]></category>
		<category><![CDATA[RESTful API]]></category>
		<category><![CDATA[Route Builder]]></category>
		<category><![CDATA[Route Constraint]]></category>
		<category><![CDATA[Route Control]]></category>
		<category><![CDATA[Route Data]]></category>
		<category><![CDATA[Route Definition]]></category>
		<category><![CDATA[Route Handler]]></category>
		<category><![CDATA[Route Options]]></category>
		<category><![CDATA[Route Parameter]]></category>
		<category><![CDATA[Route Parameter Validation]]></category>
		<category><![CDATA[Route Priority]]></category>
		<category><![CDATA[Route Restriction]]></category>
		<category><![CDATA[Route Template]]></category>
		<category><![CDATA[RouteValueDictionary]]></category>
		<category><![CDATA[Routing Configuration]]></category>
		<category><![CDATA[Routing Engine]]></category>
		<category><![CDATA[Routing Middleware]]></category>
		<category><![CDATA[Routing Rules]]></category>
		<category><![CDATA[Routing System]]></category>
		<category><![CDATA[routing table]]></category>
		<category><![CDATA[SEO Friendly]]></category>
		<category><![CDATA[SEO 친화적]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[url]]></category>
		<category><![CDATA[URL Constraint]]></category>
		<category><![CDATA[URL Design]]></category>
		<category><![CDATA[URL Matching]]></category>
		<category><![CDATA[URL Parsing]]></category>
		<category><![CDATA[URL Path Variable]]></category>
		<category><![CDATA[URL Segment]]></category>
		<category><![CDATA[URL Structure]]></category>
		<category><![CDATA[URL Validity]]></category>
		<category><![CDATA[URL 경로 변수]]></category>
		<category><![CDATA[URL 구조]]></category>
		<category><![CDATA[URL 매칭]]></category>
		<category><![CDATA[URL 설계]]></category>
		<category><![CDATA[URL 세그먼트]]></category>
		<category><![CDATA[URL 유효성]]></category>
		<category><![CDATA[URL 파싱]]></category>
		<category><![CDATA[Validation]]></category>
		<category><![CDATA[Web Application]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Framework]]></category>
		<category><![CDATA[경로 매칭]]></category>
		<category><![CDATA[경로 일치]]></category>
		<category><![CDATA[경로 제약]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[규칙 기반 라우팅]]></category>
		<category><![CDATA[기본값]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[동적 경로]]></category>
		<category><![CDATA[라우트]]></category>
		<category><![CDATA[라우트 데이터]]></category>
		<category><![CDATA[라우트 빌더]]></category>
		<category><![CDATA[라우트 옵션]]></category>
		<category><![CDATA[라우트 우선순위]]></category>
		<category><![CDATA[라우트 정의]]></category>
		<category><![CDATA[라우트 제약]]></category>
		<category><![CDATA[라우트 제어]]></category>
		<category><![CDATA[라우트 제한]]></category>
		<category><![CDATA[라우트 템플릿]]></category>
		<category><![CDATA[라우트 파라미터 유효성]]></category>
		<category><![CDATA[라우트 핸들러]]></category>
		<category><![CDATA[라우팅]]></category>
		<category><![CDATA[라우팅 규칙]]></category>
		<category><![CDATA[라우팅 매개변수]]></category>
		<category><![CDATA[라우팅 미들웨어]]></category>
		<category><![CDATA[라우팅 설정]]></category>
		<category><![CDATA[라우팅 시스템]]></category>
		<category><![CDATA[라우팅 엔진]]></category>
		<category><![CDATA[라우팅 테이블]]></category>
		<category><![CDATA[람다 핸들러]]></category>
		<category><![CDATA[매개변수 바인딩]]></category>
		<category><![CDATA[명시적 라우팅]]></category>
		<category><![CDATA[미들웨어]]></category>
		<category><![CDATA[백엔드 개발]]></category>
		<category><![CDATA[사용자 지정]]></category>
		<category><![CDATA[선택적 매개변수]]></category>
		<category><![CDATA[속성 라우팅]]></category>
		<category><![CDATA[암시적 라우팅]]></category>
		<category><![CDATA[애플리케이션 라우팅]]></category>
		<category><![CDATA[액션 메서드]]></category>
		<category><![CDATA[엔드포인트]]></category>
		<category><![CDATA[엔드포인트 라우팅]]></category>
		<category><![CDATA[요청 라우팅]]></category>
		<category><![CDATA[요청 처리]]></category>
		<category><![CDATA[웹 애플리케이션]]></category>
		<category><![CDATA[웹 프레임워크]]></category>
		<category><![CDATA[유효성 검사]]></category>
		<category><![CDATA[정규식]]></category>
		<category><![CDATA[제약 조건 매핑]]></category>
		<category><![CDATA[제약 조건 패턴]]></category>
		<category><![CDATA[제한 조건]]></category>
		<category><![CDATA[조건부 라우팅]]></category>
		<category><![CDATA[캐치올 매개변수]]></category>
		<category><![CDATA[컨트롤러]]></category>
		<category><![CDATA[플레이스홀더]]></category>
		<category><![CDATA[확장성]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40140</guid>

					<description><![CDATA[<p>🔥 ASP.NET Core Route Constraints (라우트 제한 조건) https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-8.0 ASP.NET Core의 라우팅 시스템에서 라우트 제한 조건(Route Constraints)은 URL 경로의 특정 세그먼트(라우팅 매개변수)에 대한 유효성 검사 규칙을 정의하는 강력한 기능입니다. 이를 통해 특정 URL 패턴이 매칭되기 위한 조건을 명시하고, 잘못된 형식의 URL 요청이 특정 라우트에 매칭되는 것을 방지할 수 있습니다. 1️⃣ 라우트 제한 조건의 필요성 1. [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/">ASP.NET Core Route Constraints</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-ce724247      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core-route-constraints-라우트-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Route Constraints (라우트 제한 조건)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-라우트-제한-조건의-필요성" class="uagb-toc-link__trigger">1&#x20e3; 라우트 제한 조건의 필요성</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-라우트-제한-조건-정의-방법" class="uagb-toc-link__trigger">2&#x20e3; 라우트 제한 조건 정의 방법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-주요-라우트-제한-조건-종류-및-예시" class="uagb-toc-link__trigger">3&#x20e3; 주요 라우트 제한 조건 종류 및 예시</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#타입-기반-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 타입 기반 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#길이-및-범위-기반-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 길이 및 범위 기반 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#정규식-기반-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 정규식 기반 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#다른-일반적인-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다른 일반적인 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#사용자-지정-라우트-제한-조건-custom-route-constraints" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사용자 지정 라우트 제한 조건 (Custom Route Constraints)</a></li></ul><li class="uagb-toc__list"><a href="#4-endpoint-selection-order-엔드포인트-선택-순서" class="uagb-toc-link__trigger">4&#x20e3; Endpoint Selection Order (엔드포인트 선택 순서)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#명시적-순서-order-property" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 명시적 순서 (Order Property)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#라우트-템플릿의-구체성-specificity" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 라우트 템플릿의 구체성 (Specificity):</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#정의-순서-declaration-order" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 정의 순서 (Declaration Order):</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Route Constraints (라우트 제한 조건)</h2>



<p><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-8.0</a></p>



<p>ASP.NET Core의 라우팅 시스템에서 <strong>라우트 제한 조건(Route Constraints)</strong>은 URL 경로의 특정 세그먼트(라우팅 매개변수)에 대한 유효성 검사 규칙을 정의하는 강력한 기능입니다. </p>



<p>이를 통해 특정 URL 패턴이 매칭되기 위한 조건을 명시하고, 잘못된 형식의 URL 요청이 특정 라우트에 매칭되는 것을 방지할 수 있습니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 라우트 제한 조건의 필요성</h3>



<p>1. <strong>정확한 라우트 매칭</strong>: <br>동일한 경로 프리픽스를 가지는 여러 라우트 중에서 가장 적절한 라우트를 선택할 수 있도록 돕습니다.</p>



<ul class="wp-block-list">
<li>예: <code>/products/123</code> (ID로 조회)와 <code>/products/shoes</code> (이름으로 조회)를 구분</li>
</ul>



<p>2. <strong>유효성 검사</strong>: <br>특정 매개변수가 예상하는 형식(예: 숫자, GUID, 날짜)인지 강제하여, 컨트롤러 액션 또는 핸들러에서 타입 변환 오류를 줄입니다.</p>



<p>3. <strong>보안 및 견고성</strong>: <br>잘못된 형식의 입력으로 인한 잠재적인 오류나 공격 시도를 줄이는 데 기여합니다.</p>



<p>4. <strong>URL 디자인 강화</strong>: <br>더욱 명확하고 의미론적인 URL 구조를 유지할 수 있도록 돕습니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 라우트 제한 조건 정의 방법</h3>



<p>라우트 제한 조건은 라우트 템플릿 내에서 라우팅 매개변수 이름 뒤에 <strong>콜론(<code>:</code>)을 붙이고 제한 조건 이름을 지정하여 정의</strong>합니다.</p>



<p>{매개변수명:제한조건이름}</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs 또는 Startup.cs의 UseEndpoints 블록
endpoints.MapGet("/products/{id:int}", (int id) => Results.Ok($"Product ID: {id}"));</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 주요 라우트 제한 조건 종류 및 예시</h3>



<p>ASP.NET Core는 다양한 내장 라우트 제한 조건을 제공합니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 타입 기반 제한 조건</h4>



<p>ASP.NET Core는 다양한 내장 라우트 제한 조건을 제공합니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>int</td><td>32비트 정수로 매칭됩니다.</td><td>{id:int}</td><td>/items/123</td><td>/items/abc, /items/1.5</td></tr><tr><td>bool</td><td>불리언 값(true/false)으로 매칭됩니다.</td><td>{isActive:bool}</td><td>/status/true</td><td>/status/other</td></tr><tr><td>datetime</td><td>DateTime 형식으로 매칭됩니다.</td><td>{date:datetime}</td><td>/events/2023-10-26</td><td>/events/today</td></tr><tr><td>decimal</td><td>decimal 형식으로 매칭됩니다.</td><td>{price:decimal}</td><td>/products/99.99</td><td>/products/abc</td></tr><tr><td>double</td><td>double 형식으로 매칭됩니다.</td><td>{value:double}</td><td>/data/3.14159</td><td>/data/xyz</td></tr><tr><td>float</td><td>float 형식으로 매칭됩니다.</td><td>{measurement:float}</td><td>/sensor/0.5f</td><td>/sensor/abc</td></tr><tr><td>guid</td><td>GUID(Globally Unique Identifier)로 매칭됩니다.</td><td>{itemId:guid}</td><td>/item/a1b2c3d4-e5f6-7890-1234-567890abcdef</td><td>/item/invalid-guid</td></tr><tr><td>long</td><td>64비트 정수로 매칭됩니다.</td><td>{userId:long}</td><td>/users/9876543210</td><td>/users/short</td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs 또는 Startup.cs
app.UseEndpoints(endpoints =>
{
    // 정수형 ID만 허용
    endpoints.MapGet("/products/{id:int}", (int id) => Results.Ok($"Getting product by ID: {id}"));

    // GUID 형식의 아이템 ID만 허용
    endpoints.MapGet("/items/{itemId:guid}", (Guid itemId) => Results.Ok($"Getting item by GUID: {itemId}"));

    // 특정 날짜 형식의 이벤트만 허용
    endpoints.MapGet("/events/on/{date:datetime}", (DateTime date) => Results.Ok($"Events on: {date.ToShortDateString()}"));
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 길이 및 범위 기반 제한 조건</h4>



<p>매개변수 값의 길이 또는 숫자 범위를 제한합니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>min(value)</td><td>최소값 value 이상이어야 합니다.</td><td>{age:min(18)}</td><td>/users/age/18, /users/age/25</td><td>/users/age/17, /users/age/abc</td></tr><tr><td>max(value)</td><td>최대값 value 이하여야 합니다.</td><td>{quantity:max(100)}</td><td>/order/items/50</td><td>/order/items/101</td></tr><tr><td>range(min,max)</td><td>min과 max 사이의 값이어야 합니다.</td><td>{year:range(2000,2024)}</td><td>/posts/2023</td><td>/posts/1999, /posts/2025</td></tr><tr><td>minlength(length)</td><td>최소 length 길이 이상이어야 합니다.</td><td>{code:minlength(3)}</td><td>/code/abc, /code/abcd</td><td>/code/ab</td></tr><tr><td>maxlength(length)</td><td>최대 length 길이 이하여야 합니다.</td><td>{name:maxlength(10)}</td><td>/user/john</td><td>/user/someverylongname</td></tr><tr><td>length(min,max)</td><td>min과 max 사이의 길이어야 합니다.</td><td>{zip:length(5,9)}</td><td>/zip/12345, /zip/12345-6789</td><td>/zip/123, /zip/1234567890</td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // 연령이 18세 이상인 사용자만 허용
    endpoints.MapGet("/users/age/{age:min(18)}", (int age) => Results.Ok($"Adult user, age: {age}"));

    // 재고 수량이 10개에서 100개 사이인 경우에만 유효
    endpoints.MapGet("/inventory/{count:range(10,100)}", (int count) => Results.Ok($"Checking inventory count: {count}"));

    // 최소 5자, 최대 10자의 사용자 이름만 허용
    endpoints.MapGet("/profile/{username:length(5,10)}", (string username) => Results.Ok($"Profile for user: {username}"));
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 정규식 기반 제한 조건</h4>



<p>정규 표현식(Regular Expression)을 사용하여 매개변수 값의 패턴을 지정합니다. </p>



<p>이는 가장 유연한 제한 조건입니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>regex(pattern)</td><td>pattern 정규식과 일치해야 합니다.</td><td>{code:regex(^[A-Z]{3}\\d{4}$)}</td><td>/item/ABC1234</td><td>/item/abc1234, /item/ABC123</td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // "abc"로 시작하고 숫자로 끝나는 코드만 허용
    endpoints.MapGet("/validate/{code:regex(abc\\d+)}", (string code) => Results.Ok($"Validated code: {code}"));

    // 상품 코드가 "PROD-"로 시작하고 4자리 숫자로 끝나는 경우만 허용
    endpoints.MapGet("/productcodes/{productCode:regex(^PROD-\\d{{4}}$)}", (string productCode) => Results.Ok($"Valid product code: {productCode}"));
    // 참고: 정규식 내의 중괄호는 이스케이프 필요 ({{, }})
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다른 일반적인 제한 조건</h4>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>alpha</td><td>알파벳 문자(a-z, A-Z)만 허용합니다.</td><td>{name:alpha}</td><td>/users/john</td><td>/users/john123</td></tr><tr><td>alphanum</td><td>알파벳 문자 또는 숫자만 허용합니다.</td><td>{token:alphanum}</td><td>/api/token/abc123</td><td>/api/token/abc-123</td></tr><tr><td>required</td><td>매개변수가 반드시 존재해야 합니다. (?와 함께 사용 불가)</td><td>{country:required}</td><td>/location/korea</td><td>/location/</td></tr><tr><td>url</td><td>유효한 URL 형식이어야 합니다.</td><td>{redirectUrl:url}</td><td>/redirect/https://google.com</td><td>/redirect/not-a-url</td></tr><tr><td>minlength, maxlength, length</td><td>string 형식에 대한 길이 제한</td><td>위 &#8220;길이 및 범위 기반&#8221; 섹션 참고</td><td></td><td></td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // 이름은 알파벳만 허용
    endpoints.MapGet("/greet/{name:alpha}", (string name) => Results.Ok($"Hello, {name}!"));

    // 토큰은 알파벳 또는 숫자만 허용
    endpoints.MapGet("/verify/{token:alphanum}", (string token) => Results.Ok($"Verifying token: {token}"));

    // 국가 매개변수는 반드시 제공되어야 함
    endpoints.MapGet("/country/{country:required}", (string country) => Results.Ok($"Selected country: {country}"));
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사용자 지정 라우트 제한 조건 (Custom Route Constraints)</h4>



<p>내장된 제한 조건만으로는 부족할 때, <code>IRouteConstraint</code> 인터페이스를 구현하여 자신만의 커스텀 라우트 제한 조건을 만들 수 있습니다.</p>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>IRouteConstraint</code> 인터페이스 구현</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using System.Globalization;

public class CustomYearConstraint : IRouteConstraint
{
    public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
    {
        // routeKey는 매개변수 이름 (예: "year")
        // values는 모든 라우팅 매개변수와 그 값들을 담고 있는 딕셔너리
        if (values.TryGetValue(routeKey, out object? value))
        {
            if (value is string yearString &amp;&amp; int.TryParse(yearString, out int year))
            {
                // 2000년 이후의 연도만 허용하는 예시
                return year >= 2000 &amp;&amp; year &lt;= DateTime.Now.Year + 1;
            }
        }
        return false;
    }
}</pre>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p>제한 조건 등록</p>



<p><code>Startup.cs</code>의 <code>ConfigureServices</code> 또는 <code>Program.cs</code>에서 <code>AddRouting</code> 메서드를 사용하여 커스텀 제한 조건을 등록합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRouting(options =>
{
    options.ConstraintMap.Add("customYear", typeof(CustomYearConstraint));
});

var app = builder.Build();

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    // /articles/2023 와 같이 customYear 제한 조건을 사용
    endpoints.MapGet("/articles/{year:customYear}", (int year) => Results.Ok($"Articles from year: {year}"));
});

app.Run();

//  사용 예시:
//  /articles/2023 -> 매칭됨
//  /articles/1999 -> 매칭되지 않음
//  /articles/2026 (현재 연도가 2025년이라면) -> 매칭되지 않음 (DateTime.Now.Year + 1 조건에 따라)</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; Endpoint Selection Order (엔드포인트 선택 순서)</h3>



<p>라우팅 시스템이 여러 엔드포인트 중 어떤 엔드포인트를 현재 요청에 매칭시킬지 결정하는 방식에 대한 내용입니다. </p>



<p>이는 라우트 제한 조건(Route Constraints)의 <strong>라우트 순서</strong>와 밀접하게 관련되어 있습니다.</p>



<p>ASP.NET Core의 Endpoint Selection Order는 주로 다음 원칙에 따라 이루어집니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>명시적 순서 (Order Property)</strong></h4>



<p>가장 직접적인 제어 방법은 엔드포인트에 <code>Order</code> 속성을 부여하는 것입니다. 숫자가 낮을수록 우선순위가 높습니다. </p>



<p>이는 <code>MapControllerRoute</code>나 <code>MapRazorPages</code>와 같은 메서드에는 직접 적용하기 어렵고, 주로 <code>MapGet</code> 등의 Minimal API 엔드포인트나 사용자 지정 <code>RouteEndpoint</code>를 만들 때 사용됩니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // Order가 -10인 엔드포인트 (가장 높은 우선순위)
    app.MapGet("/priority-test", () => "High priority").WithOrder(-10);

    // Order가 0인 기본 엔드포인트 (기본값)
    endpoints.MapGet("/test", () => "Normal priority");
});</pre>



<p>Note: <br><code>WithMetadata</code>를 통해 <code>RouteEndpointBuilder</code>에 직접 <code>Order</code>를 지정하는 것은 일반적인 사용법은 아니며, 실제로는 내부적으로 복잡한 로직을 통해 우선순위가 결정되거나 <code>RouteEndpoint.Order</code> 속성이 설정됩니다. <br>예를 들어, <code>MapRazorPages</code>나 <code>MapControllers</code>에서 생성되는 엔드포인트는 내부적으로 우선순위를 가집니다.)</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong>라우트 템플릿의 구체성 (Specificity)</strong>:</strong></h4>



<p>이것이 가장 일반적이고 중요한 순서 결정 기준입니다. </p>



<p>라우팅 시스템은 요청 URL과 가장 &#8220;구체적으로&#8221; 일치하는 라우트 템플릿을 선호합니다. </p>



<p>구체성은 다음과 같은 요소에 의해 결정됩니다:</p>



<p><strong>리터럴 세그먼트의 수</strong>: 더 많은 리터럴(고정된 문자열) 세그먼트를 포함하는 라우트가 더 높은 우선순위를 가집니다.</p>



<ul class="wp-block-list">
<li><code>/products/all</code> (2개의 리터럴)이 <code>/products/{id}</code> (1개의 리터럴, 1개의 매개변수)보다 우선순위가 높습니다.</li>
</ul>



<p><strong>라우트 제한 조건의 사용</strong>: 제한 조건이 있는 매개변수는 제한 조건이 없는 매개변수보다 더 구체적인 것으로 간주됩니다.</p>



<ul class="wp-block-list">
<li><code>/products/{id:int}</code>는 <code>/products/{id}</code>보다 우선순위가 높습니다. (<code>/products/123</code>이 주어지면, <code>id:int</code>가 먼저 매칭됨)</li>
</ul>



<p><strong>선택적 매개변수</strong>: 선택적 매개변수가 없는 라우트가 더 구체적인 것으로 간주됩니다.</p>



<ul class="wp-block-list">
<li><code>/users/{id}</code>가 <code>/users/{id?}</code>보다 우선순위가 높습니다.</li>
</ul>



<p><strong>Catch-all 매개변수 (<code>*</code> 또는 <code>**</code>)</strong>: 가장 덜 구체적인 것으로 간주되어 거의 항상 마지막에 매칭됩니다.</p>



<ul class="wp-block-list">
<li><code>/files/{*path}</code>는 일반적으로 다른 모든 구체적인 라우트 뒤에 위치해야 합니다.</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong><strong>정의 순서 (Declaration Order)</strong>:</strong></strong></h4>



<p>동일한 구체성을 가진 두 개 이상의 라우트가 있을 경우, <code>UseEndpoints</code> 또는 <code>Map()</code> 계열 메서드에서 <strong>먼저 정의된 라우트가 우선순위를 가집니다.</strong> </p>



<p>이 때문에 &#8220;라우트 순서&#8221; 섹션에서 언급했듯이, 더 구체적인 라우트를 항상 먼저 정의하는 것이 중요합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // 이 라우트가 먼저 정의됨
    endpoints.MapGet("/products/special", () => "Special Product Page");

    // 이 라우트는 구체성이 동일하지만 나중에 정의됨
    endpoints.MapGet("/products/{name}", (string name) => $"Product by Name: {name}");

    // /products/special 요청 시, "Special Product Page"가 반환됨
    // 만약 순서가 바뀌면 "/products/{name}"에 매칭되어 "Product by Name: special"이 반환될 수 있음
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Endpoint Selection Order는 복합적인 요소들의 상호작용으로 결정됩니다. </strong></p>



<p><strong>라우트 제한 조건은 이 선택 과정에서 라우트의 &#8220;구체성&#8221;을 높여 특정 URL 패턴에 대한 우선순위를 부여하는 핵심적인 방법입니다.</strong> </p>



<p>개발자는 이러한 원칙을 이해하고 라우트를 신중하게 정의하여 예상치 못한 라우팅 문제를 방지해야 합니다.</p>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/">ASP.NET Core Route Constraints</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core의 미들웨어(Middleware)</title>
		<link>https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/</link>
					<comments>https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Thu, 24 Jul 2025 23:32:09 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[API Gateway]]></category>
		<category><![CDATA[API 게이트웨이]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[ASP.NET Core MVC]]></category>
		<category><![CDATA[async]]></category>
		<category><![CDATA[Asynchronous]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[Await]]></category>
		<category><![CDATA[Caching]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Compression]]></category>
		<category><![CDATA[Conditional Middleware]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Configure]]></category>
		<category><![CDATA[Container]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Conventional Middleware]]></category>
		<category><![CDATA[CORS]]></category>
		<category><![CDATA[Cross-Origin Resource Sharing]]></category>
		<category><![CDATA[Custom Middleware]]></category>
		<category><![CDATA[Debugging]]></category>
		<category><![CDATA[DELETE]]></category>
		<category><![CDATA[Dependency Injection]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Developer]]></category>
		<category><![CDATA[Development Environment]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[Error Handling]]></category>
		<category><![CDATA[event]]></category>
		<category><![CDATA[Event-driven]]></category>
		<category><![CDATA[Exception Handling]]></category>
		<category><![CDATA[Extension Method]]></category>
		<category><![CDATA[Factory-based Middleware]]></category>
		<category><![CDATA[filter]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[HEAD]]></category>
		<category><![CDATA[Headers]]></category>
		<category><![CDATA[HTTP Context]]></category>
		<category><![CDATA[HTTP Method]]></category>
		<category><![CDATA[HTTP Request]]></category>
		<category><![CDATA[HTTP Response]]></category>
		<category><![CDATA[HTTP 메서드]]></category>
		<category><![CDATA[HTTP 요청]]></category>
		<category><![CDATA[HTTP 응답]]></category>
		<category><![CDATA[HTTP 컨텍스트]]></category>
		<category><![CDATA[HTTP/1.1]]></category>
		<category><![CDATA[HTTP/2]]></category>
		<category><![CDATA[HttpContext]]></category>
		<category><![CDATA[HTTPS Redirection]]></category>
		<category><![CDATA[HTTPS 리디렉션]]></category>
		<category><![CDATA[IApplicationBuilder]]></category>
		<category><![CDATA[IMiddleware]]></category>
		<category><![CDATA[Integration Testing]]></category>
		<category><![CDATA[Invoke]]></category>
		<category><![CDATA[Kestrel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Logging]]></category>
		<category><![CDATA[Logging Provider]]></category>
		<category><![CDATA[Maintainability]]></category>
		<category><![CDATA[Microservices]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[Middleware Chain]]></category>
		<category><![CDATA[Minimal APIs]]></category>
		<category><![CDATA[Modularization]]></category>
		<category><![CDATA[next]]></category>
		<category><![CDATA[Non-Terminal Middleware]]></category>
		<category><![CDATA[Ops]]></category>
		<category><![CDATA[options]]></category>
		<category><![CDATA[Order Critical]]></category>
		<category><![CDATA[PATCH]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[pipeline]]></category>
		<category><![CDATA[POST]]></category>
		<category><![CDATA[Production Environment]]></category>
		<category><![CDATA[Program.cs]]></category>
		<category><![CDATA[Protocol]]></category>
		<category><![CDATA[PUT]]></category>
		<category><![CDATA[Request Body]]></category>
		<category><![CDATA[Request Flow]]></category>
		<category><![CDATA[Request Pipeline]]></category>
		<category><![CDATA[RequestDelegate]]></category>
		<category><![CDATA[Response Body]]></category>
		<category><![CDATA[Response Flow]]></category>
		<category><![CDATA[Reusability]]></category>
		<category><![CDATA[Reverse Proxy]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Run]]></category>
		<category><![CDATA[Scalability]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Separation of Concerns]]></category>
		<category><![CDATA[Service Container]]></category>
		<category><![CDATA[Short-circuit]]></category>
		<category><![CDATA[Single Responsibility Principle]]></category>
		<category><![CDATA[SoC]]></category>
		<category><![CDATA[SSL]]></category>
		<category><![CDATA[Startup]]></category>
		<category><![CDATA[Static Files]]></category>
		<category><![CDATA[Status Code]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Terminal Middleware]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[Unit Testing]]></category>
		<category><![CDATA[Use]]></category>
		<category><![CDATA[UseWhen]]></category>
		<category><![CDATA[Web Application]]></category>
		<category><![CDATA[Web Application Framework]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Server]]></category>
		<category><![CDATA[Web Service]]></category>
		<category><![CDATA[WebApplication]]></category>
		<category><![CDATA[WebApplicationBuilder]]></category>
		<category><![CDATA[개발 환경]]></category>
		<category><![CDATA[개발자]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[관심사의 분리]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[단락]]></category>
		<category><![CDATA[단위 테스트]]></category>
		<category><![CDATA[단일 책임 원칙]]></category>
		<category><![CDATA[디버깅]]></category>
		<category><![CDATA[라우팅]]></category>
		<category><![CDATA[로깅]]></category>
		<category><![CDATA[로깅 프로바이더]]></category>
		<category><![CDATA[리버스 프록시]]></category>
		<category><![CDATA[마이크로서비스]]></category>
		<category><![CDATA[모듈화]]></category>
		<category><![CDATA[미들웨어]]></category>
		<category><![CDATA[미들웨어 체인]]></category>
		<category><![CDATA[배포]]></category>
		<category><![CDATA[보안]]></category>
		<category><![CDATA[비동기]]></category>
		<category><![CDATA[비종료 미들웨어]]></category>
		<category><![CDATA[상태 코드]]></category>
		<category><![CDATA[서비스 메쉬]]></category>
		<category><![CDATA[서비스 컨테이너]]></category>
		<category><![CDATA[설정]]></category>
		<category><![CDATA[성능]]></category>
		<category><![CDATA[순서 중요]]></category>
		<category><![CDATA[압축]]></category>
		<category><![CDATA[액션]]></category>
		<category><![CDATA[예외 처리]]></category>
		<category><![CDATA[오류 처리]]></category>
		<category><![CDATA[요청 본문]]></category>
		<category><![CDATA[요청 파이프라인]]></category>
		<category><![CDATA[요청 흐름]]></category>
		<category><![CDATA[웹 개발]]></category>
		<category><![CDATA[웹 서버]]></category>
		<category><![CDATA[웹 서비스]]></category>
		<category><![CDATA[웹 애플리케이션]]></category>
		<category><![CDATA[웹 애플리케이션 프레임워크]]></category>
		<category><![CDATA[유지보수성]]></category>
		<category><![CDATA[응답 본문]]></category>
		<category><![CDATA[응답 흐름]]></category>
		<category><![CDATA[의존성 주입]]></category>
		<category><![CDATA[이벤트]]></category>
		<category><![CDATA[이벤트 드리븐]]></category>
		<category><![CDATA[인가]]></category>
		<category><![CDATA[인증]]></category>
		<category><![CDATA[재사용성]]></category>
		<category><![CDATA[정적 파일]]></category>
		<category><![CDATA[조건부 미들웨어]]></category>
		<category><![CDATA[종료 미들웨어]]></category>
		<category><![CDATA[최소 API]]></category>
		<category><![CDATA[캐싱]]></category>
		<category><![CDATA[커스텀 미들웨어]]></category>
		<category><![CDATA[컨벤셔널 미들웨어]]></category>
		<category><![CDATA[컨테이너]]></category>
		<category><![CDATA[컨트롤러]]></category>
		<category><![CDATA[쿠버네티스]]></category>
		<category><![CDATA[클라우드]]></category>
		<category><![CDATA[테스트]]></category>
		<category><![CDATA[통합 테스트]]></category>
		<category><![CDATA[파이프라인]]></category>
		<category><![CDATA[팩토리 기반 미들웨어]]></category>
		<category><![CDATA[프로덕션 환경]]></category>
		<category><![CDATA[프로토콜]]></category>
		<category><![CDATA[필터]]></category>
		<category><![CDATA[헤더]]></category>
		<category><![CDATA[확장 메서드]]></category>
		<category><![CDATA[확장성]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40130</guid>

					<description><![CDATA[<p>❓ 미들웨어(Middleware) https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0 ASP.NET Core의 미들웨어는 모든 HTTP 요청과 응답 파이프라인을 형성하는 일련의 구성 요소입니다. 각 미들웨어 구성 요소는 다음을 수행할 수 있습니다: 이러한 파이프라인을 통해 애플리케이션의 로직을 모듈화하고, 인증, 로깅, 오류 처리, 라우팅 등과 같은 기능을 깔끔하고 유지 관리하기 쉬운 방식으로 추가할 수 있습니다. ⛓️ 미들웨어 체인 (요청 파이프라인) ASP.NET Core 요청 파이프라인은 차례로 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/">ASP.NET Core의 미들웨어(Middleware)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-db9d164c      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#미들웨어middleware" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어(Middleware)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#미들웨어-체인-요청-파이프라인" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26d3.png" alt="⛓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 체인 (요청 파이프라인)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#요청-파이프라인-단락short-circuiting" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2702.png" alt="✂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 요청 파이프라인 단락(Short-circuiting)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#단락과-nextinvoke-이후-코드-실행" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 단락과 next.Invoke() 이후 코드 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#경고-응답-전송-후-작업-주의" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 경고: 응답 전송 후 작업 주의</a></li></ul><li class="uagb-toc__list"><a href="#appuse-vs-apprun" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1fae0.png" alt="🫠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> app.Use vs. app.Run</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core의-커스텀-미들웨어" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core의 커스텀 미들웨어</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#커스텀-미들웨어-클래스의-구조" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 미들웨어 클래스의 구조</a></li></ul><li class="uagb-toc__list"><a href="#커스텀-컨벤셔널-미들웨어-custom-conventional-middleware" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 컨벤셔널 미들웨어 (Custom Conventional Middleware)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#미들웨어-파이프라인의-이상적인-순서" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f500.png" alt="🔀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 파이프라인의 이상적인 순서</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#usewhen" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen()</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#usewhen-작동-방식" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen() 작동 방식</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#usewhen-사용-시점" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f98b.png" alt="🦋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen() 사용 시점</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어(Middleware)</h2>



<p><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></p>



<p>ASP.NET Core의 <strong>미들웨어</strong>는 모든 HTTP 요청과 응답 <strong>파이프라인을 형성하는 일련의 구성 요소</strong>입니다.</p>



<p>각 미들웨어 구성 요소는 다음을 수행할 수 있습니다:</p>



<ul class="wp-block-list">
<li>들어오는 <strong>요청을 검사</strong>합니다.</li>



<li>요청 또는 응답을 <strong>수정</strong>합니다 (필요한 경우).</li>



<li>파이프라인의 다음 미들웨어를 <strong>호출</strong>하거나, 프로세스를 <strong>단락(short-circuit)</strong>시키고 자체적으로 응답을 생성합니다.</li>
</ul>



<p>이러한 파이프라인을 통해 <strong>애플리케이션의 로직을 모듈화하고, 인증, 로깅, 오류 처리, 라우팅 등과 </strong>같은 기능을 깔끔하고<strong> 유지 관리하기 쉬운 방식으로 추가</strong>할 수 있습니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26d3.png" alt="⛓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 체인 (요청 파이프라인)</h3>



<p>ASP.NET Core 요청 파이프라인은 차례로 호출되는 일련의 요청 대리자로 구성됩니다.</p>



<p>요청 파이프라인을 일련의 연결된 파이프라고 상상해 보세요. </p>



<p>각 미들웨어 조각은 이 파이프라인의 밸브와 같아서, <strong>정보의 흐름을 제어하고 다양한 단계에서 특정 작업을 적용</strong>할 수 있습니다. </p>



<p>미들웨어를 등록하는 <strong>순서는 매우 중요</strong>하며, 등록된 순서대로 실행됩니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="607" height="389" src="https://lycos7560.com/wp-content/uploads/2025/07/image-12.png" alt="" class="wp-image-40131" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-12.png 607w, https://lycos7560.com/wp-content/uploads/2025/07/image-12-300x192.png 300w" sizes="(max-width: 607px) 100vw, 607px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>각 델리게이트는 <strong>다음 델리게이트를 호출하기 전과 후에 작업</strong>을 수행할 수 있습니다. </p>



<p>예외 처리 델리게이트는 파이프라인의 후반 단계에서 발생하는 예외를 Catch할 수 있도록 파이프라인의 <strong>초기에 호출</strong>되어야 합니다.</p>



<p>가장 간단한 ASP.NET Core 앱은 모든 요청을 처리하는 <strong>단일 요청 델리게이트</strong>를 설정합니다. </p>



<p>이 경우에는 실제 요청 파이프라인이 포함되지 않습니다. </p>



<p>대신, 모든 HTTP 요청에 대한 응답으로 단일 익명 함수가 호출됩니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run(); // 이 app.Run은 위의 app.Run이 파이프라인을 종료하므로 실행되지 않습니다.</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong><code>Use</code> 메서드를 사용하면 여러 요청 델리게이트를 체인으로 연결</strong>할 수 있습니다. </p>



<p>이때 <code>next</code> 매개변수는 파이프라인의 다음 델리게이트를 나타냅니다.</p>



<p><code>next</code> 매개변수를 호출하지 않음으로써 <strong>파이프라인을 단락(short-circuit)</strong>시킬 수 있습니다. </p>



<p>다음 예시에서 보여주듯이, 일반적으로 <code>next</code> 델리게이트를 호출하기 전과 후에 모두 작업을 수행할 수 있습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더를 생성합니다.
var app = builder.Build(); // 빌더를 사용하여 웹 애플리케이션 인스턴스를 생성합니다.

// 첫 번째 미들웨어: app.Use()를 사용하여 파이프라인에 추가합니다.
app.Use(async (context, next) =>
{
    // 응답에 쓸 수 있는 작업을 수행합니다. (예: 헤더 추가, 응답의 시작 부분 작성)
    // 이 부분의 코드는 다음 미들웨어가 실행되기 전에 실행됩니다.

    await next.Invoke(); // 다음 미들웨어(이 경우 app.Run)를 호출하여 제어를 넘깁니다.

    // 응답에 쓰지 않는 로깅 또는 다른 작업을 수행합니다.
    // 이 부분의 코드는 다음 미들웨어가 실행된 후(응답이 생성된 후) 실행됩니다.
});

// 두 번째 미들웨어: app.Run()을 사용하여 파이프라인에 추가합니다.
// app.Run()은 파이프라인을 종료하는 미들웨어입니다.
app.Run(async context =>
{
    // 이 미들웨어는 "Hello from 2nd delegate."를 응답에 작성하고 파이프라인을 종료합니다.
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

// 이 app.Run()은 위의 app.Run()이 이미 파이프라인을 종료했기 때문에 실행되지 않습니다.
app.Run();</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2702.png" alt="✂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 요청 파이프라인 단락(Short-circuiting)</h3>



<p>ASP.NET Core의 미들웨어 파이프라인에서, 특정 델리게이트(미들웨어)가 요청을 <strong>다음 델리게이트로 전달하지 않을 때</strong> 이를 <strong>요청 파이프라인을 단락(short-circuiting)시킨다</strong>고 합니다. </p>



<p>파이프라인 단락은 불필요한 작업을 피할 수 있기 때문에 종종 유용합니다.</p>



<p>예를 들어, <strong>정적 파일 미들웨어(Static File Middleware)</strong>는 요청된 파일이 정적 파일(이미지, CSS, JavaScript 등)일 경우 해당 요청을 처리하고</p>



<p> 파이프라인의 나머지 부분을 단락시킴으로써 <strong>종료 미들웨어(terminal middleware)</strong> 역할을 할 수 있습니다. </p>



<p>이렇게 되면 정적 파일을 제공한 후에는 <strong>인증이나 라우팅과 같은 다른 미들웨어들이 실행될 필요가 없으므로 효율성이 높아집니다.</strong></p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 단락과 <code>next.Invoke()</code> 이후 코드 실행</h4>



<p>파이프라인의 추가적인 처리를 종료시키는 미들웨어보다 <strong>앞서 파이프라인에 추가된 미들웨어</strong>는 여전히 <code>next.Invoke()</code> 구문 이후의 코드를 처리합니다. </p>



<p>즉, 비록 파이프라인이 단락되어 최종 응답이 더 이상 아래로 전달되지 않더라도, <code>next.Invoke()</code>를 호출했던 이전 미들웨어들은 응답이 완료된 후 자신의 후처리 로직을 실행할 수 있습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 경고: 응답 전송 후 작업 주의</h4>



<p><strong>응답이 클라이언트에게 이미 전송되었거나 전송되기 시작한 후에는 <code>next.Invoke()</code>를 호출하거나 응답에 쓰려고 시도하지 마세요.</strong> </p>



<p><code>HttpResponse</code>가 시작된 후에는 응답에 변경을 가하려고 하면 <strong>예외가 발생</strong>합니다. </p>



<p>예를 들어, 응답이 시작된 후 헤더나 상태 코드를 설정하려고 하면 예외가 발생합니다.</p>



<p><code>next.Invoke()</code> 호출 후 응답 본문에 쓰는 것은 다음과 같은 문제를 야기할 수 있습니다:</p>



<ul class="wp-block-list">
<li><strong>프로토콜 위반:</strong> 명시된 <code>Content-Length</code>보다 더 많은 내용을 작성하는 것과 같은 프로토콜 위반을 초래할 수 있습니다.</li>



<li><strong>본문 형식 손상:</strong> CSS 파일에 HTML 푸터(footer)를 작성하는 것처럼 본문 형식을 손상시킬 수 있습니다.</li>
</ul>



<p><code>HasStarted</code> 속성은 헤더가 전송되었는지 또는 본문이 작성되었는지 여부를 나타내는 유용한 힌트가 될 수 있습니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1fae0.png" alt="🫠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>app.Use</code> vs. <code>app.Run</code></h3>



<p>이 두 메서드는 파이프라인에 미들웨어를 추가하는 데 기본적이지만, 핵심적인 차이점이 있습니다:</p>



<p><code><strong>app.Use(async (context, next) => { ... })</strong></code></p>



<ul class="wp-block-list">
<li><strong>요청 수정 불가:</strong> <br>마지막 단계이므로 요청을 다음으로 전달하기 전에 수정할 수 없습니다.</li>



<li><strong>비종료(Non-Terminal) 미들웨어:</strong> <br>이 유형의 미들웨어는 일반적으로 어떤 작업을 수행한 다음, <code>next</code> 델리게이트를 호출하여 파이프라인의 다음 미들웨어로 제어를 전달합니다.</li>



<li><strong>요청/응답 수정 가능:</strong> <br>요청을 다음으로 전달하기 전에 요청이나 응답을 변경할 수 있습니다.</li>



<li><strong>예시:</strong> <br>인증, 로깅, 커스텀 헤더 추가 등.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>a<strong>pp.Run(async (context) => { ... })</strong></code></p>



<ul class="wp-block-list">
<li><strong>종료(Terminal) 미들웨어:</strong> <br>이 미들웨어는 <code>next</code>를 호출하지 않습니다. <br>파이프라인을 종료하고 <strong>자체적으로 응답을 생성</strong>합니다.</li>



<li><strong>최종 응답에 주로 사용:</strong> <br>더 이상 처리가 필요 없는 요청(예: 간단한 메시지 반환)을 처리하는 데 일반적으로 사용됩니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 여러 app.Run 호출의 결과

app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello");
});

app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello again");
});

app.Run(); // 이 app.Run은 위의 app.Run들이 이미 파이프라인을 종료했기 때문에 절대 실행되지 않습니다.</pre>



<p>이 코드에서는 <strong>오직 첫 번째 <code>app.Run</code> 미들웨어만 실행됩니다.</strong> </p>



<p>&#8220;Hello&#8221;를 응답에 작성하여 파이프라인을 종료하고, 그 뒤의 <code>app.Run</code> (이것은 &#8220;Hello again&#8221;을 작성할 것임)은 실행될 기회를 얻지 못합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// app.Use와 app.Run으로 미들웨어 체인 연결하기

// 미들웨어 1
app.Use(async (context, next) => {
    await context.Response.WriteAsync("Hello "); // 1. "Hello " 작성
    await next(context); // 2. 다음 미들웨어 호출
});

// 미들웨어 2
app.Use(async (context, next) => {
    await context.Response.WriteAsync("Hello again "); // 3. "Hello again " 작성
    await next(context); // 4. 다음 미들웨어 호출 (이 경우 app.Run)
});

// 미들웨어 3 (종료 미들웨어)
app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello again"); // 5. "Hello again" 작성 후 파이프라인 종료
});</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>이 코드는 미들웨어를 올바르게 연결하는 방법을 보여줍니다.</p>



<ol start="1" class="wp-block-list">
<li>첫 번째 <code>app.Use</code>는 응답에 &#8220;Hello &#8220;를 작성하고 <code>next</code>를 호출하여 다음 미들웨어로 제어를 전달합니다.</li>



<li>두 번째 <code>app.Use</code>는 &#8220;Hello again &#8220;을 작성하고 역시 <code>next</code>를 호출합니다.</li>



<li>마지막 <code>app.Run</code> (종료 미들웨어)는 &#8220;Hello again&#8221;을 작성하고 파이프라인을 종료합니다.</li>
</ol>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core의 커스텀 미들웨어</h3>



<p>ASP.NET Core는 다양한 내장 미들웨어 구성 요소를 제공하지만, 때로는 애플리케이션 고유의 특정 요구 사항을 해결하기 위해 자신만의 <strong>커스텀 미들웨어</strong>를 만들어야 할 수도 있습니다. </p>



<p>커스텀 미들웨어를 통해 다음을 수행할 수 있습니다:</p>



<ul class="wp-block-list">
<li><strong>로직 캡슐화:</strong> <br>관련 작업(예: 로깅, 보안 검사, 사용자 정의 헤더)을 재사용 가능한 구성 요소로 묶습니다.</li>



<li><strong>동작 사용자 정의:</strong> <br>애플리케이션의 요구 사항에 정확히 맞게 요청/응답 파이프라인을 조정합니다.</li>



<li><strong>코드 구성 개선:</strong> <br>미들웨어 코드를 깔끔하고 유지 관리하기 쉽게 만듭니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 미들웨어 클래스의 구조</h4>



<p><strong><code>IMiddleware</code> 구현:</strong> 이 인터페이스는 단 하나의 메서드 <code>InvokeAsync(HttpContext context, RequestDelegate next)</code>를 요구합니다. </p>



<p>이 메서드는 미들웨어 로직의 핵심입니다</p>



<p><code>InvokeAsync</code> 또는 <code>Invoke</code> 메서드</p>



<ul class="wp-block-list">
<li><strong><code>context</code>:</strong> <br><code>HttpContext</code>는 요청 및 응답 객체에 대한 접근을 제공합니다.</li>



<li><strong><code>next</code>:</strong> <br><code>RequestDelegate</code>는 파이프라인의 다음 미들웨어를 호출할 수 있도록 합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// MyCustomMiddleware.cs
namespace MiddlewareExample.CustomMiddleware
{
    public class MyCustomMiddleware : IMiddleware // IMiddleware 인터페이스 구현
    {
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            await context.Response.WriteAsync("My Custom Middleware - Starts\n"); // 1. 응답 시작 부분에 출력
            await next(context); // 2. 다음 미들웨어 호출
            await context.Response.WriteAsync("My Custom Middleware - Ends\n"); // 3. 다음 미들웨어 완료 후 응답 끝 부분에 출력
        }
    }

    // 쉽게 등록하기 위한 확장 메서드
    public static class CustomMiddlewareExtension
    {
        public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware&lt;MyCustomMiddleware>();
        }
    }
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs (또는 Startup.cs)
using MiddlewareExample.CustomMiddleware;

// ... (다른 설정 코드) ...

builder.Services.AddTransient&lt;MyCustomMiddleware>(); // 트랜지언트 서비스로 등록

app.Use(async (HttpContext context, RequestDelegate next) => {
    await context.Response.WriteAsync("From Middleware 1\n"); // 첫 번째 미들웨어: 시작 부분
    await next(context);
});

app.UseMyCustomMiddleware(); // 확장 메서드를 사용하여 커스텀 미들웨어 추가

app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("From Middleware 3\n"); // 세 번째 미들웨어: 파이프라인 종료
});</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li><strong>등록:</strong> <br>ASP.NET Core가 필요할 때 <code>MyCustomMiddleware</code> 인스턴스를 생성할 수 있도록 이를 트랜지언트 서비스로 등록합니다.</li>



<li><strong>파이프라인 통합:</strong> <br><code>app.UseMyCustomMiddleware()</code> 확장 메서드는 커스텀 미들웨어를 파이프라인에 추가합니다.</li>



<li><strong>실행 순서:</strong> <br>미들웨어 구성 요소는 파이프라인에 추가된 순서대로 실행됩니다. 이 경우 순서는 미들웨어 1, <code>MyCustomMiddleware</code>, 그리고 미들웨어 3이 됩니다.</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 컨벤셔널 미들웨어 (Custom Conventional Middleware)</h3>



<p>ASP.NET Core 미들웨어에는 두 가지 유형이 있습니다: <strong>컨벤셔널(Conventional)</strong>과 <strong>팩토리 기반(Factory-based)</strong> </p>



<p>예시에서 보여진 컨벤셔널 미들웨어는 HTTP 요청 및 응답 처리를 위한 커스텀 로직을 캡슐화하는 간단하면서도 강력한 방법입니다.</p>



<p>주요 특징</p>



<ul class="wp-block-list">
<li><strong>클래스 기반:</strong> <br>컨벤셔널 미들웨어는 클래스로 구현됩니다.</li>



<li><strong>생성자 주입:</strong> <br>의존성(있는 경우)을 생성자를 통해 받습니다.</li>



<li><strong><code>Invoke</code> 메서드:</strong> <br>이 메서드는 각 요청을 처리하는 로직을 포함하는 미들웨어의 핵심입니다.</li>



<li><strong><code>RequestDelegate</code>:</strong> <br><code>Invoke</code> 메서드는 <code>RequestDelegate</code> 매개변수(<code>_next</code>로 명명)를 받습니다. 이 델리게이트는 파이프라인의 다음 미들웨어를 나타냅니다.</li>



<li><strong>유연성:</strong> <br><code>Invoke</code> 메서드 내에서 요청 및 응답 객체를 완벽하게 제어할 수 있습니다.</li>
</ul>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="689" height="343" src="https://lycos7560.com/wp-content/uploads/2025/07/image-13.png" alt="" class="wp-image-40132" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-13.png 689w, https://lycos7560.com/wp-content/uploads/2025/07/image-13-300x149.png 300w" sizes="(max-width: 689px) 100vw, 689px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// NameConcatenationMiddleware.cs
public class NameConcatenationMiddleware
{
    private readonly RequestDelegate _next;

    public NameConcatenationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Query.ContainsKey("firstname") &amp;&amp;
            context.Request.Query.ContainsKey("lastname"))
        {
            string fullName = $"{context.Request.Query["firstname"]} {context.Request.Query["lastname"]}";
            await context.Response.WriteAsync(fullName);
            return; // 명시적으로 반환하여 다음 미들웨어 호출 방지
        }
        
        await _next(context);
    }
}

// MiddlewareExtensions.cs
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseNameConcatenation(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware&lt;NameConcatenationMiddleware>();
    }
}</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f500.png" alt="🔀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 파이프라인의 이상적인 순서</h3>



<p>미들웨어의 순서는 애플리케이션의 동작과 효율성, 보안에 큰 영향을 미칩니다. </p>



<p>다음은 일반적으로 권장되는 이상적인 순서입니다</p>



<figure class="wp-block-image size-full"><img decoding="async" width="858" height="485" src="https://lycos7560.com/wp-content/uploads/2025/07/image-14.png" alt="" class="wp-image-40133" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-14.png 858w, https://lycos7560.com/wp-content/uploads/2025/07/image-14-300x170.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/image-14-768x434.png 768w" sizes="(max-width: 858px) 100vw, 858px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></figcaption></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>예외/오류 처리(Exception/Error Handling):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 파이프라인의 어느 곳에서든 발생하는 예외를 Catch하고 처리합니다.</li>



<li><strong>예시:</strong> <code>UseExceptionHandler</code> (프로덕션용), <code>UseDeveloperExceptionPage</code> (개발 환경용).</li>



<li><strong>이유:</strong> 예외를 초기에 Catch하여 파이프라인 아래로 전파되어 더 큰 문제를 일으키는 것을 방지합니다.</li>
</ul>



<p><strong>HTTPS 리디렉션(HTTPS Redirection):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 보안을 위해 HTTP 요청을 HTTPS로 리디렉션합니다.</li>



<li><strong>예시:</strong> <code>UseHttpsRedirection</code>.</li>



<li><strong>이유:</strong> 보안을 최우선으로 하여 모든 통신이 암호화되도록 합니다.</li>
</ul>



<p><strong>정적 파일(Static Files):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 이미지, CSS, JavaScript 파일과 같은 정적 파일을 클라이언트에게 직접 제공합니다.</li>



<li><strong>예시:</strong> <code>UseStaticFiles</code>.</li>



<li><strong>이유:</strong> 정적 파일 요청은 빠르게 처리되어야 하며, 불필요하게 파이프라인의 다른 무거운 구성 요소를 거치지 않도록 일찍 처리합니다.</li>
</ul>



<p><strong>라우팅(Routing):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> URL을 기반으로 들어오는 요청을 특정 엔드포인트에 매칭합니다.</li>



<li><strong>예시:</strong> <code>UseRouting</code>, <code>UseEndpoints</code>.</li>



<li><strong>이유:</strong> 라우팅은 애플리케이션의 핵심 로직이 요청을 어떻게 처리할지 결정하는 기반이 됩니다.</li>
</ul>



<p><strong>CORS (Cross-Origin Resource Sharing):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 다른 도메인으로부터의 안전한 교차 출처(cross-origin) 요청을 가능하게 합니다.</li>



<li><strong>예시:</strong> <code>UseCors</code>.</li>



<li><strong>이유:</strong> 인증/인가 전에 위치하여, 사전 요청(preflight request)이 불필요하게 인증/인가 미들웨어를 거치지 않도록 합니다.</li>
</ul>



<p><strong>인증(Authentication):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 사용자 신원을 확인하고 사용자 주체(principal)를 설정합니다.</li>



<li><strong>예시:</strong> <code>UseAuthentication</code>.</li>



<li><strong>이유:</strong> 사용자가 누구인지 확인한 후에 리소스에 대한 접근 권한을 부여할 수 있습니다.</li>
</ul>



<p><strong>인가(Authorization):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 사용자가 특정 리소스에 접근하거나 특정 작업을 수행할 수 있는지 여부를 결정합니다.</li>



<li><strong>예시:</strong> <code>UseAuthorization</code>.</li>



<li><strong>이유:</strong> 인증된 사용자에게만 권한 부여 여부를 검사합니다.</li>
</ul>



<p><strong>커스텀 미들웨어(Custom Middleware):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 로깅, 기능 플래그 등 애플리케이션별 미들웨어 구성 요소를 처리합니다.</li>



<li><strong>이유:</strong> 애플리케이션별 로직을 적절한 단계에서 파이프라인 내에 배치합니다.</li>
</ul>



<p><strong>MVC/Razor Pages/Minimal APIs:</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 실제 애플리케이션의 최종 엔드포인트 처리 로직을 실행합니다.</li>



<li><strong>예시:</strong> <code>MapControllers()</code>, <code>MapRazorPages()</code>, <code>MapGet()</code> 등.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="848" height="589" src="https://lycos7560.com/wp-content/uploads/2025/07/image-15.png" alt="" class="wp-image-40134" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-15.png 848w, https://lycos7560.com/wp-content/uploads/2025/07/image-15-300x208.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/image-15-768x533.png 768w" sizes="(max-width: 848px) 100vw, 848px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></figcaption></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>Program.cs</code> 파일에 미들웨어 구성 요소가 추가되는 순서는 요청 시 미들웨어 구성 요소가 호출되는 순서를 정의하며, 응답 시에는 역순으로 호출됩니다. </p>



<p>이러한 <strong>순서는 보안, 성능 및 기능에 매우 중요합니다.</strong></p>



<p><code>Program.cs</code>의 다음 강조 표시된 코드는 보안 관련 미들웨어 구성 요소를 일반적으로 권장되는 순서로 추가하는 예시입니다:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="19-43" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext&lt;ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity&lt;IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores&lt;ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen()</h3>



<p><code>UseWhen()</code>은 ASP.NET Core의 <code>IApplicationBuilder</code> 인터페이스에 있는 강력한 확장 메서드입니다. </p>



<p>이는 <strong>조건(predicate)에 따라 미들웨어를 요청 파이프라인에 조건부로 추가</strong>할 수 있도록 합니다. </p>



<p>즉, 특정 조건이 충족될 때만 특정 미들웨어 구성 요소가 실행되는 동적인 파이프라인을 만들 수 있습니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseWhen(
    context => /* 여기에 조건 */, // HttpContext를 받아 true/false 반환
    app => /* 이 분기에서 실행될 미들웨어 구성 */ // 조건이 true일 때 실행될 미들웨어 파이프라인
);</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li><strong><code>context</code>:</strong> <br>현재 요청을 나타내는 <code>HttpContext</code> 객체입니다.</li>



<li><strong>Predicate (조건):</strong> <br><code>HttpContext</code>를 받아들이고 미들웨어 분기가 실행되어야 할 경우 <code>true</code>를, 그렇지 않을 경우 <code>false</code>를 반환하는 함수입니다.</li>



<li><strong>Middleware Configuration (미들웨어 구성):</strong> <br>조건이 <code>true</code>일 때 실행되어야 할 미들웨어 구성 요소를 구성하는 액션입니다. <br>여기서 <code>app.Use()</code>, <code>app.Run()</code>, 또는 다른 미들웨어 등록 메서드를 사용합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>UseWhen()</code> 작동 방식</h4>



<ul class="wp-block-list">
<li><strong>조건 평가:</strong> <br>요청이 들어오면 <code>UseWhen()</code> 메서드는 먼저 <code>HttpContext</code>에 대해 조건 함수를 평가합니다.</li>



<li><strong>분기(조건이 true일 경우):</strong> <br>조건이 <code>true</code>를 반환하면, 구성 액션에 지정된 미들웨어 분기가 실행됩니다. <br>요청은 이 분기를 통해 흐르며, 수정되거나 응답을 생성할 수 있습니다.</li>



<li><strong>메인 파이프라인 재진입:</strong> <br>분기가 실행된 후(또는 조건이 <code>false</code>여서 건너뛰어진 경우), 요청 흐름은 메인 파이프라인으로 다시 진입하여 <code>UseWhen()</code> 호출 뒤에 등록된 다음 미들웨어 구성 요소로 계속 진행됩니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseWhen(
    context => context.Request.Query.ContainsKey("username"), // 조건: 쿼리 문자열에 "username"이 있는지 확인
    app => {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("Hello from Middleware branch\n"); // 분기 미들웨어: "Hello from Middleware branch" 작성
            await next(); // 다음 미들웨어 호출
        });
    });

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from middleware at main chain"); // 메인 파이프라인 미들웨어
});</pre>



<ul class="wp-block-list">
<li><strong>조건:</strong> <br><code>context.Request.Query.ContainsKey("username")</code> 조건은 쿼리 문자열에 &#8220;username&#8221;이라는 매개변수가 포함되어 있는지 확인합니다.</li>



<li><strong>분기 미들웨어:</strong> <br>&#8220;username&#8221; 매개변수가 존재하면 분기 미들웨어가 실행됩니다. <br>이 미들웨어는 응답에 &#8220;Hello from Middleware branch&#8221;를 작성하고 <code>next</code>를 호출하여 나머지 파이프라인이 계속되도록 합니다.</li>



<li><strong>메인 파이프라인:</strong> <br>마지막 <code>app.Run</code> 미들웨어는 메인 파이프라인의 일부입니다. <br>이는 응답에 &#8220;Hello from middleware at main chain&#8221;을 작성합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f98b.png" alt="🦋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>UseWhen()</code> 사용 시점</h4>



<ul class="wp-block-list">
<li><strong>조건부 기능:</strong> <br>요청에 따라 특정 기능을 활성화하거나 비활성화합니다 (예: 특정 사용자에게만 로깅, 쿼리 매개변수에 따른 캐싱 규칙 적용).</li>



<li><strong>동적 파이프라인:</strong> <br>다양한 요청에 맞춰 조정되는 파이프라인을 만듭니다 (예: 특정 경로에 대해 다른 인증 미들웨어).</li>



<li><strong>A/B 테스트:</strong> <br>실험을 위해 사용자 하위 집합을 대체 미들웨어 분기를 통해 라우팅합니다.</li>



<li><strong>디버깅 및 진단:</strong> <br>개발 환경에서만 진단 미들웨어를 적용합니다.</li>
</ul>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/">ASP.NET Core의 미들웨어(Middleware)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MimeKit과 MailKit 패키지 빌드 오류</title>
		<link>https://lycos7560.com/c/asp-net/mimekit%ea%b3%bc-mailkit-%ed%8c%a8%ed%82%a4%ec%a7%80-%eb%b9%8c%eb%93%9c-%ec%98%a4%eb%a5%98/40164/</link>
					<comments>https://lycos7560.com/c/asp-net/mimekit%ea%b3%bc-mailkit-%ed%8c%a8%ed%82%a4%ec%a7%80-%eb%b9%8c%eb%93%9c-%ec%98%a4%eb%a5%98/40164/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Mon, 21 Jul 2025 02:31:45 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[ASP.NET Core MVC]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[MailKit]]></category>
		<category><![CDATA[MimeKit]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[패키지]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40164</guid>

					<description><![CDATA[<p>MimeKit과 MailKit 패키지가 프로젝트에 정상적으로 설치되지 않았거나, NuGet 패키지를 빌드할 수 없는 상황이 발생 ✅ 1. 패키지 설치 확인 프로젝트 파일 (.csproj)에 다음이 반드시 있어야 합니다: ✅ 2. NuGet 패키지 수동 설치 (Visual Studio 없이) ✅ 3. 패키지 복원 아래 명령어로 패키지를 복원합니다: ✅ 4. MailKit 간단한 사용 예시</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/mimekit%ea%b3%bc-mailkit-%ed%8c%a8%ed%82%a4%ec%a7%80-%eb%b9%8c%eb%93%9c-%ec%98%a4%eb%a5%98/40164/">MimeKit과 MailKit 패키지 빌드 오류</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><code>MimeKit</code>과 <code>MailKit</code> 패키지가 프로젝트에 <strong>정상적으로 설치되지 않았거나</strong>, NuGet 패키지를 빌드할 수 없는 상황이 발생</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 1. 패키지 설치 확인</h2>



<p><strong>프로젝트 파일 (<code>.csproj</code>)에 다음이 반드시 있어야 합니다:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;ItemGroup>
  &lt;PackageReference Include="MimeKit" Version="[버전]" />
  &lt;PackageReference Include="MailKit" Version="[버전]" />
&lt;/ItemGroup>

예시
&lt;ItemGroup>
  &lt;PackageReference Include="MimeKit" Version="4.13.0" />
  &lt;PackageReference Include="MailKit" Version="4.13.0" />
&lt;/ItemGroup></pre>



<ul class="wp-block-list">
<li>위 항목이 없다면 <code>.csproj</code> 파일에 직접 추가하거나 아래 명령어로 설치</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 2. NuGet 패키지 수동 설치 (Visual Studio 없이)</h2>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">dotnet add package MimeKit --version [버전]
dotnet add package MailKit --version [버전]


예시 
dotnet add package MimeKit --version 4.13.0
dotnet add package MailKit --version 4.13.0</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 3. 패키지 복원</h2>



<p>아래 명령어로 패키지를 복원합니다:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">dotnet restore</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 4. MailKit 간단한 사용 예시</h2>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using MimeKit;
using MailKit.Net.Smtp;
using MailKit.Security;

namespace "namespace"
{
    public class EmailService : IEmailService
    {
        private readonly ILogger&lt;EmailService> _logger;

        public EmailService(ILogger&lt;EmailService> logger)
        {
            _logger = logger;
        }

        public async Task SendEmailAsync(string to, string subject, string body, string mimeType = "plain")
        {
            var email = new MimeMessage();
            email.From.Add(MailboxAddress.Parse("noreply@lycos7560.com"));
            email.To.Add(MailboxAddress.Parse(to));
            email.Subject = subject;

            email.Body = new TextPart(mimeType)
            {
                Text = body
            };

            using var smtp = new SmtpClient();
            try
            {
                await smtp.ConnectAsync("localhost", 포트, SecureSocketOptions.None); 
                await smtp.SendAsync(email);
                await smtp.DisconnectAsync(true);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "메일 전송 실패");
                throw;
            }
        }
    }

}
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">namespace "namespace"
{
    public interface IEmailService
    {
        public Task SendEmailAsync(string to, string subject, string body, string mimeType = "plain");
    }
}
</pre>



<figure class="wp-block-image size-full"><img decoding="async" width="391" height="254" src="https://lycos7560.com/wp-content/uploads/2025/07/image-18.png" alt="" class="wp-image-40165" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-18.png 391w, https://lycos7560.com/wp-content/uploads/2025/07/image-18-300x195.png 300w" sizes="(max-width: 391px) 100vw, 391px" /></figure>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/mimekit%ea%b3%bc-mailkit-%ed%8c%a8%ed%82%a4%ec%a7%80-%eb%b9%8c%eb%93%9c-%ec%98%a4%eb%a5%98/40164/">MimeKit과 MailKit 패키지 빌드 오류</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/mimekit%ea%b3%bc-mailkit-%ed%8c%a8%ed%82%a4%ec%a7%80-%eb%b9%8c%eb%93%9c-%ec%98%a4%eb%a5%98/40164/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dependency Inversion Principle(DIP, 의존성 역전 원리)</title>
		<link>https://lycos7560.com/c/dependency-inversion-principledip-%ec%9d%98%ec%a1%b4%ec%84%b1-%ec%97%ad%ec%a0%84-%ec%9b%90%eb%a6%ac/38731/</link>
					<comments>https://lycos7560.com/c/dependency-inversion-principledip-%ec%9d%98%ec%a1%b4%ec%84%b1-%ec%97%ad%ec%a0%84-%ec%9b%90%eb%a6%ac/38731/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Sun, 03 Nov 2024 07:48:04 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Blazor]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[AddModelError]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Consuming]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Create]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[Data access logic]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[DbSet]]></category>
		<category><![CDATA[DELETE]]></category>
		<category><![CDATA[Dependency]]></category>
		<category><![CDATA[Dependency Inversion]]></category>
		<category><![CDATA[Deploying]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[DIP]]></category>
		<category><![CDATA[Domain]]></category>
		<category><![CDATA[Domain Model]]></category>
		<category><![CDATA[File]]></category>
		<category><![CDATA[FileDescription]]></category>
		<category><![CDATA[Filestream]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[IFormFile]]></category>
		<category><![CDATA[Image]]></category>
		<category><![CDATA[Image Controller]]></category>
		<category><![CDATA[IsValid]]></category>
		<category><![CDATA[Migration]]></category>
		<category><![CDATA[Model]]></category>
		<category><![CDATA[ModelState]]></category>
		<category><![CDATA[pepe]]></category>
		<category><![CDATA[POST]]></category>
		<category><![CDATA[program]]></category>
		<category><![CDATA[Program.cs]]></category>
		<category><![CDATA[PUT]]></category>
		<category><![CDATA[Repository]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[REST Web API]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Upload]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[web API]]></category>
		<category><![CDATA[wpf]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[배포]]></category>
		<category><![CDATA[의존성]]></category>
		<category><![CDATA[의존성 역전]]></category>
		<category><![CDATA[의존성 역전 원리]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=38731</guid>

					<description><![CDATA[<p>Dependency Inversion Principle(DIP, 의존성 역전 원리) https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion The direction of dependency within the application should be in the direction of abstraction, not implementation details.애플리케이션 내의 종속성 방향은 구현 세부 사항이 아닌 추상화 방향이어야 합니다. Most applications are written such that compile-time dependency flows in the direction of runtime execution, producing a direct dependency graph.대부분의 애플리케이션은 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/dependency-inversion-principledip-%ec%9d%98%ec%a1%b4%ec%84%b1-%ec%97%ad%ec%a0%84-%ec%9b%90%eb%a6%ac/38731/">Dependency Inversion Principle(DIP, 의존성 역전 원리)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="dependency-inversion">Dependency Inversion Principle(DIP, 의존성 역전 원리)</h2>



<p><a href="https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion</a></p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The direction of dependency within the application should be in the direction of abstraction, not implementation details.<br>애플리케이션 내의 종속성 방향은 구현 세부 사항이 아닌 추상화 방향이어야 합니다. <br>Most applications are written such that compile-time dependency flows in the direction of runtime execution, producing a direct dependency graph.<br>대부분의 애플리케이션은 컴파일 타임 종속성이 런타임 실행 방향으로 흐르도록 작성되어 직접적인 종속성 그래프를 생성합니다.</p>



<p>That is, if class A calls a method of class B and class B calls a method of class C, then at compile time class A will depend on class B, and class B will depend on class C.<br>즉, class A가 class B의 메서드를 호출하고 class B가 class C의 메서드를 호출하는 경우 컴파일 타임에 class A는 class B에 종속되고 class B는 class C에 종속됩니다.</p>



<p>코드 구조에서 세부 구현 내용에 의존하지 말고, 더 추상적이고 일반적인 개념에 의존해야 한다는 뜻<br>이렇게 하면 코드가 더 유연해지고, 변경하기 쉽고, 재사용하기도 쉬워진다.<br>예를 들어, 어떤 기능을 <strong>직접 호출하는 것보다 그 기능을 일반적으로 설명하는 인터페이스를 사용하는 것이 더 좋다는 의미</strong>)</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="669" height="556" src="https://lycos7560.com/wp-content/uploads/2024/11/image-73.png" alt="" class="wp-image-38735" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-73.png 669w, https://lycos7560.com/wp-content/uploads/2024/11/image-73-300x249.png 300w" sizes="(max-width: 669px) 100vw, 669px" /><figcaption class="wp-element-caption">Direct dependency graph.</figcaption></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Applying the dependency inversion principle allows A to call methods on an abstraction that B implements, making it possible for A to call B at run time, but for B to depend on an interface controlled by A at compile time (thus,&nbsp;<em>inverting</em>&nbsp;the typical compile-time dependency).<br>종속성 반전 원칙을 적용하면 A가 B가 구현하는 추상화에 대한 메서드를 호출할 수 있으므로 A가 런타임에 B를 호출할 수 있지만 B는 컴파일 타임에 A에 의해 제어되는 인터페이스에 의존할 수 있습니다<br>(따라서 일반적인 컴파일 타임 종속성을&nbsp;<em>반전시킵니다</em>).&nbsp;</p>



<p>At run time, the flow of program execution remains unchanged, but the introduction of interfaces means that different implementations of these interfaces can easily be plugged in.<br>런타임에 프로그램 실행의 흐름은 변경되지 않지만 인터페이스의 도입은 이러한 인터페이스의 다른 구현을 쉽게 연결할 수 있음을 의미합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="838" height="455" src="https://lycos7560.com/wp-content/uploads/2024/11/image-74.png" alt="" class="wp-image-38736" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-74.png 838w, https://lycos7560.com/wp-content/uploads/2024/11/image-74-300x163.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-74-768x417.png 768w" sizes="(max-width: 838px) 100vw, 838px" /><figcaption class="wp-element-caption">Inverted dependency graph.</figcaption></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Dependency inversion</strong>&nbsp;is a key part of building loosely coupled applications, since implementation details can be written to depend on and implement higher-level abstractions, rather than the other way around.<br><strong>종속성 반전</strong>은 느슨하게 결합된 애플리케이션을 구축하는 데 있어 중요한 부분인데, 그 이유는 구현 세부 정보를 작성하여 더 높은 수준의 추상화에 의존하고 구현하도록 할 수 있기 때문입니다.</p>



<p>The resulting applications are more testable, modular, and maintainable as a result.<br>그 결과 응용 프로그램은 더 쉽게 테스트할 수 있고, 모듈화되며, 유지 관리가 더 용이합니다.&nbsp;</p>



<p>The practice of&nbsp;<em>dependency injection</em>&nbsp;is made possible by following the dependency inversion principle.<br><em>종속성 주입</em>의 연습은 종속성 반전 원칙을 따름으로써 가능합니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)"/>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>애플리케이션의 의존성을 구체적인 구현이 아니라 추상화된 개념에 맞추는 것</p>



<p>대부분의 애플리케이션은 A 클래스가 B 클래스를, B 클래스가 C 클래스를 호출하는 형태로 작성됩니다. </p>



<p>이는 A가 B에, B가 C에 의존하게 만든다는 뜻입니다. 종속성 반전 원칙을 적용하면 A는 B의 구체적인 구현이 아니라 B가 구현하는 추상 개념에 의존하게 됩니다. </p>



<p>이렇게 하면 코드를 더 쉽게 테스트할 수 있고, 변경 및 유지보수가 용이해집니다. </p>



<p>간단히 말해, 코드를 유연하고 재사용 가능하게 만들어줍니다.</p>



<div style="height:102px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>종속성 반전 적용 전</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 엔진 클래스
public class Engine
{
    public void Start()
    {
        Console.WriteLine("Engine starts.");
    }
}

// 자동차 클래스
public class Car
{
    private Engine _engine = new Engine();

    public void Start()
    {
        _engine.Start();
        Console.WriteLine("Car starts.");
    }
}

// 메인 프로그램
public class Program
{
    public static void Main(string[] args)
    {
        Car car = new Car();
        car.Start();
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>종속성 반전 적용 후</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 인터페이스 정의
public interface IEngine
{
    void Start();
}

// 엔진 클래스
public class Engine : IEngine
{
    public void Start()
    {
        Console.WriteLine("Engine starts.");
    }
}

// 전기 엔진 클래스
public class ElectricEngine : IEngine
{
    public void Start()
    {
        Console.WriteLine("Electric engine starts.");
    }
}

// 자동차 클래스
public class Car
{
    private IEngine _engine;

    public Car(IEngine engine)
    {
        _engine = engine;
    }

    public void Start()
    {
        _engine.Start();
        Console.WriteLine("Car starts.");
    }
}

// 메인 프로그램
public class Program
{
    public static void Main(string[] args)
    {
        IEngine engine = new Engine();
        Car car = new Car(engine);
        car.Start();
        
        IEngine electricEngine = new ElectricEngine();
        Car electricCar = new Car(electricEngine);
        electricCar.Start();
    }
}
</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/c/dependency-inversion-principledip-%ec%9d%98%ec%a1%b4%ec%84%b1-%ec%97%ad%ec%a0%84-%ec%9b%90%eb%a6%ac/38731/">Dependency Inversion Principle(DIP, 의존성 역전 원리)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/dependency-inversion-principledip-%ec%9d%98%ec%a1%b4%ec%84%b1-%ec%97%ad%ec%a0%84-%ec%9b%90%eb%a6%ac/38731/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Deploying ASP.NET Web API To Azure</title>
		<link>https://lycos7560.com/c/deploying-asp-net-web-api-to-azure/38646/</link>
					<comments>https://lycos7560.com/c/deploying-asp-net-web-api-to-azure/38646/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Fri, 01 Nov 2024 08:08:45 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[AddModelError]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Consuming]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Create]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[Data access logic]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[DbSet]]></category>
		<category><![CDATA[DELETE]]></category>
		<category><![CDATA[Deploying]]></category>
		<category><![CDATA[Domain]]></category>
		<category><![CDATA[Domain Model]]></category>
		<category><![CDATA[File]]></category>
		<category><![CDATA[FileDescription]]></category>
		<category><![CDATA[Filestream]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[IFormFile]]></category>
		<category><![CDATA[Image]]></category>
		<category><![CDATA[Image Controller]]></category>
		<category><![CDATA[IsValid]]></category>
		<category><![CDATA[Migration]]></category>
		<category><![CDATA[Model]]></category>
		<category><![CDATA[ModelState]]></category>
		<category><![CDATA[pepe]]></category>
		<category><![CDATA[POST]]></category>
		<category><![CDATA[program]]></category>
		<category><![CDATA[Program.cs]]></category>
		<category><![CDATA[PUT]]></category>
		<category><![CDATA[Repository]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[REST Web API]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Upload]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[web API]]></category>
		<category><![CDATA[wpf]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[배포]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=38646</guid>

					<description><![CDATA[<p>Deploying ASP.NET Web API To Azure https://azure.microsoft.com/ko-kr</p>
<p>The post <a href="https://lycos7560.com/c/deploying-asp-net-web-api-to-azure/38646/">Deploying ASP.NET Web API To Azure</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Deploying ASP.NET Web API To Azure</h2>



<p><a href="https://azure.microsoft.com/ko-kr" target="_blank" rel="noreferrer noopener">https://azure.microsoft.com/ko-kr</a></p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1462" height="565" src="https://lycos7560.com/wp-content/uploads/2024/11/image.png" alt="" class="wp-image-38647" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image.png 1462w, https://lycos7560.com/wp-content/uploads/2024/11/image-300x116.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-768x297.png 768w" sizes="(max-width: 1462px) 100vw, 1462px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1242" height="801" src="https://lycos7560.com/wp-content/uploads/2024/11/image-1.png" alt="" class="wp-image-38650" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-1.png 1242w, https://lycos7560.com/wp-content/uploads/2024/11/image-1-300x193.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-1-768x495.png 768w" sizes="(max-width: 1242px) 100vw, 1242px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1019" height="525" src="https://lycos7560.com/wp-content/uploads/2024/11/image-2.png" alt="" class="wp-image-38651" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-2.png 1019w, https://lycos7560.com/wp-content/uploads/2024/11/image-2-300x155.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-2-768x396.png 768w" sizes="(max-width: 1019px) 100vw, 1019px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="745" height="541" src="https://lycos7560.com/wp-content/uploads/2024/11/image-3.png" alt="" class="wp-image-38652" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-3.png 745w, https://lycos7560.com/wp-content/uploads/2024/11/image-3-300x218.png 300w" sizes="(max-width: 745px) 100vw, 745px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1242" height="801" src="https://lycos7560.com/wp-content/uploads/2024/11/image-4.png" alt="" class="wp-image-38653" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-4.png 1242w, https://lycos7560.com/wp-content/uploads/2024/11/image-4-300x193.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-4-768x495.png 768w" sizes="(max-width: 1242px) 100vw, 1242px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1242" height="801" src="https://lycos7560.com/wp-content/uploads/2024/11/image-10.png" alt="" class="wp-image-38659" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-10.png 1242w, https://lycos7560.com/wp-content/uploads/2024/11/image-10-300x193.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-10-768x495.png 768w" sizes="(max-width: 1242px) 100vw, 1242px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1456" height="399" src="https://lycos7560.com/wp-content/uploads/2024/11/image-11.png" alt="" class="wp-image-38661" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-11.png 1456w, https://lycos7560.com/wp-content/uploads/2024/11/image-11-300x82.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-11-768x210.png 768w" sizes="(max-width: 1456px) 100vw, 1456px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="936" height="769" src="https://lycos7560.com/wp-content/uploads/2024/11/image-12.png" alt="" class="wp-image-38662" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-12.png 936w, https://lycos7560.com/wp-content/uploads/2024/11/image-12-300x246.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-12-768x631.png 768w" sizes="(max-width: 936px) 100vw, 936px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1176" height="457" src="https://lycos7560.com/wp-content/uploads/2024/11/image-13.png" alt="" class="wp-image-38663" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-13.png 1176w, https://lycos7560.com/wp-content/uploads/2024/11/image-13-300x117.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-13-768x298.png 768w" sizes="(max-width: 1176px) 100vw, 1176px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1510" height="801" src="https://lycos7560.com/wp-content/uploads/2024/11/image-14.png" alt="" class="wp-image-38664" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-14.png 1510w, https://lycos7560.com/wp-content/uploads/2024/11/image-14-300x159.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-14-768x407.png 768w" sizes="(max-width: 1510px) 100vw, 1510px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image"><img decoding="async" width="1146" height="521" src="https://lycos7560.com/wp-content/uploads/2024/11/image-15.png" alt="" class="wp-image-38665" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-15.png 1146w, https://lycos7560.com/wp-content/uploads/2024/11/image-15-300x136.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-15-768x349.png 768w" sizes="(max-width: 1146px) 100vw, 1146px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="845" height="518" src="https://lycos7560.com/wp-content/uploads/2024/11/image-16.png" alt="" class="wp-image-38666" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-16.png 845w, https://lycos7560.com/wp-content/uploads/2024/11/image-16-300x184.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-16-768x471.png 768w" sizes="(max-width: 845px) 100vw, 845px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="662" height="978" src="https://lycos7560.com/wp-content/uploads/2024/11/image-17.png" alt="" class="wp-image-38668" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-17.png 662w, https://lycos7560.com/wp-content/uploads/2024/11/image-17-203x300.png 203w" sizes="(max-width: 662px) 100vw, 662px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="661" height="820" src="https://lycos7560.com/wp-content/uploads/2024/11/image-18.png" alt="" class="wp-image-38670" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-18.png 661w, https://lycos7560.com/wp-content/uploads/2024/11/image-18-242x300.png 242w" sizes="(max-width: 661px) 100vw, 661px" /></figure>
</div>
</div>



<figure class="wp-block-image size-full"><img decoding="async" width="1726" height="984" src="https://lycos7560.com/wp-content/uploads/2024/11/image-20.png" alt="" class="wp-image-38672" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-20.png 1726w, https://lycos7560.com/wp-content/uploads/2024/11/image-20-300x171.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-20-768x438.png 768w, https://lycos7560.com/wp-content/uploads/2024/11/image-20-1536x876.png 1536w" sizes="(max-width: 1726px) 100vw, 1726px" /></figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="895" height="295" src="https://lycos7560.com/wp-content/uploads/2024/11/image-21.png" alt="" class="wp-image-38673" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-21.png 895w, https://lycos7560.com/wp-content/uploads/2024/11/image-21-300x99.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-21-768x253.png 768w" sizes="(max-width: 895px) 100vw, 895px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="688" height="328" src="https://lycos7560.com/wp-content/uploads/2024/11/image-25.png" alt="" class="wp-image-38677" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-25.png 688w, https://lycos7560.com/wp-content/uploads/2024/11/image-25-300x143.png 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="645" height="987" src="https://lycos7560.com/wp-content/uploads/2024/11/image-27.png" alt="" class="wp-image-38679" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-27.png 645w, https://lycos7560.com/wp-content/uploads/2024/11/image-27-196x300.png 196w" sizes="(max-width: 645px) 100vw, 645px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="696" height="1020" src="https://lycos7560.com/wp-content/uploads/2024/11/image-26.png" alt="" class="wp-image-38678" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-26.png 696w, https://lycos7560.com/wp-content/uploads/2024/11/image-26-205x300.png 205w" sizes="(max-width: 696px) 100vw, 696px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="824" height="415" src="https://lycos7560.com/wp-content/uploads/2024/11/image-28.png" alt="" class="wp-image-38680" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-28.png 824w, https://lycos7560.com/wp-content/uploads/2024/11/image-28-300x151.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-28-768x387.png 768w" sizes="(max-width: 824px) 100vw, 824px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="826" height="330" src="https://lycos7560.com/wp-content/uploads/2024/11/image-29.png" alt="" class="wp-image-38681" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-29.png 826w, https://lycos7560.com/wp-content/uploads/2024/11/image-29-300x120.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-29-768x307.png 768w" sizes="(max-width: 826px) 100vw, 826px" /></figure>
</div>
</div>



<figure class="wp-block-image size-full"><img decoding="async" width="1404" height="1005" src="https://lycos7560.com/wp-content/uploads/2024/11/image-30.png" alt="" class="wp-image-38682" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-30.png 1404w, https://lycos7560.com/wp-content/uploads/2024/11/image-30-300x215.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-30-768x550.png 768w" sizes="(max-width: 1404px) 100vw, 1404px" /></figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="714" height="604" src="https://lycos7560.com/wp-content/uploads/2024/11/image-31.png" alt="" class="wp-image-38683" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-31.png 714w, https://lycos7560.com/wp-content/uploads/2024/11/image-31-300x254.png 300w" sizes="(max-width: 714px) 100vw, 714px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="618" height="372" src="https://lycos7560.com/wp-content/uploads/2024/11/image-32.png" alt="" class="wp-image-38684" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-32.png 618w, https://lycos7560.com/wp-content/uploads/2024/11/image-32-300x181.png 300w" sizes="(max-width: 618px) 100vw, 618px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-full"><img decoding="async" width="473" height="496" src="https://lycos7560.com/wp-content/uploads/2024/11/image-33.png" alt="" class="wp-image-38685" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-33.png 473w, https://lycos7560.com/wp-content/uploads/2024/11/image-33-286x300.png 286w" sizes="(max-width: 473px) 100vw, 473px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="762" height="475" src="https://lycos7560.com/wp-content/uploads/2024/11/image-34.png" alt="" class="wp-image-38686" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-34.png 762w, https://lycos7560.com/wp-content/uploads/2024/11/image-34-300x187.png 300w" sizes="(max-width: 762px) 100vw, 762px" /><figcaption class="wp-element-caption">Azure 계정확인</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="596" height="362" src="https://lycos7560.com/wp-content/uploads/2024/11/image-35.png" alt="" class="wp-image-38687" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-35.png 596w, https://lycos7560.com/wp-content/uploads/2024/11/image-35-300x182.png 300w" sizes="(max-width: 596px) 100vw, 596px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-full"><img decoding="async" width="781" height="549" src="https://lycos7560.com/wp-content/uploads/2024/11/image-36.png" alt="" class="wp-image-38688" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-36.png 781w, https://lycos7560.com/wp-content/uploads/2024/11/image-36-300x211.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-36-768x540.png 768w" sizes="(max-width: 781px) 100vw, 781px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="787" height="541" src="https://lycos7560.com/wp-content/uploads/2024/11/image-38.png" alt="" class="wp-image-38690" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-38.png 787w, https://lycos7560.com/wp-content/uploads/2024/11/image-38-300x206.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-38-768x528.png 768w" sizes="(max-width: 787px) 100vw, 787px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="779" height="545" src="https://lycos7560.com/wp-content/uploads/2024/11/image-39.png" alt="" class="wp-image-38691" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-39.png 779w, https://lycos7560.com/wp-content/uploads/2024/11/image-39-300x210.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-39-768x537.png 768w" sizes="(max-width: 779px) 100vw, 779px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-full"><img decoding="async" width="764" height="523" src="https://lycos7560.com/wp-content/uploads/2024/11/image-40.png" alt="" class="wp-image-38692" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-40.png 764w, https://lycos7560.com/wp-content/uploads/2024/11/image-40-300x205.png 300w" sizes="(max-width: 764px) 100vw, 764px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50.01%">
<figure class="wp-block-image size-full"><img decoding="async" width="785" height="547" src="https://lycos7560.com/wp-content/uploads/2024/11/image-41.png" alt="" class="wp-image-38693" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-41.png 785w, https://lycos7560.com/wp-content/uploads/2024/11/image-41-300x209.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-41-768x535.png 768w" sizes="(max-width: 785px) 100vw, 785px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:49.99%">
<figure class="wp-block-image size-full"><img decoding="async" width="1133" height="772" src="https://lycos7560.com/wp-content/uploads/2024/11/image-42.png" alt="" class="wp-image-38694" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-42.png 1133w, https://lycos7560.com/wp-content/uploads/2024/11/image-42-300x204.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-42-768x523.png 768w" sizes="(max-width: 1133px) 100vw, 1133px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1074" height="288" src="https://lycos7560.com/wp-content/uploads/2024/11/image-43.png" alt="" class="wp-image-38695" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-43.png 1074w, https://lycos7560.com/wp-content/uploads/2024/11/image-43-300x80.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-43-768x206.png 768w" sizes="(max-width: 1074px) 100vw, 1074px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="786" height="549" src="https://lycos7560.com/wp-content/uploads/2024/11/image-44.png" alt="" class="wp-image-38697" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-44.png 786w, https://lycos7560.com/wp-content/uploads/2024/11/image-44-300x210.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-44-768x536.png 768w" sizes="(max-width: 786px) 100vw, 786px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="777" height="540" src="https://lycos7560.com/wp-content/uploads/2024/11/image-45.png" alt="" class="wp-image-38698" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-45.png 777w, https://lycos7560.com/wp-content/uploads/2024/11/image-45-300x208.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-45-768x534.png 768w" sizes="(max-width: 777px) 100vw, 777px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="775" height="543" src="https://lycos7560.com/wp-content/uploads/2024/11/image-46.png" alt="" class="wp-image-38699" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-46.png 775w, https://lycos7560.com/wp-content/uploads/2024/11/image-46-300x210.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-46-768x538.png 768w" sizes="(max-width: 775px) 100vw, 775px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="788" height="564" src="https://lycos7560.com/wp-content/uploads/2024/11/image-47.png" alt="" class="wp-image-38700" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-47.png 788w, https://lycos7560.com/wp-content/uploads/2024/11/image-47-300x215.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-47-768x550.png 768w" sizes="(max-width: 788px) 100vw, 788px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1059" height="338" src="https://lycos7560.com/wp-content/uploads/2024/11/image-48.png" alt="" class="wp-image-38701" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-48.png 1059w, https://lycos7560.com/wp-content/uploads/2024/11/image-48-300x96.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-48-768x245.png 768w" sizes="(max-width: 1059px) 100vw, 1059px" /><figcaption class="wp-element-caption">위와 같은 작업 반복</figcaption></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="858" height="260" src="https://lycos7560.com/wp-content/uploads/2024/11/image-49.png" alt="" class="wp-image-38702" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-49.png 858w, https://lycos7560.com/wp-content/uploads/2024/11/image-49-300x91.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-49-768x233.png 768w" sizes="(max-width: 858px) 100vw, 858px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1117" height="772" src="https://lycos7560.com/wp-content/uploads/2024/11/image-54.png" alt="" class="wp-image-38708" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-54.png 1117w, https://lycos7560.com/wp-content/uploads/2024/11/image-54-300x207.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-54-768x531.png 768w" sizes="(max-width: 1117px) 100vw, 1117px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1151" height="377" src="https://lycos7560.com/wp-content/uploads/2024/11/image-53.png" alt="" class="wp-image-38711" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-53.png 1151w, https://lycos7560.com/wp-content/uploads/2024/11/image-53-300x98.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-53-768x252.png 768w" sizes="(max-width: 1151px) 100vw, 1151px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="686" height="544" src="https://lycos7560.com/wp-content/uploads/2024/11/image-55.png" alt="" class="wp-image-38712" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-55.png 686w, https://lycos7560.com/wp-content/uploads/2024/11/image-55-300x238.png 300w" sizes="(max-width: 686px) 100vw, 686px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1170" height="123" src="https://lycos7560.com/wp-content/uploads/2024/11/image-50.png" alt="" class="wp-image-38703" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-50.png 1170w, https://lycos7560.com/wp-content/uploads/2024/11/image-50-300x32.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-50-768x81.png 768w" sizes="(max-width: 1170px) 100vw, 1170px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1046" height="319" src="https://lycos7560.com/wp-content/uploads/2024/11/image-56.png" alt="" class="wp-image-38713" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-56.png 1046w, https://lycos7560.com/wp-content/uploads/2024/11/image-56-300x91.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-56-768x234.png 768w" sizes="(max-width: 1046px) 100vw, 1046px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="687" height="537" src="https://lycos7560.com/wp-content/uploads/2024/11/image-57.png" alt="" class="wp-image-38714" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-57.png 687w, https://lycos7560.com/wp-content/uploads/2024/11/image-57-300x234.png 300w" sizes="(max-width: 687px) 100vw, 687px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-full"><img decoding="async" width="319" height="342" src="https://lycos7560.com/wp-content/uploads/2024/11/image-65.png" alt="" class="wp-image-38722" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-65.png 319w, https://lycos7560.com/wp-content/uploads/2024/11/image-65-280x300.png 280w" sizes="(max-width: 319px) 100vw, 319px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="1091" height="731" src="https://lycos7560.com/wp-content/uploads/2024/11/image-58.png" alt="" class="wp-image-38715" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-58.png 1091w, https://lycos7560.com/wp-content/uploads/2024/11/image-58-300x201.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-58-768x515.png 768w" sizes="(max-width: 1091px) 100vw, 1091px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="672" height="599" src="https://lycos7560.com/wp-content/uploads/2024/11/image-59.png" alt="" class="wp-image-38716" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-59.png 672w, https://lycos7560.com/wp-content/uploads/2024/11/image-59-300x267.png 300w" sizes="(max-width: 672px) 100vw, 672px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1066" height="750" src="https://lycos7560.com/wp-content/uploads/2024/11/image-60.png" alt="" class="wp-image-38717" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-60.png 1066w, https://lycos7560.com/wp-content/uploads/2024/11/image-60-300x211.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-60-768x540.png 768w" sizes="(max-width: 1066px) 100vw, 1066px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="304" height="432" src="https://lycos7560.com/wp-content/uploads/2024/11/image-61.png" alt="" class="wp-image-38718" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-61.png 304w, https://lycos7560.com/wp-content/uploads/2024/11/image-61-211x300.png 211w" sizes="(max-width: 304px) 100vw, 304px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="528" height="628" src="https://lycos7560.com/wp-content/uploads/2024/11/image-62.png" alt="" class="wp-image-38719" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-62.png 528w, https://lycos7560.com/wp-content/uploads/2024/11/image-62-252x300.png 252w" sizes="(max-width: 528px) 100vw, 528px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1034" height="568" src="https://lycos7560.com/wp-content/uploads/2024/11/image-63.png" alt="" class="wp-image-38720" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-63.png 1034w, https://lycos7560.com/wp-content/uploads/2024/11/image-63-300x165.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-63-768x422.png 768w" sizes="(max-width: 1034px) 100vw, 1034px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1005" height="434" src="https://lycos7560.com/wp-content/uploads/2024/11/image-64.png" alt="" class="wp-image-38721" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-64.png 1005w, https://lycos7560.com/wp-content/uploads/2024/11/image-64-300x130.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-64-768x332.png 768w" sizes="(max-width: 1005px) 100vw, 1005px" /></figure>
</div>
</div>



<figure class="wp-block-image size-full"><img decoding="async" width="1593" height="225" src="https://lycos7560.com/wp-content/uploads/2024/11/image-66.png" alt="" class="wp-image-38723" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-66.png 1593w, https://lycos7560.com/wp-content/uploads/2024/11/image-66-300x42.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-66-768x108.png 768w, https://lycos7560.com/wp-content/uploads/2024/11/image-66-1536x217.png 1536w" sizes="(max-width: 1593px) 100vw, 1593px" /></figure>



<figure class="wp-block-image size-full"><img decoding="async" width="1359" height="598" src="https://lycos7560.com/wp-content/uploads/2024/11/image-67.png" alt="" class="wp-image-38724" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-67.png 1359w, https://lycos7560.com/wp-content/uploads/2024/11/image-67-300x132.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-67-768x338.png 768w" sizes="(max-width: 1359px) 100vw, 1359px" /></figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="940" height="534" src="https://lycos7560.com/wp-content/uploads/2024/11/image-68.png" alt="" class="wp-image-38725" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-68.png 940w, https://lycos7560.com/wp-content/uploads/2024/11/image-68-300x170.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-68-768x436.png 768w" sizes="(max-width: 940px) 100vw, 940px" /><figcaption class="wp-element-caption">계정 생성</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1435" height="379" src="https://lycos7560.com/wp-content/uploads/2024/11/image-69.png" alt="" class="wp-image-38726" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-69.png 1435w, https://lycos7560.com/wp-content/uploads/2024/11/image-69-300x79.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-69-768x203.png 768w" sizes="(max-width: 1435px) 100vw, 1435px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img decoding="async" width="927" height="637" data-id="38727" src="https://lycos7560.com/wp-content/uploads/2024/11/image-70.png" alt="" class="wp-image-38727" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-70.png 927w, https://lycos7560.com/wp-content/uploads/2024/11/image-70-300x206.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-70-768x528.png 768w" sizes="(max-width: 927px) 100vw, 927px" /></figure>
</figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="939" height="447" src="https://lycos7560.com/wp-content/uploads/2024/11/image-71.png" alt="" class="wp-image-38728" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-71.png 939w, https://lycos7560.com/wp-content/uploads/2024/11/image-71-300x143.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-71-768x366.png 768w" sizes="(max-width: 939px) 100vw, 939px" /></figure>
</div>
</div>



<figure class="wp-block-image size-full"><img decoding="async" width="942" height="762" src="https://lycos7560.com/wp-content/uploads/2024/11/image-72.png" alt="" class="wp-image-38729" srcset="https://lycos7560.com/wp-content/uploads/2024/11/image-72.png 942w, https://lycos7560.com/wp-content/uploads/2024/11/image-72-300x243.png 300w, https://lycos7560.com/wp-content/uploads/2024/11/image-72-768x621.png 768w" sizes="(max-width: 942px) 100vw, 942px" /></figure>
<p>The post <a href="https://lycos7560.com/c/deploying-asp-net-web-api-to-azure/38646/">Deploying ASP.NET Web API To Azure</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/deploying-asp-net-web-api-to-azure/38646/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Consuming REST Web APIs</title>
		<link>https://lycos7560.com/c/consuming-rest-web-apis/38585/</link>
					<comments>https://lycos7560.com/c/consuming-rest-web-apis/38585/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Thu, 31 Oct 2024 09:56:32 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[AddModelError]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Consuming]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Create]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[Data access logic]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[DbSet]]></category>
		<category><![CDATA[DELETE]]></category>
		<category><![CDATA[Domain]]></category>
		<category><![CDATA[Domain Model]]></category>
		<category><![CDATA[File]]></category>
		<category><![CDATA[FileDescription]]></category>
		<category><![CDATA[Filestream]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[IFormFile]]></category>
		<category><![CDATA[Image]]></category>
		<category><![CDATA[Image Controller]]></category>
		<category><![CDATA[IsValid]]></category>
		<category><![CDATA[Migration]]></category>
		<category><![CDATA[Model]]></category>
		<category><![CDATA[ModelState]]></category>
		<category><![CDATA[pepe]]></category>
		<category><![CDATA[POST]]></category>
		<category><![CDATA[program]]></category>
		<category><![CDATA[Program.cs]]></category>
		<category><![CDATA[PUT]]></category>
		<category><![CDATA[Repository]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[REST Web API]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Upload]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[web API]]></category>
		<category><![CDATA[wpf]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=38585</guid>

					<description><![CDATA[<p>Consuming REST Web APIs 1. 프로젝트 생성(MVC UI) ASP.Net Core Web App (MVC) 2. GET Controller 생성 HttpClient 적용 .Net에서 제공하는 HttpClient Class https://learn.microsoft.com/ko-kr/dotnet/api/system.net.http.httpclient?view=net-8.0 HTTP 요청을 보내고 URI로 식별된 리소스에서 HTTP 응답을 수신하기 위한 클래스를 제공합니다. 사용 예제 HttpClient를 사용하려면, Program.cs 파일에 HttpClient Factory를 설정 이를 통해 효율적으로 HttpClient 인스턴스를 관리하고, 특히 성능 문제를 해결할 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/consuming-rest-web-apis/38585/">Consuming REST Web APIs</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Consuming REST Web APIs</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-b7f87031      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#consuming-rest-web-apis" class="uagb-toc-link__trigger">Consuming REST Web APIs</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-프로젝트-생성mvc-ui" class="uagb-toc-link__trigger">1. 프로젝트 생성(MVC UI)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-get-controller-생성" class="uagb-toc-link__trigger">2. GET Controller 생성</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#httpclient-적용" class="uagb-toc-link__trigger">HttpClient 적용</a></li></ul><li class="uagb-toc__list"><a href="#3-indexcshtml-layoutcshtml-생성-및-수정" class="uagb-toc-link__trigger">3. Index.cshtml , _Layout.cshtml 생성 및 수정</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-regiondtocs-생성-및-indexcshtml-적용" class="uagb-toc-link__trigger">4. RegionDto.cs 생성 및 Index.cshtml 적용</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-post-method-생성" class="uagb-toc-link__trigger">5. POST Method 생성</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#6-단일-편집-기능" class="uagb-toc-link__trigger">6. 단일 편집 기능</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#7-삭제-기능" class="uagb-toc-link__trigger">7. 삭제 기능</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1. 프로젝트 생성(MVC UI)</h3>



<p>ASP.Net Core Web App (MVC)</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="807" height="611" src="https://lycos7560.com/wp-content/uploads/2024/10/image-154.png" alt="" class="wp-image-38586" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-154.png 807w, https://lycos7560.com/wp-content/uploads/2024/10/image-154-300x227.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-154-768x581.png 768w" sizes="(max-width: 807px) 100vw, 807px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1005" height="626" src="https://lycos7560.com/wp-content/uploads/2024/10/image-155.png" alt="" class="wp-image-38587" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-155.png 1005w, https://lycos7560.com/wp-content/uploads/2024/10/image-155-300x187.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-155-768x478.png 768w" sizes="(max-width: 1005px) 100vw, 1005px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1207" height="800" src="https://lycos7560.com/wp-content/uploads/2024/10/image-156.png" alt="" class="wp-image-38588" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-156.png 1207w, https://lycos7560.com/wp-content/uploads/2024/10/image-156-300x199.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-156-768x509.png 768w" sizes="(max-width: 1207px) 100vw, 1207px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="957" height="805" src="https://lycos7560.com/wp-content/uploads/2024/10/image-157.png" alt="" class="wp-image-38589" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-157.png 957w, https://lycos7560.com/wp-content/uploads/2024/10/image-157-300x252.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-157-768x646.png 768w" sizes="(max-width: 957px) 100vw, 957px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="787" height="539" src="https://lycos7560.com/wp-content/uploads/2024/10/image-158.png" alt="" class="wp-image-38590" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-158.png 787w, https://lycos7560.com/wp-content/uploads/2024/10/image-158-300x205.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-158-768x526.png 768w" sizes="(max-width: 787px) 100vw, 787px" /><figcaption class="wp-element-caption">여러 개의 시작 프로젝트로 변경</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1905" height="1023" src="https://lycos7560.com/wp-content/uploads/2024/10/image-159.png" alt="" class="wp-image-38591" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-159.png 1905w, https://lycos7560.com/wp-content/uploads/2024/10/image-159-300x161.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-159-768x412.png 768w, https://lycos7560.com/wp-content/uploads/2024/10/image-159-1536x825.png 1536w" sizes="(max-width: 1905px) 100vw, 1905px" /><figcaption class="wp-element-caption">MVC UI와 API(Swagger)</figcaption></figure>
</div>
</div>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2. GET Controller 생성</h3>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1286" height="394" src="https://lycos7560.com/wp-content/uploads/2024/10/image-160.png" alt="" class="wp-image-38592" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-160.png 1286w, https://lycos7560.com/wp-content/uploads/2024/10/image-160-300x92.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-160-768x235.png 768w" sizes="(max-width: 1286px) 100vw, 1286px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="935" height="406" src="https://lycos7560.com/wp-content/uploads/2024/10/image-161.png" alt="" class="wp-image-38593" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-161.png 935w, https://lycos7560.com/wp-content/uploads/2024/10/image-161-300x130.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-161-768x333.png 768w" sizes="(max-width: 935px) 100vw, 935px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="992" height="431" src="https://lycos7560.com/wp-content/uploads/2024/10/image-162.png" alt="" class="wp-image-38594" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-162.png 992w, https://lycos7560.com/wp-content/uploads/2024/10/image-162-300x130.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-162-768x334.png 768w" sizes="(max-width: 992px) 100vw, 992px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1251" height="440" src="https://lycos7560.com/wp-content/uploads/2024/10/image-171.png" alt="" class="wp-image-38603" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-171.png 1251w, https://lycos7560.com/wp-content/uploads/2024/10/image-171-300x106.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-171-768x270.png 768w" sizes="(max-width: 1251px) 100vw, 1251px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="737" height="619" src="https://lycos7560.com/wp-content/uploads/2024/10/image-172.png" alt="" class="wp-image-38604" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-172.png 737w, https://lycos7560.com/wp-content/uploads/2024/10/image-172-300x252.png 300w" sizes="(max-width: 737px) 100vw, 737px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1032" height="440" src="https://lycos7560.com/wp-content/uploads/2024/10/image-177.png" alt="" class="wp-image-38609" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-177.png 1032w, https://lycos7560.com/wp-content/uploads/2024/10/image-177-300x128.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-177-768x327.png 768w" sizes="(max-width: 1032px) 100vw, 1032px" /></figure>
</div>
</div>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">HttpClient 적용</h4>



<p>.Net에서 제공하는 <strong>HttpClient Class</strong></p>



<p><a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.net.http.httpclient?view=net-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/ko-kr/dotnet/api/system.net.http.httpclient?view=net-8.0</a></p>



<p>HTTP 요청을 보내고 URI로 식별된 리소스에서 HTTP 응답을 수신하기 위한 클래스를 제공합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class HttpClient : System.Net.Http.HttpMessageInvoker</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>사용 예제</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// HttpClient는 매번 사용될 때마다 인스턴스화되지 않고, 애플리케이션 전체에서 한 번만 인스턴스화되어야 합니다. 참고 사항을 참조하세요.
static readonly HttpClient client = new HttpClient();

static async Task Main()
{
    // 비동기 네트워크 메서드를 try/catch 블록 내에서 호출하여 예외를 처리합니다.
    try
    {
        // 비동기적으로 GET 요청을 보냅니다.
        using HttpResponseMessage response = await client.GetAsync("http://www.contoso.com/");
        
        // 요청이 성공적으로 완료되었는지 확인합니다.
        response.EnsureSuccessStatusCode();
        
        // 응답 본문을 문자열로 읽어옵니다.
        string responseBody = await response.Content.ReadAsStringAsync();
        
        // 위의 세 줄은 아래 새로운 헬퍼 메서드로 대체할 수 있습니다.
        // string responseBody = await client.GetStringAsync(uri);
        
        // 응답 본문을 콘솔에 출력합니다.
        Console.WriteLine(responseBody);
    }
    catch (HttpRequestException e)
    {
        // 예외가 발생했을 때 예외 메시지를 콘솔에 출력합니다.
        Console.WriteLine("\nException Caught!");
        Console.WriteLine("Message :{0} ", e.Message);
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.net.http.httpclient?view=net-8.0#remarks"></a></p>



<p>HttpClient를 사용하려면, Program.cs 파일에 HttpClient Factory를 설정</p>



<p>이를 통해 효율적으로 <strong>HttpClient 인스턴스를 관리하고, 특히 성능 문제를 해결</strong>할 수 있음</p>



<p>Program.cs</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

// HttpClient 삽입
builder.Services.AddHttpClient();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

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

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="920" height="469" src="https://lycos7560.com/wp-content/uploads/2024/10/image-178.png" alt="" class="wp-image-38610" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-178.png 920w, https://lycos7560.com/wp-content/uploads/2024/10/image-178-300x153.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-178-768x392.png 768w" sizes="(max-width: 920px) 100vw, 920px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1077" height="459" src="https://lycos7560.com/wp-content/uploads/2024/10/image-179.png" alt="" class="wp-image-38611" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-179.png 1077w, https://lycos7560.com/wp-content/uploads/2024/10/image-179-300x128.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-179-768x327.png 768w" sizes="(max-width: 1077px) 100vw, 1077px" /><figcaption class="wp-element-caption">NZWalks.API의 launchSettings.json</figcaption></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1306" height="778" src="https://lycos7560.com/wp-content/uploads/2024/10/image-180.png" alt="" class="wp-image-38612" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-180.png 1306w, https://lycos7560.com/wp-content/uploads/2024/10/image-180-300x179.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-180-768x458.png 768w" sizes="(max-width: 1306px) 100vw, 1306px" /><figcaption class="wp-element-caption">RegionsController의 GetAll Api를 사용</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="907" height="668" src="https://lycos7560.com/wp-content/uploads/2024/10/image-181.png" alt="" class="wp-image-38613" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-181.png 907w, https://lycos7560.com/wp-content/uploads/2024/10/image-181-300x221.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-181-768x566.png 768w" sizes="(max-width: 907px) 100vw, 907px" /><figcaption class="wp-element-caption">테스트를 위해서 인증 주석</figcaption></figure>
</div>
</div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>RegionsController.cs 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;

namespace NZWalksUI.Controllers
{
    public class RegionsController : Controller
    {
        private readonly IHttpClientFactory httpClientFactory;

        // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성
        public RegionsController(IHttpClientFactory httpClientFactory)
        {
            this.httpClientFactory = httpClientFactory;
        }

        // Index 액션 메서드
        public async Task&lt;IActionResult> Index()
        {
            try
            {
                // Get All Regions From Web API
                // 새로운 Http 클라이언트가 생성
                var client = httpClientFactory.CreateClient();
                
                // Web API로 GET 요청을 보냄
                var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions");
                
                // 요청이 성공적으로 완료되었는지 확인
                httpResponseMessage.EnsureSuccessStatusCode();
                
                // 응답 본문을 문자열로 읽어옴
                var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();
                
                // 응답 내용을 ViewBag에 저장
                ViewBag.Response = stringResponse;
                
                // 응답 메시지를 반환
                return Ok(httpResponseMessage);
            }
            catch (Exception ex)
            {
                // 예외를 로깅 (로그 기록)
            }
            
            // View를 반환
            return View();
        }
    }
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3. Index.cshtml , <strong>_Layout.cshtml </strong>생성 및 수정 </h3>



<p><strong>Index.cshtml</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

&lt;h1 class="mt-3">Regions&lt;/h1>

@if (ViewBag.Respose is not null)
{
    &lt;p>ViewBag.Respose&lt;/p>
}</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>_Layout.cshtml</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!DOCTYPE html>
&lt;html lang="en">
&lt;head>
    &lt;meta charset="utf-8" />
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" />
    &lt;title>@ViewData["Title"] - NZWalksUI&lt;/title>
    &lt;link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    &lt;link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    &lt;link rel="stylesheet" href="~/NZWalksUI.styles.css" asp-append-version="true" />
&lt;/head>
&lt;body>
    &lt;header>
        &lt;nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            &lt;div class="container-fluid">
                &lt;a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">NZWalksUI&lt;/a>
                &lt;button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    &lt;span class="navbar-toggler-icon">&lt;/span>
                &lt;/button>
                &lt;div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    &lt;ul class="navbar-nav flex-grow-1">
                        &lt;li class="nav-item">
                            &lt;a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home&lt;/a>
                        &lt;/li>
                        &lt;li class="nav-item">
                            &lt;a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy&lt;/a>
                        &lt;/li>
                        &lt;li class="nav-item">
                            &lt;a class="nav-link text-dark" asp-area="" asp-controller="Regions" asp-action="Index">Regions&lt;/a>
                        &lt;/li>
                    &lt;/ul>
                &lt;/div>
            &lt;/div>
        &lt;/nav>
    &lt;/header>
    &lt;div class="container">
        &lt;main role="main" class="pb-3">
            @RenderBody()
        &lt;/main>
    &lt;/div>

    &lt;footer class="border-top footer text-muted">
        &lt;div class="container">
            &amp;copy; 2024 - NZWalksUI - &lt;a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy&lt;/a>
        &lt;/div>
    &lt;/footer>
    &lt;script src="~/lib/jquery/dist/jquery.min.js">&lt;/script>
    &lt;script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js">&lt;/script>
    &lt;script src="~/js/site.js" asp-append-version="true">&lt;/script>
    @await RenderSectionAsync("Scripts", required: false)
&lt;/body>
&lt;/html>
</pre>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1261" height="380" src="https://lycos7560.com/wp-content/uploads/2024/10/image-184.png" alt="" class="wp-image-38616" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-184.png 1261w, https://lycos7560.com/wp-content/uploads/2024/10/image-184-300x90.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-184-768x231.png 768w" sizes="(max-width: 1261px) 100vw, 1261px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1553" height="518" src="https://lycos7560.com/wp-content/uploads/2024/10/image-182.png" alt="" class="wp-image-38617" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-182.png 1553w, https://lycos7560.com/wp-content/uploads/2024/10/image-182-300x100.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-182-768x256.png 768w, https://lycos7560.com/wp-content/uploads/2024/10/image-182-1536x512.png 1536w" sizes="(max-width: 1553px) 100vw, 1553px" /><figcaption class="wp-element-caption">폴더 이름 수정</figcaption></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1308" height="556" src="https://lycos7560.com/wp-content/uploads/2024/10/image-183.png" alt="" class="wp-image-38618" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-183.png 1308w, https://lycos7560.com/wp-content/uploads/2024/10/image-183-300x128.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-183-768x326.png 768w" sizes="(max-width: 1308px) 100vw, 1308px" /><figcaption class="wp-element-caption">break point</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1381" height="600" src="https://lycos7560.com/wp-content/uploads/2024/10/image-186.png" alt="" class="wp-image-38620" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-186.png 1381w, https://lycos7560.com/wp-content/uploads/2024/10/image-186-300x130.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-186-768x334.png 768w" sizes="(max-width: 1381px) 100vw, 1381px" /><figcaption class="wp-element-caption">API 적용 확인</figcaption></figure>
</div>
</div>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4. RegionDto.cs 생성 및 Index.cshtml 적용</h3>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>RegionDto.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">namespace NZWalksUI.Models.DTO
{
    public class RegionDto
    {
        public Guid Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public string? RegionImageUrl { get; set; } // Nullable
    }
}</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Index.cshtml </strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@model IEnumerable&lt;NZWalksUI.Models.DTO.RegionDto> &lt;!-- 뷰 모델을 IEnumerable&lt;RegionDto>로 설정 -->

@* MVC를 빈 프로젝트에 대해 활성화하는 방법에 대한 추가 정보는 https://go.microsoft.com/fwlink/?LinkID=397860를 참조 *@

@{} &lt;!-- 블록 코드, 현재 비어 있음 -->

&lt;h1 class="mt-3">Regions&lt;/h1> &lt;!-- 페이지 제목 -->

&lt;table>
    &lt;thead>
        &lt;tr>
            &lt;th>Id&lt;/th> &lt;!-- Id 열 -->
            &lt;th>Code&lt;/th> &lt;!-- Code 열 -->
            &lt;th>Name&lt;/th> &lt;!-- Name 열 -->
        &lt;/tr>
    &lt;/thead>
    &lt;tbody>
        @foreach (var walk in Model) &lt;!-- Model을 반복하여 각 walk에 대해 테이블 행 생성 -->
        {
            &lt;tr>
                &lt;th>@walk.Id&lt;/th> &lt;!-- 현재 walk의 Id 표시 -->
                &lt;th>@walk.Code&lt;/th> &lt;!-- 현재 walk의 Code 표시 -->
                &lt;th>@walk.Name&lt;/th> &lt;!-- 현재 walk의 Name 표시 -->
            &lt;/tr>
        }
    &lt;/tbody>
&lt;/table>
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1188" height="696" src="https://lycos7560.com/wp-content/uploads/2024/10/image-187.png" alt="" class="wp-image-38621" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-187.png 1188w, https://lycos7560.com/wp-content/uploads/2024/10/image-187-300x176.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-187-768x450.png 768w" sizes="(max-width: 1188px) 100vw, 1188px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1360" height="575" src="https://lycos7560.com/wp-content/uploads/2024/10/image-188.png" alt="" class="wp-image-38622" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-188.png 1360w, https://lycos7560.com/wp-content/uploads/2024/10/image-188-300x127.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-188-768x325.png 768w" sizes="(max-width: 1360px) 100vw, 1360px" /></figure>
</div>
</div>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5. POST Method 생성 </h3>



<p><strong>Index.cshtml 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@model IEnumerable&lt;NZWalksUI.Models.DTO.RegionDto>
@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

&lt;h1 class="mt-3">Regions&lt;/h1>

&lt;div class="d-flex justify-content-end">
    &lt;a class="btn btn-secondary" asp-controller="Regions" asp-action="Add">Add Region&lt;/a>
&lt;/div>


&lt;table class="table-bordered">
    &lt;thead>
        &lt;tr>
            &lt;th>Id&lt;/th>
            &lt;th>Code&lt;/th>
            &lt;th>Name&lt;/th>
        &lt;/tr>
    &lt;/thead>
    &lt;tbody>
        @foreach (var walk in Model)
        {
            &lt;tr>
                &lt;td>@walk.Id&lt;/td>
                &lt;td>@walk.Code&lt;/td>
                &lt;td>@walk.Name&lt;/td>
            &lt;/tr>
        }
    &lt;/tbody>
&lt;/table></pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>AddRegionViewModel.cs 생성</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">namespace NZWalksUI.Models
{
    public class AddRegionViewModel
    {
        public string Code { get; set; }
        public string Name { get; set; }
        public string RegionImageUrl { get; set; }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>RegionsController.cs 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;
using NZWalksUI.Models;
using NZWalksUI.Models.DTO;
using System.Net.Http;
using System.Text;
using System.Text.Json;

namespace NZWalksUI.Controllers
{
    public class RegionsController : Controller
    {
        private readonly IHttpClientFactory httpClientFactory;

        // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성
        public RegionsController(IHttpClientFactory httpClientFactory)
        {
            this.httpClientFactory = httpClientFactory;
        }

        [HttpGet]
        // Index 액션 메서드
        public async Task&lt;IActionResult> Index()
        {
            List&lt;RegionDto> response = new List&lt;RegionDto>();
            try
            {
                // Get All Regions From Web API
                // 새로운 Http 클라이언트가 생성
                var client = httpClientFactory.CreateClient();

                // Web API로 GET 요청을 보냄
                var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions");

                // 요청이 성공적으로 완료되었는지 확인
                httpResponseMessage.EnsureSuccessStatusCode();

                // 응답 본문을 JSON으로 읽어와 리스트에 추가
                response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync&lt;IEnumerable&lt;RegionDto>>());
            }
            catch (HttpRequestException e)
            {
                // 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"Request error: {e.Message}");
            }
            catch (Exception ex)
            {
                // 기타 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"An error occurred: {ex.Message}");
            }

            // View를 반환
            return View(response);
        }

        [HttpGet]
        public IActionResult Add()
        {
            return View(); // Add View 반환
        }

        [HttpPost]
        public async Task&lt;IActionResult> Add(AddRegionViewModel model)
        {
            var client = httpClientFactory.CreateClient();

            var httpRequestMessage = new HttpRequestMessage()
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri("https://localhost:7256/api/regions"),
                Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json") // JSON 요청 본문 설정
            };

            var httpResponseMessage = await client.SendAsync(httpRequestMessage);
            httpResponseMessage.EnsureSuccessStatusCode(); // 요청이 성공적으로 완료되었는지 확인

            var response = await httpResponseMessage.Content.ReadFromJsonAsync&lt;RegionDto>();

            if (response is not null)
            {
                return RedirectToAction("Index", "Regions"); // Index 액션으로 리디렉션
            }

            return View(); // View 반환
        }
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="553" height="334" src="https://lycos7560.com/wp-content/uploads/2024/10/image-193.png" alt="" class="wp-image-38627" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-193.png 553w, https://lycos7560.com/wp-content/uploads/2024/10/image-193-300x181.png 300w" sizes="(max-width: 553px) 100vw, 553px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<p><strong>Add.cshtml</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="css" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@model NZWalksUI.Models.AddRegionViewModel;

@*
*@

@{
}

&lt;h1 class="mt-3">Add Region&lt;/h1>

&lt;form method="post">

    &lt;div class="mt-3">
        &lt;label class="form-label">Code&lt;/label>
        &lt;input type="text" class="form-control" asp-for="Code" />
    &lt;/div>

    &lt;div class="mt-3">
        &lt;label class="form-label">Name&lt;/label>
        &lt;input type="text" class="form-control" asp-for="Name" />
    &lt;/div>

    &lt;div class="mt-3">
        &lt;label class="form-label">Image URL&lt;/label>
        &lt;input type="text" class="form-control" asp-for="RegionImageUrl" />
    &lt;/div>

    &lt;div class="mt-3">
        &lt;button type="submit" class="btn btn-primary">Save&lt;/button>
    &lt;/div>

&lt;/form></pre>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="831" height="346" src="https://lycos7560.com/wp-content/uploads/2024/10/image-194.png" alt="" class="wp-image-38628" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-194.png 831w, https://lycos7560.com/wp-content/uploads/2024/10/image-194-300x125.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-194-768x320.png 768w" sizes="(max-width: 831px) 100vw, 831px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1175" height="462" src="https://lycos7560.com/wp-content/uploads/2024/10/image-195.png" alt="" class="wp-image-38630" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-195.png 1175w, https://lycos7560.com/wp-content/uploads/2024/10/image-195-300x118.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-195-768x302.png 768w" sizes="(max-width: 1175px) 100vw, 1175px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1013" height="436" src="https://lycos7560.com/wp-content/uploads/2024/10/image-196.png" alt="" class="wp-image-38631" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-196.png 1013w, https://lycos7560.com/wp-content/uploads/2024/10/image-196-300x129.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-196-768x331.png 768w" sizes="(max-width: 1013px) 100vw, 1013px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="903" height="407" src="https://lycos7560.com/wp-content/uploads/2024/10/image-197.png" alt="" class="wp-image-38632" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-197.png 903w, https://lycos7560.com/wp-content/uploads/2024/10/image-197-300x135.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-197-768x346.png 768w" sizes="(max-width: 903px) 100vw, 903px" /></figure>
</div>
</div>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">6. 단일 편집 기능</h3>



<p><strong>RegionsController.cs 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;
using NZWalksUI.Models;
using NZWalksUI.Models.DTO;
using System.Net.Http;
using System.Text;
using System.Text.Json;

namespace NZWalksUI.Controllers
{
    public class RegionsController : Controller
    {
        private readonly IHttpClientFactory httpClientFactory;

        // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성
        public RegionsController(IHttpClientFactory httpClientFactory)
        {
            this.httpClientFactory = httpClientFactory;
        }

        [HttpGet]
        // Index 액션 메서드
        public async Task&lt;IActionResult> Index()
        {
            List&lt;RegionDto> response = new List&lt;RegionDto>();


            try
            {
                // Get All Regions From Web API
                // 새로운 Http 클라이언트가 생성
                var client = httpClientFactory.CreateClient();

                // Web API로 GET 요청을 보냄
                var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions");
               

                // 요청이 성공적으로 완료되었는지 확인
                httpResponseMessage.EnsureSuccessStatusCode();

                // // 응답 본문을 문자열로 읽어옴
                // var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();


                response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync&lt;IEnumerable&lt;RegionDto>>());

            }
            catch (HttpRequestException e)
            {
                // 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"Request error: {e.Message}");
            }
            catch (Exception ex)
            {
                // 기타 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"An error occurred: {ex.Message}");
            }

            // View를 반환
            return View(response);
        }

        [HttpGet]
        public IActionResult Add()
        { 
            return View();
        }

        [HttpPost]
        public async Task&lt;IActionResult> Add(AddRegionViewModel model)
        {
            var client = httpClientFactory.CreateClient();

            var httpRequestMessage = new HttpRequestMessage()
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri("https://localhost:7256/api/regions"),
                Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json")
            };

            var httpResponseMessage = await client.SendAsync(httpRequestMessage);
            httpResponseMessage.EnsureSuccessStatusCode();

            var respose = await httpResponseMessage.Content.ReadFromJsonAsync&lt;RegionDto>();

            if (respose is not null)
            {
                return RedirectToAction("Index", "Regions");
            }

            return View();
        }

        [HttpGet]
        public async Task&lt;IActionResult> Edit(Guid id)
        {
            ViewBag.Id = id;
            return View();
        
        }
    }
}
</pre>



<figure class="wp-block-image size-full"><img decoding="async" width="502" height="194" src="https://lycos7560.com/wp-content/uploads/2024/10/image-198.png" alt="" class="wp-image-38633" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-198.png 502w, https://lycos7560.com/wp-content/uploads/2024/10/image-198-300x116.png 300w" sizes="(max-width: 502px) 100vw, 502px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Edite View 생성</p>



<p><strong>Edit.cshtml</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

&lt;h1 class="mt-3">Edit Region&lt;/h1>

&lt;p>
    @ViewBag.Id
&lt;/p>
</pre>



<div style="height:41px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Index.cshtml 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@model IEnumerable&lt;NZWalksUI.Models.DTO.RegionDto>
@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

&lt;h1 class="mt-3">Regions&lt;/h1>

&lt;div class="d-flex justify-content-end">
    &lt;a class="btn btn-secondary" asp-controller="Regions" asp-action="Add">Add Region&lt;/a>
&lt;/div>


&lt;table class="table-bordered">
    &lt;thead>
        &lt;tr>
            &lt;th>Id&lt;/th>
            &lt;th>Code&lt;/th>
            &lt;th>Name&lt;/th>
            &lt;th> &lt;/th>
        &lt;/tr>
    &lt;/thead>
    &lt;tbody>
        @foreach (var walk in Model)
        {
            &lt;tr>
                &lt;td>@walk.Id&lt;/td>
                &lt;td>@walk.Code&lt;/td>
                &lt;td>@walk.Name&lt;/td>
                &lt;td>
                    &lt;a asp-controller="Regions" asp-action="Edit" asp-route-id="@walk.Id" 
                    class="btn btn-light">Eidt&lt;/a>
                &lt;/td>
            &lt;/tr>
        }
    &lt;/tbody>
&lt;/table></pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="824" height="472" src="https://lycos7560.com/wp-content/uploads/2024/10/image-199.png" alt="" class="wp-image-38634" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-199.png 824w, https://lycos7560.com/wp-content/uploads/2024/10/image-199-300x172.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-199-768x440.png 768w" sizes="(max-width: 824px) 100vw, 824px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="776" height="369" src="https://lycos7560.com/wp-content/uploads/2024/10/image-201.png" alt="" class="wp-image-38636" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-201.png 776w, https://lycos7560.com/wp-content/uploads/2024/10/image-201-300x143.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-201-768x365.png 768w" sizes="(max-width: 776px) 100vw, 776px" /></figure>
</div>
</div>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide"/>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Error.cshtml 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@model NZWalksUI.Models.DTO.RegionDto

@{
}

&lt;h1 class="mt-3">Edit Region&lt;/h1>

@if (Model is not null)
{
    &lt;form method="post">
        &lt;div class="mt-3">
            &lt;label class="form-label">Id&lt;/label>
            &lt;input type="text" class="form-control" asp-for="Id"  readonly/>
        &lt;/div>
        &lt;div class="mt-3">
            &lt;label class="form-label">Code&lt;/label>
            &lt;input type="text" class="form-control" asp-for="Code" />
        &lt;/div>

        &lt;div class="mt-3">
            &lt;label class="form-label">Name&lt;/label>
            &lt;input type="text" class="form-control" asp-for="Name" />
        &lt;/div>

        &lt;div class="mt-3">
            &lt;label class="form-label">Image URL&lt;/label>
            &lt;input type="text" class="form-control" asp-for="RegionImageUrl" />
        &lt;/div>

        &lt;div class="mt-3">
            &lt;button type="submit" class="btn btn-primary">Save&lt;/button>
        &lt;/div>

    &lt;/form>
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>RegionsController.cs 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;
using NZWalksUI.Models;
using NZWalksUI.Models.DTO;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using static System.Net.WebRequestMethods;

namespace NZWalksUI.Controllers
{
    public class RegionsController : Controller
    {
        private readonly IHttpClientFactory httpClientFactory;

        // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성
        public RegionsController(IHttpClientFactory httpClientFactory)
        {
            this.httpClientFactory = httpClientFactory;
        }

        [HttpGet]
        // Index 액션 메서드
        public async Task&lt;IActionResult> Index()
        {
            List&lt;RegionDto> response = new List&lt;RegionDto>();


            try
            {
                // Get All Regions From Web API
                // 새로운 Http 클라이언트가 생성
                var client = httpClientFactory.CreateClient();

                // Web API로 GET 요청을 보냄
                var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions");
               

                // 요청이 성공적으로 완료되었는지 확인
                httpResponseMessage.EnsureSuccessStatusCode();

                // // 응답 본문을 문자열로 읽어옴
                // var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();


                response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync&lt;IEnumerable&lt;RegionDto>>());

            }
            catch (HttpRequestException e)
            {
                // 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"Request error: {e.Message}");
            }
            catch (Exception ex)
            {
                // 기타 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"An error occurred: {ex.Message}");
            }

            // View를 반환
            return View(response);
        }

        [HttpGet]
        public IActionResult Add()
        { 
            return View();
        }

        [HttpPost]
        public async Task&lt;IActionResult> Add(AddRegionViewModel model)
        {
            var client = httpClientFactory.CreateClient();

            var httpRequestMessage = new HttpRequestMessage()
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri("https://localhost:7256/api/regions"),
                Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json")
            };

            var httpResponseMessage = await client.SendAsync(httpRequestMessage);
            httpResponseMessage.EnsureSuccessStatusCode();

            var respose = await httpResponseMessage.Content.ReadFromJsonAsync&lt;RegionDto>();

            if (respose is not null)
            {
                return RedirectToAction("Index", "Regions");
            }

            return View();
        }

        [HttpGet]
        public async Task&lt;IActionResult> Edit(Guid id)
        {
            var client = httpClientFactory.CreateClient();

            var response = await client.GetFromJsonAsync&lt;RegionDto>($"https://localhost:7256/api/regions/{id.ToString()}");

            if (response is not null) 
            { 
                return View(response);
            }

            return View(null);
        
        }

        [HttpPost]
        public async Task&lt;IActionResult> Edit(RegionDto request) 
        {
            var client = httpClientFactory.CreateClient();

            var httpRequestMessage = new HttpRequestMessage()
            {
                Method = HttpMethod.Put,
                RequestUri = new Uri($"https://localhost:7256/api/regions/{request.Id}"),
                Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")
            };

            var httpResponseMessage = await client.SendAsync(httpRequestMessage);
            httpResponseMessage.EnsureSuccessStatusCode();

            var respose = await httpResponseMessage.Content.ReadFromJsonAsync&lt;RegionDto>();

            if (respose is not null)
            {
                return RedirectToAction("Edit", "Regions");
            }

            return View();
        }

    }
}
</pre>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="895" height="503" src="https://lycos7560.com/wp-content/uploads/2024/10/image-202.png" alt="" class="wp-image-38637" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-202.png 895w, https://lycos7560.com/wp-content/uploads/2024/10/image-202-300x169.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-202-768x432.png 768w" sizes="(max-width: 895px) 100vw, 895px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="884" height="478" src="https://lycos7560.com/wp-content/uploads/2024/10/image-204.png" alt="" class="wp-image-38639" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-204.png 884w, https://lycos7560.com/wp-content/uploads/2024/10/image-204-300x162.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-204-768x415.png 768w" sizes="(max-width: 884px) 100vw, 884px" /></figure>
</div>
</div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">7. 삭제 기능</h3>



<p><strong>Edit.cshtml 수정</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@model NZWalksUI.Models.DTO.RegionDto

@{
}

&lt;h1 class="mt-3">Edit Region&lt;/h1>

@if (Model is not null)
{
    &lt;form method="post">
        &lt;div class="mt-3">
            &lt;label class="form-label">Id&lt;/label>
            &lt;input type="text" class="form-control" asp-for="Id"  readonly/>
        &lt;/div>
        &lt;div class="mt-3">
            &lt;label class="form-label">Code&lt;/label>
            &lt;input type="text" class="form-control" asp-for="Code" />
        &lt;/div>

        &lt;div class="mt-3">
            &lt;label class="form-label">Name&lt;/label>
            &lt;input type="text" class="form-control" asp-for="Name" />
        &lt;/div>

        &lt;div class="mt-3">
            &lt;label class="form-label">Image URL&lt;/label>
            &lt;input type="text" class="form-control" asp-for="RegionImageUrl" />
        &lt;/div>

        &lt;div class="mt-3 d-flex justify-content-between">
            &lt;button type="submit" class="btn btn-primary">Save&lt;/button>
            &lt;button type="submit" asp-controller="Regions" asp-action="Delete" class="btn btn-danger">Delete&lt;/button>
        &lt;/div>

    &lt;/form>
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>RegionsController.cs 수정</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;
using NZWalksUI.Models;
using NZWalksUI.Models.DTO;
using System.Text;
using System.Text.Json;

namespace NZWalksUI.Controllers
{
    public class RegionsController : Controller
    {
        private readonly IHttpClientFactory httpClientFactory;

        // Http 클라이언트 팩토리를 삽입하기 위한 생성자를 생성
        public RegionsController(IHttpClientFactory httpClientFactory)
        {
            this.httpClientFactory = httpClientFactory;
        }

        [HttpGet]
        // Index 액션 메서드
        public async Task&lt;IActionResult> Index()
        {
            List&lt;RegionDto> response = new List&lt;RegionDto>();


            try
            {
                // Get All Regions From Web API
                // 새로운 Http 클라이언트가 생성
                var client = httpClientFactory.CreateClient();

                // Web API로 GET 요청을 보냄
                var httpResponseMessage = await client.GetAsync("https://localhost:7256/api/regions");
               

                // 요청이 성공적으로 완료되었는지 확인
                httpResponseMessage.EnsureSuccessStatusCode();

                // // 응답 본문을 문자열로 읽어옴
                // var stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();


                response.AddRange(await httpResponseMessage.Content.ReadFromJsonAsync&lt;IEnumerable&lt;RegionDto>>());

            }
            catch (HttpRequestException e)
            {
                // 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"Request error: {e.Message}");
            }
            catch (Exception ex)
            {
                // 기타 예외 발생 시 예외 메시지를 로그로 기록
                Console.WriteLine($"An error occurred: {ex.Message}");
            }

            // View를 반환
            return View(response);
        }

        [HttpGet]
        public IActionResult Add()
        { 
            return View();
        }

        [HttpPost]
        public async Task&lt;IActionResult> Add(AddRegionViewModel model)
        {
            var client = httpClientFactory.CreateClient();

            var httpRequestMessage = new HttpRequestMessage()
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri("https://localhost:7256/api/regions"),
                Content = new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json")
            };

            var httpResponseMessage = await client.SendAsync(httpRequestMessage);
            httpResponseMessage.EnsureSuccessStatusCode();

            var respose = await httpResponseMessage.Content.ReadFromJsonAsync&lt;RegionDto>();

            if (respose is not null)
            {
                return RedirectToAction("Index", "Regions");
            }

            return View();
        }

        [HttpGet]
        public async Task&lt;IActionResult> Edit(Guid id)
        {
            var client = httpClientFactory.CreateClient();

            var response = await client.GetFromJsonAsync&lt;RegionDto>($"https://localhost:7256/api/regions/{id.ToString()}");

            if (response is not null) 
            { 
                return View(response);
            }

            return View(null);
        
        }

        [HttpPost]
        public async Task&lt;IActionResult> Edit(RegionDto request) 
        {
            var client = httpClientFactory.CreateClient();

            var httpRequestMessage = new HttpRequestMessage()
            {
                Method = HttpMethod.Put,
                RequestUri = new Uri($"https://localhost:7256/api/regions/{request.Id}"),
                Content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json")
            };

            var httpResponseMessage = await client.SendAsync(httpRequestMessage);
            httpResponseMessage.EnsureSuccessStatusCode();

            var respose = await httpResponseMessage.Content.ReadFromJsonAsync&lt;RegionDto>();

            if (respose is not null)
            {
                return RedirectToAction("Edit", "Regions");
            }

            return View();
        }

        [HttpPost]
        public async Task&lt;IActionResult> Delete(RegionDto request)
        {
            try 
            {
                var client = httpClientFactory.CreateClient();

                var httpResponseMessage = await client.DeleteAsync($"https://localhost:7256/api/regions/{request.Id}");
                httpResponseMessage.EnsureSuccessStatusCode();

                return RedirectToAction("Index", "Regions");

            }
            catch (Exception ex) 
            { 
            
            }

            return View("Edit");
        }

    }
}
</pre>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1214" height="479" src="https://lycos7560.com/wp-content/uploads/2024/10/image-205.png" alt="" class="wp-image-38640" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-205.png 1214w, https://lycos7560.com/wp-content/uploads/2024/10/image-205-300x118.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-205-768x303.png 768w" sizes="(max-width: 1214px) 100vw, 1214px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="557" height="315" src="https://lycos7560.com/wp-content/uploads/2024/10/image-206.png" alt="" class="wp-image-38641" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-206.png 557w, https://lycos7560.com/wp-content/uploads/2024/10/image-206-300x170.png 300w" sizes="(max-width: 557px) 100vw, 557px" /></figure>
</div>
</div>



<p></p>
<p>The post <a href="https://lycos7560.com/c/consuming-rest-web-apis/38585/">Consuming REST Web APIs</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/consuming-rest-web-apis/38585/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Image Upload In ASP.NET Core Web API</title>
		<link>https://lycos7560.com/c/image-upload-in-asp-net-core-web-api/38567/</link>
					<comments>https://lycos7560.com/c/image-upload-in-asp-net-core-web-api/38567/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Wed, 30 Oct 2024 08:41:06 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[AddModelError]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Create]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[Data access logic]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[DbSet]]></category>
		<category><![CDATA[Domain]]></category>
		<category><![CDATA[Domain Model]]></category>
		<category><![CDATA[File]]></category>
		<category><![CDATA[FileDescription]]></category>
		<category><![CDATA[Filestream]]></category>
		<category><![CDATA[IFormFile]]></category>
		<category><![CDATA[Image]]></category>
		<category><![CDATA[Image Controller]]></category>
		<category><![CDATA[IsValid]]></category>
		<category><![CDATA[Migration]]></category>
		<category><![CDATA[Model]]></category>
		<category><![CDATA[ModelState]]></category>
		<category><![CDATA[pepe]]></category>
		<category><![CDATA[program]]></category>
		<category><![CDATA[Program.cs]]></category>
		<category><![CDATA[Repository]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Upload]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[web API]]></category>
		<category><![CDATA[wpf]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=38567</guid>

					<description><![CDATA[<p>1. Domain Model 생성 Image.cs 2. DBContext 생성 3. Image Controller 및 Method 생성 ImageController.cs ImageUploadRequestDto.cs IImageRepository.cs LocalImageRepository.cs Program.cs 변경</p>
<p>The post <a href="https://lycos7560.com/c/image-upload-in-asp-net-core-web-api/38567/">Image Upload In ASP.NET Core Web API</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-0f1b551f      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-domain-model-생성" class="uagb-toc-link__trigger">1. Domain Model 생성</a><li class="uagb-toc__list"><a href="#2-dbcontext-생성" class="uagb-toc-link__trigger">2. DBContext 생성</a><li class="uagb-toc__list"><a href="#3-image-controller-및-method-생성" class="uagb-toc-link__trigger">3. Image Controller 및 Method 생성</a></ol>					</div>
									</div>
				</div>
			


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1. Domain Model 생성</h3>



<p><strong>Image.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System.ComponentModel.DataAnnotations.Schema;

namespace NZWalks.API.Models.Domain
{
    public class Image
    {
        public Guid Id { get; set; } // 이미지의 고유 식별자

        [NotMapped] // Entity Framework가 해당 속성을 데이터베이스 테이블에 매핑하지 않도록 지정
        public IFormFile File { get; set; } // File Data 객체를 나타내는 IFormFile 객체

        public string Filename { get; set; } // 파일 이름
        public string? FileDescription { get; set; } // 파일 설명 (nullable)
        public string FileExtension { get; set; } // 파일 확장자 (예: .jpg, .png)
        public long FileSizeInBytes { get; set; } // 파일 크기 (바이트 단위)
        public string FilePath { get; set; } // 파일 저장 경로
    }
}
</pre>



<figure class="wp-block-image size-full"><img decoding="async" width="1288" height="614" src="https://lycos7560.com/wp-content/uploads/2024/10/image-142.png" alt="" class="wp-image-38568" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-142.png 1288w, https://lycos7560.com/wp-content/uploads/2024/10/image-142-300x143.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-142-768x366.png 768w" sizes="(max-width: 1288px) 100vw, 1288px" /></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2. DBContext 생성</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="1431" height="611" src="https://lycos7560.com/wp-content/uploads/2024/10/image-143.png" alt="" class="wp-image-38569" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-143.png 1431w, https://lycos7560.com/wp-content/uploads/2024/10/image-143-300x128.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-143-768x328.png 768w" sizes="(max-width: 1431px) 100vw, 1431px" /><figcaption class="wp-element-caption">Image DbSet 추가</figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="powershell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Package Manager Console 

Add-Migration "Adding Images Table" -Context "NZWalksDbcontext"
Update-Database -Context "NZWalksDbcontext"
</pre>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="936" height="285" src="https://lycos7560.com/wp-content/uploads/2024/10/image-144.png" alt="" class="wp-image-38570" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-144.png 936w, https://lycos7560.com/wp-content/uploads/2024/10/image-144-300x91.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-144-768x234.png 768w" sizes="(max-width: 936px) 100vw, 936px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1267" height="449" src="https://lycos7560.com/wp-content/uploads/2024/10/image-145.png" alt="" class="wp-image-38571" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-145.png 1267w, https://lycos7560.com/wp-content/uploads/2024/10/image-145-300x106.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-145-768x272.png 768w" sizes="(max-width: 1267px) 100vw, 1267px" /></figure>
</div>
</div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3. Image Controller 및 Method 생성</h3>



<p><strong>ImageController.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Models.Domain;
using NZWalks.API.Models.DTO;
using NZWalks.API.Repositories;

namespace NZWalks.API.Controllers
{
    [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    public class ImageController : ControllerBase
    {
        private readonly IImageRepository imageRepository;

        // IImageRepository를 주입받아 초기화
        public ImageController(IImageRepository imageRepository)
        {
            this.imageRepository = imageRepository;
        }

        // POST: /api/Images/Upload
        [HttpPost]
        [Route("Upload")]
        public async Task&lt;IActionResult> Upload([FromForm] ImageUploadRequestDto request)
        {
            // 파일 업로드 유효성 검사
            ValidateFileUpload(request);

            if (ModelState.IsValid)
            {
                // DTO를 도메인 모델로 변환
                var imageDomainModel = new Image
                {
                    File = request.File,
                    FileExtension = Path.GetExtension(request.File.FileName),
                    FileSizeInBytes = request.File.Length,
                    Filename = request.File.FileName,
                    FileDescription = request.FileDescription
                };

                // 이미지 업로드를 위해 리포지토리 사용
                await imageRepository.Uploade(imageDomainModel);

                return Ok(imageDomainModel); // 업로드된 이미지 정보 반환
            }

            return BadRequest(ModelState); // 유효성 검사 실패 시 오류 반환
        }

        // 파일 업로드 유효성 검사 메서드
        private void ValidateFileUpload(ImageUploadRequestDto request)
        {
            var allowedExtensions = new string[] { ".jpg", ".jpeg", ".Png" }; // 허용되는 확장자 목록

            // 파일 확장자 검사
            if (!allowedExtensions.Contains(Path.GetExtension(request.File.FileName)))
            {
                ModelState.AddModelError("file", "Unsupported file extension"); // 지원되지 않는 파일 확장자 오류 추가
            }

            // 파일 크기 검사 (10MB 초과 여부)
            if (request.File.Length > 10485760) // 10MB
            {
                ModelState.AddModelError("file", "File Size more than 10MB, Please upload a smaller size file"); // 파일 크기 초과 오류 추가
            }
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>ImageUploadRequestDto.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System.ComponentModel.DataAnnotations;

namespace NZWalks.API.Models.DTO
{
    public class ImageUploadRequestDto
    {
        [Required]
        public IFormFile File { get; set; }
        
        [Required]
        public string Filename { get; set; }

        public string? FileDescription { get; set; }

    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>IImageRepository.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using NZWalks.API.Models.Domain;

namespace NZWalks.API.Repositories
{
    public interface IImageRepository
    {
        Task&lt;Image> Uploade(Image image);
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>LocalImageRepository.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using NZWalks.API.Data; // 데이터 컨텍스트를 사용하기 위한 네임스페이스
using NZWalks.API.Models.Domain; // 도메인 모델을 사용하기 위한 네임스페이스

namespace NZWalks.API.Repositories
{
    public class LocalImageRepository : IImageRepository
    {
        private readonly IWebHostEnvironment webHostEnvironment;
        private readonly IHttpContextAccessor httpContextAccessor;
        private readonly NZWalksDbcontext dbcontext;

        // LocalImageRepository 생성자
        // 필요한 종속성들(IWebHostEnvironment, IHttpContextAccessor, NZWalksDbcontext)을 주입받아 초기화
        public LocalImageRepository(IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor, NZWalksDbcontext dbcontext)
        {
            this.webHostEnvironment = webHostEnvironment;
            this.httpContextAccessor = httpContextAccessor;
            this.dbcontext = dbcontext;
        }

        // 이미지 업로드 메서드
        public async Task&lt;Image> Uploade(Image image)
        {
            // 로컬 파일 경로 생성
            var localFilePath = Path.Combine(webHostEnvironment.ContentRootPath, "Images", $"{Path.GetFileNameWithoutExtension(image.Filename)}{image.FileExtension}");

            // 로컬 경로에 이미지 업로드
            using var stream = new FileStream(localFilePath, FileMode.Create);
            await image.File.CopyToAsync(stream);

            // URL 경로 생성
            var urlFilePath = $"{httpContextAccessor.HttpContext.Request.Scheme}://{httpContextAccessor.HttpContext.Request.Host}{httpContextAccessor.HttpContext.Request.PathBase}/Images/{Path.GetFileNameWithoutExtension(image.Filename)}{image.FileExtension}";
            image.FilePath = urlFilePath;

            // 이미지 정보를 Images 테이블에 추가
            await dbcontext.Images.AddAsync(image);
            await dbcontext.SaveChangesAsync();

            return image; // 업로드된 이미지 반환
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Program.cs 변경</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스
using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using NZWalks.API.Data;
using NZWalks.API.Mappings;
using NZWalks.API.Models.Domain;
using NZWalks.API.Repositories;
using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스

var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성

// Add services to the container.
builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원

builder.Services.AddHttpContextAccessor(); // HttpContextAccessor를 서비스 컨테이너에 추가

// Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고
builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성

// Swagger 생성을 위한 서비스 추가
builder.Services.AddSwaggerGen(options => {    
    // 제목과 버전을 정의하여 Swagger 문서 설정
    options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo    {        
        Title = "NZ Walks API",        
        Version = "v1"    
    });    
    // JWT Bearer 보안 정의 추가
    options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme,        
        new Microsoft.OpenApi.Models.OpenApiSecurityScheme()        
        {            
            Name = "Authorization",  // 인증 헤더 이름                    
            In = ParameterLocation.Header,  // 인증 정보의 위치 (헤더)                    
            Type = SecuritySchemeType.ApiKey,  // 인증 유형 (API 키)                    
            Scheme = JwtBearerDefaults.AuthenticationScheme  // 스키마 설정        
        }    
    );    
    // 정의된 보안 스키마를 사용한 보안 요구 사항 추가
    options.AddSecurityRequirement(new OpenApiSecurityRequirement {        
        {            
            new OpenApiSecurityScheme {                
                Reference = new OpenApiReference {                    
                    Type = ReferenceType.SecurityScheme,  // 참조 유형 설정                    
                    Id = JwtBearerDefaults.AuthenticationScheme  // 참조 ID 설정                
                },                
                Scheme = "Oauth2",  // 스키마 설정                
                Name = JwtBearerDefaults.AuthenticationScheme,  // 스키마 이름                
                In = ParameterLocation.Header  // 위치 설정            
            },            
            new List&lt;string>()        
        }    
    });
});

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksDbcontext>(options =>    
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString"))
);

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksAuthDbcontext>(options =>    
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString"))
);

builder.Services.AddScoped&lt;IRegionRepository, SQLRegionRepository>(); // Main DB Region
builder.Services.AddScoped&lt;IWalkRepository, SQLWalkRepository>(); // Main DB Walk
builder.Services.AddScoped&lt;ITokenRepository, TokenRepository>(); // Token - Controller에서 사용 가능
builder.Services.AddScoped&lt;IImageRepository, LocalImageRepository>(); // Image DB
// builder.Services.AddScoped&lt;IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory)

builder.Services.AddAutoMapper(typeof(AutoMapperProfiles)); // AutoMapper 프로필 추가

// Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정
builder.Services.AddIdentityCore&lt;IdentityUser>()    
    // IdentityRole을 추가하여 역할 관리를 지원
    .AddRoles&lt;IdentityRole>()    
    // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정
    .AddTokenProvider&lt;DataProtectorTokenProvider&lt;IdentityUser>>("NZWalks")    
    // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리
    .AddEntityFrameworkStores&lt;NZWalksAuthDbcontext>()    
    // 기본 토큰 제공자 추가
    .AddDefaultTokenProviders();

// Identity 옵션을 구성하여 비밀번호 정책 설정
builder.Services.Configure&lt;IdentityOptions>(options =>{    
    options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음    
    options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음    
    options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음    
    options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음    
    options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자    
    options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요
});

// JWT 인증 설정 추가
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용    
    .AddJwtBearer(options => // JWT Bearer 인증 설정    
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters    
    {        
        ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화        
        ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화        
        ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화        
        ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화        
        ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정        
        ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정        
        IssuerSigningKey = new SymmetricSecurityKey(            
            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정    
    });

var app = builder.Build(); // 애플리케이션 빌드

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment()){    
    // 개발 환경에서만 Swagger를 사용하도록 설정    
    app.UseSwagger();    
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션

app.UseAuthentication(); // JWT 인증 미들웨어 추가
// 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함

// 정적 파일 제공을 위한 미들웨어 설정
app.UseStaticFiles(new StaticFileOptions {     
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "Images")),
    RequestPath = "/Images"
});

app.UseAuthorization(); // 권한 부여 미들웨어 사용
// 인증된 사용자의 권한을 검증함

app.MapControllers(); // 컨트롤러에 대한 요청을 매핑

app.Run(); // 애플리케이션 실행
</pre>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="982" height="145" src="https://lycos7560.com/wp-content/uploads/2024/10/image-146.png" alt="" class="wp-image-38572" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-146.png 982w, https://lycos7560.com/wp-content/uploads/2024/10/image-146-300x44.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-146-768x113.png 768w" sizes="(max-width: 982px) 100vw, 982px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1027" height="213" src="https://lycos7560.com/wp-content/uploads/2024/10/image-147.png" alt="" class="wp-image-38573" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-147.png 1027w, https://lycos7560.com/wp-content/uploads/2024/10/image-147-300x62.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-147-768x159.png 768w" sizes="(max-width: 1027px) 100vw, 1027px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1307" height="1008" src="https://lycos7560.com/wp-content/uploads/2024/10/image-148.png" alt="" class="wp-image-38574" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-148.png 1307w, https://lycos7560.com/wp-content/uploads/2024/10/image-148-300x231.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-148-768x592.png 768w" sizes="(max-width: 1307px) 100vw, 1307px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1267" height="1002" src="https://lycos7560.com/wp-content/uploads/2024/10/image-149.png" alt="" class="wp-image-38575" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-149.png 1267w, https://lycos7560.com/wp-content/uploads/2024/10/image-149-300x237.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-149-768x607.png 768w" sizes="(max-width: 1267px) 100vw, 1267px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="1520" height="404" src="https://lycos7560.com/wp-content/uploads/2024/10/image-150.png" alt="" class="wp-image-38576" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-150.png 1520w, https://lycos7560.com/wp-content/uploads/2024/10/image-150-300x80.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-150-768x204.png 768w" sizes="(max-width: 1520px) 100vw, 1520px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.34%">
<figure class="wp-block-image size-full"><img decoding="async" width="450" height="303" src="https://lycos7560.com/wp-content/uploads/2024/10/image-152.png" alt="" class="wp-image-38580" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-152.png 450w, https://lycos7560.com/wp-content/uploads/2024/10/image-152-300x202.png 300w" sizes="(max-width: 450px) 100vw, 450px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-full"><img decoding="async" width="409" height="432" src="https://lycos7560.com/wp-content/uploads/2024/10/image-153.png" alt="" class="wp-image-38581" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-153.png 409w, https://lycos7560.com/wp-content/uploads/2024/10/image-153-284x300.png 284w" sizes="(max-width: 409px) 100vw, 409px" /><figcaption class="wp-element-caption">정적 파일</figcaption></figure>
</div>
</div>



<p></p>
<p>The post <a href="https://lycos7560.com/c/image-upload-in-asp-net-core-web-api/38567/">Image Upload In ASP.NET Core Web API</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/image-upload-in-asp-net-core-web-api/38567/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>JSON Web Token(JWT) 적용</title>
		<link>https://lycos7560.com/c/json-web-tokenjwt-%ec%a0%81%ec%9a%a9/38503/</link>
					<comments>https://lycos7560.com/c/json-web-tokenjwt-%ec%a0%81%ec%9a%a9/38503/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Tue, 29 Oct 2024 06:34:23 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[[Authorize]]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[App]]></category>
		<category><![CDATA[appsettings.json]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Authorize]]></category>
		<category><![CDATA[Claim]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[Dependency Injection]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[dnpq]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[JSON Web Token(JWT)]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[RestServer]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Swagger]]></category>
		<category><![CDATA[Token]]></category>
		<category><![CDATA[TokenRepository]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[web API]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[웹]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=38503</guid>

					<description><![CDATA[<p>JSON Web Token(JWT) 적용 패키지 추가 appsettings.json 변경 appsettings.json 서비스 등록을 통한 의존성 주입(Dependency Injection, DI) Program.cs Controller에 [Authorize] 적용 인증을 위한 DB 생성 및 연결 연결문자열 추가 appsettings.json NZWalksDbcontext.cs 생성 의존성 주입(Dependency Injection, DI) NZWalksDbcontext.cs 패키지 관리자 콘솔에 명령어 입력 Identity Core 서비스를 추가 및 옵션 Program.cs 인증 컨트롤러 만들기 AuthController.cs RegisterRequestDto.cs Login 기능 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/json-web-tokenjwt-%ec%a0%81%ec%9a%a9/38503/">JSON Web Token(JWT) 적용</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-2670862f      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#json-web-tokenjwt-적용" class="uagb-toc-link__trigger">JSON Web Token(JWT) 적용</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#패키지-추가" class="uagb-toc-link__trigger">패키지 추가</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#appsettingsjson-변경" class="uagb-toc-link__trigger">appsettings.json 변경</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#서비스-등록을-통한-의존성-주입dependency-injection-di" class="uagb-toc-link__trigger">서비스 등록을 통한 의존성 주입(Dependency Injection, DI)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#controller에-authorize-적용" class="uagb-toc-link__trigger">Controller에 [Authorize] 적용</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#인증을-위한-db-생성-및-연결" class="uagb-toc-link__trigger">인증을 위한 DB  생성 및 연결</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#연결문자열-추가" class="uagb-toc-link__trigger">연결문자열 추가</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#nzwalksdbcontextcs-생성" class="uagb-toc-link__trigger">NZWalksDbcontext.cs 생성</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#의존성-주입dependency-injection-di" class="uagb-toc-link__trigger">의존성 주입(Dependency Injection, DI)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#패키지-관리자-콘솔에-명령어-입력" class="uagb-toc-link__trigger">패키지 관리자 콘솔에 명령어 입력</a></li></ul><li class="uagb-toc__list"><a href="#identity-core-서비스를-추가-및-옵션" class="uagb-toc-link__trigger">Identity Core 서비스를 추가 및 옵션</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#인증-컨트롤러-만들기" class="uagb-toc-link__trigger">인증 컨트롤러 만들기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#login-기능-만들기" class="uagb-toc-link__trigger">Login 기능 만들기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#jwt-토큰-생성-및-적용" class="uagb-toc-link__trigger">JWT 토큰 생성 및 적용</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#jwt-토큰-생성" class="uagb-toc-link__trigger">JWT 토큰 생성</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#jwt-토큰-적용" class="uagb-toc-link__trigger">JWT 토큰 적용</a></li></ul><li class="uagb-toc__list"><a href="#swagger-적용" class="uagb-toc-link__trigger">Swagger 적용</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">JSON Web Token(JWT) 적용</h2>



<h3 class="wp-block-heading">패키지 추가</h3>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="511" height="239" src="https://lycos7560.com/wp-content/uploads/2024/10/image-86.png" alt="" class="wp-image-38504" style="width:511px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-86.png 511w, https://lycos7560.com/wp-content/uploads/2024/10/image-86-300x140.png 300w" sizes="(max-width: 511px) 100vw, 511px" /></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">패키지 추가
Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.IdentityModel.Tokens
System.IdentityModel.Tokens.Jwt
Microsoft.AspNetCore.Identity.EntityFrameworkCore
</pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>appsettings.json 변경</strong></h3>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:49.97%">
<figure class="wp-block-image size-full"><img decoding="async" width="846" height="677" src="https://lycos7560.com/wp-content/uploads/2024/10/image-87.png" alt="" class="wp-image-38505" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-87.png 846w, https://lycos7560.com/wp-content/uploads/2024/10/image-87-300x240.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-87-768x615.png 768w" sizes="(max-width: 846px) 100vw, 846px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50.03%">
<figure class="wp-block-image size-full"><img decoding="async" width="767" height="502" src="https://lycos7560.com/wp-content/uploads/2024/10/image-88.png" alt="" class="wp-image-38506" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-88.png 767w, https://lycos7560.com/wp-content/uploads/2024/10/image-88-300x196.png 300w" sizes="(max-width: 767px) 100vw, 767px" /></figure>
</div>
</div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>appsettings.json</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "NZWalksConnectionString": "Server=[서버],[포트];Database=[DB이름];User ID=[DB아이디];Password=[DB비];Trusted_Connection=True;TrustServerCertificate=True"
  },
  "Jwt": {
    "key": "TestCustomKey",
    "Issuer": "https://localhost:7256",
    "Audience": "https://localhost:7256"
  }
}
</pre>



<div style="height:0px" aria-hidden="true" class="wp-block-spacer"></div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">서비스 등록을 통한 <strong>의존성 주입(Dependency Injection, DI)</strong> </h3>



<p><strong>Program.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스
using Microsoft.IdentityModel.Tokens;
using NZWalks.API.Data;
using NZWalks.API.Mappings;
using NZWalks.API.Models.Domain;
using NZWalks.API.Repositories;
using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스

var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성

// Add services to the container.
builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원

// Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고
builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성
builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString")));

builder.Services.AddScoped&lt;IRegionRepository, SQLRegionRepository>(); // Main DB Region
builder.Services.AddScoped&lt;IWalkRepository, SQLWalkRepository>(); // Main DB Walk
// builder.Services.AddScoped&lt;IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory)
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles));

// JWT 인증 설정 추가
builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용
    .AddJwtBearer(options => // JWT Bearer 인증 설정
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화
        ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화
        ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화
        ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화
        ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정
        ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정
    });

var app = builder.Build(); // 애플리케이션 빌드

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    // 개발 환경에서만 Swagger를 사용하도록 설정
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션

app.UseAuthentication(); // JWT 인증 미들웨어 추가
// 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함

app.UseAuthorization(); // 권한 부여 미들웨어 사용
// 인증된 사용자의 권한을 검증함

app.MapControllers(); // 컨트롤러에 대한 요청을 매핑

app.Run(); // 애플리케이션 실행
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="962" height="394" src="https://lycos7560.com/wp-content/uploads/2024/10/image-89.png" alt="" class="wp-image-38507" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-89.png 962w, https://lycos7560.com/wp-content/uploads/2024/10/image-89-300x123.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-89-768x315.png 768w" sizes="(max-width: 962px) 100vw, 962px" /><figcaption class="wp-element-caption">JSON Web Token 파트</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="588" height="465" src="https://lycos7560.com/wp-content/uploads/2024/10/image-90.png" alt="" class="wp-image-38508" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-90.png 588w, https://lycos7560.com/wp-content/uploads/2024/10/image-90-300x237.png 300w" sizes="(max-width: 588px) 100vw, 588px" /><figcaption class="wp-element-caption">UseAuthentication(); 추가로 사용자 요청을 검증</figcaption></figure>
</div>
</div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Controller에 [Authorize] 적용</h3>



<figure class="wp-block-image size-full"><img decoding="async" width="595" height="207" src="https://lycos7560.com/wp-content/uploads/2024/10/image-91.png" alt="" class="wp-image-38509" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-91.png 595w, https://lycos7560.com/wp-content/uploads/2024/10/image-91-300x104.png 300w" sizes="(max-width: 595px) 100vw, 595px" /><figcaption class="wp-element-caption">[Authorize] 적용</figcaption></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Data;
using NZWalks.API.Models.Domain;
using NZWalks.API.Models.DTO;
using AutoMapper;
using NZWalks.API.CustomActionFilter;
using Microsoft.AspNetCore.Authorization;

namespace NZWalks.API.Controllers
{

    [Route("api/[controller]")] // 컨트롤러의 기본 Route를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    [Authorize] // 인증된 사용자만 접급 가능
    public class RegionsController : ControllerBase
    {

        private readonly NZWalksDbcontext dbcontext;
        private readonly IRegionRepository regionRepository;
        private readonly IMapper mapper;

        public RegionsController(NZWalksDbcontext dbContext, IRegionRepository regionRepository, IMapper mapper)
        {
            this.dbcontext = dbContext;
            this.regionRepository = regionRepository;
            this.mapper = mapper;
        }

        // GET ALL REGIONS
        // GET: https://localhost:1234/api/regions
        [HttpGet]
        public async Task&lt;IActionResult> GetAll()
        {
            // Get Data From Database - Domain models
            // var regionDomain = await dbcontext.Regions.ToListAsync(); // Change Async
            var regionDomain = await regionRepository.GetAllAsync();

            // Map Domain Models to DTOs
            // var regionDto = new List&lt;RegionDto>();
            // foreach (var region in regionDomain) 
            // {
            //     regionDto.Add(new RegionDto() 
            //     {
            //         Id = region.Id,
            //         Code = region.Code,
            //         Name = region.Name,
            //         RegionImageUrl = region.RegionImageUrl
            //     });
            // }

            // Map Domain Models to DTOs
            var regionDto = mapper.Map&lt;List&lt;RegionDto>>(regionDomain);

            return Ok(regionDto);
        }

        // GET SINGLE REGION (Get Region By ID)
        // GET: https://localhost:1234/api/regions/{id}
        [HttpGet]
        [Route("{id:guid}")]
        public async Task&lt;IActionResult> GetById([FromRoute]Guid id) 
        {
            // Get Data From Database - Domain models
            // var region = dbcontext.Regions.Find(id);
            // var regionDomain = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            var regionDomain = await regionRepository.GetByIdAsync(id);
            if (regionDomain == null)
            {
                return NotFound();
            }

            // Map Domain Models to DTOs
            // var regionDto = new RegionDto() 
            // {
            //     Id = regionDomain.Id,
            //     Code = regionDomain.Code,
            //     Name = regionDomain.Name,
            //     RegionImageUrl = regionDomain.RegionImageUrl
            // };

            var regionDto = mapper.Map&lt;RegionDto>(regionDomain);

            return Ok(regionDto);

        }

        // POST To Create New Region
        // POST : https://localhost:1234/api/regions
        [HttpPost]
        [ValidateModel]
        public async Task&lt;IActionResult> Create([FromBody] AddRegionRequestDto addRegionRequestDto)
        {
            /* [ValidateModel] 적용전
                        if (ModelState.IsValid) // [ValidateModel] 
            {
                // [Require]
                // string Code, Name
                // string? RegionImageUrl

                // Map or Convert DTO to Domain Model
                //var regionDomainModel = new Region
                //{
                //    Code = addRegionRequestDto.Code,
                //    Name = addRegionRequestDto.Name,
                //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
                //};

                // Map or Convert DTO to Domain Model
                var regionDomainModel = mapper.Map&lt;Region>(addRegionRequestDto);

                // Use Domain Model to create Region
                // await dbcontext.Regions.AddAsync(regionDomainModel);
                // await dbcontext.SaveChangesAsync();

                await regionRepository.CreateAsync(regionDomainModel);


                // Map Domain model back to DTO
                //var regionDto = new RegionDto 
                //{ 
                //    Id = regionDomainModel.Id,
                //    Code = addRegionRequestDto.Code,
                //    Name = regionDomainModel.Name, 
                //    RegionImageUrl = regionDomainModel.RegionImageUrl

                //};

                var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

                // 201 Created 응답으로 생성된 DTO 반환
                return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
                // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
                // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
            }
            else
            {
                return BadRequest(ModelState);
            }
             
            */

            // [Require]
            // string Code, Name
            // string? RegionImageUrl

            // Map or Convert DTO to Domain Model
            //var regionDomainModel = new Region
            //{
            //    Code = addRegionRequestDto.Code,
            //    Name = addRegionRequestDto.Name,
            //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
            //};

            // Map or Convert DTO to Domain Model
            var regionDomainModel = mapper.Map&lt;Region>(addRegionRequestDto);

            // Use Domain Model to create Region
            // await dbcontext.Regions.AddAsync(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            await regionRepository.CreateAsync(regionDomainModel);


            // Map Domain model back to DTO
            //var regionDto = new RegionDto 
            //{ 
            //    Id = regionDomainModel.Id,
            //    Code = addRegionRequestDto.Code,
            //    Name = regionDomainModel.Name, 
            //    RegionImageUrl = regionDomainModel.RegionImageUrl

            //};

            var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

            // 201 Created 응답으로 생성된 DTO 반환
            return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
            // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
            // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
        }



        // Update Region
        // PUT: https://localhost:1234/api/regions/{id}
        [HttpPut]
        [Route("{id:guid}")]
        [ValidateModel]
        public async Task&lt;IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateRegionRequestDto updateRegionRequestDto)
        {
            /* [ValidateModel] 적용 전
            if (ModelState.IsValid)
            {
                // Map 
                //var regionDomainModel = new Region
                //{
                //    Code = updateRegionRequestDto.Code,
                //    Name = updateRegionRequestDto.Name,
                //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

                //};

                var regionDomainModel = mapper.Map&lt;Region>(updateRegionRequestDto);

                // Check if region exists
                // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
                regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

                if (regionDomainModel == null)
                {
                    return NotFound();
                }

                //// Map DTO to Domain model
                //regionDomainModel.Code = updateRegionRequestDto.Code;
                //regionDomainModel.Name = updateRegionRequestDto.Name;
                //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

                //await dbcontext.SaveChangesAsync();

                // Convert Domain Model to DTO
                //var regionDto = new RegionDto
                //{
                //    Id = regionDomainModel.Id,
                //    Code = regionDomainModel.Code,
                //    Name = regionDomainModel.Name,
                //    RegionImageUrl = regionDomainModel.RegionImageUrl
                //};

                var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

                return Ok(regionDto);

            }
            else 
            { 
                return BadRequest(ModelState);
            }
            */
            // Map 
            //var regionDomainModel = new Region
            //{
            //    Code = updateRegionRequestDto.Code,
            //    Name = updateRegionRequestDto.Name,
            //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

            //};

            var regionDomainModel = mapper.Map&lt;Region>(updateRegionRequestDto);

            // Check if region exists
            // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

            if (regionDomainModel == null)
            {
                return NotFound();
            }

            //// Map DTO to Domain model
            //regionDomainModel.Code = updateRegionRequestDto.Code;
            //regionDomainModel.Name = updateRegionRequestDto.Name;
            //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

            //await dbcontext.SaveChangesAsync();

            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

            return Ok(regionDto);
        }


        // Delete Region
        // Delete https://localhost:1234/api/regions/{id}
        [HttpDelete]
        [Route("{id:guid}")]
        public async Task&lt;IActionResult> Update([FromRoute] Guid id)
        {
            //var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);

            var regionDomainModel = await regionRepository.DeleteAsync(id);


            if (regionDomainModel == null)
            {
                return NotFound();
            }

            // dbcontext.Regions.Remove(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            // return deleted Region back
            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map&lt;Region>(regionDomainModel);

            return Ok(regionDto);
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1310" height="746" src="https://lycos7560.com/wp-content/uploads/2024/10/image-92.png" alt="" class="wp-image-38510" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-92.png 1310w, https://lycos7560.com/wp-content/uploads/2024/10/image-92-300x171.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-92-768x437.png 768w" sizes="(max-width: 1310px) 100vw, 1310px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1286" height="815" src="https://lycos7560.com/wp-content/uploads/2024/10/image-93.png" alt="" class="wp-image-38511" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-93.png 1286w, https://lycos7560.com/wp-content/uploads/2024/10/image-93-300x190.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-93-768x487.png 768w" sizes="(max-width: 1286px) 100vw, 1286px" /><figcaption class="wp-element-caption">승인되지 않음 401</figcaption></figure>
</div>
</div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">인증을 위한 DB  생성 및 연결</h3>



<h4 class="wp-block-heading"><strong>연결문자열 추가</strong></h4>



<p><strong>appsettings.json</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "NZWalksConnectionString": "Server=[서버],[포트];Database=[DB이름];User ID=[DB아이디];Password=[DB비];Trusted_Connection=True;TrustServerCertificate=True",
    "NZWalksAuthConnectionString": "Server=[서버],[포트];Database=[DB이름];User ID=[DB아이디];Password=[DB비];Trusted_Connection=True;TrustServerCertificate=True"
  },
  "Jwt": {
    "key": "TestCustomKey",
    "Issuer": "https://localhost:7256",
    "Audience": "https://localhost:7256"
  }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><strong>NZWalksDbcontext.cs 생성</strong></h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace NZWalks.API.Data
{
    public class NZWalksAuthDbcontext : IdentityDbContext
    {
        public NZWalksAuthDbcontext(DbContextOptions options) : base(options)
        {
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><strong>의존성 주입(Dependency Injection, DI) </strong></h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스
using Microsoft.IdentityModel.Tokens;
using NZWalks.API.Data;
using NZWalks.API.Mappings;
using NZWalks.API.Models.Domain;
using NZWalks.API.Repositories;
using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스

var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성

// Add services to the container.
builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원

// Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고
builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성
builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString")));

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksAuthDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString")));


builder.Services.AddScoped&lt;IRegionRepository, SQLRegionRepository>(); // Main DB Region
builder.Services.AddScoped&lt;IWalkRepository, SQLWalkRepository>(); // Main DB Walk
// builder.Services.AddScoped&lt;IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory)
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles));

// JWT 인증 설정 추가
builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용
    .AddJwtBearer(options => // JWT Bearer 인증 설정
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화
        ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화
        ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화
        ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화
        ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정
        ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정
    });

var app = builder.Build(); // 애플리케이션 빌드

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    // 개발 환경에서만 Swagger를 사용하도록 설정
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션

app.UseAuthentication(); // JWT 인증 미들웨어 추가
// 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함

app.UseAuthorization(); // 권한 부여 미들웨어 사용
// 인증된 사용자의 권한을 검증함

app.MapControllers(); // 컨트롤러에 대한 요청을 매핑

app.Run(); // 애플리케이션 실행
</pre>



<figure class="wp-block-image size-full"><img decoding="async" width="1065" height="211" src="https://lycos7560.com/wp-content/uploads/2024/10/image-94.png" alt="" class="wp-image-38512" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-94.png 1065w, https://lycos7560.com/wp-content/uploads/2024/10/image-94-300x59.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-94-768x152.png 768w" sizes="(max-width: 1065px) 100vw, 1065px" /></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1128" height="639" src="https://lycos7560.com/wp-content/uploads/2024/10/image-96.png" alt="" class="wp-image-38514" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-96.png 1128w, https://lycos7560.com/wp-content/uploads/2024/10/image-96-300x170.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-96-768x435.png 768w" sizes="(max-width: 1128px) 100vw, 1128px" /><figcaption class="wp-element-caption">[Authorize] 없이 사용 시</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1282" height="795" src="https://lycos7560.com/wp-content/uploads/2024/10/image-95.png" alt="" class="wp-image-38513" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-95.png 1282w, https://lycos7560.com/wp-content/uploads/2024/10/image-95-300x186.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-95-768x476.png 768w" sizes="(max-width: 1282px) 100vw, 1282px" /></figure>
</div>
</div>



<figure class="wp-block-image size-full"><img decoding="async" width="1286" height="939" src="https://lycos7560.com/wp-content/uploads/2024/10/image-97.png" alt="" class="wp-image-38515" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-97.png 1286w, https://lycos7560.com/wp-content/uploads/2024/10/image-97-300x219.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-97-768x561.png 768w" sizes="(max-width: 1286px) 100vw, 1286px" /></figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1066" height="240" src="https://lycos7560.com/wp-content/uploads/2024/10/image-99.png" alt="" class="wp-image-38518" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-99.png 1066w, https://lycos7560.com/wp-content/uploads/2024/10/image-99-300x68.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-99-768x173.png 768w" sizes="(max-width: 1066px) 100vw, 1066px" /><figcaption class="wp-element-caption">NZWalksDbcontext.cs 수정</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1079" height="312" src="https://lycos7560.com/wp-content/uploads/2024/10/image-98.png" alt="" class="wp-image-38519" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-98.png 1079w, https://lycos7560.com/wp-content/uploads/2024/10/image-98-300x87.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-98-768x222.png 768w" sizes="(max-width: 1079px) 100vw, 1079px" /><figcaption class="wp-element-caption">NZWalksAuthDbcontext.cs 수정</figcaption></figure>
</div>
</div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>NZWalksDbcontext.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NZWalks.API.Models.Domain;

namespace NZWalks.API.Data
{
    public class NZWalksAuthDbcontext : IdentityDbContext
    {
        public NZWalksAuthDbcontext(DbContextOptions&lt;NZWalksAuthDbcontext> options) : base(options)
        {


        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            var readerRoleId = "4597cc15-43f1-481c-904a-27097ce8f294";
            var writerRoleId = "4b9e9601-65e4-4b6d-9a30-bda7550c73e6";

            var roles = new List&lt;IdentityRole>
            {
                new IdentityRole
                {
                    Id = readerRoleId,
                    ConcurrencyStamp = readerRoleId,
                    Name = "Reader",
                    NormalizedName = "Reader".ToUpper()

                },
                new IdentityRole
                {
                    Id = writerRoleId,
                    ConcurrencyStamp = writerRoleId,
                    Name = "writer",
                    NormalizedName = "writer".ToUpper()

                },

            };

            // Seed difficulties to the database
            modelBuilder.Entity&lt;IdentityRole>().HasData(roles);

            // Add-Migration "Creating Auth Database" -Context "NZWalksAuthDbcontext"
            // Update-Database
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><strong>패키지 관리자 콘솔에 명령어 입력</strong></h4>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
Add-Migration "Creating Auth Database" -Context "NZWalksAuthDbcontext"
Update-Database -Context "NZWalksAuthDbcontext"
</pre>



<figure class="wp-block-image size-full"><img decoding="async" width="1341" height="810" src="https://lycos7560.com/wp-content/uploads/2024/10/image-101.png" alt="" class="wp-image-38522" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-101.png 1341w, https://lycos7560.com/wp-content/uploads/2024/10/image-101-300x181.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-101-768x464.png 768w" sizes="(max-width: 1341px) 100vw, 1341px" /></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>Identity Core 서비스를 추가 및 옵션</strong></h3>



<p><strong>Program.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스
using Microsoft.IdentityModel.Tokens;
using NZWalks.API.Data;
using NZWalks.API.Mappings;
using NZWalks.API.Models.Domain;
using NZWalks.API.Repositories;
using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스

var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성

// Add services to the container.
builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원

// Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고
builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성
builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString")));

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksAuthDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString")));


builder.Services.AddScoped&lt;IRegionRepository, SQLRegionRepository>(); // Main DB Region
builder.Services.AddScoped&lt;IWalkRepository, SQLWalkRepository>(); // Main DB Walk
// builder.Services.AddScoped&lt;IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory)
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles));


// Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정
builder.Services.AddIdentityCore&lt;IdentityUser>()
    // IdentityRole을 추가하여 역할 관리를 지원
    .AddRoles&lt;IdentityRole>()
    // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정
    .AddTokenProvider&lt;DataProtectorTokenProvider&lt;IdentityUser>>("NZWalks")
    // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리
    .AddEntityFrameworkStores&lt;NZWalksAuthDbcontext>()
    // 기본 토큰 제공자 추가
    .AddDefaultTokenProviders();

// Identity 옵션을 구성하여 비밀번호 정책 설정
builder.Services.Configure&lt;IdentityOptions>(options =>
{
    options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음
    options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음
    options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음
    options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음
    options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자
    options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요
});

// JWT 인증 설정 추가
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용
    .AddJwtBearer(options => // JWT Bearer 인증 설정
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화
        ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화
        ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화
        ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화
        ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정
        ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정
    });

var app = builder.Build(); // 애플리케이션 빌드

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    // 개발 환경에서만 Swagger를 사용하도록 설정
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션

app.UseAuthentication(); // JWT 인증 미들웨어 추가
// 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함

app.UseAuthorization(); // 권한 부여 미들웨어 사용
// 인증된 사용자의 권한을 검증함

app.MapControllers(); // 컨트롤러에 대한 요청을 매핑

app.Run(); // 애플리케이션 실행
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="984" height="485" src="https://lycos7560.com/wp-content/uploads/2024/10/image-102.png" alt="" class="wp-image-38523" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-102.png 984w, https://lycos7560.com/wp-content/uploads/2024/10/image-102-300x148.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-102-768x379.png 768w" sizes="(max-width: 984px) 100vw, 984px" /></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">인증 컨트롤러 만들기</h3>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1164" height="474" src="https://lycos7560.com/wp-content/uploads/2024/10/image-103.png" alt="" class="wp-image-38524" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-103.png 1164w, https://lycos7560.com/wp-content/uploads/2024/10/image-103-300x122.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-103-768x313.png 768w" sizes="(max-width: 1164px) 100vw, 1164px" /></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>AuthController.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Models.DTO;

namespace NZWalks.API.Controllers
{
    [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    public class AuthController : ControllerBase
    {
        private readonly UserManager&lt;IdentityUser> userManager;

        // UserManager를 주입받아 초기화
        public AuthController(UserManager&lt;IdentityUser> userManager)
        {
            this.userManager = userManager;
        }

        // Post: /api/Auth/Register
        [HttpPost]
        [Route("Register")]
        public async Task&lt;IActionResult> Register([FromBody] RegisterRequestDto registerRequestDto)
        {
            // 새로운 IdentityUser 생성
            var identityUser = new IdentityUser
            {
                UserName = registerRequestDto.Username,
                Email = registerRequestDto.Username
            };

            // UserManager를 사용하여 사용자 생성
            var identityResult = await userManager.CreateAsync(identityUser, registerRequestDto.Password);

            if (identityResult.Succeeded)
            {
                // 사용자에게 역할(Role) 추가
                if (registerRequestDto.Roles != null &amp;&amp; registerRequestDto.Roles.Any())
                {
                    identityResult = await userManager.AddToRolesAsync(identityUser, registerRequestDto.Roles);
                    if (identityResult.Succeeded)
                    {
                        return Ok("User was registered! Please login."); // 사용자 등록 성공 메시지 반환
                    }
                }
            }
            return BadRequest("Something went wrong!"); // 오류 발생 시 반환 메시지
        }
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>RegisterRequestDto.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System.ComponentModel.DataAnnotations;

namespace NZWalks.API.Models.DTO
{
    public class RegisterRequestDto
    {
        [Required]
        [DataType(DataType.EmailAddress)]
        public string Username { get; set; }
        [Required]
        [DataType(DataType.EmailAddress)]
        public string Password { get; set; }

        public string[] Roles { get; set; }

    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1300" height="624" src="https://lycos7560.com/wp-content/uploads/2024/10/image-104.png" alt="" class="wp-image-38525" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-104.png 1300w, https://lycos7560.com/wp-content/uploads/2024/10/image-104-300x144.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-104-768x369.png 768w" sizes="(max-width: 1300px) 100vw, 1300px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1287" height="743" src="https://lycos7560.com/wp-content/uploads/2024/10/image-105.png" alt="" class="wp-image-38526" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-105.png 1287w, https://lycos7560.com/wp-content/uploads/2024/10/image-105-300x173.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-105-768x443.png 768w" sizes="(max-width: 1287px) 100vw, 1287px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1279" height="1024" src="https://lycos7560.com/wp-content/uploads/2024/10/image-106.png" alt="" class="wp-image-38527" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-106.png 1279w, https://lycos7560.com/wp-content/uploads/2024/10/image-106-300x240.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-106-768x615.png 768w" sizes="(max-width: 1279px) 100vw, 1279px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1484" height="433" src="https://lycos7560.com/wp-content/uploads/2024/10/image-107.png" alt="" class="wp-image-38528" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-107.png 1484w, https://lycos7560.com/wp-content/uploads/2024/10/image-107-300x88.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-107-768x224.png 768w" sizes="(max-width: 1484px) 100vw, 1484px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1091" height="414" src="https://lycos7560.com/wp-content/uploads/2024/10/image-108.png" alt="" class="wp-image-38530" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-108.png 1091w, https://lycos7560.com/wp-content/uploads/2024/10/image-108-300x114.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-108-768x291.png 768w" sizes="(max-width: 1091px) 100vw, 1091px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1250" height="401" src="https://lycos7560.com/wp-content/uploads/2024/10/image-109.png" alt="" class="wp-image-38531" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-109.png 1250w, https://lycos7560.com/wp-content/uploads/2024/10/image-109-300x96.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-109-768x246.png 768w" sizes="(max-width: 1250px) 100vw, 1250px" /></figure>
</div>
</div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Login 기능 만들기</h3>



<p><strong>AuthController.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Models.DTO;

namespace NZWalks.API.Controllers
{
    [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    public class AuthController : ControllerBase
    {
        private readonly UserManager&lt;IdentityUser> userManager;

        // UserManager를 주입받아 초기화
        public AuthController(UserManager&lt;IdentityUser> userManager)
        {
            this.userManager = userManager;
        }

        // Post: /api/Auth/Register
        [HttpPost]
        [Route("Register")]
        public async Task&lt;IActionResult> Register([FromBody] RegisterRequestDto registerRequestDto)
        {
            // 새로운 IdentityUser 생성
            var identityUser = new IdentityUser
            {
                UserName = registerRequestDto.Username,
                Email = registerRequestDto.Username
            };

            // UserManager를 사용하여 사용자 생성
            var identityResult = await userManager.CreateAsync(identityUser, registerRequestDto.Password);

            if (identityResult.Succeeded)
            {
                // 사용자에게 역할(Role) 추가
                if (registerRequestDto.Roles != null &amp;&amp; registerRequestDto.Roles.Any())
                {
                    identityResult = await userManager.AddToRolesAsync(identityUser, registerRequestDto.Roles);
                    if (identityResult.Succeeded)
                    {
                        return Ok("User was registered! Please login."); // 사용자 등록 성공 메시지 반환
                    }
                }
            }
            return BadRequest("Something went wrong!"); // 오류 발생 시 반환 메시지
        }


        // Post: /api/Auth/Login
        [HttpPost]
        [Route("Login")]
        public async Task&lt;IActionResult> Login([FromBody] LoginRequestDto loginRequestDto)
        {
            // 사용자 이메일로 사용자 찾기
            var user = await userManager.FindByEmailAsync(loginRequestDto.Username);

            if (user != null)
            {
                // 사용자의 비밀번호 확인
                var checkPasswordResult = await userManager.CheckPasswordAsync(user, loginRequestDto.Password);

                if (checkPasswordResult)
                {
                    // 토큰 생성 로직
                    return Ok(); // 성공 응답
                }
            }

            return BadRequest("Username or Password incorrect"); // 사용자 이름 또는 비밀번호가 잘못된 경우 오류 응답
        }


    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>LoginRequestDto.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System.ComponentModel.DataAnnotations;

namespace NZWalks.API.Models.DTO
{
    public class LoginRequestDto
    {
        [Required]
        [DataType(DataType.EmailAddress)]
        public string Username { get; set; }

        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1287" height="1026" src="https://lycos7560.com/wp-content/uploads/2024/10/image-116.png" alt="" class="wp-image-38537" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-116.png 1287w, https://lycos7560.com/wp-content/uploads/2024/10/image-116-300x239.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-116-768x612.png 768w" sizes="(max-width: 1287px) 100vw, 1287px" /><figcaption class="wp-element-caption"><strong>로그인 실패 (400)</strong></figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1288" height="1016" src="https://lycos7560.com/wp-content/uploads/2024/10/image-117.png" alt="" class="wp-image-38539" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-117.png 1288w, https://lycos7560.com/wp-content/uploads/2024/10/image-117-300x237.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-117-768x606.png 768w" sizes="(max-width: 1288px) 100vw, 1288px" /><figcaption class="wp-element-caption"><strong>로그인 성공 (200)</strong></figcaption></figure>
</div>
</div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">JWT 토큰 생성 및 적용</h3>



<h4 class="wp-block-heading">JWT 토큰 생성</h4>



<p><strong>TokenRepository.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace NZWalks.API.Repositories
{
    public class TokenRepository : ITokenRepository
    {
        private readonly IConfiguration configuration;

        // IConfiguration 주입받아 초기화
        public TokenRepository(IConfiguration configuration)
        {
            this.configuration = configuration;
        }

        // JWT 토큰 생성 메서드
        public string CreateJWTToken(IdentityUser user, List&lt;string> roles)
        {
            // 클레임 생성
            // Claim은 사용자의 특정 속성이나 권한을 나타내는 정보 조각
            // JWT 토큰에서는 이러한 클레임을 사용하여 사용자의 신원과 권한을 나타냄
            var claims = new List&lt;Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Email, user.Email),
                new Claim(ClaimTypes.Name, user.UserName)
            };

            // 역할(Role)을 클레임에 추가
            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }

            // 대칭 보안 키 생성 (Jwt:Key 사용)
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]));

            // 서명 자격 증명 생성 (HmacSha256 알고리즘 사용)
            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            // JWT 토큰 생성
            var token = new JwtSecurityToken(
                configuration["Jwt:Issuer"], // 유효한 발행자
                configuration["Jwt:Audience"], // 유효한 수신자
                claims, // 클레임
                expires: DateTime.Now.AddMinutes(15), // 토큰 만료 시간 (현재 시간부터 15분 후)
                signingCredentials: credentials // 서명 자격 증명
            );

            // 토큰 문자열로 반환
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>ITokenRepository.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;

namespace NZWalks.API.Repositories
{
    public interface ITokenRepository
    {
        string CreateJWTToken(IdentityUser user, List&lt;string> roles)
    }
}
</pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">JWT 토큰 적용</h4>



<p><strong>program.cs</strong> 수정</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스
using Microsoft.IdentityModel.Tokens;
using NZWalks.API.Data;
using NZWalks.API.Mappings;
using NZWalks.API.Models.Domain;
using NZWalks.API.Repositories;
using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스

var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성

// Add services to the container.
builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원

// Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고
builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성
builder.Services.AddSwaggerGen(); // Swagger 생성을 위한 서비스 추가

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString")));

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksAuthDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString")));


builder.Services.AddScoped&lt;IRegionRepository, SQLRegionRepository>(); // Main DB Region
builder.Services.AddScoped&lt;IWalkRepository, SQLWalkRepository>(); // Main DB Walk
builder.Services.AddScoped&lt;ITokenRepository, TokenRepository>(); // Token - Controller에서 사용가능
// builder.Services.AddScoped&lt;IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory)
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles));


// Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정
builder.Services.AddIdentityCore&lt;IdentityUser>()
    // IdentityRole을 추가하여 역할 관리를 지원
    .AddRoles&lt;IdentityRole>()
    // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정
    .AddTokenProvider&lt;DataProtectorTokenProvider&lt;IdentityUser>>("NZWalks")
    // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리
    .AddEntityFrameworkStores&lt;NZWalksAuthDbcontext>()
    // 기본 토큰 제공자 추가
    .AddDefaultTokenProviders();

// Identity 옵션을 구성하여 비밀번호 정책 설정
builder.Services.Configure&lt;IdentityOptions>(options =>
{
    options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음
    options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음
    options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음
    options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음
    options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자
    options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요
});

// JWT 인증 설정 추가
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용
    .AddJwtBearer(options => // JWT Bearer 인증 설정
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화
        ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화
        ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화
        ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화
        ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정
        ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정
    });

var app = builder.Build(); // 애플리케이션 빌드

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    // 개발 환경에서만 Swagger를 사용하도록 설정
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션

app.UseAuthentication(); // JWT 인증 미들웨어 추가
// 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함

app.UseAuthorization(); // 권한 부여 미들웨어 사용
// 인증된 사용자의 권한을 검증함

app.MapControllers(); // 컨트롤러에 대한 요청을 매핑

app.Run(); // 애플리케이션 실행
</pre>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="963" height="131" src="https://lycos7560.com/wp-content/uploads/2024/10/image-118.png" alt="" class="wp-image-38540" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-118.png 963w, https://lycos7560.com/wp-content/uploads/2024/10/image-118-300x41.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-118-768x104.png 768w" sizes="(max-width: 963px) 100vw, 963px" /><figcaption class="wp-element-caption"><strong>program.cs</strong> 수정</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="977" height="377" src="https://lycos7560.com/wp-content/uploads/2024/10/image-119.png" alt="" class="wp-image-38541" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-119.png 977w, https://lycos7560.com/wp-content/uploads/2024/10/image-119-300x116.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-119-768x296.png 768w" sizes="(max-width: 977px) 100vw, 977px" /><figcaption class="wp-element-caption"><strong>AuthController.cs</strong> 생성자 수정</figcaption></figure>
</div>
</div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>AuthController.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Models.DTO;
using NZWalks.API.Repositories;

namespace NZWalks.API.Controllers
{
    [Route("api/[controller]")] // 컨트롤러의 기본 경로를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    public class AuthController : ControllerBase
    {
        private readonly UserManager&lt;IdentityUser> userManager;
        private readonly ITokenRepository tokenRepository;

        // UserManager와 ITokenRepository를 주입받아 초기화
        public AuthController(UserManager&lt;IdentityUser> userManager, ITokenRepository tokenRepository)
        {
            this.userManager = userManager;
            this.tokenRepository = tokenRepository;
        }

        // Post: /api/Auth/Register
        [HttpPost]
        [Route("Register")]
        public async Task&lt;IActionResult> Register([FromBody] RegisterRequestDto registerRequestDto)
        {
            // 새로운 IdentityUser 생성
            var identityUser = new IdentityUser
            {
                UserName = registerRequestDto.Username,
                Email = registerRequestDto.Username
            };

            // UserManager를 사용하여 사용자 생성
            var identityResult = await userManager.CreateAsync(identityUser, registerRequestDto.Password);

            if (identityResult.Succeeded)
            {
                // 사용자에게 역할(Role) 추가
                if (registerRequestDto.Roles != null &amp;&amp; registerRequestDto.Roles.Any())
                {
                    identityResult = await userManager.AddToRolesAsync(identityUser, registerRequestDto.Roles);
                    if (identityResult.Succeeded)
                    {
                        return Ok("User was registered! Please login."); // 사용자 등록 성공 메시지 반환
                    }
                }
            }
            return BadRequest("Something went wrong!"); // 오류 발생 시 반환 메시지
        }

        // Post: /api/Auth/Login
        [HttpPost]
        [Route("Login")]
        public async Task&lt;IActionResult> Login([FromBody] LoginRequestDto loginRequestDto)
        {
            // 사용자 이메일로 사용자 찾기
            var user = await userManager.FindByEmailAsync(loginRequestDto.Username);

            if (user != null)
            {
                // 사용자의 비밀번호 확인
                var checkPasswordResult = await userManager.CheckPasswordAsync(user, loginRequestDto.Password);

                if (checkPasswordResult)
                {
                    // 사용자 역할(Role) 가져오기
                    var roles = await userManager.GetRolesAsync(user);

                    if (roles != null)
                    {
                        // 토큰 생성 로직
                        var jwtToken = tokenRepository.CreateJWTToken(user, roles.ToList());
                        var response = new LoginResposeDto
                        {
                            JwtToken = jwtToken
                        };
                        return Ok(response); // 생성된 토큰을 포함한 응답 반환
                    }
                }
            }
            return BadRequest("Username or Password incorrect"); // 사용자 이름 또는 비밀번호가 잘못된 경우 오류 응답
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>LoginResposeDto.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">namespace NZWalks.API.Models.DTO
{
    public class LoginResposeDto
    {
        public string JwtToken { get; set; }
    }
}
</pre>



<p></p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1308" height="817" src="https://lycos7560.com/wp-content/uploads/2024/10/image-120.png" alt="" class="wp-image-38542" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-120.png 1308w, https://lycos7560.com/wp-content/uploads/2024/10/image-120-300x187.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-120-768x480.png 768w" sizes="(max-width: 1308px) 100vw, 1308px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1297" height="980" src="https://lycos7560.com/wp-content/uploads/2024/10/image-121.png" alt="" class="wp-image-38543" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-121.png 1297w, https://lycos7560.com/wp-content/uploads/2024/10/image-121-300x227.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-121-768x580.png 768w" sizes="(max-width: 1297px) 100vw, 1297px" /><figcaption class="wp-element-caption">현재 사용 중인 대칭 키가 HS256 암호화 알고리즘에 필요한 최소 128비트 키보다 작기 때문에 발생</figcaption></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="684" height="223" src="https://lycos7560.com/wp-content/uploads/2024/10/image-122.png" alt="" class="wp-image-38544" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-122.png 684w, https://lycos7560.com/wp-content/uploads/2024/10/image-122-300x98.png 300w" sizes="(max-width: 684px) 100vw, 684px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1294" height="983" src="https://lycos7560.com/wp-content/uploads/2024/10/image-123.png" alt="" class="wp-image-38545" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-123.png 1294w, https://lycos7560.com/wp-content/uploads/2024/10/image-123-300x228.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-123-768x583.png 768w" sizes="(max-width: 1294px) 100vw, 1294px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1284" height="825" src="https://lycos7560.com/wp-content/uploads/2024/10/image-124.png" alt="" class="wp-image-38546" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-124.png 1284w, https://lycos7560.com/wp-content/uploads/2024/10/image-124-300x193.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-124-768x493.png 768w" sizes="(max-width: 1284px) 100vw, 1284px" /><figcaption class="wp-element-caption">아직 사용하지 못하는 API</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1427" height="604" src="https://lycos7560.com/wp-content/uploads/2024/10/image-126.png" alt="" class="wp-image-38548" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-126.png 1427w, https://lycos7560.com/wp-content/uploads/2024/10/image-126-300x127.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-126-768x325.png 768w" sizes="(max-width: 1427px) 100vw, 1427px" /></figure>
</div>
</div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>RegionsController.cs &#8211; Role에 따른 접근으로 변경</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Data;
using NZWalks.API.Models.Domain;
using NZWalks.API.Models.DTO;
using AutoMapper;
using NZWalks.API.CustomActionFilter;
using Microsoft.AspNetCore.Authorization;

namespace NZWalks.API.Controllers
{

    [Route("api/[controller]")] // 컨트롤러의 기본 Route를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    public class RegionsController : ControllerBase
    {

        private readonly NZWalksDbcontext dbcontext;
        private readonly IRegionRepository regionRepository;
        private readonly IMapper mapper;

        public RegionsController(NZWalksDbcontext dbContext, IRegionRepository regionRepository, IMapper mapper)
        {
            this.dbcontext = dbContext;
            this.regionRepository = regionRepository;
            this.mapper = mapper;
        }

        // GET ALL REGIONS
        // GET: https://localhost:1234/api/regions
        [HttpGet]
        [Authorize(Roles = "Reader, writer")] // Reader, writer에서 인증된 사용자만 접근 가능
        public async Task&lt;IActionResult> GetAll()
        {
            // Get Data From Database - Domain models
            // var regionDomain = await dbcontext.Regions.ToListAsync(); // Change Async
            var regionDomain = await regionRepository.GetAllAsync();

            // Map Domain Models to DTOs
            // var regionDto = new List&lt;RegionDto>();
            // foreach (var region in regionDomain) 
            // {
            //     regionDto.Add(new RegionDto() 
            //     {
            //         Id = region.Id,
            //         Code = region.Code,
            //         Name = region.Name,
            //         RegionImageUrl = region.RegionImageUrl
            //     });
            // }

            // Map Domain Models to DTOs
            var regionDto = mapper.Map&lt;List&lt;RegionDto>>(regionDomain);

            return Ok(regionDto);
        }

        // GET SINGLE REGION (Get Region By ID)
        // GET: https://localhost:1234/api/regions/{id}
        [HttpGet]
        [Route("{id:guid}")]
        [Authorize(Roles = "Reader")] // Reader에서 인증된 사용자만 접근 가능
        public async Task&lt;IActionResult> GetById([FromRoute]Guid id) 
        {
            // Get Data From Database - Domain models
            // var region = dbcontext.Regions.Find(id);
            // var regionDomain = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            var regionDomain = await regionRepository.GetByIdAsync(id);
            if (regionDomain == null)
            {
                return NotFound();
            }

            // Map Domain Models to DTOs
            // var regionDto = new RegionDto() 
            // {
            //     Id = regionDomain.Id,
            //     Code = regionDomain.Code,
            //     Name = regionDomain.Name,
            //     RegionImageUrl = regionDomain.RegionImageUrl
            // };

            var regionDto = mapper.Map&lt;RegionDto>(regionDomain);

            return Ok(regionDto);

        }

        // POST To Create New Region
        // POST : https://localhost:1234/api/regions
        [HttpPost]
        [ValidateModel]
        [Authorize(Roles = "writer")] // writer에서 인증된 사용자만 접근 가능
        public async Task&lt;IActionResult> Create([FromBody] AddRegionRequestDto addRegionRequestDto)
        {
            /* [ValidateModel] 적용전
                        if (ModelState.IsValid) // [ValidateModel] 
            {
                // [Require]
                // string Code, Name
                // string? RegionImageUrl

                // Map or Convert DTO to Domain Model
                //var regionDomainModel = new Region
                //{
                //    Code = addRegionRequestDto.Code,
                //    Name = addRegionRequestDto.Name,
                //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
                //};

                // Map or Convert DTO to Domain Model
                var regionDomainModel = mapper.Map&lt;Region>(addRegionRequestDto);

                // Use Domain Model to create Region
                // await dbcontext.Regions.AddAsync(regionDomainModel);
                // await dbcontext.SaveChangesAsync();

                await regionRepository.CreateAsync(regionDomainModel);


                // Map Domain model back to DTO
                //var regionDto = new RegionDto 
                //{ 
                //    Id = regionDomainModel.Id,
                //    Code = addRegionRequestDto.Code,
                //    Name = regionDomainModel.Name, 
                //    RegionImageUrl = regionDomainModel.RegionImageUrl

                //};

                var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

                // 201 Created 응답으로 생성된 DTO 반환
                return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
                // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
                // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
            }
            else
            {
                return BadRequest(ModelState);
            }
             
            */

            // [Require]
            // string Code, Name
            // string? RegionImageUrl

            // Map or Convert DTO to Domain Model
            //var regionDomainModel = new Region
            //{
            //    Code = addRegionRequestDto.Code,
            //    Name = addRegionRequestDto.Name,
            //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
            //};

            // Map or Convert DTO to Domain Model
            var regionDomainModel = mapper.Map&lt;Region>(addRegionRequestDto);

            // Use Domain Model to create Region
            // await dbcontext.Regions.AddAsync(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            await regionRepository.CreateAsync(regionDomainModel);


            // Map Domain model back to DTO
            //var regionDto = new RegionDto 
            //{ 
            //    Id = regionDomainModel.Id,
            //    Code = addRegionRequestDto.Code,
            //    Name = regionDomainModel.Name, 
            //    RegionImageUrl = regionDomainModel.RegionImageUrl

            //};

            var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

            // 201 Created 응답으로 생성된 DTO 반환
            return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
            // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
            // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
        }



        // Update Region
        // PUT: https://localhost:1234/api/regions/{id}
        [HttpPut]
        [Route("{id:guid}")]
        [ValidateModel]
        [Authorize(Roles = "writer")] // writer에서 인증된 사용자만 접근 가능
        public async Task&lt;IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateRegionRequestDto updateRegionRequestDto)
        {
            /* [ValidateModel] 적용 전
            if (ModelState.IsValid)
            {
                // Map 
                //var regionDomainModel = new Region
                //{
                //    Code = updateRegionRequestDto.Code,
                //    Name = updateRegionRequestDto.Name,
                //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

                //};

                var regionDomainModel = mapper.Map&lt;Region>(updateRegionRequestDto);

                // Check if region exists
                // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
                regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

                if (regionDomainModel == null)
                {
                    return NotFound();
                }

                //// Map DTO to Domain model
                //regionDomainModel.Code = updateRegionRequestDto.Code;
                //regionDomainModel.Name = updateRegionRequestDto.Name;
                //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

                //await dbcontext.SaveChangesAsync();

                // Convert Domain Model to DTO
                //var regionDto = new RegionDto
                //{
                //    Id = regionDomainModel.Id,
                //    Code = regionDomainModel.Code,
                //    Name = regionDomainModel.Name,
                //    RegionImageUrl = regionDomainModel.RegionImageUrl
                //};

                var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

                return Ok(regionDto);

            }
            else 
            { 
                return BadRequest(ModelState);
            }
            */
            // Map 
            //var regionDomainModel = new Region
            //{
            //    Code = updateRegionRequestDto.Code,
            //    Name = updateRegionRequestDto.Name,
            //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

            //};

            var regionDomainModel = mapper.Map&lt;Region>(updateRegionRequestDto);

            // Check if region exists
            // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

            if (regionDomainModel == null)
            {
                return NotFound();
            }

            //// Map DTO to Domain model
            //regionDomainModel.Code = updateRegionRequestDto.Code;
            //regionDomainModel.Name = updateRegionRequestDto.Name;
            //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

            //await dbcontext.SaveChangesAsync();

            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

            return Ok(regionDto);
        }


        // Delete Region
        // Delete https://localhost:1234/api/regions/{id}
        [HttpDelete]
        [Route("{id:guid}")]
        [Authorize(Roles = "writer")] // writer에서 인증된 사용자만 접근 가능
        public async Task&lt;IActionResult> Update([FromRoute] Guid id)
        {
            //var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);

            var regionDomainModel = await regionRepository.DeleteAsync(id);


            if (regionDomainModel == null)
            {
                return NotFound();
            }

            // dbcontext.Regions.Remove(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            // return deleted Region back
            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map&lt;Region>(regionDomainModel);

            return Ok(regionDto);
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1147" height="946" src="https://lycos7560.com/wp-content/uploads/2024/10/image-127.png" alt="" class="wp-image-38549" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-127.png 1147w, https://lycos7560.com/wp-content/uploads/2024/10/image-127-300x247.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-127-768x633.png 768w" sizes="(max-width: 1147px) 100vw, 1147px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1286" height="1008" src="https://lycos7560.com/wp-content/uploads/2024/10/image-129.png" alt="" class="wp-image-38551" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-129.png 1286w, https://lycos7560.com/wp-content/uploads/2024/10/image-129-300x235.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-129-768x602.png 768w" sizes="(max-width: 1286px) 100vw, 1286px" /><figcaption class="wp-element-caption">writer 등록</figcaption></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1107" height="643" src="https://lycos7560.com/wp-content/uploads/2024/10/image-128.png" alt="" class="wp-image-38550" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-128.png 1107w, https://lycos7560.com/wp-content/uploads/2024/10/image-128-300x174.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-128-768x446.png 768w" sizes="(max-width: 1107px) 100vw, 1107px" /><figcaption class="wp-element-caption">Reader가 Write 역할의 Api 사용 불가 (403)</figcaption></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1128" height="905" src="https://lycos7560.com/wp-content/uploads/2024/10/image-130.png" alt="" class="wp-image-38552" style="width:652px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-130.png 1128w, https://lycos7560.com/wp-content/uploads/2024/10/image-130-300x241.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-130-768x616.png 768w" sizes="(max-width: 1128px) 100vw, 1128px" /><figcaption class="wp-element-caption">Write 역할을 이용하여 제거</figcaption></figure>
</div>
</div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Swagger 적용</h3>



<p><strong>Program.cs</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; // Entity Framework Core 기능을 사용하기 위한 네임스페이스
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using NZWalks.API.Data;
using NZWalks.API.Mappings;
using NZWalks.API.Models.Domain;
using NZWalks.API.Repositories;
using System.Text; // 데이터 컨텍스트를 사용하기 위한 네임스페이스

var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더 객체 생성

// Add services to the container.
builder.Services.AddControllers(); // 컨트롤러 서비스를 추가하여 MVC 패턴을 지원

// Swagger/OpenAPI 설정에 대한 더 많은 정보를 얻으려면 https://aka.ms/aspnetcore/swashbuckle 참고
builder.Services.AddEndpointsApiExplorer(); // API 탐색기를 추가하여 API 문서를 자동 생성

// Swagger 생성을 위한 서비스 추가
builder.Services.AddSwaggerGen(options => {
    // 제목과 버전을 정의하여 Swagger 문서 설정
    options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
    {
        Title = "NZ Walks API",
        Version = "v1"
    });

    // JWT Bearer 보안 정의 추가
    options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme,
        new Microsoft.OpenApi.Models.OpenApiSecurityScheme()
        {
            Name = "Authorization",  // 인증 헤더 이름        
            In = ParameterLocation.Header,  // 인증 정보의 위치 (헤더)        
            Type = SecuritySchemeType.ApiKey,  // 인증 유형 (API 키)        
            Scheme = JwtBearerDefaults.AuthenticationScheme  // 스키마 설정
        }
    );

    // 정의된 보안 스키마를 사용한 보안 요구 사항 추가
    options.AddSecurityRequirement(new OpenApiSecurityRequirement {
        {
            new OpenApiSecurityScheme {
                Reference = new OpenApiReference {
                    Type = ReferenceType.SecurityScheme,  // 참조 유형 설정
                    Id = JwtBearerDefaults.AuthenticationScheme  // 참조 ID 설정
                },
                Scheme = "Oauth2",  // 스키마 설정
                Name = JwtBearerDefaults.AuthenticationScheme,  // 스키마 이름
                In = ParameterLocation.Header  // 위치 설정
            },
            new List&lt;string>()
        }
    });
});


// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksConnectionString")));

// DbContext 서비스를 추가하여 SQL Server를 사용하도록 설정
builder.Services.AddDbContext&lt;NZWalksAuthDbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("NZWalksAuthConnectionString")));


builder.Services.AddScoped&lt;IRegionRepository, SQLRegionRepository>(); // Main DB Region
builder.Services.AddScoped&lt;IWalkRepository, SQLWalkRepository>(); // Main DB Walk
builder.Services.AddScoped&lt;ITokenRepository, TokenRepository>(); // Token - Controller에서 사용가능
// builder.Services.AddScoped&lt;IRegionRepository, InMemoryRegionRepository>(); // Test DB (In Memory)
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles));


// Identity Core 서비스를 추가하여 IdentityUser를 사용하도록 설정
builder.Services.AddIdentityCore&lt;IdentityUser>()
    // IdentityRole을 추가하여 역할 관리를 지원
    .AddRoles&lt;IdentityRole>()
    // 데이터 보호 토큰 제공자를 추가하여 "NZWalks"라는 이름의 토큰 제공자 설정
    .AddTokenProvider&lt;DataProtectorTokenProvider&lt;IdentityUser>>("NZWalks")
    // Entity Framework 스토어를 사용하여 NZWalksAuthDbcontext를 통한 저장소 관리
    .AddEntityFrameworkStores&lt;NZWalksAuthDbcontext>()
    // 기본 토큰 제공자 추가
    .AddDefaultTokenProviders();

// Identity 옵션을 구성하여 비밀번호 정책 설정
builder.Services.Configure&lt;IdentityOptions>(options =>
{
    options.Password.RequireDigit = false; // 비밀번호에 숫자 필요 없음
    options.Password.RequireLowercase = false; // 비밀번호에 소문자 필요 없음
    options.Password.RequireNonAlphanumeric = false; // 비밀번호에 알파벳 이외의 문자 필요 없음
    options.Password.RequireUppercase = false; // 비밀번호에 대문자 필요 없음
    options.Password.RequiredLength = 6; // 비밀번호 최소 길이 6자
    options.Password.RequiredUniqueChars = 1; // 비밀번호에 최소 1개의 고유 문자 필요
});

// JWT 인증 설정 추가
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // JWT Bearer 인증 스키마 사용
    .AddJwtBearer(options => // JWT Bearer 인증 설정
    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
    {
        ValidateIssuer = true, // 발행자(issuer) 유효성 검사 활성화
        ValidateAudience = true, // 수신자(audience) 유효성 검사 활성화
        ValidateLifetime = true, // 토큰의 유효 기간 검사 활성화
        ValidateIssuerSigningKey = true, // 서명 키 유효성 검사 활성화
        ValidIssuer = builder.Configuration["Jwt:Issuer"], // 유효한 발행자 설정
        ValidAudience = builder.Configuration["Jwt:Audience"], // 유효한 수신자 설정
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) // 서명 키 설정
    });

var app = builder.Build(); // 애플리케이션 빌드

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    // 개발 환경에서만 Swagger를 사용하도록 설정
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리디렉션

app.UseAuthentication(); // JWT 인증 미들웨어 추가
// 이 코드는 JWT 인증을 처리하여 사용자 요청을 검증함

app.UseAuthorization(); // 권한 부여 미들웨어 사용
// 인증된 사용자의 권한을 검증함

app.MapControllers(); // 컨트롤러에 대한 요청을 매핑

app.Run(); // 애플리케이션 실행
</pre>



<figure class="wp-block-image size-full"><img decoding="async" width="799" height="760" src="https://lycos7560.com/wp-content/uploads/2024/10/image-131.png" alt="" class="wp-image-38553" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-131.png 799w, https://lycos7560.com/wp-content/uploads/2024/10/image-131-300x285.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-131-768x731.png 768w" sizes="(max-width: 799px) 100vw, 799px" /></figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1301" height="976" src="https://lycos7560.com/wp-content/uploads/2024/10/image-132.png" alt="" class="wp-image-38554" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-132.png 1301w, https://lycos7560.com/wp-content/uploads/2024/10/image-132-300x225.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-132-768x576.png 768w" sizes="(max-width: 1301px) 100vw, 1301px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1263" height="1011" src="https://lycos7560.com/wp-content/uploads/2024/10/image-133.png" alt="" class="wp-image-38555" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-133.png 1263w, https://lycos7560.com/wp-content/uploads/2024/10/image-133-300x240.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-133-768x615.png 768w" sizes="(max-width: 1263px) 100vw, 1263px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1291" height="958" src="https://lycos7560.com/wp-content/uploads/2024/10/image-134.png" alt="" class="wp-image-38556" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-134.png 1291w, https://lycos7560.com/wp-content/uploads/2024/10/image-134-300x223.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-134-768x570.png 768w" sizes="(max-width: 1291px) 100vw, 1291px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full"><img decoding="async" width="1345" height="913" src="https://lycos7560.com/wp-content/uploads/2024/10/image-140.png" alt="" class="wp-image-38562" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-140.png 1345w, https://lycos7560.com/wp-content/uploads/2024/10/image-140-300x204.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-140-768x521.png 768w" sizes="(max-width: 1345px) 100vw, 1345px" /></figure>
</div>
</div>



<figure class="wp-block-image size-full"><img decoding="async" width="1290" height="1012" src="https://lycos7560.com/wp-content/uploads/2024/10/image-141.png" alt="" class="wp-image-38563" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-141.png 1290w, https://lycos7560.com/wp-content/uploads/2024/10/image-141-300x235.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-141-768x602.png 768w" sizes="(max-width: 1290px) 100vw, 1290px" /></figure>



<p></p>
<p>The post <a href="https://lycos7560.com/c/json-web-tokenjwt-%ec%a0%81%ec%9a%a9/38503/">JSON Web Token(JWT) 적용</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/json-web-tokenjwt-%ec%a0%81%ec%9a%a9/38503/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Model validation in ASP.NET Core</title>
		<link>https://lycos7560.com/c/model-validation-in-asp-net-core/38496/</link>
					<comments>https://lycos7560.com/c/model-validation-in-asp-net-core/38496/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Mon, 28 Oct 2024 22:44:14 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[ApiController]]></category>
		<category><![CDATA[App]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[attribute]]></category>
		<category><![CDATA[Attributes]]></category>
		<category><![CDATA[binding]]></category>
		<category><![CDATA[IsValid]]></category>
		<category><![CDATA[Model]]></category>
		<category><![CDATA[model binding]]></category>
		<category><![CDATA[Model state]]></category>
		<category><![CDATA[Model validation]]></category>
		<category><![CDATA[ModelState]]></category>
		<category><![CDATA[ModelState.IsValid]]></category>
		<category><![CDATA[Razor]]></category>
		<category><![CDATA[Rerun validation]]></category>
		<category><![CDATA[State]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Validation]]></category>
		<category><![CDATA[Validation attributes]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[속성]]></category>
		<category><![CDATA[유효성]]></category>
		<category><![CDATA[입력]]></category>
		<category><![CDATA[입력 유효성]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=38496</guid>

					<description><![CDATA[<p>Model Validations https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0 ASP.NET Core MVC 또는 Razor Pages 앱에서 사용자 입력의 유효성을 검사하는 방법 Model state Model state represents errors that come from two subsystems: model binding and model validation.Model state는 model binding(모델 바인딩)과 model validation(모델 유효성 검사)라는 두 가지 서브 시스템에서 발생한 오류를 나타냅니다. Errors that originate from&#160;model binding&#160;are generally data conversion errors.model [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/model-validation-in-asp-net-core/38496/">Model validation in ASP.NET Core</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Model Validations</h2>



<p><a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0</a></p>



<p> ASP.NET Core MVC 또는 Razor Pages 앱에서 <strong>사용자 입력의 유효성을 검사하는 방법</strong></p>


				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-460a819b      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#model-validations" class="uagb-toc-link__trigger">Model Validations</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#model-state" class="uagb-toc-link__trigger">Model state</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#rerun-validation" class="uagb-toc-link__trigger">Rerun validation</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#validation-attributes" class="uagb-toc-link__trigger">Validation attributes</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#built-in-attributes" class="uagb-toc-link__trigger">Built-in attributes</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#error-messages" class="uagb-toc-link__trigger">Error messages</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="model-state">Model state</h3>



<p>Model state represents errors that come from two subsystems: model binding and model validation.<br><strong>Model state</strong>는 <strong>model binding(모델 바인딩)</strong>과 <strong>model validation(모델 유효성 검사)</strong>라는 두 가지 서브 시스템에서 발생한 오류를 나타냅니다.</p>



<p>Errors that originate from&nbsp;<a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener"><strong>model binding</strong></a>&nbsp;are generally data conversion errors.<br><a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener"><strong>model binding</strong></a>에서 발생한 오류는 일반적으로 <strong>데이터 변환 오류</strong>입니다.</p>



<p>For example, an &#8220;x&#8221; is entered in an integer field.<br>예를 들어, 정수(int) 필드에 &#8216;x'(char)가 입력된 경우입니다. </p>



<p><strong>Model validation</strong> occurs after model binding and reports errors where data doesn&#8217;t conform to business rules.<br><strong>Model validation</strong>는 모델 바인딩 후에 발생하며, 데이터가 비즈니스 규칙에 부합하지 않는 경우 오류를 보고합니다.</p>



<p>For example, a 0 is entered in a field that expects a rating between 1 and 5.<br>예를 들어, 1에서 5 사이의 값을 기대하는 필드에 0이 입력된 경우입니다.</p>



<p>Both <strong>model binding</strong> and <strong>model validation</strong> occur before the execution of a controller action or a Razor Pages handler method.<br><strong>model binding</strong>과 <strong>model validation</strong>는 <strong>컨트롤러 액션이나 Razor Pages 핸들러 메서드가 실행되기 전</strong>에 발생합니다.</p>



<p>For web apps, it&#8217;s the app&#8217;s responsibility to inspect<strong>&nbsp;<code>ModelState.IsValid</code></strong>&nbsp;and react appropriately.<br>웹 애플리케이션에서는 <code><strong>ModelState.IsValid</strong></code>를 검사하고 적절하게 반응하는 것이 <strong>애플리케이션의 책임</strong>입니다.</p>



<p>Web apps typically redisplay the page with an error message, as shown in the following Razor Pages example:<br>웹 애플리케이션은 일반적으로 오류 메시지가 포함된 페이지를 다시 표시합니다. 다음은 Razor Pages의 예제입니다:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public async Task&lt;IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }
    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>ASP.NET Core MVC 컨트롤러와 뷰를 사용하는 경우, 다음 예제는 컨트롤러 액션 내부에서 <code>ModelState.IsValid</code>를 확인하는 방법을 보여줍니다:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public async Task&lt;IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }
    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Web API 컨트롤러는 <code>[ApiController]</code> 특성을 가진 경우 <code>ModelState.IsValid</code>를 확인할 필요가 없습니다. </p>



<p>이 경우, 모델 상태가 유효하지 않으면 오류 세부 정보가 포함된 자동 HTTP 400 응답이 반환됩니다. </p>



<p>자세한 내용은 <a href="https://aka.ms/aspnetcore-docs-mvc-model-validation?form=MG0AV3" target="_blank" rel="noreferrer noopener">Automatic HTTP 400 responses</a>에서 확인할 수 있습니다.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="rerun-validation">Rerun validation</h3>



<p>Validation is automatic, but you might want to repeat it manually.<br>유효성 검사 다시 실행 유효성 검사는 자동으로 이루어지지만, <strong>수동으로 다시 실행</strong>하고 싶을 때가 있습니다.</p>



<p>For example, you might compute a value for a property and want to rerun validation after setting the property to the computed value.<br>예를 들어, 속성의 값을 계산한 후 해당 <strong>속성에 계산된 값을 설정한 후에 유효성 검사를 다시 실행</strong>하고 싶을 수 있습니다.</p>



<p>To rerun validation, call&nbsp;<a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelbinding.modelstatedictionary.clearvalidationstate">ModelStateDictionary.ClearValidationState</a>&nbsp;to clear validation specific to the model being validated followed by&nbsp;<code>TryValidateModel</code>:<br>유효성 검사를 다시 실행하려면, <a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelbinding.modelstatedictionary.clearvalidationstate"><strong>ModelStateDictionary.ClearValidationState</strong></a>를 호출하여 검사를 수행할 모델의 유효성 검사를 지우고, <strong><code>TryValidateModel</code>을 호출</strong>합니다:</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public async Task&lt;IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;
    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }
    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}
</pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="validation-attributes">Validation attributes </h3>



<p>Validation attributes let you specify validation rules for model properties.<br>V<strong>alidation attributes(유효성 검사 속성)</strong>을 사용하면 모델 속성에 대한 <strong>유효성 검사 규칙을 지정</strong>할 수 있습니다</p>



<p>The following example from the&nbsp;<a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/mvc/models/validation/samples" target="_blank" rel="noreferrer noopener">sample app</a>&nbsp;shows a model class that is annotated with validation attributes.<br>다음 예제는 <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/mvc/models/validation/samples" target="_blank" rel="noreferrer noopener">sample app</a>에서 Validation attributes으로 주석이 달린 모델 클래스를 보여줍니다.</p>



<p>The&nbsp;<code><strong>[ClassicMovie]</strong></code>&nbsp;attribute is a custom validation attribute and the others are built in.<br><code><strong>[ClassicMovie]</strong></code> 속성은 <strong>custom validation attribute(사용자 정의 속성)</strong>이고, 나머지는 내장된 속성입니다.</p>



<p>Not shown is&nbsp;<code><strong>[ClassicMovieWithClientValidator]</strong></code>, which shows an alternative way to implement a custom attribute.<br><code><strong>[ClassicMovieWithClientValidator]</strong></code>는 사용자 정의 속성을 구현하는 또 다른 방법을 보여줍니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>ClassicMovieAttribute.cs</strong> (<a href="https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/mvc/models/validation/samples/6.x/ValidationSample/Validation/ClassicMovieAttribute.cs" target="_blank" rel="noreferrer noopener">https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/mvc/models/validation/samples/6.x/ValidationSample/Validation/ClassicMovieAttribute.cs</a>)</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System.ComponentModel.DataAnnotations;
using ValidationSample.Models;

namespace ValidationSample.Validation;

// &lt;snippet_Class>
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic &amp;&amp; releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}
// &lt;/snippet_Class></pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class Movie
{
    public int Id { get; set; } // 영화의 고유 식별자

    [Required] // 이 속성은 필수 입력 값임을 나타냄
    [StringLength(100)] // 문자열 최대 길이를 100자로 제한
    public string Title { get; set; } = null!; // 영화 제목

    [ClassicMovie(1960)] // 사용자 정의 유효성 검사 속성, 1960년 이전의 클래식 영화일 경우 특정 조건을 검증
    [DataType(DataType.Date)] // 날짜 형식을 지정
    [Display(Name = "Release Date")] // 표시 이름을 "Release Date"로 지정
    public DateTime ReleaseDate { get; set; } // 영화 개봉일

    [Required] // 이 속성은 필수 입력 값임을 나타냄
    [StringLength(1000)] // 문자열 최대 길이를 1000자로 제한
    public string Description { get; set; } = null!; // 영화 설명

    [Range(0, 999.99)] // 값의 범위를 0에서 999.99로 제한
    public decimal Price { get; set; } // 영화 가격

    public Genre Genre { get; set; } // 영화 장르, Genre 클래스와 연관

    public bool Preorder { get; set; } // 영화 사전 예약 가능 여부
}
</pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="built-in-attributes">Built-in attributes</h3>



<p>Here are some of the built-in validation attributes:<br>다음은 일부 built-in(내장된) validation attributes입니다:</p>



<ul class="wp-block-list">
<li><a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelbinding.validation.validateneverattribute" target="_blank" rel="noreferrer noopener">[ValidateNever]</a>: <br>Indicates that a property or parameter should be excluded from validation.<br>속성 또는 매개 변수를 유효성 검사에서 제외합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.creditcardattribute" target="_blank" rel="noreferrer noopener">[CreditCard]</a>: <br>Validates that the property has a credit card format. Requires&nbsp;<a href="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/additional-methods.js" target="_blank" rel="noreferrer noopener">jQuery Validation Additional Methods</a>.<br>속성이 신용카드 형식인지 유효성 검사합니다. <a href="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/additional-methods.js" target="_blank" rel="noreferrer noopener">jQuery Validation Additional Methods</a>가 필요합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.compareattribute" target="_blank" rel="noreferrer noopener">[Compare]</a>: <br>Validates that two properties in a model match.<br>모델의 두 속성이 일치하는지 유효성 검사합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.emailaddressattribute" target="_blank" rel="noreferrer noopener">[EmailAddress]</a>: <br>Validates that the property has an email format.<br>속성이 이메일 형식인지 유효성 검사합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.phoneattribute" target="_blank" rel="noreferrer noopener">[Phone]</a>: <br>Validates that the property has a telephone number format.<br>속성이 전화번호 형식인지 유효성 검사합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.rangeattribute" target="_blank" rel="noreferrer noopener">[Range]</a>: <br>Validates that the property value falls within a specified range.<br>속성 값이 지정된 범위 내에 있는지 유효성 검사합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.regularexpressionattribute" target="_blank" rel="noreferrer noopener">[RegularExpression]</a>: <br>Validates that the property value matches a specified regular expression.<br>속성 값이 지정된 정규 표현식과 일치하는지 유효성 검사합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.requiredattribute" target="_blank" rel="noreferrer noopener">[Required]</a>: <br>Validates that the field isn&#8217;t null. See&nbsp;<a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0#non-nullable-reference-types-and-required-attribute"><code>[Required]</code>&nbsp;attribute</a>&nbsp;for details about this attribute&#8217;s behavior.<br>필드가 null이 아닌지 유효성 검사합니다. 이 속성의 동작에 대한 자세한 내용은 [Required] 속성을 참조하세요.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.stringlengthattribute">[StringLength]</a>: <br>Validates that a string property value doesn&#8217;t exceed a specified length limit.<br>문자열 속성 값이 지정된 길이 제한을 초과하지 않는지 유효성 검사합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.urlattribute" target="_blank" rel="noreferrer noopener">[Url]</a>: <br>Validates that the property has a URL format.<br>속성이 URL 형식인지 유효성 검사합니다.</li>



<li><a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.remoteattribute" target="_blank" rel="noreferrer noopener">[Remote]</a>: <br>Validates input on the client by calling an action method on the server.<br>서버의 액션 메서드를 호출하여 클라이언트에서 입력을 유효성 검사합니다.<br>See&nbsp;<a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0#remote-attribute" target="_blank" rel="noreferrer noopener"><code>[Remote]</code>&nbsp;attribute</a>&nbsp;for details about this attribute&#8217;s behavior.<br>이 속성의 동작에 대한 자세한 내용은 <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0#remote-attribute" target="_blank" rel="noreferrer noopener"><code>[Remote]</code>&nbsp;attribute</a>을 참조하세요.</li>
</ul>



<p>A complete list of validation attributes can be found in the&nbsp;<a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations" target="_blank" rel="noreferrer noopener">System.ComponentModel.DataAnnotations</a>&nbsp;namespace.<br>유효성 검사 속성의 전체 목록은 <code>System.ComponentModel.DataAnnotations</code> 네임스페이스에서 확인할 수 있습니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

public class ExampleModel
{
    public int Id { get; set; } // 고유 식별자

    [Required] // 필수 입력 값
    [StringLength(100)] // 문자열 최대 길이 100자
    public string Title { get; set; } = null!; // 제목

    [CreditCard] // 신용카드 형식인지 유효성 검사
    public string CreditCardNumber { get; set; } = null!; // 신용카드 번호

    [Compare("Password", ErrorMessage = "Passwords do not match.")] // 두 속성이 일치하는지 유효성 검사
    public string ConfirmPassword { get; set; } = null!; // 비밀번호 확인

    [EmailAddress] // 이메일 형식인지 유효성 검사
    public string Email { get; set; } = null!; // 이메일 주소

    [Phone] // 전화번호 형식인지 유효성 검사
    public string PhoneNumber { get; set; } = null!; // 전화번호

    [Range(1, 100)] // 값의 범위를 1에서 100으로 제한
    public int Quantity { get; set; } // 수량

    [RegularExpression(@"^[a-zA-Z0-9]*$", ErrorMessage = "Only alphanumeric characters are allowed.")] // 알파벳과 숫자만 허용
    public string Username { get; set; } = null!; // 사용자 이름

    [Url] // URL 형식인지 유효성 검사
    public string Homepage { get; set; } = null!; // 홈페이지 URL

    [Remote(action: "VerifyUsername", controller: "Users")] // 서버의 액션 메서드를 호출하여 유효성 검사
    public string UsernameRemote { get; set; } = null!; // 원격 유효성 검사용 사용자 이름

    [ValidateNever] // 유효성 검사에서 제외
    public string InternalCode { get; set; } = null!; // 내부 코드, 유효성 검사 제외

    [ClassicMovie(1960)] // 사용자 정의 유효성 검사 속성
    [DataType(DataType.Date)] // 날짜 형식을 지정
    [Display(Name = "Release Date")] // 표시 이름을 "Release Date"로 지정
    public DateTime ReleaseDate { get; set; } // 영화 개봉일
}

</pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="error-messages">Error messages</h3>



<p>Validation attributes let you specify the error message to be displayed for invalid input. For example:<br>Validation attributes을 사용하면 <strong>잘못된 입력에 대해 표시할 오류 메시지</strong>를 지정할 수 있습니다. 예를 들어:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Internally, the attributes call&nbsp;<a href="https://learn.microsoft.com/en-us/dotnet/api/system.string.format">String.Format</a>&nbsp;with a placeholder for the field name and sometimes additional placeholders. For example:<br>내부적으로, 속성은 필드 이름과 때때로 추가 자리 표시자를 위한 String.Format을 호출합니다. 예를 들어:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>When applied to a&nbsp;<code>Name</code>&nbsp;property, the error message created by the preceding code would be &#8220;Name length must be between 6 and 8.&#8221;.<br>이 코드가 Name 속성에 적용되면, 생성된 오류 메시지는 &#8220;Name length must be between 6 and 8.&#8221;가 됩니다.</p>



<p>To find out which parameters are passed to&nbsp;<code>String.Format</code>&nbsp;for a particular attribute&#8217;s error message, see the&nbsp;<a href="https://github.com/dotnet/runtime/tree/main/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations" target="_blank" rel="noreferrer noopener">DataAnnotations source code</a>.<br>특정 속성의 오류 메시지에 대해 String.Format에 전달되는 매개변수를 알아보려면&nbsp;<a href="https://github.com/dotnet/runtime/tree/main/src/libraries/System.ComponentModel.Annotations/src/System/ComponentModel/DataAnnotations" target="_blank" rel="noreferrer noopener">DataAnnotations source code</a> 를 참조하세요.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<p>추가 내용<br><a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0#required-validation-on-the-server" target="_blank" rel="noreferrer noopener"><strong>https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0#required-validation-on-the-server</strong></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System.ComponentModel.DataAnnotations;

namespace NZWalks.API.Models.DTO
{
    public class AddRegionRequestDto
    {
        [Required]
        [MinLength(3, ErrorMessage = "Code has to be a minimum of 3 characters")]
        [MaxLength(3, ErrorMessage = "Code has to be a maximum of 3 characters")]
        public string Code { get; set; }

        [Required]
        [MaxLength(100, ErrorMessage = "Code has to be a maximum of 100 characters")]
        public string Name { get; set; }
        public string? RegionImageUrl { get; set; } // Nullable

    }

}
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Data;
using NZWalks.API.Models.Domain;
using NZWalks.API.Models.DTO;
using static System.Net.WebRequestMethods;
using System;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
using System.Collections.Generic;
using NZWalks.API.CustomActionFilter;

namespace NZWalks.API.Controllers
{

    [Route("api/[controller]")] // 컨트롤러의 기본 Route를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    public class RegionsController : ControllerBase
    {

        private readonly NZWalksDbcontext dbcontext;
        private readonly IRegionRepository regionRepository;
        private readonly IMapper mapper;

        public RegionsController(NZWalksDbcontext dbContext, IRegionRepository regionRepository, IMapper mapper)
        {
            this.dbcontext = dbContext;
            this.regionRepository = regionRepository;
            this.mapper = mapper;
        }

        // GET ALL REGIONS
        // GET: https://localhost:1234/api/regions
        [HttpGet]
        public async Task&lt;IActionResult> GetAll()
        {
            // Get Data From Database - Domain models
            // var regionDomain = await dbcontext.Regions.ToListAsync(); // Change Async
            var regionDomain = await regionRepository.GetAllAsync();

            // Map Domain Models to DTOs
            // var regionDto = new List&lt;RegionDto>();
            // foreach (var region in regionDomain) 
            // {
            //     regionDto.Add(new RegionDto() 
            //     {
            //         Id = region.Id,
            //         Code = region.Code,
            //         Name = region.Name,
            //         RegionImageUrl = region.RegionImageUrl
            //     });
            // }

            // Map Domain Models to DTOs
            var regionDto = mapper.Map&lt;List&lt;RegionDto>>(regionDomain);

            return Ok(regionDto);
        }

        // GET SINGLE REGION (Get Region By ID)
        // GET: https://localhost:1234/api/regions/{id}
        [HttpGet]
        [Route("{id:guid}")]
        public async Task&lt;IActionResult> GetById([FromRoute]Guid id) 
        {
            // Get Data From Database - Domain models
            // var region = dbcontext.Regions.Find(id);
            // var regionDomain = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            var regionDomain = await regionRepository.GetByIdAsync(id);
            if (regionDomain == null)
            {
                return NotFound();
            }

            // Map Domain Models to DTOs
            // var regionDto = new RegionDto() 
            // {
            //     Id = regionDomain.Id,
            //     Code = regionDomain.Code,
            //     Name = regionDomain.Name,
            //     RegionImageUrl = regionDomain.RegionImageUrl
            // };

            var regionDto = mapper.Map&lt;RegionDto>(regionDomain);

            return Ok(regionDto);

        }

        // POST To Create New Region
        // POST : https://localhost:1234/api/regions
        [HttpPost]
        [ValidateModel]
        public async Task&lt;IActionResult> Create([FromBody] AddRegionRequestDto addRegionRequestDto)
        {
            /* [ValidateModel] 적용전
                        if (ModelState.IsValid) // [ValidateModel] 
            {
                // [Require]
                // string Code, Name
                // string? RegionImageUrl

                // Map or Convert DTO to Domain Model
                //var regionDomainModel = new Region
                //{
                //    Code = addRegionRequestDto.Code,
                //    Name = addRegionRequestDto.Name,
                //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
                //};

                // Map or Convert DTO to Domain Model
                var regionDomainModel = mapper.Map&lt;Region>(addRegionRequestDto);

                // Use Domain Model to create Region
                // await dbcontext.Regions.AddAsync(regionDomainModel);
                // await dbcontext.SaveChangesAsync();

                await regionRepository.CreateAsync(regionDomainModel);


                // Map Domain model back to DTO
                //var regionDto = new RegionDto 
                //{ 
                //    Id = regionDomainModel.Id,
                //    Code = addRegionRequestDto.Code,
                //    Name = regionDomainModel.Name, 
                //    RegionImageUrl = regionDomainModel.RegionImageUrl

                //};

                var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

                // 201 Created 응답으로 생성된 DTO 반환
                return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
                // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
                // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
            }
            else
            {
                return BadRequest(ModelState);
            }
             
            */

            // [Require]
            // string Code, Name
            // string? RegionImageUrl

            // Map or Convert DTO to Domain Model
            //var regionDomainModel = new Region
            //{
            //    Code = addRegionRequestDto.Code,
            //    Name = addRegionRequestDto.Name,
            //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
            //};

            // Map or Convert DTO to Domain Model
            var regionDomainModel = mapper.Map&lt;Region>(addRegionRequestDto);

            // Use Domain Model to create Region
            // await dbcontext.Regions.AddAsync(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            await regionRepository.CreateAsync(regionDomainModel);


            // Map Domain model back to DTO
            //var regionDto = new RegionDto 
            //{ 
            //    Id = regionDomainModel.Id,
            //    Code = addRegionRequestDto.Code,
            //    Name = regionDomainModel.Name, 
            //    RegionImageUrl = regionDomainModel.RegionImageUrl

            //};

            var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

            // 201 Created 응답으로 생성된 DTO 반환
            return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
            // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
            // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
        }



        // Update Region
        // PUT: https://localhost:1234/api/regions/{id}
        [HttpPut]
        [Route("{id:guid}")]
        [ValidateModel]
        public async Task&lt;IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateRegionRequestDto updateRegionRequestDto)
        {
            /* [ValidateModel] 적용 전
            if (ModelState.IsValid)
            {
                // Map 
                //var regionDomainModel = new Region
                //{
                //    Code = updateRegionRequestDto.Code,
                //    Name = updateRegionRequestDto.Name,
                //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

                //};

                var regionDomainModel = mapper.Map&lt;Region>(updateRegionRequestDto);

                // Check if region exists
                // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
                regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

                if (regionDomainModel == null)
                {
                    return NotFound();
                }

                //// Map DTO to Domain model
                //regionDomainModel.Code = updateRegionRequestDto.Code;
                //regionDomainModel.Name = updateRegionRequestDto.Name;
                //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

                //await dbcontext.SaveChangesAsync();

                // Convert Domain Model to DTO
                //var regionDto = new RegionDto
                //{
                //    Id = regionDomainModel.Id,
                //    Code = regionDomainModel.Code,
                //    Name = regionDomainModel.Name,
                //    RegionImageUrl = regionDomainModel.RegionImageUrl
                //};

                var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

                return Ok(regionDto);

            }
            else 
            { 
                return BadRequest(ModelState);
            }
            */
            // Map 
            //var regionDomainModel = new Region
            //{
            //    Code = updateRegionRequestDto.Code,
            //    Name = updateRegionRequestDto.Name,
            //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

            //};

            var regionDomainModel = mapper.Map&lt;Region>(updateRegionRequestDto);

            // Check if region exists
            // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

            if (regionDomainModel == null)
            {
                return NotFound();
            }

            //// Map DTO to Domain model
            //regionDomainModel.Code = updateRegionRequestDto.Code;
            //regionDomainModel.Name = updateRegionRequestDto.Name;
            //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

            //await dbcontext.SaveChangesAsync();

            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map&lt;RegionDto>(regionDomainModel);

            return Ok(regionDto);
        }


        // Delete Region
        // Delete https://localhost:1234/api/regions/{id}
        [HttpDelete]
        [Route("{id:guid}")]
        public async Task&lt;IActionResult> Update([FromRoute] Guid id)
        {
            //var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);

            var regionDomainModel = await regionRepository.DeleteAsync(id);


            if (regionDomainModel == null)
            {
                return NotFound();
            }

            // dbcontext.Regions.Remove(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            // return deleted Region back
            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map&lt;Region>(regionDomainModel);

            return Ok(regionDto);
        }
    }
}
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NZWalks.API.CustomActionFilter;
using NZWalks.API.Models.Domain;
using NZWalks.API.Models.DTO;
using NZWalks.API.Repositories;

namespace NZWalks.API.Controllers
{
    // /api/walks
    [Route("api/[controller]")]
    [ApiController]
    public class WalksController : ControllerBase
    {
        private readonly IMapper mapper;
        private readonly IWalkRepository walkRepository;

        public WalksController(IMapper mapper, IWalkRepository walkRepository)
        {
            this.mapper = mapper;
            this.walkRepository = walkRepository;
        }


        // CREATE walk
        // POST: /api/walks 
        [HttpPost]
        [ValidateModel]
        public async Task&lt;IActionResult> Create([FromBody] AddWalkRequestDto addWalkRequestDto)
        {
            /* [ValidateModel] 적용전
            if (ModelState.IsValid)
            {
                // Map DTO to Domain Model
                var walkDomaiModel = mapper.Map&lt;Walk>(addWalkRequestDto);

                await walkRepository.CreateAsync(walkDomaiModel);

                // Map Domain model to DTO
                var walkDto = mapper.Map&lt;WalkDto>(walkDomaiModel);

                return Ok(walkDto);
            }
            else 
            {
                return BadRequest(ModelState);
            }


            */

            // Map DTO to Domain Model
            var walkDomaiModel = mapper.Map&lt;Walk>(addWalkRequestDto);

            await walkRepository.CreateAsync(walkDomaiModel);

            // Map Domain model to DTO
            var walkDto = mapper.Map&lt;WalkDto>(walkDomaiModel);

            return Ok(walkDto);
        }

        // GET Walks
        // Get: /api/walks
        [HttpGet]
        public async Task&lt;IActionResult> GetAllAsync()
        {
            var walksDomain = await walkRepository.GetAllAsync();

            // Map Domain Model to DTO
            var walksDto = mapper.Map&lt;List&lt;WalkDto>>(walksDomain);

            return Ok(walksDto);
        }


        // Get Walk By Id
        // GET: /api/Walks/{id}
        [HttpGet]
        [Route("{id:Guid}")]
        public async Task&lt;IActionResult> GetByIdAsync([FromRoute] Guid id)
        {
            var walksDomain = await walkRepository.GetByIdAsync(id);

            if (walksDomain == null)
            {
                return NotFound();
            }

            var WalkDto = mapper.Map&lt;WalkDto>(walksDomain);
            
            return Ok(WalkDto);
        }

        // Update Walk By Id
        // Put: / api/Walks/{id}
        [HttpPut]
        [Route("{id:Guid}")]
        [ValidateModel]
        public async Task&lt;IActionResult> Update([FromRoute] Guid id, UpdateWalkRequestDto updateWalkRequestDto)
        {
            /*  [ValidateModel] 적용전
            if (ModelState.IsValid)
            {
                // Map DTO to Domain Model
                var walksDomain = mapper.Map&lt;Walk>(updateWalkRequestDto);

                walksDomain = await walkRepository.UpdateAsync(id, walksDomain);

                if (walksDomain == null)
                {
                    return NotFound();
                }

                var walksDto = mapper.Map&lt;Walk>(walksDomain);

                // Map Domain Model to DTO
                return Ok(walksDto);
            }
            else
            {
                return BadRequest(ModelState);
            }

            */

            // Map DTO to Domain Model
            var walksDomain = mapper.Map&lt;Walk>(updateWalkRequestDto);

            walksDomain = await walkRepository.UpdateAsync(id, walksDomain);

            if (walksDomain == null)
            {
                return NotFound();
            }

            var walksDto = mapper.Map&lt;Walk>(walksDomain);

            // Map Domain Model to DTO
            return Ok(walksDto);
        }

        // Delete a Walk By Id
        // DELETE: /api/Walks/{id}
        [HttpDelete]
        [Route("{id:Guid}")]
        public async Task&lt;IActionResult> Delete([FromRoute] Guid id)
        { 
            var deleteWalkDomainModel = await walkRepository.DeleteAsync(id);

            if (deleteWalkDomainModel == null)
            {
                return NotFound();
            }

            // Map Domain Model to DTO
            var deleteWalkDto = mapper.Map&lt;WalkDto>(deleteWalkDomainModel);

            return Ok(deleteWalkDto);
        }
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1289" height="1014" src="https://lycos7560.com/wp-content/uploads/2024/10/image-82.png" alt="" class="wp-image-38497" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-82.png 1289w, https://lycos7560.com/wp-content/uploads/2024/10/image-82-300x236.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-82-768x604.png 768w" sizes="(max-width: 1289px) 100vw, 1289px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1264" height="721" src="https://lycos7560.com/wp-content/uploads/2024/10/image-85.png" alt="" class="wp-image-38501" srcset="https://lycos7560.com/wp-content/uploads/2024/10/image-85.png 1264w, https://lycos7560.com/wp-content/uploads/2024/10/image-85-300x171.png 300w, https://lycos7560.com/wp-content/uploads/2024/10/image-85-768x438.png 768w" sizes="(max-width: 1264px) 100vw, 1264px" /></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace NZWalks.API.CustomActionFilter
{
    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (context.ModelState.IsValid == false)
            { 
                context.Result = new BadRequestResult();
            }
        }
    }
}
</pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<p></p>
<p>The post <a href="https://lycos7560.com/c/model-validation-in-asp-net-core/38496/">Model validation in ASP.NET Core</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/model-validation-in-asp-net-core/38496/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
