<?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/category/c/asp-net/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>생각의 흐름을 타고 다니며 만드는 블로그</description>
	<lastBuildDate>Thu, 25 Sep 2025 20:27:43 +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>IServiceScopeFactory 인터페이스</title>
		<link>https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/</link>
					<comments>https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/#comments</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Thu, 25 Sep 2025 20:27:40 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40295</guid>

					<description><![CDATA[<p>IServiceScopeFactory 란? https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory?view=net-8.0 IServiceScopeFactory 는 ASP.NET Core의 의존성 주입(DI) 컨테이너에서 새로운 서비스 범위(Scope) 를 만들기 위한 팩토리 인터페이스입니다. 기본적으로 DI 컨테이너는 Singleton / Scoped / Transient 세 가지 생명주기를 제공합니다. 일반적으로 Scoped 서비스는 HTTP 요청(Request)마다 생성되고, 요청이 끝나면 해제됩니다. 하지만 BackgroundService, Singleton 클래스 안에서는 Scoped 서비스를 직접 DI 받을 수 없습니다. 이를 해결하기 위하여 IServiceScopeFactory [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/">IServiceScopeFactory 인터페이스</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-091bc55d      "
					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="#iservicescopefactory-란" class="uagb-toc-link__trigger">IServiceScopeFactory 란?</a><li class="uagb-toc__list"><a href="#사용법" class="uagb-toc-link__trigger">사용법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-기본적인-사용법" class="uagb-toc-link__trigger">1. 기본적인 사용법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-backgroundservice에서-dbcontext-사용하기" class="uagb-toc-link__trigger">2. BackgroundService에서 DbContext 사용하기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-scoped-서비스-임시-실행" class="uagb-toc-link__trigger">3. Scoped 서비스 임시 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-여러-scoped-서비스-조합" class="uagb-toc-link__trigger">4. 여러 Scoped 서비스 조합</a></li></ul></li><li class="uagb-toc__list"><a href="#정리" class="uagb-toc-link__trigger">정리</a></ul></ol>					</div>
									</div>
				</div>
			


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



<h2 class="wp-block-heading">IServiceScopeFactory 란?</h2>



<p><a href="https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory?view=net-8.0">https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory?view=net-8.0</a></p>



<p><code>IServiceScopeFactory</code> 는 ASP.NET Core의 <strong>의존성 주입(DI) 컨테이너</strong>에서 새로운 <strong>서비스 범위(Scope)</strong> 를 만들기 위한 팩토리 인터페이스입니다.</p>



<p>기본적으로 DI 컨테이너는 <strong>Singleton / Scoped / Transient</strong> 세 가지 생명주기를 제공합니다.</p>



<p>일반적으로 Scoped 서비스는 HTTP 요청(Request)마다 생성되고, 요청이 끝나면 해제됩니다.</p>



<p>하지만 <code>BackgroundService</code>, <code>Singleton</code> 클래스 안에서는 Scoped 서비스를 직접 DI 받을 수 없습니다. </p>



<p>이를 해결하기 위하여 <code>IServiceScopeFactory</code> 를 이용해 <strong>임시 Scope를 만들어서 Scoped 서비스를 안전하게 사용</strong>합니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>구분</th><th>Singleton</th><th>Scoped</th><th>Transient</th></tr></thead><tbody><tr><td><strong>인스턴스 생성 시점</strong></td><td>애플리케이션 시작 시 한 번</td><td>HTTP 요청 시작 시 한 번</td><td>의존성 주입 요청 시마다</td></tr><tr><td><strong>인스턴스 수명</strong></td><td>애플리케이션 전체 수명</td><td>HTTP 요청 수명</td><td>가장 짧은 수명</td></tr><tr><td><strong>공유 범위</strong></td><td>전체 애플리케이션에서 단일 인스턴스 공유</td><td>동일 HTTP 요청 내에서 동일 인스턴스 공유</td><td>매번 새로운 인스턴스 생성</td></tr><tr><td><strong>사용 사례</strong></td><td>&#8211; 캐시 서비스<br>&#8211; 설정 관리자<br>&#8211; 로깅 서비스</td><td>&#8211; DB Context<br>&#8211; 사용자 세션<br>&#8211; 트랜잭션 관리</td><td>&#8211; 경량 서비스<br>&#8211; 상태 없는 헬퍼<br>&#8211; 일회성 작업</td></tr><tr><td><strong>주의사항</strong></td><td>상태 저장 시 thread-safe 고려 필요</td><td>요청 간 상태 공유 불가</td><td>빈번한 생성으로 성능 영향 가능성</td></tr></tbody></table></figure>



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



<h2 class="wp-block-heading">사용법</h2>



<h3 class="wp-block-heading">1. 기본적인 사용법</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 (var scope = scopeFactory.CreateScope())
{
    var myService = scope.ServiceProvider.GetRequiredService&lt;MyScopedService>();
    myService.DoSomething();
}
// scope.Dispose()가 호출되면서 MyScopedService도 정리됨
</pre>



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



<h3 class="wp-block-heading">2. BackgroundService에서 DbContext 사용하기</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="">public class MyWorker : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public MyWorker(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _scopeFactory.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService&lt;MyDbContext>();

            var users = await db.Users.ToListAsync(stoppingToken);
            Console.WriteLine($"현재 사용자 수: {users.Count}");

            await Task.Delay(5000, stoppingToken);
        }
    }
}

// MyDbContext 가 Scoped 로 등록되어 있어도 Worker에서 안전하게 사용 가능.</pre>



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



<h3 class="wp-block-heading">3. Scoped 서비스 임시 실행</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="">public class ReportGenerator
{
    private readonly IServiceScopeFactory _scopeFactory;

    public ReportGenerator(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public void Generate()
    {
        using var scope = _scopeFactory.CreateScope();
        var service = scope.ServiceProvider.GetRequiredService&lt;ReportService>();

        service.CreateDailyReport();
    }
}

// ReportService 가 Scoped 로 등록되어 있어도 IServiceScopeFactory 를 사용해 특정 시점에만 실행 가능.</pre>



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



<h3 class="wp-block-heading">4. 여러 Scoped 서비스 조합</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="">public class CleanupJob
{
    private readonly IServiceScopeFactory _scopeFactory;

    public CleanupJob(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task RunAsync()
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService&lt;MyDbContext>();
        var emailService = scope.ServiceProvider.GetRequiredService&lt;EmailService>();

        var expiredUsers = db.Users.Where(u => u.IsExpired).ToList();
        db.Users.RemoveRange(expiredUsers);
        await db.SaveChangesAsync();

        await emailService.SendCleanupReport(expiredUsers.Count);
    }
}
</pre>



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



<h2 class="wp-block-heading">정리</h2>



<ul class="wp-block-list">
<li><code>IServiceScopeFactory</code> 는 <strong>Scoped 서비스의 생명주기를 직접 제어</strong>할 수 있게 해줍니다.</li>



<li>주로 <strong>Singleton 객체나 BackgroundService</strong> 에서 Scoped 서비스를 안전하게 사용하기 위해 필요합니다.</li>



<li><code>CreateScope()</code> 로 범위를 열고, 작업이 끝나면 자동으로 Dispose 되어 메모리 누수 없이 관리됩니다.</li>
</ul>



<p></p>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/">IServiceScopeFactory 인터페이스</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<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>Ubuntu export 명령어 (.NET)</title>
		<link>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/</link>
					<comments>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Fri, 08 Aug 2025 06:21:42 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[app-config]]></category>
		<category><![CDATA[appsettings]]></category>
		<category><![CDATA[appsettings.development.json]]></category>
		<category><![CDATA[appsettings.json]]></category>
		<category><![CDATA[appsettings.production.json]]></category>
		<category><![CDATA[appsettings.staging.json]]></category>
		<category><![CDATA[asp-net-core]]></category>
		<category><![CDATA[aspnetcore]]></category>
		<category><![CDATA[aspnetcore-environment]]></category>
		<category><![CDATA[aspnetcore-urls]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[bash_profile]]></category>
		<category><![CDATA[bashrc]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[child-process]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[command-line]]></category>
		<category><![CDATA[config]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[connection-string-env]]></category>
		<category><![CDATA[connection-strings]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[database-config]]></category>
		<category><![CDATA[database-connection]]></category>
		<category><![CDATA[db-connection]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[dotnet]]></category>
		<category><![CDATA[dotnet-config]]></category>
		<category><![CDATA[dotnet-core]]></category>
		<category><![CDATA[dotnet-environment]]></category>
		<category><![CDATA[dotnet-environment-variable]]></category>
		<category><![CDATA[dotnet-publish]]></category>
		<category><![CDATA[dotnet-run]]></category>
		<category><![CDATA[dotnet-settings]]></category>
		<category><![CDATA[env]]></category>
		<category><![CDATA[Environment]]></category>
		<category><![CDATA[environment-config]]></category>
		<category><![CDATA[environment-example]]></category>
		<category><![CDATA[environment-variable]]></category>
		<category><![CDATA[environment-variable-setup]]></category>
		<category><![CDATA[export]]></category>
		<category><![CDATA[export-command]]></category>
		<category><![CDATA[export-example]]></category>
		<category><![CDATA[export-variable]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-dotnet]]></category>
		<category><![CDATA[linux-export]]></category>
		<category><![CDATA[localhost]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[path]]></category>
		<category><![CDATA[port-config]]></category>
		<category><![CDATA[printenv]]></category>
		<category><![CDATA[Process]]></category>
		<category><![CDATA[prod]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[production-server]]></category>
		<category><![CDATA[publish]]></category>
		<category><![CDATA[release]]></category>
		<category><![CDATA[secrets]]></category>
		<category><![CDATA[secure-environment-variable]]></category>
		<category><![CDATA[server-config]]></category>
		<category><![CDATA[server-environment]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[shell-variable]]></category>
		<category><![CDATA[stage]]></category>
		<category><![CDATA[Staging]]></category>
		<category><![CDATA[staging-server]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[system-environment]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[systemd-service]]></category>
		<category><![CDATA[Terminal]]></category>
		<category><![CDATA[test-environment]]></category>
		<category><![CDATA[ubuntu-dotnet]]></category>
		<category><![CDATA[ubuntu-dotnet-deploy]]></category>
		<category><![CDATA[url-binding]]></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=40241</guid>

					<description><![CDATA[<p>📄 Ubuntu export 명령어 1️⃣ 개요 export 명령어는 현재 셸 환경에 환경 변수를 등록하거나 수정하는 데 사용 등록된 변수는 현재 셸과 그 자식 프로세스에서 사용할 수 있음 2️⃣ 기본 문법 3️⃣ 특징 4️⃣ 확인 방법 💡 .NET에서의 사용 예시 1️⃣ ASP.NET Core에서 환경 지정 프로젝트 유형 권장 환경 변수 이유 ASP.NET Core 웹앱 ASPNETCORE_ENVIRONMENT 웹 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/">Ubuntu export 명령어 (.NET)</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-b32ec816      "
					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="#ubuntu-export-명령어" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Ubuntu export 명령어</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><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-확인-방법" class="uagb-toc-link__trigger">4&#x20e3; 확인 방법</a></li></ul></li><li class="uagb-toc__list"><a href="#net에서의-사용-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .NET에서의 사용 예시</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-aspnet-core에서-환경-지정" class="uagb-toc-link__trigger">1&#x20e3; ASP.NET Core에서 환경 지정</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-net에서-특정-포트로-실행" class="uagb-toc-link__trigger">2&#x20e3; .NET에서 특정 포트로 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-영구-설정" class="uagb-toc-link__trigger">3&#x20e3; 영구 설정</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/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Ubuntu export 명령어</h2>



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



<h3 class="wp-block-heading">1&#x20e3; 개요</h3>



<p><code>export</code> 명령어는 <strong>현재 셸 환경에 환경 변수를 등록하거나 수정</strong>하는 데 사용</p>



<p>등록된 변수는 <strong>현재 셸과 그 자식 프로세스</strong>에서 사용할 수 있음</p>



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



<h3 class="wp-block-heading">2&#x20e3; 기본 문법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">export 변수명=값

또는

export 변수명</pre>



<ul class="wp-block-list">
<li><code>변수명=값</code> : 새 환경 변수를 만들거나 기존 변수 값을 변경</li>



<li><code>변수명</code> : 이미 선언된 변수를 환경 변수로 승격</li>
</ul>



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



<h3 class="wp-block-heading">3&#x20e3; 특징</h3>



<ul class="wp-block-list">
<li>환경 변수는 <strong>프로세스 간 전달</strong> 가능 (부모 → 자식).</li>



<li><code>export</code>로 설정한 변수는 <strong>현재 세션</strong>에서만 유효.</li>



<li>영구적으로 유지하려면 <code>~/.bashrc</code> 또는 <code>~/.bash_profile</code>에 추가해야 함.</li>
</ul>



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



<h3 class="wp-block-heading">4&#x20e3; 확인 방법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">echo $변수명       # 변수 값 출력
printenv 변수명    # 환경 변수 확인
env                # 전체 환경 변수 목록
</pre>



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



<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/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .NET에서의 사용 예시</h2>



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



<h3 class="wp-block-heading">1&#x20e3; ASP.NET Core에서 환경 지정</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">export ASPNETCORE_ENVIRONMENT=Development
export ASPNETCORE_ENVIRONMENT=Staging
export ASPNETCORE_ENVIRONMENT=Production

export DOTNET_ENVIRONMENT=Development
export DOTNET_ENVIRONMENT=Staging // 실제 운영 환경(Production)과 거의 동일하지만, 사용자에게는 서비스되지 않는 사전 검증용 환경
export DOTNET_ENVIRONMENT=Production

dotnet run</pre>



<ul class="wp-block-list">
<li>예를 들어 <code>Development</code>로 설정하면, 개발 모드 환경 설정(<code>appsettings.Development.json</code>)을 자동으로 사용</li>
</ul>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>프로젝트 유형</th><th>권장 환경 변수</th><th>이유</th></tr></thead><tbody><tr><td>ASP.NET Core 웹앱</td><td>ASPNETCORE_ENVIRONMENT</td><td>웹 호스팅에 최적화된 공식 표준이며 <code>DOTNET_ENVIRONMENT</code>보다 <strong>우선순위가 높</strong></td></tr><tr><td>Worker Service</td><td>DOTNET_ENVIRONMENT</td><td>웹과 관련 없는 일반적인 .NET 호스팅 환경을 위한 표준 변수</td></tr><tr><td>콘솔 앱</td><td>DOTNET_ENVIRONMENT</td><td>범용적 사용</td></tr><tr><td>Blazor</td><td>ASPNETCORE_ENVIRONMENT</td><td>ASP.NET Core 호스팅 모델을 기반으로 하므로 웹 앱과 동일한 기준</td></tr></tbody></table></figure>



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



<h3 class="wp-block-heading">2&#x20e3; .NET에서 특정 포트로 실행</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># 5005번 포트로 애플리케이션 실행
export ASPNETCORE_URLS="http://localhost:5005"

dotnet run</pre>



<ul class="wp-block-list">
<li><code>ASPNETCORE_URLS</code> 환경 변수를 이용해 실행 시 포트를 변경할 수 있음</li>
</ul>



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



<h3 class="wp-block-heading">3&#x20e3; 영구 설정</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># 1. .bashrc 파일에 환경 변수 설정 명령어를 추가
echo 'export DOTNET_ENVIRONMENT=Production' >> ~/.bashrc

# 2. 수정된 .bashrc 설정을 현재 터미널 세션에 바로 적용
source ~/.bashrc

# 3. 설정 확인
echo $DOTNET_ENVIRONMENT</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/">Ubuntu export 명령어 (.NET)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core IdentityOptions 기본 예제</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Wed, 06 Aug 2025 09:44:34 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[2FA]]></category>
		<category><![CDATA[2단계 인증]]></category>
		<category><![CDATA[Admin]]></category>
		<category><![CDATA[appsettings.json]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Audit]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[BCrypt]]></category>
		<category><![CDATA[Bearer Token]]></category>
		<category><![CDATA[Claim]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[EF Core]]></category>
		<category><![CDATA[Entity Framework]]></category>
		<category><![CDATA[GDPR]]></category>
		<category><![CDATA[Guest]]></category>
		<category><![CDATA[Identity]]></category>
		<category><![CDATA[IdentityOptions]]></category>
		<category><![CDATA[ILogger]]></category>
		<category><![CDATA[IP 차단]]></category>
		<category><![CDATA[IPasswordValidator]]></category>
		<category><![CDATA[IUserValidator]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Lockout]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[password]]></category>
		<category><![CDATA[PasswordOptions]]></category>
		<category><![CDATA[Policy]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Recovery Code]]></category>
		<category><![CDATA[Role]]></category>
		<category><![CDATA[Salt]]></category>
		<category><![CDATA[Seed Data]]></category>
		<category><![CDATA[SignIn]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[SQLite]]></category>
		<category><![CDATA[Stores]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Token]]></category>
		<category><![CDATA[User]]></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>
		<category><![CDATA[회원가입]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40232</guid>

					<description><![CDATA[<p>✨ ASP.NET Core IdentityOptions 개요 https://github.com/dotnet/aspnetcore/blob/3f1acb59718cadf111a0a796681e3d3509bb3381/src/Identity/Extensions.Core/src/IdentityOptions.cs#L17C65-L65C66 ASP.NET Core의 IdentityOptions는 시스템의 설정을 한 곳에 몰아서 관리하는 클래스입니다. (중앙 집중식 구성) 이를 통해 패스워드 정책, 사용자 설정, 로그인 정책, 잠금 정책 등을 세밀하게 제어할 수 있습니다. 🔥 주요 구성 요소 1️⃣ Password 옵션 (PasswordOptions) 패스워드 복잡성 요구사항을 설정합니다. 2️⃣ Lockout 옵션 (LockoutOptions) 계정 잠금 정책을 설정합니다. 3️⃣ [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/">ASP.NET Core IdentityOptions 기본 예제</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-346b5c77      "
					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-identityoptions-개요" 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;" /> ASP.NET Core IdentityOptions 개요</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/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주요 구성 요소</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-password-옵션-passwordoptions" class="uagb-toc-link__trigger">1&#x20e3; Password 옵션 (PasswordOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-lockout-옵션-lockoutoptions" class="uagb-toc-link__trigger">2&#x20e3; Lockout 옵션 (LockoutOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-signin-옵션-signinoptions" class="uagb-toc-link__trigger">3&#x20e3; SignIn 옵션 (SignInOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-user-옵션-useroptions" class="uagb-toc-link__trigger">4&#x20e3; User 옵션 (UserOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-stores-옵션-storeoptions" class="uagb-toc-link__trigger">5&#x20e3; Stores 옵션 (StoreOptions)</a></li></ul><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/1f431.png" alt="🐱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 활용 시나리오</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></li></ul><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/2747.png" alt="❇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 참고 사항</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></ul></ul></ol>					</div>
									</div>
				</div>
			


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



<h1 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;" /> ASP.NET Core IdentityOptions 개요</h1>



<p><a href="https://github.com/dotnet/aspnetcore/blob/3f1acb59718cadf111a0a796681e3d3509bb3381/src/Identity/Extensions.Core/src/IdentityOptions.cs#L17C65-L65C66" target="_blank" rel="noreferrer noopener">https://github.com/dotnet/aspnetcore/blob/3f1acb59718cadf111a0a796681e3d3509bb3381/src/Identity/Extensions.Core/src/IdentityOptions.cs#L17C65-L65C66</a></p>



<p>ASP.NET Core의 IdentityOptions는 시스템의 설정을 한 곳에 몰아서 관리하는 클래스입니다. (중앙 집중식 구성)</p>



<p>이를 통해 <strong>패스워드 정책, 사용자 설정, 로그인 정책, 잠금 정책 등</strong>을 세밀하게 제어할 수 있습니다.</p>



<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/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주요 구성 요소</h2>



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



<figure class="wp-block-image size-full"><img decoding="async" width="872" height="287" src="https://lycos7560.com/wp-content/uploads/2025/08/image-12.png" alt="" class="wp-image-40235" srcset="https://lycos7560.com/wp-content/uploads/2025/08/image-12.png 872w, https://lycos7560.com/wp-content/uploads/2025/08/image-12-300x99.png 300w, https://lycos7560.com/wp-content/uploads/2025/08/image-12-768x253.png 768w" sizes="(max-width: 872px) 100vw, 872px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.aspnetcore.identity.identityoptions?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.aspnetcore.identity.identityoptions?view=aspnetcore-8.0</a></figcaption></figure>



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



<h3 class="wp-block-heading">1&#x20e3; Password 옵션 (PasswordOptions)</h3>



<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

// 개발 환경용 - 간단한 패스워드
public void ConfigureDevelopmentPassword(IdentityOptions options)
{
    options.Password.RequireDigit = false;
    options.Password.RequireLowercase = false;
    options.Password.RequireUppercase = false;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequiredLength = 4;
}

// 프로덕션 환경용 - 강력한 패스워드
public void ConfigureProductionPassword(IdentityOptions options)
{
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequiredLength = 12;
    options.Password.RequiredUniqueChars = 4;
}</pre>



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



<h3 class="wp-block-heading">2&#x20e3; Lockout 옵션 (LockoutOptions)</h3>



<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="">public void ConfigureProgressiveLockout(IdentityOptions options)
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);   // 1분부터 시작
    options.Lockout.MaxFailedAccessAttempts = 3;                        // 3회 실패시 잠금
    options.Lockout.AllowedForNewUsers = true;
}

// 커스텀 잠금 시간 증가 로직 (서비스에서 구현)
public class CustomLockoutService
{
    public TimeSpan CalculateLockoutDuration(int failedAttempts)
    {
        return failedAttempts switch
        {
            &lt;= 3 => TimeSpan.FromMinutes(5),
            &lt;= 6 => TimeSpan.FromMinutes(15),
            &lt;= 10 => TimeSpan.FromHours(1),
            _ => TimeSpan.FromHours(24)
        };
    }
}

public void ConfigureProgressiveLockout(IdentityOptions options)
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);   // 1분부터 시작
    options.Lockout.MaxFailedAccessAttempts = 3;                        // 3회 실패시 잠금
    options.Lockout.AllowedForNewUsers = true;
}

// 커스텀 잠금 시간 증가 로직 (서비스에서 구현)
public class CustomLockoutService
{
    public TimeSpan CalculateLockoutDuration(int failedAttempts)
    {
        return failedAttempts switch
        {
            &lt;= 3 => TimeSpan.FromMinutes(5),
            &lt;= 6 => TimeSpan.FromMinutes(15),
            &lt;= 10 => TimeSpan.FromHours(1),
            _ => TimeSpan.FromHours(24)
        };
    }
}</pre>



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



<h3 class="wp-block-heading">3&#x20e3; SignIn 옵션 (SignInOptions)</h3>



<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="">// 이메일 인증 필수 설정
public void ConfigureEmailConfirmation(IdentityOptions options)
{
    options.SignIn.RequireConfirmedEmail = true;
}

// 회원가입 컨트롤러
[HttpPost]
public async Task&lt;IActionResult> Register(RegisterViewModel model)
{
    var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
    var result = await _userManager.CreateAsync(user, model.Password);
    
    if (result.Succeeded)
    {
        // 이메일 인증 토큰 생성
        var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        var confirmationLink = Url.Action("ConfirmEmail", "Account", 
            new { userId = user.Id, token = token }, Request.Scheme);
        
        // 이메일 발송
        await _emailSender.SendEmailAsync(model.Email, "이메일 인증", 
            $"다음 링크를 클릭하여 계정을 활성화하세요: {confirmationLink}");
        
        return View("EmailConfirmationSent");
    }
    
    return View(model);
}

// 이메일 인증 필수 설정
public void ConfigureEmailConfirmation(IdentityOptions options)
{
    options.SignIn.RequireConfirmedEmail = true;
}

// 회원가입 컨트롤러
[HttpPost]
public async Task&lt;IActionResult> Register(RegisterViewModel model)
{
    var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
    var result = await _userManager.CreateAsync(user, model.Password);
    
    if (result.Succeeded)
    {
        // 이메일 인증 토큰 생성
        var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        var confirmationLink = Url.Action("ConfirmEmail", "Account", 
            new { userId = user.Id, token = token }, Request.Scheme);
        
        // 이메일 발송
        await _emailSender.SendEmailAsync(model.Email, "이메일 인증", 
            $"다음 링크를 클릭하여 계정을 활성화하세요: {confirmationLink}");
        
        return View("EmailConfirmationSent");
    }
    
    return View(model);
}
</pre>



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



<h3 class="wp-block-heading">4&#x20e3; User 옵션 (UserOptions)</h3>



<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="">builder.Services.Configure&lt;IdentityOptions>(options =>
{
    // 사용자 설정
    options.User.AllowedUserNameCharacters = 
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
    options.User.RequireUniqueEmail = true;  // 중복 이메일 방지
});


사용자명 정책 예제:
public void ConfigureUserPolicies(IdentityOptions options)
{
    // 한국어 사용자명 허용
    options.User.AllowedUserNameCharacters = 
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+가-힣";
    
    // 이메일을 고유하게 설정
    options.User.RequireUniqueEmail = true;
}

// 커스텀 사용자명 검증
public class CustomUserValidator&lt;TUser> : IUserValidator&lt;TUser> 
    where TUser : class
{
    public Task&lt;IdentityResult> ValidateAsync(UserManager&lt;TUser> manager, TUser user)
    {
        var userName = manager.GetUserNameAsync(user).Result;
        
        // 사용자명이 숫자로만 구성되면 안됨
        if (userName.All(char.IsDigit))
        {
            return Task.FromResult(
                IdentityResult.Failed(new IdentityError
                {
                    Code = "NumericUserName",
                    Description = "사용자명은 숫자로만 구성될 수 없습니다."
                }));
        }
        
        return Task.FromResult(IdentityResult.Success);
    }
}</pre>



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



<h3 class="wp-block-heading">5&#x20e3; Stores 옵션 (StoreOptions)</h3>



<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="">builder.Services.Configure&lt;IdentityOptions>(options =>
{
    // 저장소 설정
    options.Stores.MaxLengthForKeys = 128;              // 키 최대 길이
    options.Stores.ProtectPersonalData = false;         // 개인정보 암호화 여부
});</pre>



<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/1f431.png" alt="🐱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 활용 시나리오</h2>



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



<h3 class="wp-block-heading">1&#x20e3; 다단계 인증 설정</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="">public void ConfigureTwoFactorAuthentication(IdentityOptions options)
{
    options.SignIn.RequireConfirmedPhoneNumber = true;  // 전화번호 인증 필수
    options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider;
}

// 2FA 활성화 코드
[HttpPost]
public async Task&lt;IActionResult> EnableTwoFactorAuthentication()
{
    var user = await _userManager.GetUserAsync(User);
    await _userManager.SetTwoFactorEnabledAsync(user, true);
    
    return RedirectToAction("ShowRecoveryCodes");
}</pre>



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



<h3 class="wp-block-heading">2&#x20e3; 소셜 로그인과 연계</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="">public void ConfigureExternalLogins(IdentityOptions options)
{
    // 외부 로그인 시에는 이메일 인증 건너뛰기
    options.SignIn.RequireConfirmedEmail = false;
    
    // 하지만 고유 이메일은 필수
    options.User.RequireUniqueEmail = true;
}

// 외부 로그인 처리
[HttpPost]
public IActionResult ExternalLogin(string provider)
{
    var redirectUrl = Url.Action("ExternalLoginCallback");
    var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
    return Challenge(properties, provider);
}</pre>



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



<h3 class="wp-block-heading">3&#x20e3; 커스텀 검증 규칙 적용</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="">// 커스텀 패스워드 검증기
public class CustomPasswordValidator : IPasswordValidator&lt;ApplicationUser>
{
    public Task&lt;IdentityResult> ValidateAsync(UserManager&lt;ApplicationUser> manager, 
        ApplicationUser user, string password)
    {
        // 사용자명과 패스워드가 너무 유사하면 안됨
        if (password.Contains(user.UserName, StringComparison.OrdinalIgnoreCase))
        {
            return Task.FromResult(IdentityResult.Failed(new IdentityError
            {
                Code = "PasswordContainsUserName",
                Description = "패스워드에 사용자명이 포함될 수 없습니다."
            }));
        }
        
        return Task.FromResult(IdentityResult.Success);
    }
}

// 서비스 등록
builder.Services.AddTransient&lt;IPasswordValidator&lt;ApplicationUser>, CustomPasswordValidator>();</pre>



<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/2747.png" alt="❇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 참고 사항</h2>



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



<h3 class="wp-block-heading">1&#x20e3; 모니터링 및 로깅</h3>



<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="">public class SecurityEventLogger
{
    private readonly ILogger&lt;SecurityEventLogger> _logger;
    
    public SecurityEventLogger(ILogger&lt;SecurityEventLogger> logger)
    {
        _logger = logger;
    }
    
    public void LogFailedLogin(string userName, string ipAddress)
    {
        _logger.LogWarning("Failed login attempt for user {UserName} from IP {IpAddress}", 
            userName, ipAddress);
    }
    
    public void LogAccountLocked(string userName)
    {
        _logger.LogWarning("Account locked for user {UserName}", userName);
    }
}</pre>



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



<h3 class="wp-block-heading">2&#x20e3; 환경별 설정 관리</h3>



<p>appsettings.json을 활용한 설정</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "Identity": {
    "Password": {
      "RequiredLength": 8,
      "RequireDigit": true,
      "RequireUppercase": true,
      "RequireNonAlphanumeric": false
    },
    "Lockout": {
      "MaxFailedAccessAttempts": 5,
      "DefaultLockoutTimeSpanMinutes": 15
    },
    "SignIn": {
      "RequireConfirmedEmail": true
    }
  }
}</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="">// 설정 바인딩
public class IdentityConfig
{
    public PasswordConfig Password { get; set; }
    public LockoutConfig Lockout { get; set; }
    public SignInConfig SignIn { get; set; }
}

// Program.cs에서 적용
var identityConfig = builder.Configuration.GetSection("Identity").Get&lt;IdentityConfig>();

builder.Services.Configure&lt;IdentityOptions>(options =>
{
    options.Password.RequiredLength = identityConfig.Password.RequiredLength;
    options.Password.RequireDigit = identityConfig.Password.RequireDigit;
    // ... 기타 설정
});</pre>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/">ASP.NET Core IdentityOptions 기본 예제</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>리플렉션(Reflection)을 사용한 ActionResult를 상속받는 모든 클래스 찾기</title>
		<link>https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/</link>
					<comments>https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/#comments</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Mon, 28 Jul 2025 08:39:46 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[ActionResult]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[리플렉션]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40153</guid>

					<description><![CDATA[<p>The post <a href="https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/">리플렉션(Reflection)을 사용한 ActionResult를 상속받는 모든 클래스 찾기</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<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 System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;

// 1. ActionResult를 상속받는 모든 구체적인 클래스 찾기
var actionResultTypes = typeof(ActionResult).Assembly
    .GetTypes()
    .Where(t => t.IsClass &amp;&amp; !t.IsAbstract &amp;&amp; typeof(ActionResult).IsAssignableFrom(t))
    .ToList();

foreach (var type in actionResultTypes)
{
    Console.WriteLine(type.FullName);
}

// 2. IActionResult를 직접 구현하는 모든 클래스 찾기 (ActionResult 제외)
var iActionResultTypes = typeof(IActionResult).Assembly
    .GetTypes()
    .Where(t => t.IsClass &amp;&amp; !t.IsAbstract &amp;&amp; typeof(IActionResult).IsAssignableFrom(t) &amp;&amp; !typeof(ActionResult).IsAssignableFrom(t))
    .ToList();</pre>



<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="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Microsoft.AspNetCore.Mvc.AcceptedAtActionResult
Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult
Microsoft.AspNetCore.Mvc.AcceptedResult
Microsoft.AspNetCore.Mvc.ActionResult
Microsoft.AspNetCore.Mvc.AntiforgeryValidationFailedResult
Microsoft.AspNetCore.Mvc.BadRequestObjectResult
Microsoft.AspNetCore.Mvc.BadRequestResult
Microsoft.AspNetCore.Mvc.ChallengeResult
Microsoft.AspNetCore.Mvc.ConflictObjectResult
Microsoft.AspNetCore.Mvc.ConflictResult
Microsoft.AspNetCore.Mvc.ContentResult
Microsoft.AspNetCore.Mvc.CreatedAtActionResult
Microsoft.AspNetCore.Mvc.CreatedAtRouteResult
Microsoft.AspNetCore.Mvc.CreatedResult
Microsoft.AspNetCore.Mvc.EmptyResult
Microsoft.AspNetCore.Mvc.FileContentResult
Microsoft.AspNetCore.Mvc.FileResult
Microsoft.AspNetCore.Mvc.FileStreamResult
Microsoft.AspNetCore.Mvc.ForbidResult
Microsoft.AspNetCore.Mvc.HttpActionResult
Microsoft.AspNetCore.Mvc.JsonResult
Microsoft.AspNetCore.Mvc.LocalRedirectResult
Microsoft.AspNetCore.Mvc.NoContentResult
Microsoft.AspNetCore.Mvc.NotFoundObjectResult
Microsoft.AspNetCore.Mvc.NotFoundResult
Microsoft.AspNetCore.Mvc.ObjectResult
Microsoft.AspNetCore.Mvc.OkObjectResult
Microsoft.AspNetCore.Mvc.OkResult
Microsoft.AspNetCore.Mvc.PhysicalFileResult
Microsoft.AspNetCore.Mvc.RedirectResult
Microsoft.AspNetCore.Mvc.RedirectToActionResult
Microsoft.AspNetCore.Mvc.RedirectToPageResult
Microsoft.AspNetCore.Mvc.RedirectToRouteResult
Microsoft.AspNetCore.Mvc.SignInResult
Microsoft.AspNetCore.Mvc.SignOutResult
Microsoft.AspNetCore.Mvc.StatusCodeResult
Microsoft.AspNetCore.Mvc.UnauthorizedObjectResult
Microsoft.AspNetCore.Mvc.UnauthorizedResult
Microsoft.AspNetCore.Mvc.UnprocessableEntityObjectResult
Microsoft.AspNetCore.Mvc.UnprocessableEntityResult
Microsoft.AspNetCore.Mvc.UnsupportedMediaTypeResult
Microsoft.AspNetCore.Mvc.VirtualFileResult</pre>
<p>The post <a href="https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/">리플렉션(Reflection)을 사용한 ActionResult를 상속받는 모든 클래스 찾기</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/feed/</wfw:commentRss>
			<slash:comments>1</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>리버스 프록시(Reverse Proxy) 개념</title>
		<link>https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/</link>
					<comments>https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Sun, 27 Jul 2025 17:54:12 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[기타]]></category>
		<category><![CDATA[A/B Testing]]></category>
		<category><![CDATA[A/B 테스트]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[API Gateway]]></category>
		<category><![CDATA[API 게이트웨이]]></category>
		<category><![CDATA[Application Proxy]]></category>
		<category><![CDATA[Asynchronous Processing]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Backend Server]]></category>
		<category><![CDATA[Caching]]></category>
		<category><![CDATA[CDN]]></category>
		<category><![CDATA[Certificate Management]]></category>
		<category><![CDATA[client]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Compression]]></category>
		<category><![CDATA[Container]]></category>
		<category><![CDATA[Content Delivery]]></category>
		<category><![CDATA[Content Delivery Network]]></category>
		<category><![CDATA[Cookie Management]]></category>
		<category><![CDATA[DDoS Protection]]></category>
		<category><![CDATA[DDoS 방어]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Developer]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Distributed Processing]]></category>
		<category><![CDATA[Edge Computing]]></category>
		<category><![CDATA[Envoy]]></category>
		<category><![CDATA[Error Page]]></category>
		<category><![CDATA[firewall]]></category>
		<category><![CDATA[Gateway]]></category>
		<category><![CDATA[Gateway Pattern]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[HAProxy]]></category>
		<category><![CDATA[Header Manipulation]]></category>
		<category><![CDATA[High Availability]]></category>
		<category><![CDATA[HTTP]]></category>
		<category><![CDATA[HTTP/2]]></category>
		<category><![CDATA[HTTPS]]></category>
		<category><![CDATA[Inbound Traffic]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Intermediary]]></category>
		<category><![CDATA[Kestrel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[L7 스위치]]></category>
		<category><![CDATA[Layer 7 Switch]]></category>
		<category><![CDATA[Load Balancing]]></category>
		<category><![CDATA[Log Analysis]]></category>
		<category><![CDATA[Microservices]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[MSA]]></category>
		<category><![CDATA[Multi-tenancy]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[Offloading]]></category>
		<category><![CDATA[On-premise]]></category>
		<category><![CDATA[Operations]]></category>
		<category><![CDATA[Outbound Proxy]]></category>
		<category><![CDATA[Performance Optimization]]></category>
		<category><![CDATA[Protocol Conversion]]></category>
		<category><![CDATA[Proxy]]></category>
		<category><![CDATA[QUIC]]></category>
		<category><![CDATA[Request Forwarding]]></category>
		<category><![CDATA[Request Routing]]></category>
		<category><![CDATA[Response]]></category>
		<category><![CDATA[Reverse Proxy]]></category>
		<category><![CDATA[Scalability]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Security Layer]]></category>
		<category><![CDATA[Server Protection]]></category>
		<category><![CDATA[Service Discovery]]></category>
		<category><![CDATA[Service Mesh]]></category>
		<category><![CDATA[Service Proxy]]></category>
		<category><![CDATA[Session Persistence]]></category>
		<category><![CDATA[SSL Termination]]></category>
		<category><![CDATA[SSL 종료]]></category>
		<category><![CDATA[State Management]]></category>
		<category><![CDATA[Sticky Session]]></category>
		<category><![CDATA[System Architecture]]></category>
		<category><![CDATA[TLS Handshake]]></category>
		<category><![CDATA[TLS 핸드셰이크]]></category>
		<category><![CDATA[Traffic Management]]></category>
		<category><![CDATA[Traffic Splitting]]></category>
		<category><![CDATA[URL Rewriting]]></category>
		<category><![CDATA[URL 재작성]]></category>
		<category><![CDATA[User Experience]]></category>
		<category><![CDATA[UX]]></category>
		<category><![CDATA[Virtual Hosting]]></category>
		<category><![CDATA[WAF]]></category>
		<category><![CDATA[Web Acceleration]]></category>
		<category><![CDATA[Web Application]]></category>
		<category><![CDATA[Web Application Firewall]]></category>
		<category><![CDATA[Web Cache]]></category>
		<category><![CDATA[Web Proxy]]></category>
		<category><![CDATA[Web Server]]></category>
		<category><![CDATA[Web Service]]></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>
		<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=40122</guid>

					<description><![CDATA[<p>❓ 리버스 프록시(Reverse Proxy) 리버스 프록시(Reverse Proxy) 서버는 인터넷으로부터의 클라이언트 요청을 받아 백엔드 서버(실제 콘텐츠나 애플리케이션을 호스팅하는 서버)로 전달하고, 백엔드 서버로부터 받은 응답을 다시 클라이언트에게 전달하는 서버입니다. 정리하자면 웹 서비스의 최전선에 위치하여 클라이언트와 실제 서버 사이의 중개자 역할 일반적인 포워드 프록시(Forward Proxy)가 클라이언트가 인터넷에 접속하기 위해 사용하는 것(예: 회사 네트워크에서 외부 웹사이트 접속)과는 반대로, 리버스 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/">리버스 프록시(Reverse Proxy) 개념</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-32d5faea      "
					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="#리버스-프록시reverse-proxy" 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;" /> 리버스 프록시(Reverse Proxy)</a><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/1f300.png" alt="🌀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 리버스 프록시는 왜 사용하는가?</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-보안-강화-security" class="uagb-toc-link__trigger">1&#x20e3; 보안 강화 (Security)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-로드-밸런싱-load-balancing" class="uagb-toc-link__trigger">2&#x20e3; 로드 밸런싱 (Load Balancing)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-성능-최적화-performance-optimization" class="uagb-toc-link__trigger">3&#x20e3; 성능 최적화 (Performance Optimization)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-쉬운-유지보수-및-확장-maintainability-scalability" class="uagb-toc-link__trigger">4&#x20e3; 쉬운 유지보수 및 확장 (Maintainability &amp; Scalability)</a></li></ul></li><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/1f4d9.png" alt="📙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 동작 방식 요약</a><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/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 대표적인 리버스 프록시 소프트웨어</a></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;" /> 리버스 프록시(Reverse Proxy)</h2>



<p><strong>리버스 프록시(Reverse Proxy)</strong> 서버는 인터넷으로부터의 클라이언트 요청을 받아 백엔드 서버(실제 콘텐츠나 애플리케이션을 호스팅하는 서버)로 전달하고, </p>



<p>백엔드 서버로부터 받은 응답을 다시 클라이언트에게 전달하는 서버입니다. </p>



<p>정리하자면 <strong>웹 서비스의 최전선에 위치하여 클라이언트와 실제 서버 사이의 중개자 역할</strong></p>



<p>일반적인 <strong>포워드 프록시(Forward Proxy)가 클라이언트가 인터넷에 접속하기 위해 사용하는 것(예: 회사 네트워크에서 외부 웹사이트 접속)</strong>과는 반대로, </p>



<p><strong>리버스 프록시(Reverse Proxy)는 서버 앞단에서 서버로 들어오는 요청을 처리</strong>합니다.</p>



<div class="wp-block-uagb-container uagb-block-fbf8b3c1 alignfull uagb-is-root-container"><div class="uagb-container-inner-blocks-wrap">
<div class="wp-block-uagb-container uagb-block-3a8748f6">
<figure class="wp-block-image size-full"><img decoding="async" width="1920" height="720" src="https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_.png" alt="" class="wp-image-40123" srcset="https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_.png 1920w, https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_-300x113.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_-768x288.png 768w, https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_-1536x576.png 1536w" sizes="(max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption">Reverse Proxy<br><a href="https://ko.wikipedia.org/wiki/%EB%A6%AC%EB%B2%84%EC%8A%A4_%ED%94%84%EB%A1%9D%EC%8B%9C#/media/%ED%8C%8C%EC%9D%BC:Reverse_proxy_h2g2bob.svg" target="_blank" rel="noreferrer noopener">https://ko.wikipedia.org/wiki/%EB%A6%AC%EB%B2%84%EC%8A%A4_%ED%94%84%EB%A1%9D%EC%8B%9C#/media/%ED%8C%8C%EC%9D%BC:Reverse_proxy_h2g2bob.svg</a></figcaption></figure>
</div>



<div class="wp-block-uagb-container uagb-block-68dc1c5c">
<figure class="wp-block-image size-full"><img decoding="async" width="1920" height="960" src="https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow.png" alt="" class="wp-image-40124" srcset="https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow.png 1920w, https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow-300x150.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow-768x384.png 768w, https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow-1536x768.png 1536w" sizes="(max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption"><strong>Forward Proxy</strong><br><a href="https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/" target="_blank" rel="noreferrer noopener">https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/</a></figcaption></figure>
</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/1f300.png" alt="🌀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 리버스 프록시는 왜 사용하는가?</h2>



<p>리버스 프록시는 아래과 같은 주요 이점 때문에 웹 아키텍처에서 필수적인 요소로 자리를 잡았습니다.</p>



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



<h3 class="wp-block-heading">1&#x20e3; 보안 강화 (Security)</h3>



<ul class="wp-block-list">
<li><strong>직접적인 서버 노출 방지:</strong> <br>실제 백엔드 서버의 IP 주소나 내부 구조를 외부 클라이언트에게 숨겨 보안 위험을 줄입니다. 공격자는 리버스 프록시만 볼 수 있습니다.</li>



<li><strong>공격 방어:</strong> <br>DDoS 공격, SQL Injection, XSS 공격 등 다양한 웹 공격에 대한 1차 방어선 역할을 수행합니다. 수상한 트래픽을 필터링하거나 차단할 수 있습니다.</li>



<li><strong>SSL/TLS 암호화 종료 (SSL/TLS Termination):</strong> <br>클라이언트와의 암호화된 통신(HTTPS)을 리버스 프록시가 대신 처리하고, 백엔드 서버와의 통신은 암호화되지 않은 HTTP로 전환할 수 있습니다.<br>이는 백엔드 서버의 부하를 줄여주고, SSL 인증서 관리의 중앙화를 가능하게 합니다.</li>
</ul>



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



<h3 class="wp-block-heading">2&#x20e3; 로드 밸런싱 (Load Balancing)</h3>



<ul class="wp-block-list">
<li>리버스 프록시는 여러 개의 백엔드 서버로 트래픽을 효율적으로 분산하여 <strong>특정 서버에 부하가 집중되는 것을 방지</strong>합니다. <br>이를 통해 서비스의 <strong>안정성과 가용성을 높이고, 대규모 트래픽을 처리할 수 있도록 확장성을 제공</strong>합니다.</li>



<li>서버 중 하나에 문제가 발생하면, 해당 서버로의 트래픽을 중단하고 정상 작동하는 다른 서버로 요청을 보낼 수 있습니다.</li>
</ul>



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



<h3 class="wp-block-heading">3&#x20e3; 성능 최적화 (Performance Optimization)</h3>



<ul class="wp-block-list">
<li><strong>캐싱 (Caching):</strong> <br>자주 요청되는 정적 콘텐츠(이미지, CSS, JavaScript 파일 등)를 리버스 프록시 서버에 캐싱하여, <br>백엔드 서버로의 요청을 줄이고 클라이언트에게 더 빠르게 응답할 수 있습니다.</li>



<li><strong>압축 (Compression):</strong> <br>클라이언트에게 보내는 응답 데이터를 압축하여 전송량을 줄이고 로딩 속도를 향상시킵니다.</li>
</ul>



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



<h3 class="wp-block-heading">4&#x20e3; 쉬운 유지보수 및 확장 (Maintainability &amp; Scalability)</h3>



<ul class="wp-block-list">
<li>백엔드 서버를 추가하거나 제거할 때, 클라이언트는 리버스 프록시의 주소만 알기 때문에 변경 사항을 알 필요가 없습니다. <br>이는 서버 아키텍처를 유연하게 관리할 수 있도록 해줍니다.</li>



<li>A/B 테스트, URL 재작성, 특정 요청 라우팅 등 복잡한 트래픽 관리 정책을 구현하기 용이합니다.</li>
</ul>



<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/1f4d9.png" alt="📙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 동작 방식 요약</h2>



<ol class="wp-block-list">
<li><strong>클라이언트 요청:</strong> <br>웹 브라우저와 같은 클라이언트가 특정 웹 서비스(예: <code>www.example.com</code>)에 요청을 보냅니다.</li>



<li><strong>리버스 프록시 수신:</strong> <br>이 요청은 DNS 설정을 통해 <strong>리버스 프록시 서버</strong>로 전달됩니다.</li>



<li><strong>요청 처리 및 전달:</strong> <br>리버스 프록시 서버는 요청을 검사하고, 설정된 규칙(로드 밸런싱 알고리즘, URL 경로 등)에 따라 적절한 <strong>백엔드 서버</strong>를 선택하여 요청을 전달합니다. <br>(이때, SSL/TLS 종료가 이루어질 수 있습니다.)</li>



<li><strong>응답 수신 및 전달:</strong> <br>백엔드 서버는 요청을 처리하고 응답을 리버스 프록시 서버로 보냅니다. <br>리버스 프록시는 이 응답을 받아 (캐싱, 압축 등의 처리를 거쳐) 최종적으로 클라이언트에게 전달합니다.</li>
</ol>



<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/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 대표적인 리버스 프록시 소프트웨어</h2>



<ul class="wp-block-list">
<li><strong>Nginx (엔진엑스):</strong> <br>가장 널리 사용되는 웹 서버이자 리버스 프록시 서버입니다. 고성능과 경량성이 특징입니다.</li>



<li><strong>Apache HTTP Server (아파치):</strong> <br><code>mod_proxy</code> 모듈을 통해 리버스 프록시 기능을 제공합니다.</li>



<li><strong>HAProxy:</strong> <br>고가용성 로드 밸런싱 및 프록싱에 특화된 솔루션입니다.</li>



<li><strong>Envoy Proxy:</strong> <br>마이크로서비스 아키텍처에서 많이 사용되는 최신 프록시입니다.</li>
</ul>



<p></p>
<p>The post <a href="https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/">리버스 프록시(Reverse Proxy) 개념</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/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>.NET의 파이프 작업 (Pipe Operations in .NET)</title>
		<link>https://lycos7560.com/c/net%ec%9d%98-%ed%8c%8c%ec%9d%b4%ed%94%84-%ec%9e%91%ec%97%85-pipe-operations-in-net/40115/</link>
					<comments>https://lycos7560.com/c/net%ec%9d%98-%ed%8c%8c%ec%9d%b4%ed%94%84-%ec%9e%91%ec%97%85-pipe-operations-in-net/40115/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Tue, 15 Jul 2025 05:08:17 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.NETCore]]></category>
		<category><![CDATA[.NETFramework]]></category>
		<category><![CDATA[AnonymousPipe]]></category>
		<category><![CDATA[Asynchronous]]></category>
		<category><![CDATA[BackgroundService]]></category>
		<category><![CDATA[CancellationToken]]></category>
		<category><![CDATA[ChildProcess]]></category>
		<category><![CDATA[client]]></category>
		<category><![CDATA[CrossProcess]]></category>
		<category><![CDATA[DataExchange]]></category>
		<category><![CDATA[DataTransfer]]></category>
		<category><![CDATA[DependencyInjection]]></category>
		<category><![CDATA[Duplex]]></category>
		<category><![CDATA[ErrorHandling]]></category>
		<category><![CDATA[Flush]]></category>
		<category><![CDATA[GenericHost]]></category>
		<category><![CDATA[HandleInheritability]]></category>
		<category><![CDATA[HostBuilder]]></category>
		<category><![CDATA[IHostedService]]></category>
		<category><![CDATA[Impersonation]]></category>
		<category><![CDATA[InterprocessCommunication]]></category>
		<category><![CDATA[IOException]]></category>
		<category><![CDATA[IPC]]></category>
		<category><![CDATA[IPCMechanism]]></category>
		<category><![CDATA[IPC메커니즘]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Lifecycle]]></category>
		<category><![CDATA[LocalCommunication]]></category>
		<category><![CDATA[MemoryBuffer]]></category>
		<category><![CDATA[MessageBased]]></category>
		<category><![CDATA[NamedPipe]]></category>
		<category><![CDATA[NetworkCommunication]]></category>
		<category><![CDATA[OneWay]]></category>
		<category><![CDATA[Operations]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[ParentProcess]]></category>
		<category><![CDATA[pipe]]></category>
		<category><![CDATA[Process]]></category>
		<category><![CDATA[Read]]></category>
		<category><![CDATA[Reconnect]]></category>
		<category><![CDATA[Resilience]]></category>
		<category><![CDATA[SecurityContext]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[Stream]]></category>
		<category><![CDATA[StreamReader]]></category>
		<category><![CDATA[StreamWriter]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Sync]]></category>
		<category><![CDATA[Threading]]></category>
		<category><![CDATA[Timeout]]></category>
		<category><![CDATA[UDS]]></category>
		<category><![CDATA[UnixDomainSockets]]></category>
		<category><![CDATA[VirtualFile]]></category>
		<category><![CDATA[WindowsPipes]]></category>
		<category><![CDATA[Write]]></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=40115</guid>

					<description><![CDATA[<p>네임드 파이프(Named Pipe)는 프로세스 간 통신(IPC) 방법 중 하나로, 파일 시스템에 이름이 지정된 파이프를 생성하여 프로세스들이 해당 파이프를 통해 데이터를 주고받을 수 있도록 합니다. 일반적인 파이프(익명 파이프)는 부모-자식 관계와 같이 명확히 연관된 프로세스 간에만 사용 가능하지만, 네임드 파이프는 서로 관련 없는 프로세스들도 이름을 통해 통신할 수 있다는 특징을 가집니다. </p>
<p>The post <a href="https://lycos7560.com/c/net%ec%9d%98-%ed%8c%8c%ec%9d%b4%ed%94%84-%ec%9e%91%ec%97%85-pipe-operations-in-net/40115/">.NET의 파이프 작업 (Pipe Operations in .NET)</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-65798460      "
					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="#net의-파이프-작업" 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;" /> .NET의 파이프 작업</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#익명-파이프-anonymous-pipes" 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;" /> 익명 파이프 (Anonymous Pipes)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#익명-파이프-anonymous-pipes-사용-방법" class="uagb-toc-link__trigger">익명 파이프 (Anonymous Pipes) 사용 방법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#예제-1-서버-프로세스-부모-프로세스" class="uagb-toc-link__trigger">예제 1: 서버 프로세스 (부모 프로세스)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#예제-2-클라이언트-프로세스-자식-프로세스" class="uagb-toc-link__trigger">예제 2: 클라이언트 프로세스 (자식 프로세스)</a></li></ul></li></ul><li class="uagb-toc__list"><a href="#명명된-파이프-named-pipes" 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;" /> 명명된 파이프 (Named Pipes)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#명명된-파이프named-pipes-사용-방법" class="uagb-toc-link__trigger">명명된 파이프(Named Pipes) 사용 방법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#예제-1-namedpipeserverstream-클래스를-사용하여-명명된-파이프-생성하기" class="uagb-toc-link__trigger">예제 1: NamedPipeServerStream 클래스를 사용하여 명명된 파이프 생성하기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#예제-2-namedpipeclientstream-클래스를-사용하는-클라이언트-프로세스" class="uagb-toc-link__trigger">예제 2: NamedPipeClientStream 클래스를 사용하는 클라이언트 프로세스</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#강력한-프로그래밍-named-pipe-클라이언트서버-설정" class="uagb-toc-link__trigger">강력한 프로그래밍: Named Pipe 클라이언트/서버 설정</a></li></ul></li></ul><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></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" 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;" /> .NET의 파이프 작업</h2>



<p><a href="https://learn.microsoft.com/en-us/dotnet/standard/io/pipe-operations" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/dotnet/standard/io/pipe-operations</a></p>



<p>파이프는 <strong>프로세스 간 통신(Interprocess Communication, IPC)을 위한 수단을 제공</strong>합니다. </p>



<p>파이프에는 두 가지 유형이 있습니다:</p>



<ul class="wp-block-list">
<li>익명 파이프 (Anonymous Pipes)</li>



<li>명명된 파이프 (Named Pipes)</li>
</ul>



<div style="height:20px" 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/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 익명 파이프 (Anonymous Pipes)</h3>



<p>익명 파이프는 로컬 컴퓨터에서 프로세스 간 통신을 제공합니다. </p>



<p>익명 파이프는 명명된 파이프보다 오버헤드가 적지만 제공하는 서비스가 제한적입니다. </p>



<p>익명 파이프는 <strong>단방향</strong>이며 네트워크를 통해 사용할 수 없습니다. 또한 <strong>단일 서버 인스턴스만 지원</strong>합니다. </p>



<p>익명 파이프는 스레드 간 통신, 또는 파이프 핸들을 자식 프로세스가 생성될 때 쉽게 전달할 수 있는 부모-자식 프로세스 간 통신에 유용합니다.</p>



<p>.NET에서는 <strong><code>AnonymousPipeServerStream</code> </strong>및 <strong><code>AnonymousPipeClientStream</code> </strong>클래스를 사용하여 익명 파이프를 구현합니다.</p>



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



<h4 class="wp-block-heading">익명 파이프 (Anonymous Pipes) 사용 방법</h4>



<p><a href="https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-use-anonymous-pipes-for-local-interprocess-communication" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-use-anonymous-pipes-for-local-interprocess-communication</a></p>



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



<h5 class="wp-block-heading">예제 1: 서버 프로세스 (부모 프로세스)</h5>



<p>다음 예제는 익명 파이프를 사용하여 부모 프로세스에서 자식 프로세스로 문자열을 보내는 방법을 보여줍니다.</p>



<p>이 예제에서는 부모 프로세스에 <code>PipeDirection.Out</code> 값을 가진 <code>AnonymousPipeServerStream</code> 객체를 생성합니다. </p>



<p>그런 다음 부모 프로세스는 클라이언트 핸들을 사용하여 <code>AnonymousPipeClientStream</code> 객체를 생성하는 자식 프로세스를 시작합니다. </p>



<p>자식 프로세스는 <code>PipeDirection.In</code> 값을 가집니다.</p>



<p>이후 부모 프로세스는 사용자가 입력한 문자열을 자식 프로세스로 보냅니다. </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="">using System;
using System.IO;
using System.IO.Pipes;
using System.Diagnostics; // Process 클래스를 사용하기 위해 추가

class PipeServer
{
    static void Main()
    {
        Process pipeClient = new Process(); // 자식 프로세스를 제어할 Process 객체 생성

        pipeClient.StartInfo.FileName = "pipeClient.exe"; // 실행할 자식 프로세스의 파일 이름 설정

        // AnonymousPipeServerStream 객체 생성
        // PipeDirection.Out: 서버 -> 클라이언트로 데이터를 보냄 (단방향)
        // HandleInheritability.Inheritable: 자식 프로세스가 이 파이프 핸들을 상속받을 수 있도록 설정
        using (AnonymousPipeServerStream pipeServer =
            new AnonymousPipeServerStream(PipeDirection.Out,
            HandleInheritability.Inheritable))
        {
            Console.WriteLine($"[SERVER] 현재 전송 모드: {pipeServer.TransmissionMode}.");

            // 자식 프로세스에게 서버 파이프의 클라이언트 핸들을 문자열로 전달합니다.
            pipeClient.StartInfo.Arguments =
                pipeServer.GetClientHandleAsString();
            pipeClient.StartInfo.UseShellExecute = false; // 셸 실행 대신 직접 프로세스 시작
            pipeClient.Start(); // 자식 프로세스 시작

            // 자식 프로세스가 핸들을 상속받은 후, 부모 프로세스는 자신의 클라이언트 핸들 복사본을 해제합니다.
            // 이는 불필요한 리소스 유지를 방지합니다.
            pipeServer.DisposeLocalCopyOfClientHandle();

            try
            {
                // 사용자 입력을 읽고 그 내용을 클라이언트 프로세스로 보냅니다.
                using (StreamWriter sw = new StreamWriter(pipeServer))
                {
                    sw.AutoFlush = true; // 매 쓰기 작업 후 스트림 자동 플러시 설정

                    // '동기화 메시지'를 클라이언트로 보내고 클라이언트가 이를 받을 때까지 기다립니다.
                    sw.WriteLine("SYNC");
                    pipeServer.WaitForPipeDrain(); // 파이프에 기록된 모든 데이터가 전송될 때까지 대기

                    // 콘솔 입력을 클라이언트 프로세스로 보냅니다.
                    Console.Write("[SERVER] 텍스트를 입력하세요: ");
                    sw.WriteLine(Console.ReadLine());
                }
            }
            // 파이프가 끊어지거나 연결이 해제될 때 발생하는 IOException을 잡습니다.
            catch (IOException e)
            {
                Console.WriteLine($"[SERVER] 오류: {e.Message}");
            }
        } // using 블록을 벗어나면 pipeServer는 자동으로 Dispose됩니다.

        // 자식 프로세스가 종료될 때까지 기다립니다.
        pipeClient.WaitForExit();
        // 자식 프로세스 객체를 닫습니다.
        pipeClient.Close();
        Console.WriteLine("[SERVER] 클라이언트 종료. 서버도 종료합니다.");
    }
}</pre>



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



<h5 class="wp-block-heading">예제 2: 클라이언트 프로세스 (자식 프로세스)</h5>



<p>다음 예제는 클라이언트 프로세스를 보여줍니다. </p>



<p>서버 프로세스는 클라이언트 프로세스를 시작하고 해당 프로세스에 클라이언트 핸들을 전달합니다. </p>



<p>클라이언트 코드로부터 생성된 실행 파일은 <code>pipeClient.exe</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.IO;
using System.IO.Pipes;

class PipeClient
{
    static void Main(string[] args)
    {
        // 프로그램 인수가 있는지 확인합니다.
        // 서버가 클라이언트 핸들을 인수로 전달하기 때문에 필요합니다.
        if (args.Length > 0)
        {
            // AnonymousPipeClientStream 객체를 생성합니다.
            // PipeDirection.In: 클라이언트는 서버로부터 데이터를 받음 (단방향)
            // args[0]: 서버가 전달한 파이프 핸들 문자열
            using (PipeStream pipeClient =
                new AnonymousPipeClientStream(PipeDirection.In, args[0]))
            {
                Console.WriteLine($"[CLIENT] 현재 전송 모드: {pipeClient.TransmissionMode}.");

                // 파이프 스트림에서 텍스트를 읽기 위한 StreamReader 생성
                using (StreamReader sr = new StreamReader(pipeClient))
                {
                    string temp;

                    // 서버로부터 '동기화 메시지'가 올 때까지 기다립니다.
                    do
                    {
                        Console.WriteLine("[CLIENT] 동기화 대기 중...");
                        temp = sr.ReadLine();
                    }
                    while (!temp.StartsWith("SYNC")); // 'SYNC'로 시작하는 메시지를 받을 때까지 반복

                    // 서버 데이터를 읽고 콘솔에 출력합니다.
                    while ((temp = sr.ReadLine()) != null) // 스트림의 끝(null)이 아닐 때까지 읽기
                    {
                        Console.WriteLine("[CLIENT] 에코: " + temp);
                    }
                }
            } // using 블록을 벗어나면 pipeClient는 자동으로 Dispose됩니다.
        }
        Console.Write("[CLIENT] 계속하려면 Enter 키를 누르세요...");
        Console.ReadLine(); // 사용자가 Enter를 누를 때까지 대기
    }
}</pre>



<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)"/>



<h3 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;" /> 명명된 파이프 (Named Pipes)</h3>



<p>명명된 파이프는 파이프 서버와 하나 이상의 파이프 클라이언트 간 프로세스 간 통신을 제공합니다. </p>



<p>명명된 파이프는 <strong>단방향 또는 양방향</strong>이 될 수 있습니다. </p>



<p>이는 <strong>메시지 기반 통신을 지원</strong>하며, <strong>여러 클라이언트가 동일한 파이프 이름을 사용하여 서버 프로세스에 동시에 연결</strong>할 수 있도록 합니다. </p>



<p>명명된 파이프는 또한 가장(impersonation)을 지원하는데, 이를 통해 연결 프로세스가 원격 서버에서 자신의 권한을 사용할 수 있습니다.</p>



<p>.NET에서는 <strong><code>NamedPipeServerStream</code> </strong>및 <strong><code>NamedPipeClientStream</code> </strong>클래스를 사용하여 명명된 파이프를 구현합니다.</p>



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



<p><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;" /> 가장(Impersonation)이란?</p>



<p>컴퓨터 시스템에서 어떤 리소스(예: 파일, 네트워크 공유, 데이터베이스)에 접근하려면 해당 작업을 수행하는 <strong>사용자 계정의 권한</strong>이 필요</p>



<p>일반적으로 서버 애플리케이션은 특정 계정(예: 시스템 계정, 서비스 계정)의 권한으로 실행됩니다.</p>



<p>만약 서버가 <strong>클라이언트가 요청한 작업을 클라이언트 자신의 권한으로 수행</strong>해야 할 때가 있습니다. 예를 들어:</p>



<ul class="wp-block-list">
<li><strong>클라이언트가 접근 권한을 가진 특정 파일 읽기/쓰기:</strong> <br>서버는 클라이언트가 보낸 파일 경로에 대해 읽기/쓰기 요청을 받았습니다. <br>하지만 서버는 해당 파일에 직접 접근할 권한이 없고, <strong>오직 클라이언트 계정만 그 파일에 접근할 권한</strong>을 가지고 있을 수 있습니다.</li>



<li><strong>클라이언트의 네트워크 자격 증명 사용:</strong> <br>클라이언트가 원격 네트워크 공유에 있는 리소스에 접근하고 싶을 때, <br>서버가 클라이언트를 &#8216;가장&#8217;하여 클라이언트의 네트워크 자격 증명(사용자 이름과 비밀번호)을 사용하여 해당 공유에 접근할 수 있습니다.</li>
</ul>



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



<h4 class="wp-block-heading">명명된 파이프(Named Pipes) 사용 방법</h4>



<p><a href="https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication</a></p>



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



<h5 class="wp-block-heading">예제 1: <code>NamedPipeServerStream</code> 클래스를 사용하여 명명된 파이프 생성하기</h5>



<p>다음 예제는 <code>NamedPipeServerStream</code> 클래스를 사용하여 <strong>명명된 파이프를 생성하는 방법</strong>을 보여줍니다. </p>



<p>이 예제에서 서버 프로세스는 <strong>4개의 스레드를 생성</strong>합니다. 각 스레드는 <strong>하나의 클라이언트 연결을 수락</strong>할 수 있습니다. </p>



<p>연결된 클라이언트 프로세스는 서버에 파일 이름을 제공합니다. </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="">using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;

public class PipeServer
{
    // 동시에 처리할 서버 스레드의 개수 (클라이언트 연결을 수락할 인스턴스 수)
    private static int numThreads = 4;

    public static void Main()
    {
        int i;
        // 서버 스레드를 담을 배열 선언
        Thread?[] servers = new Thread[numThreads];

        Console.WriteLine("\n*** 명명된 파이프 서버 스트림과 가장(impersonation) 예제 ***\n");
        Console.WriteLine("클라이언트 연결 대기 중...\n");

        // numThreads 개수만큼 서버 스레드를 생성하고 시작합니다.
        for (i = 0; i &lt; numThreads; i++)
        {
            servers[i] = new Thread(ServerThread); // ServerThread 메서드를 실행할 새 스레드 생성
            servers[i]?.Start(); // 스레드 시작
        }

        // 서버 스레드가 시작될 시간을 잠시 기다립니다.
        Thread.Sleep(250);

        // 모든 서버 스레드가 작업을 완료할 때까지 대기하는 루프
        // 'i'는 아직 완료되지 않은 스레드의 개수를 추적합니다.
        while (i > 0)
        {
            for (int j = 0; j &lt; numThreads; j++)
            {
                if (servers[j] != null) // 스레드가 아직 활성 상태인 경우
                {
                    // 스레드가 250ms 내에 완료되는지 확인합니다.
                    // 완료되면 해당 스레드를 null로 설정하고 카운트를 줄입니다.
                    if (servers[j]!.Join(250))
                    {
                        Console.WriteLine($"서버 스레드[{servers[j]!.ManagedThreadId}] 작업 완료.");
                        servers[j] = null; // 완료된 스레드 참조 제거
                        i--;               // 완료되지 않은 스레드 개수 감소
                    }
                }
            }
        }
        Console.WriteLine("\n서버 스레드 모두 소진, 종료합니다.");
    }

    // 각 클라이언트 연결을 처리할 서버 스레드의 진입점 메서드
    private static void ServerThread(object? data)
    {
        // NamedPipeServerStream 인스턴스 생성
        // "testpipe": 파이프 이름 (클라이언트가 이 이름으로 연결 시도)
        // PipeDirection.InOut: 양방향 통신
        // numThreads: 이 파이프 이름으로 동시에 허용할 서버 인스턴스(클라이언트 연결)의 최대 수
        NamedPipeServerStream pipeServer =
            new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads);

        int threadId = Thread.CurrentThread.ManagedThreadId; // 현재 스레드의 ID

        // 클라이언트가 연결될 때까지 대기합니다.
        // 이 메서드는 블로킹(blocking) 호출입니다.
        pipeServer.WaitForConnection();

        Console.WriteLine($"클라이언트가 스레드[{threadId}]에 연결되었습니다.");
        try
        {
            // 클라이언트로부터 요청을 읽습니다.
            // 클라이언트가 파이프에 데이터를 쓰면, 클라이언트의 보안 토큰을 사용할 수 있게 됩니다.
            StreamString ss = new StreamString(pipeServer);

            // 연결된 클라이언트에게 서버임을 증명하는 문자열을 보냅니다.
            // 클라이언트는 이 문자열을 예상하고 있습니다.
            ss.WriteString("나는 유일한 참된 서버입니다!");
            // 클라이언트로부터 파일 이름을 읽습니다.
            string filename = ss.ReadString();

            // 클라이언트를 가장(impersonate)하여 파일 내용을 읽어 스트림으로 보냅니다.
            // ReadFileToStream 클래스는 파일 읽기 로직을 캡슐화합니다.
            ReadFileToStream fileReader = new ReadFileToStream(ss, filename);

            // 현재 가장하고 있는 사용자의 이름을 출력합니다.
            Console.WriteLine($"파일 읽기: {filename} (스레드[{threadId}]) 사용자: {pipeServer.GetImpersonationUserName()}로.");
            // 클라이언트를 가장한 보안 컨텍스트 내에서 fileReader.Start 메서드를 실행합니다.
            // 이 시점부터 Start() 메서드 내의 파일 접근은 클라이언트의 권한으로 이루어집니다.
            pipeServer.RunAsClient(fileReader.Start);
        }
        // 파이프가 끊어지거나 연결이 해제될 때 발생하는 IOException을 잡습니다.
        catch (IOException e)
        {
            Console.WriteLine($"오류: {e.Message}");
        }
        finally
        {
            // 파이프 서버 스트림을 닫고 리소스를 해제합니다.
            pipeServer.Close();
            Console.WriteLine($"스레드[{threadId}] 파이프 서버 닫힘.");
        }
    }
}

// 스트림에서 문자열을 읽고 쓰기 위한 데이터 프로토콜을 정의하는 클래스
public class StreamString
{
    private Stream ioStream; // 기본 스트림 (NamedPipeServerStream 또는 NamedPipeClientStream)
    private UnicodeEncoding streamEncoding; // 문자열 인코딩 방식

    public StreamString(Stream ioStream)
    {
        this.ioStream = ioStream;
        streamEncoding = new UnicodeEncoding(); // UTF-16 인코딩 사용
    }

    // 스트림에서 문자열을 읽습니다.
    public string ReadString()
    {
        // 먼저 문자열의 길이를 2바이트로 읽습니다. (Big-endian 방식으로)
        int len = 0;
        len = ioStream.ReadByte() * 256; // 첫 번째 바이트 (상위 8비트)
        len += ioStream.ReadByte();      // 두 번째 바이트 (하위 8비트)

        // 읽을 길이만큼의 바이트 배열을 생성합니다.
        byte[] inBuffer = new byte[len];
        // 스트림에서 해당 길이만큼의 바이트를 읽어 버퍼에 채웁니다.
        ioStream.Read(inBuffer, 0, len);

        // 바이트 배열을 문자열로 디코딩하여 반환합니다.
        return streamEncoding.GetString(inBuffer);
    }

    // 스트림에 문자열을 씁니다.
    public int WriteString(string outString)
    {
        // 문자열을 바이트 배열로 인코딩합니다.
        byte[] outBuffer = streamEncoding.GetBytes(outString);
        int len = outBuffer.Length;

        // 문자열 길이가 UInt16.MaxValue (65535)를 초과하면 잘라냅니다.
        // (프로토콜이 2바이트 길이 필드를 사용하므로 최대 65535 바이트까지 표현 가능)
        if (len > UInt16.MaxValue)
        {
            len = (int)UInt16.MaxValue;
        }

        // 문자열의 길이를 2바이트로 스트림에 씁니다. (Big-endian 방식으로)
        ioStream.WriteByte((byte)(len / 256)); // 상위 바이트
        ioStream.WriteByte((byte)(len &amp; 255)); // 하위 바이트
        // 문자열 바이트 배열을 스트림에 씁니다.
        ioStream.Write(outBuffer, 0, len);
        // 버퍼에 남아있는 데이터를 즉시 스트림으로 밀어 넣습니다.
        ioStream.Flush();

        // 전송된 총 바이트 수 (문자열 바이트 + 길이 2바이트)를 반환합니다.
        return outBuffer.Length + 2;
    }
}

// 가장된 사용자(클라이언트)의 컨텍스트에서 실행될 메서드를 포함하는 클래스
public class ReadFileToStream
{
    private string fn; // 읽을 파일 이름
    private StreamString ss; // 파일 내용을 쓸 스트림 (StreamString 헬퍼 사용)

    public ReadFileToStream(StreamString str, string filename)
    {
        fn = filename;
        ss = str;
    }

    // 이 메서드는 pipeServer.RunAsClient()에 의해 호출되며,
    // 호출되는 동안 현재 스레드의 보안 컨텍스트가 클라이언트로 변경됩니다.
    public void Start()
    {
        // 클라이언트의 권한으로 파일을 읽습니다.
        string contents = File.ReadAllText(fn);
        // 읽은 파일 내용을 스트림을 통해 클라이언트에게 다시 보냅니다.
        ss.WriteString(contents);
    }
}</pre>



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



<h5 class="wp-block-heading">예제 2: <code>NamedPipeClientStream</code> 클래스를 사용하는 클라이언트 프로세스</h5>



<p>다음 예제는 <code>NamedPipeClientStream</code> 클래스를 사용하는 클라이언트 프로세스를 보여줍니다. </p>



<p>이 클라이언트는 서버 프로세스에 연결하여 서버로 파일 이름을 전송합니다. </p>



<p>이 예제에서는 <strong>가장(impersonation)</strong> 기능을 사용하므로, 클라이언트 애플리케이션을 실행하는 <strong>사용자 계정에 해당 파일에 접근할 수 있는 권한이 있어야</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="">using System;
using System.Diagnostics; // 프로세스 관련 기능을 위해 추가
using System.IO;          // 파일 및 스트림 관련 기능을 위해 추가
using System.IO.Pipes;    // Named Pipe 관련 기능을 위해 추가
using System.Security.Principal; // TokenImpersonationLevel을 위해 추가
using System.Text;        // 인코딩을 위해 추가
using System.Threading;   // 스레드 및 Sleep을 위해 추가

public class PipeClient
{
    // 생성할 클라이언트 프로세스의 개수
    private static int numClients = 4;

    public static void Main(string[] args)
    {
        // 프로그램이 'spawnclient' 인수를 가지고 실행되었는지 확인합니다.
        // 이 인수는 메인 프로세스가 다른 클라이언트 프로세스를 시작할 때 사용됩니다.
        if (args.Length > 0)
        {
            if (args[0] == "spawnclient")
            {
                // 'spawnclient' 인수가 있는 경우, 명명된 파이프 클라이언트를 생성하고 서버에 연결합니다.
                var pipeClient =
                    new NamedPipeClientStream(
                        ".",                 // 파이프가 로컬 컴퓨터에 있음을 나타냅니다. (서버 주소)
                        "testpipe",          // 연결할 파이프 이름 (서버의 파이프 이름과 일치해야 합니다)
                        PipeDirection.InOut, // 양방향 통신
                        PipeOptions.None,    // 추가 파이프 옵션 없음
                        TokenImpersonationLevel.Impersonation); // 클라이언트의 보안 토큰을 서버로 전송하여 가장을 허용합니다.

                Console.WriteLine("서버에 연결 중...\n");
                // 서버에 연결될 때까지 대기합니다.
                pipeClient.Connect();

                var ss = new StreamString(pipeClient);
                // 서버의 서명 문자열("I am the one true server!")을 읽어 유효성을 검사합니다.
                if (ss.ReadString() == "I am the one true server!")
                {
                    // 첫 번째 쓰기 작업과 함께 클라이언트의 보안 토큰이 서버로 전송됩니다.
                    // 서버가 파일 내용을 반환할 파일 이름을 서버로 보냅니다.
                    ss.WriteString("c:\\textfile.txt"); // 서버가 읽을 파일 경로를 지정합니다.

                    // 서버로부터 파일 내용을 읽어 콘솔에 출력합니다.
                    Console.Write(ss.ReadString());
                }
                else
                {
                    Console.WriteLine("서버를 확인할 수 없습니다.");
                }
                // 파이프 클라이언트 스트림을 닫고 리소스를 해제합니다.
                pipeClient.Close();
                // 클라이언트 프로세스가 결과를 표시할 시간을 주기 위해 4초 동안 대기합니다.
                Thread.Sleep(4000);
            }
        }
        else // 'spawnclient' 인수가 없는 경우, 여러 클라이언트 프로세스를 시작합니다.
        {
            Console.WriteLine("\n*** 가장(impersonation) 예제를 포함한 명명된 파이프 클라이언트 스트림 ***\n");
            // 클라이언트 프로세스를 시작하는 헬퍼 함수를 호출합니다.
            StartClients();
        }
    }

    // 파이프 클라이언트 프로세스를 생성하는 헬퍼 함수
    private static void StartClients()
    {
        // 현재 실행 중인 프로세스의 전체 명령줄을 가져옵니다. (예: "C:\path\to\YourApp.exe")
        string currentProcessName = Environment.CommandLine;

        // Visual Studio에서 실행될 때 추가되는 따옴표나 공백을 제거합니다.
        currentProcessName = currentProcessName.Trim('"', ' ');

        // 확장자를 .exe로 변경하여 실행 파일 경로를 확보합니다.
        currentProcessName = Path.ChangeExtension(currentProcessName, ".exe");
        Process?[] plist = new Process?[numClients]; // 생성될 클라이언트 프로세스들을 저장할 배열

        Console.WriteLine("클라이언트 프로세스들을 생성 중...\n");

        // 현재 디렉토리 경로가 명령줄에 포함되어 있다면 제거합니다.
        // 이는 Process.Start가 올바른 실행 파일을 찾도록 돕기 위함입니다.
        if (currentProcessName.Contains(Environment.CurrentDirectory))
        {
            currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty);
        }

        // Visual Studio 실행 시 발생할 수 있는 추가적인 문자(백슬래시, 따옴표)를 제거합니다.
        currentProcessName = currentProcessName.Replace("\\", String.Empty);
        currentProcessName = currentProcessName.Replace("\"", String.Empty);

        int i;
        for (i = 0; i &lt; numClients; i++)
        {
            // '이' 프로그램(PipeClient) 자체를 새 프로세스로 시작하되,
            // 'spawnclient' 인수를 전달하여 명명된 파이프 클라이언트 모드로 실행되도록 합니다.
            plist[i] = Process.Start(currentProcessName, "spawnclient");
        }

        // 모든 클라이언트 프로세스가 종료될 때까지 대기하는 루프
        // 'i'는 아직 종료되지 않은 프로세스의 개수를 추적합니다.
        while (i > 0)
        {
            for (int j = 0; j &lt; numClients; j++)
            {
                if (plist[j] != null) // 프로세스가 아직 활성 상태인 경우
                {
                    // 프로세스가 종료되었는지 확인합니다.
                    if (plist[j]!.HasExited)
                    {
                        Console.WriteLine($"클라이언트 프로세스[{plist[j]?.Id}]가 종료되었습니다.");
                        plist[j] = null; // 종료된 프로세스 참조 제거
                        i--;             // 종료되지 않은 프로세스 개수 감소
                    }
                    else
                    {
                        // 아직 종료되지 않았다면 잠시 대기합니다.
                        Thread.Sleep(250);
                    }
                }
            }
        }
        Console.WriteLine("\n클라이언트 프로세스들이 모두 완료되어 종료합니다.");
    }
}

// 스트림에서 문자열을 읽고 쓰기 위한 데이터 프로토콜을 정의하는 클래스
// (이 클래스는 서버 예제와 동일합니다)
public class StreamString
{
    private Stream ioStream;           // 기본 스트림 (NamedPipeClientStream)
    private UnicodeEncoding streamEncoding; // 문자열 인코딩 방식

    public StreamString(Stream ioStream)
    {
        this.ioStream = ioStream;
        streamEncoding = new UnicodeEncoding(); // UTF-16 인코딩 사용
    }

    // 스트림에서 문자열을 읽습니다.
    public string ReadString()
    {
        // 먼저 문자열의 길이를 2바이트로 읽습니다. (Big-endian 방식으로)
        int len;
        len = ioStream.ReadByte() * 256; // 첫 번째 바이트 (상위 8비트)
        len += ioStream.ReadByte();      // 두 번째 바이트 (하위 8비트)

        // 읽을 길이만큼의 바이트 배열을 생성합니다.
        var inBuffer = new byte[len];
        // 스트림에서 해당 길이만큼의 바이트를 읽어 버퍼에 채웁니다.
        ioStream.Read(inBuffer, 0, len);

        // 바이트 배열을 문자열로 디코딩하여 반환합니다.
        return streamEncoding.GetString(inBuffer);
    }

    // 스트림에 문자열을 씁니다.
    public int WriteString(string outString)
    {
        // 문자열을 바이트 배열로 인코딩합니다.
        byte[] outBuffer = streamEncoding.GetBytes(outString);
        int len = outBuffer.Length;

        // 문자열 길이가 UInt16.MaxValue (65535)를 초과하면 잘라냅니다.
        // (프로토콜이 2바이트 길이 필드를 사용하므로 최대 65535 바이트까지 표현 가능)
        if (len > UInt16.MaxValue)
        {
            len = (int)UInt16.MaxValue;
        }

        // 문자열의 길이를 2바이트로 스트림에 씁니다. (Big-endian 방식으로)
        ioStream.WriteByte((byte)(len / 256)); // 상위 바이트
        ioStream.WriteByte((byte)(len &amp; 255)); // 하위 바이트
        // 문자열 바이트 배열을 스트림에 씁니다.
        ioStream.Write(outBuffer, 0, len);
        // 버퍼에 남아있는 데이터를 즉시 스트림으로 밀어 넣습니다.
        ioStream.Flush();

        // 전송된 총 바이트 수 (문자열 바이트 + 길이 2바이트)를 반환합니다.
        return outBuffer.Length + 2;
    }
}</pre>



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



<h5 class="wp-block-heading">강력한 프로그래밍: Named Pipe 클라이언트/서버 설정</h5>



<p>이 예제에서 클라이언트와 서버 프로세스는 <strong>동일한 컴퓨터에서 실행되도록</strong> 설계되었습니다. </p>



<p>그래서 <strong><code>NamedPipeClientStream</code> </strong>객체에 제공된 서버 이름이 <strong>&#8220;.&#8221;</strong> 입니다.</p>



<p>만약 클라이언트와 서버 프로세스가 <strong>다른 컴퓨터에 있다면</strong>, <strong>&#8220;.&#8221;</strong> 은 서버 프로세스를 실행하는 컴퓨터의 <strong>네트워크 이름</strong>으로 대체되어야 합니다.</p>



<div style="height:100px" 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;" />추가 정리</h3>



<p><strong>명명된 파이프에 대한 동작 개념</strong></p>



<p>명명된 파이프는 <strong>파일 시스템의 &#8216;이름&#8217;이라는 개념을 빌려와</strong> 프로세스들이 서로를 찾아 연결할 수 있게 해주지만, 실제 데이터 교환은 <strong>메모리를 통해 이루어지는 IPC(프로세스 간 통신) 메커니즘</strong>입니다.</p>



<p>이러한 특성 덕분에 명명된 파이프는 로컬 컴퓨터 내의 여러 프로세스 간 통신에 매우 효율적이며, Windows에서는 네트워크를 통해 다른 컴퓨터의 파이프에도 연결할 수 있어 더욱 유연하게 사용할 수 있습니다.</p>



<p></p>
<p>The post <a href="https://lycos7560.com/c/net%ec%9d%98-%ed%8c%8c%ec%9d%b4%ed%94%84-%ec%9e%91%ec%97%85-pipe-operations-in-net/40115/">.NET의 파이프 작업 (Pipe Operations in .NET)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/net%ec%9d%98-%ed%8c%8c%ec%9d%b4%ed%94%84-%ec%9e%91%ec%97%85-pipe-operations-in-net/40115/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
