[C#/ASP.NET MVC/.NETCORE] Microsoft.AspNetCore.HeaderPropagation 누겟 설치하기
■ Microsoft.AspNetCore.HeaderPropagation 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ Microsoft.AspNetCore.HeaderPropagation 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ IHttpClientFactory 인터페이스를 콘솔 앱에서 사용하는 방법을 보여준다. ▶ ITestService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System.Threading.Tasks; namespace TestProject { /// <summary> /// 테스트 서비스 인터페이스 /// </summary> public interface ITestService { //////////////////////////////////////////////////////////////////////////////////////////////////// Method #region 페이지 구하기 - GetPage() /// <summary> /// 페이지 구하기 /// </summary> /// <returns>페이지 태스크</returns> Task<string> GetPage(); #endregion } } |
▶ TestService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace TestProject { /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> /// <returns>처리 결과 태스크</returns> private static async Task<int> Main() { IHostBuilder builder = new HostBuilder() .ConfigureServices ( (hostContext, services) => { services.AddHttpClient(); services.AddTransient<ITestService, TestService>(); } ) .UseConsoleLifetime(); IHost host = builder.Build(); using(IServiceScope serviceScope = host.Services.CreateScope()) { IServiceProvider serviceProvider = serviceScope.ServiceProvider; try { ITestService service = serviceProvider.GetRequiredService<ITestService>(); string pageContent = await service.GetPage(); Console.WriteLine(pageContent.Substring(0, 500)); } catch(Exception exception) { ILogger<Program> logger = serviceProvider.GetRequiredService<ILogger<Program>>(); logger.LogError(exception, "An error occurred."); } } return 0; } #endregion } } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace TestProject { /// <summary> /// 테스트 서비스 /// </summary> public class TestService : ITestService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// HTTP 클라이언트 팩토리 /// </summary> private readonly IHttpClientFactory factory; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - TestService(factory) /// <summary> /// 생성자 /// </summary> /// <param name="factory">HTTP 클라이언트 팩토리</param> public TestService(IHttpClientFactory factory) { this.factory = factory; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 페이지 구하기 - GetPage() /// <summary> /// 페이지 구하기 /// </summary> /// <returns>페이지</returns> public async Task<string> GetPage() { HttpRequestMessage requestMessage = new HttpRequestMessage ( HttpMethod.Get, "https://www.bbc.co.uk/programmes/b006q2x0" ); HttpClient client = this.factory.CreateClient(); HttpResponseMessage responseMessage = await client.SendAsync(requestMessage); if(responseMessage.IsSuccessStatusCode) { byte[] byteArray = await responseMessage.Content.ReadAsByteArrayAsync(); return Encoding.UTF8.GetString(byteArray, 0, byteArray.Length); } else { return $"상태 코드 : {responseMessage.StatusCode}"; } } #endregion } } |
※ 패키지 설치 : Microsoft.Extensions.Hosting,
■ HttpClientBuilderExtensions 클래스의 ConfigurePrimaryHttpMessageHandler 확장 메소드를 사용해 HttpMessageHandler를 구성하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Net.Http; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("test") .ConfigurePrimaryHttpMessageHandler ( () => { return new HttpClientHandler() { AllowAutoRedirect = false, UseDefaultCredentials = true }; } ); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); endpoints.MapRazorPages(); } ); } #endregion } } |
■ HttpClientBuilderExtensions 클래스의 ConfigurePrimaryHttpMessageHandler 확장 메소드를 사용해 자동 :::no-loc(cookie)::: 처리를 방지하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Net.Http; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup7 { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup7(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("test") .ConfigurePrimaryHttpMessageHandler ( () => { return new SocketsHttpHandler() { UseCookies = false }; } ); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); endpoints.MapRazorPages(); } ); } #endregion } } |
■ HttpClientBuilderExtensions 클래스의 SetHandlerLifetime 확장 메소드를 사용해 HttpMessageHandler 인스턴스 수명을 설정하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("test") .SetHandlerLifetime(TimeSpan.FromMinutes(5)); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); endpoints.MapRazorPages(); } ); } #endregion } } |
– IHttpClientFactory에서 CreateClient가 호출될 때마다 새로운
■ PollyServiceCollectionExtensions 클래스의 AddPolicyRegistry 확장 메소드를 사용해 Polly 레지스트리에 정책을 추가하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Net.Http; using Polly; using Polly.Timeout; using Polly.Registry; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { AsyncTimeoutPolicy<HttpResponseMessage> regularTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage> ( TimeSpan.FromSeconds(10) ); AsyncTimeoutPolicy<HttpResponseMessage> longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage> ( TimeSpan.FromSeconds(30) ); IPolicyRegistry<string> policyRegistry = services.AddPolicyRegistry(); policyRegistry.Add("regular", regularTimeoutPolicy); policyRegistry.Add("long" , longTimeoutPolicy ); services.AddHttpClient("regularClient") .AddPolicyHandlerFromRegistry("regular"); services.AddHttpClient("longClient") .AddPolicyHandlerFromRegistry("long"); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); endpoints.MapRazorPages(); } ); } #endregion } } |
※ 설치 패키지 : Microsoft.Extensions.Http.Polly
■ PollyHttpClientBuilderExtensions 클래스의 AddTransientHttpErrorPolicy 확장 메소드를 사용해 Polly 정책을 중첩하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using Polly; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("test") // 첫번째 처리기는 재시도 정책을 추가하기 위해 AddTransientHttpErrorPolicy를 사용한다. // 실패한 요청은 최대 3번까지 다시 시도된다. .AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.RetryAsync(3)) // 두번째 호출 AddTransientHttpErrorPolicy는 회로 차단기 정책을 추가한다. // 5번의 시도가 순차적으로 실패하는 경우 추가적인 외부 요청은 30초 동안 차단된다. // 회로 차단기 정책은 상태를 저장한다. // 이 클라이언트를 통한 모든 호출은 동일한 회로 상태를 공유한다. .AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); endpoints.MapRazorPages(); } ); } #endregion } } |
※ 설치 패키지 : Microsoft.Extensions.Http.Polly
■ PollyHttpClientBuilderExtensions 클래스의 AddPolicyHandler 확장 메소드를 사용해 동적으로 정책을 선택하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Net.Http; using Polly; using Polly.Timeout; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { AsyncTimeoutPolicy<HttpResponseMessage> regularTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage> ( TimeSpan.FromSeconds(10) ); AsyncTimeoutPolicy<HttpResponseMessage> longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage> ( TimeSpan.FromSeconds(30) ); services.AddHttpClient("test") .AddPolicyHandler ( request => request.Method == HttpMethod.Get ? regularTimeoutPolicy : longTimeoutPolicy ); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); endpoints.MapRazorPages(); } ); } #endregion } } |
※ 설치 패키지 : Microsoft.Extensions.Http.Polly
■ PollyHttpClientBuilderExtensions 클래스의 AddTransientHttpErrorPolicy 확장 메소드를 사용해 일시적인 오류를 처리하는 방법을 보여준다. ▶ UnreliableEndpointCallerService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
using System.Net.Http; using System.Threading.Tasks; namespace TestProject { /// <summary> /// 신뢰할 수 없는 엔드포인트 호출자 서비스 /// </summary> public class UnreliableEndpointCallerService { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region HTTP 클라이언트 - Client /// <summary> /// HTTP 클라이언트 /// </summary> public HttpClient Client { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - UnreliableEndpointCallerService(client) /// <summary> /// 생성자 /// </summary> /// <param name="client">HTTP 클라이언트</param> public UnreliableEndpointCallerService(HttpClient client) { // ConfigureServices에서 이 유형이 지정된 클라이언트에 대한 등록을 기반으로 // 이 클라이언트는 Polly WaitAndRetry 핸들러를 사용한다. Client = client; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 데이터 구하기 (비동기) - GetDataAsync(requestURL) /// <summary> /// 데이터 구하기 (비동기) /// </summary> /// <param name="requestURL">요청 URL</param> /// <returns>문자열 태스크</returns> public async Task<string> GetDataAsync(string requestURL) { HttpResponseMessage httpResponseMessage = await Client.GetAsync(requestURL); return httpResponseMessage.IsSuccessStatusCode ? "Succeeded" : "Failed"; } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using Polly; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddHttpClient<UnreliableEndpointCallerService>() .AddTransientHttpErrorPolicy ( // 실패한 요청은 최대 3번까지 다시 시도되며 시도 간 600밀리초의 지연 간격을 둔다. policyBuilder => policyBuilder.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)) ); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); endpoints.MapRazorPages(); } ); } #endregion } } |
※ 설치 패키지 :
■ Microsoft.Extensions.Http.Polly 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ HttpClientBuilderExtensions 클래스의 AddHttpMessageHandler 확장 메소드를 사용해 위임 처리기를 만드는 방법을 보여준다. ▶ Handlers/ValidateHeaderHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace TestProject.Handlers { /// <summary> /// 헤더 검증 핸들러 /// </summary> public class ValidateHeaderHandler : DelegatingHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 송신하기 (비동기) - SendAsync(request, cancellationToken) /// <summary> /// 송신하기 (비동기) /// </summary> /// <param name="httpRequestMessage">HTTP 요청 메시지</param> /// <param name="cancellationToken">취소 토큰</param> /// <returns>HTTP 응답 메시지 태스크</returns> protected override async Task<HttpResponseMessage> SendAsync ( HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken ) { if(!httpRequestMessage.Headers.Contains("X-API-KEY")) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("You must supply an API key header called X-API-KEY") }; } return await base.SendAsync(httpRequestMessage, cancellationToken); } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using TestProject.Handlers; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddTransient<ValidateHeaderHandler>(); services.AddHttpClient ( "test", client => { // X-API-KEY가 필요한 "외부" 서비스라고 가정한다. client.BaseAddress = new Uri("https://localhost:5001/"); } ) .AddHttpMessageHandler<ValidateHeaderHandler>(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); } ); } #endregion } } |
※ 위임 처리기를 사용하는
■ HttpClientBuilderExtensions 클래스의 AddHttpMessageHandler 확장 메소드를 사용해 위임 처리기를 만들기 ▶ Handlers/SecureRequestHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace TestProject.Handlers { /// <summary> /// 보안 요청 핸들러 /// </summary> public class SecureRequestHandler : DelegatingHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 송신하기 (비동기) - SendAsync(request, cancellationToken) /// <summary> /// 송신하기 (비동기) /// </summary> /// <param name="httpRequestMessage">HTTP 요청 메시지</param> /// <param name="cancellationToken">취소 토큰</param> /// <returns>HTTP 응답 메시지 태스크</returns> protected override Task<HttpResponseMessage> SendAsync ( HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken ) { if(httpRequestMessage.RequestUri.Scheme == Uri.UriSchemeHttp) { UriBuilder uriBuilder = new UriBuilder(httpRequestMessage.RequestUri) { Scheme = Uri.UriSchemeHttps }; httpRequestMessage.RequestUri = uriBuilder.Uri; } return base.SendAsync(httpRequestMessage, cancellationToken); } #endregion } } |
▶ Handlers/RequestDataHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
using Microsoft.Extensions.Logging; using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace TestProject.Handlers { /// <summary> /// 데이터 요청 핸들러 /// </summary> public class RequestDataHandler : DelegatingHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 로그 기록기 /// </summary> private readonly ILogger<RequestDataHandler> logger; /// <summary> /// 요청 소스 헤더명 /// </summary> private const string RequestSourceHeaderName = "Request-Source"; /// <summary> /// 요청 소스 /// </summary> private const string RequestSource = "TestProject"; /// <summary> /// 요청 ID 헤더명 /// </summary> private const string RequestIDHeaderName = "Request-Identifier"; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - RequestDataHandler(logger) /// <summary> /// 생성자 /// </summary> /// <param name="logger">로그 기록기</param> public RequestDataHandler(ILogger<RequestDataHandler> logger) { this.logger = logger; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 송신하기 (비동기) - SendAsync(httpRequestMessage, cancellationToken) /// <summary> /// 송신하기 (비동기) /// </summary> /// <param name="httpRequestMessage">HTTP 요청 메시지</param> /// <param name="cancellationToken">취소 토큰</param> /// <returns>HTTP 응답 메시지 태스크</returns> protected override Task<HttpResponseMessage> SendAsync ( HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken ) { Guid identifierGUID = Guid.NewGuid(); this.logger.LogInformation("Starting request {Identifier}", identifierGUID); httpRequestMessage.Headers.Add(RequestSourceHeaderName, RequestSource ); httpRequestMessage.Headers.Add(RequestIDHeaderName , identifierGUID.ToString()); return base.SendAsync(httpRequestMessage, cancellationToken); } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TestProject.Handlers; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구성 - Configuration /// <summary> /// 구성 /// </summary> public IConfiguration Configuration { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - Startup(configuration) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> public Startup(IConfiguration configuration) { Configuration = configuration; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddTransient<SecureRequestHandler>(); services.AddTransient<RequestDataHandler>(); services.AddHttpClient("test") // 이 핸들러는 외부에 있으며 요청 중에 먼저 호출되고 응답 중에 마지막으로 호출된다. .AddHttpMessageHandler<SecureRequestHandler>() // 이 핸들러는 전송되는 요청에 가장 가까운 내부에 있다. .AddHttpMessageHandler<RequestDataHandler>(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapControllerRoute ( name : "default", pattern : "{controller=Home}/{action=Index}/{id?}" ); } ); } #endregion } } |
※ 위임 처리기를
■ DelegatingHandler 클래스를 사용해 헤더 검증 핸들러를 만드는 방법을 보여준다. ▶ ValidateHeaderHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace TestProject { /// <summary> /// 헤더 검증 핸들러 /// </summary> public class ValidateHeaderHandler : DelegatingHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 송신하기 (비동기) - SendAsync(request, cancellationToken) /// <summary> /// 송신하기 (비동기) /// </summary> /// <param name="httpRequestMessage">HTTP 요청 메시지</param> /// <param name="cancellationToken">취소 토큰</param> /// <returns>HTTP 응답 메시지 태스크</returns> protected override async Task<HttpResponseMessage> SendAsync ( HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken ) { if(!httpRequestMessage.Headers.Contains("X-API-KEY")) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent("You must supply an API key header called X-API-KEY") }; } return await base.SendAsync(httpRequestMessage, cancellationToken); } #endregion } } |
■ DelegatingHandler 클래스를 사용해 데이터 요청 핸들러를 만드는 방법을 보여준다. ▶ RequestDataHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace TestProject.Handlers { /// <summary> /// 데이터 요청 핸들러 /// </summary> public class RequestDataHandler : DelegatingHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 로그 기록기 /// </summary> private readonly ILogger<RequestDataHandler> logger; /// <summary> /// 요청 소스 헤더명 /// </summary> private const string RequestSourceHeaderName = "Request-Source"; /// <summary> /// 요청 소스 /// </summary> private const string RequestSource = "TestProject"; /// <summary> /// 요청 ID 헤더명 /// </summary> private const string RequestIDHeaderName = "Request-Identifier"; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - RequestDataHandler(logger) /// <summary> /// 생성자 /// </summary> /// <param name="logger">로그 기록기</param> public RequestDataHandler(ILogger<RequestDataHandler> logger) { this.logger = logger; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 송신하기 (비동기) - SendAsync(httpRequestMessage, cancellationToken) /// <summary> /// 송신하기 (비동기) /// </summary> /// <param name="httpRequestMessage">HTTP 요청 메시지</param> /// <param name="cancellationToken">취소 토큰</param> /// <returns>HTTP 응답 메시지 태스크</returns> protected override Task<HttpResponseMessage> SendAsync ( HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken ) { Guid identifierGUID = Guid.NewGuid(); this.logger.LogInformation("Starting request {Identifier}", identifierGUID); httpRequestMessage.Headers.Add(RequestSourceHeaderName, RequestSource ); httpRequestMessage.Headers.Add(RequestIDHeaderName , identifierGUID.ToString()); return base.SendAsync(httpRequestMessage, cancellationToken); } #endregion } } |
■ DelegatingHandler 클래스를 사용해 보안 요청 핸들러를 만드는 방법을 보여준다. ▶ SecureRequestHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace TestProject { /// <summary> /// 보안 요청 핸들러 /// </summary> public class SecureRequestHandler : DelegatingHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 송신하기 (비동기) - SendAsync(request, cancellationToken) /// <summary> /// 송신하기 (비동기) /// </summary> /// <param name="httpRequestMessage">HTTP 요청 메시지</param> /// <param name="cancellationToken">취소 토큰</param> /// <returns>HTTP 응답 메시지 태스크</returns> protected override Task<HttpResponseMessage> SendAsync ( HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken ) { if(httpRequestMessage.RequestUri.Scheme == Uri.UriSchemeHttp) { UriBuilder uriBuilder = new UriBuilder(httpRequestMessage.RequestUri) { Scheme = Uri.UriSchemeHttps }; httpRequestMessage.RequestUri = uriBuilder.Uri; } return base.SendAsync(httpRequestMessage, cancellationToken); } #endregion } } |
■ HttpServiceCollectionExtensions 클래스의 AddHttpContextAccessor 확장 메소드를 사용해 IHttpContextAccessor 서비스 디폴트 구현 기능을 추가하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using TestProject.Data; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddDbContext<DatabaseContext> ( options => options.UseInMemoryDatabase("TestDB") ); services.AddHttpContextAccessor(); services.AddHttpClient<TodoClient> ( (serviceProvider, httpClient) => { IHttpContextAccessor httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>(); HttpRequest httpRequest = httpContextAccessor.HttpContext.Request; httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme, httpRequest.Host, httpRequest.PathBase)); httpClient.Timeout = TimeSpan.FromSeconds(5); } ); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); } ); } #endregion } } |
TestProject.zip
■ HttpClientFactoryServiceCollectionExtensions 클래스의 AddHttpClient 확장 메소드를 사용해 HttpClient 관련 객체 의존성 주입을 사용하는 방법을 보여준다. ▶ TodoClient.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; using TestProject.Models; namespace TestProject { /// <summary> /// 할일 클라이언트 /// </summary> public class TodoClient { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// JSON 직렬화기 옵션 /// </summary> private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// HTTP 클라이언트 /// </summary> private readonly HttpClient httpClient; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - TodoClient(httpClient) /// <summary> /// 생성자 /// </summary> /// <param name="httpClient">HTTP 클라이언트</param> public TodoClient(HttpClient httpClient) { this.httpClient = httpClient; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Metohd ////////////////////////////////////////////////////////////////////////////////////////// Public #region 리스트 구하기 (비동기) - GetListAsync() /// <summary> /// 리스트 구하기 (비동기) /// </summary> /// <returns>할일 리스트 태스크</returns> public async Task<List<TodoModel>> GetListAsync() { using HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync("/api/todo"); httpResponseMessage.EnsureSuccessStatusCode(); using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync(); return await JsonSerializer.DeserializeAsync<List<TodoModel>>(stream, _jsonSerializerOptions); } #endregion #region 항목 구하기 (비동기) - GetItemAsync(id) /// <summary> /// 항목 구하기 (비동기) /// </summary> /// <param name="id">ID</param> /// <returns>할일 태스크</returns> public async Task<TodoModel> GetItemAsync(long id) { using HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync($"/api/todo/{id}"); if(httpResponseMessage.StatusCode == HttpStatusCode.NotFound) { return null; } httpResponseMessage.EnsureSuccessStatusCode(); using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync(); return await JsonSerializer.DeserializeAsync<TodoModel>(stream, _jsonSerializerOptions); } #endregion #region 항목 생성하기 (비동기) - CreateItemAsync(todo) /// <summary> /// 항목 생성하기 (비동기) /// </summary> /// <param name="todo">할일</param> /// <returns>태스크</returns> public async Task CreateItemAsync(TodoModel todo) { StringContent stringContent = new StringContent ( JsonSerializer.Serialize(todo, _jsonSerializerOptions), Encoding.UTF8, "application/json" ); using HttpResponseMessage httpResponseMessage = await this.httpClient.PostAsync ( "/api/todo", stringContent ); httpResponseMessage.EnsureSuccessStatusCode(); } #endregion #region 항목 저장하기 (비동기) - SaveItemAsync(todo) /// <summary> /// 항목 저장하기 (비동기) /// </summary> /// <param name="todo">할일</param> /// <returns>태스크</returns> public async Task SaveItemAsync(TodoModel todo) { StringContent stringContent = new StringContent ( JsonSerializer.Serialize(todo), Encoding.UTF8, "application/json" ); using HttpResponseMessage httpResponseMessage = await this.httpClient.PutAsync ( $"/api/todo/{todo.ID}", stringContent ); httpResponseMessage.EnsureSuccessStatusCode(); } #endregion #region 항목 삭제하기 (비동기) - DeleteItemAsync(id) /// <summary> /// 항목 삭제하기 (비동기) /// </summary> /// <param name="id">ID</param> /// <returns>태스크</returns> public async Task DeleteItemAsync(long id) { using HttpResponseMessage httpResponseMessage = await this.httpClient.DeleteAsync($"/api/todo/{id}"); httpResponseMessage.EnsureSuccessStatusCode(); } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using TestProject.Data; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddDbContext<DatabaseContext> ( options => options.UseInMemoryDatabase("TestDB") ); services.AddHttpContextAccessor(); services.AddHttpClient<TodoClient> ( (serviceProvider, httpClient) => { HttpRequest httpRequest = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext.Request; httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme, httpRequest.Host, httpRequest.PathBase)); httpClient.Timeout = TimeSpan.FromSeconds(5); } ); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); } ); } #endregion } } |
▶
■ HttpClient 클래스를 사용해 GET/POST/PUT/DELETE를 요청하는 방법을 보여준다. ▶ Controllers/TodoController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; using TestProject.Data; using TestProject.Models; namespace TestProject.Controllers { /// <summary> /// 할일 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] public class TodoController : ControllerBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 데이터베이스 컨텍스트 /// </summary> private readonly DatabaseContext context; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - TodoController(context) /// <summary> /// 생성자 /// </summary> /// <param name="context">데이터베이스 컨텍스트</param> public TodoController(DatabaseContext context) { this.context = context; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region GET 처리하기 - Get() /// <summary> /// GET 처리하기 /// </summary> /// <returns>할일 리스트 태스크</returns> [HttpGet] public async Task<List<TodoModel>> Get() { return await this.context.Todo.AsNoTracking().ToListAsync(); } #endregion #region GET 처리하기 - Get(id) /// <summary> /// GET 처리하기 /// </summary> /// <param name="id">ID</param> /// <returns>할일 액션 결과 태스크</returns> [HttpGet("{id}")] public async Task<ActionResult<TodoModel>> Get(long id) { TodoModel todo = await this.context.Todo.FindAsync(id); if(todo == null) { return NotFound(); } return todo; } #endregion #region POST 처리하기 - Post(todo) /// <summary> /// POST 처리하기 /// </summary> /// <param name="todo">할일</param> /// <returns>액션 결과 태스크</returns> [HttpPost] public async Task<IActionResult> Post(TodoModel todo) { this.context.Todo.Add(todo); await this.context.SaveChangesAsync(); return CreatedAtAction(nameof(Get), new { id = todo.ID, todo }); } #endregion #region PUT 처리하기 - Put(id, todo) /// <summary> /// PUT 처리하기 /// </summary> /// <param name="id">ID</param> /// <param name="todo">할일</param> /// <returns>액션 결과 태스크</returns> [HttpPut("{id}")] public async Task<IActionResult> Put(long id, TodoModel todo) { if(todo.ID != id) { return BadRequest(); } this.context.Update(todo); await this.context.SaveChangesAsync(); return NoContent(); } #endregion #region DELETE 처리하기 - Delete(id) /// <summary> /// DELETE 처리하기 /// </summary> /// <param name="id">ID</param> /// <returns>액션 결과 태스크</returns> [HttpDelete("{id}")] public async Task<IActionResult> Delete(long id) { TodoModel todo = await this.context.Todo.FindAsync(id); if(todo == null) { return NotFound(); } this.context.Todo.Remove(todo); await this.context.SaveChangesAsync(); return NoContent(); } #endregion } } |
▶ TodoClient.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; using TestProject.Models; namespace TestProject { /// <summary> /// 할일 클라이언트 /// </summary> public class TodoClient { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// JSON 직렬화기 옵션 /// </summary> private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// HTTP 클라이언트 /// </summary> private readonly HttpClient httpClient; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - TodoClient(httpClient) /// <summary> /// 생성자 /// </summary> /// <param name="httpClient">HTTP 클라이언트</param> public TodoClient(HttpClient httpClient) { this.httpClient = httpClient; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Metohd ////////////////////////////////////////////////////////////////////////////////////////// Public #region 리스트 구하기 (비동기) - GetListAsync() /// <summary> /// 리스트 구하기 (비동기) /// </summary> /// <returns>할일 리스트 태스크</returns> public async Task<List<TodoModel>> GetListAsync() { using HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync("/api/todo"); httpResponseMessage.EnsureSuccessStatusCode(); using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync(); return await JsonSerializer.DeserializeAsync<List<TodoModel>>(stream, _jsonSerializerOptions); } #endregion #region 항목 구하기 (비동기) - GetItemAsync(id) /// <summary> /// 항목 구하기 (비동기) /// </summary> /// <param name="id">ID</param> /// <returns>할일 태스크</returns> public async Task<TodoModel> GetItemAsync(long id) { using HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync($"/api/todo/{id}"); if(httpResponseMessage.StatusCode == HttpStatusCode.NotFound) { return null; } httpResponseMessage.EnsureSuccessStatusCode(); using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync(); return await JsonSerializer.DeserializeAsync<TodoModel>(stream, _jsonSerializerOptions); } #endregion #region 항목 생성하기 (비동기) - CreateItemAsync(todo) /// <summary> /// 항목 생성하기 (비동기) /// </summary> /// <param name="todo">할일</param> /// <returns>태스크</returns> public async Task CreateItemAsync(TodoModel todo) { StringContent stringContent = new StringContent ( JsonSerializer.Serialize(todo, _jsonSerializerOptions), Encoding.UTF8, "application/json" ); using HttpResponseMessage httpResponseMessage = await this.httpClient.PostAsync ( "/api/todo", stringContent ); httpResponseMessage.EnsureSuccessStatusCode(); } #endregion #region 항목 저장하기 (비동기) - SaveItemAsync(todo) /// <summary> /// 항목 저장하기 (비동기) /// </summary> /// <param name="todo">할일</param> /// <returns>태스크</returns> public async Task SaveItemAsync(TodoModel todo) { StringContent stringContent = new StringContent ( JsonSerializer.Serialize(todo), Encoding.UTF8, "application/json" ); using HttpResponseMessage httpResponseMessage = await this.httpClient.PutAsync ( $"/api/todo/{todo.ID}", stringContent ); httpResponseMessage.EnsureSuccessStatusCode(); } #endregion #region 항목 삭제하기 (비동기) - DeleteItemAsync(id) /// <summary> /// 항목 삭제하기 (비동기) /// </summary> /// <param name="id">ID</param> /// <returns>태스크</returns> public async Task DeleteItemAsync(long id) { using HttpResponseMessage httpResponseMessage = await this.httpClient.DeleteAsync($"/api/todo/{id}"); httpResponseMessage.EnsureSuccessStatusCode(); } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using TestProject.Data; namespace TestProject { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddDbContext<DatabaseContext> ( options => options.UseInMemoryDatabase("TestDB") ); services.AddHttpContextAccessor(); services.AddHttpClient<TodoClient> ( (serviceProvider, httpClient) => { HttpRequest httpRequest = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext.Request; httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme, httpRequest.Host, httpRequest.PathBase)); httpClient.Timeout = TimeSpan.FromSeconds(5); } ); services.AddControllers(); services.AddRazorPages(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints ( endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); } ); } #endregion } } |
▶ Pages/Index.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
@page @using TestProject.Models @model IndexModel @{ ViewData["Title"] = "Home"; } <div class="row justify-content-center"> <div class="col-md-6"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <form asp-page-handler="Create" method="post"> <div class="input-group input-group-sm"> <input name="name" class="form-control form-control-sm" placeholder="Something I need to do"> <div class="input-group-append"> <button class="btn btn-primary">Add to List</button> </div> </div> </form> </div> </div> @if(Model.IncompleteTodoList.Any()) { <hr> <div class="row justify-content-center"> <div class="col-md-6"> <ul class="list-group mb-2"> @foreach(TodoModel todo in Model.IncompleteTodoList) { <a asp-page="/Item" asp-route-id="@todo.ID" class="list-group-item list-group-item-action"> @todo.Name </a> } </ul> </div> </div> } @if(Model.CompleteTodoList.Any()) { <div class="row justify-content-center"> <div class="col-md-6"> <p class="text-center"> <small>@Model.CompleteTodoList.Count completed</small> </p> <ul class="list-group"> @foreach(TodoModel todo in Model.CompleteTodoList) { <a asp-page="/Item" asp-route-id="@todo.ID" class="list-group-item list-group-item-action text-muted"> @todo.Name </a> } </ul> </div> </div> } @section Styles { <style>.list-group-item.text-muted{text-decoration:line-through}</style> } |
▶ Pages/Index.cshtml.cs
■ HttpClientFactoryServiceCollectionExtensions 클래스의 AddHttpClient 확장 메소드를 사용해 HttpClient 관련 객체 의존성 주입을 사용하는 방법을 보여준다. ▶ APIService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
using System; using System.Net.Http; using System.Threading.Tasks; using IdentityModel.Client; namespace TestClient { /// <summary> /// API 서비스 /// </summary> public class APIService { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region HTTP 클라이언트 - Client /// <summary> /// HTTP 클라이언트 /// </summary> public HttpClient Client { get; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - APIService(client) /// <summary> /// 생성자 /// </summary> /// <param name="client">HTTP 클라이언트</param> public APIService(HttpClient client) { client.BaseAddress = new Uri("https://localhost:44310/"); client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); Client = client; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 비밀 구하기 - GetSecret(accessToken) /// <summary> /// 비밀 구하기 /// </summary> /// <param name="accessToken">액세스 토큰</param> /// <returns>문자열 태스크</returns> public async Task<string> GetSecret(string accessToken) { Client.SetBearerToken(accessToken); var response = await Client.GetAsync("secret"); response.EnsureSuccessStatusCode(); HttpResponseMessage apiResponse = await Client.GetAsync("https://localhost:44310/secret"); string content = await apiResponse.Content.ReadAsStringAsync(); return content; } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace TestClient { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication ( options => { options.DefaultScheme = "Cookie"; options.DefaultChallengeScheme = "oidc"; } ) .AddCookie("Cookie") .AddOpenIdConnect ( "oidc", options => { options.Authority = "https://localhost:44300/"; options.ClientId = "CLIENTID0003"; options.ClientSecret = "CLIENTSECRET0003"; options.SaveTokens = true; options.ResponseType = "code"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("API1" ); } ); services.AddHttpClient<APIService>(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶
■ IHttpClientFactory 인터페이스의 CreateClient 메소드를 사용해 명명된 HttpClient 객체를 생성하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; namespace TestClient { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication ( options => { options.DefaultScheme = "Cookie"; options.DefaultChallengeScheme = "oidc"; } ) .AddCookie("Cookie") .AddOpenIdConnect ( "oidc", options => { options.Authority = "https://localhost:44300/"; options.ClientId = "CLIENTID0003"; options.ClientSecret = "CLIENTSECRET0003"; options.SaveTokens = true; options.ResponseType = "code"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("API1" ); } ); services.AddHttpClient ( "TestAPIServer1", options => { options.BaseAddress = new Uri("https://localhost:44310/"); options.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); } ); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶ Controllers/HomeController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.IdentityModel.Tokens.Jwt; using System.Net.Http; using System.Threading.Tasks; using IdentityModel.Client; namespace TestClient.Controllers { /// <summary> /// 홈 컨트롤러 /// </summary> public class HomeController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// HTTP 클라이언트 팩토리 /// </summary> private readonly IHttpClientFactory factory; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - HomeController(factory) /// <summary> /// 생성자 /// </summary> /// <param name="factory">HTTP 클라이언트 팩토리</param> public HomeController(IHttpClientFactory factory) { this.factory = factory; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이지 처리하기 - Index() /// <summary> /// 인덱스 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> public IActionResult Index() { return View(); } #endregion #region 비밀 페이지 처리하기 - Secret() /// <summary> /// 비밀 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> [Authorize] public async Task<IActionResult> Secret() { string accessToken = await HttpContext.GetTokenAsync("access_token"); JwtSecurityToken accessJwtSecurityToken = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); string secret = await GetSecret(accessToken); ViewData["Secret"] = secret; return View(); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 비밀 구하기 - GetSecret(accessToken) /// <summary> /// 비밀 구하기 /// </summary> /// <param name="accessToken">액세스 토큰</param> /// <returns>비밀</returns> public async Task<string> GetSecret(string accessToken) { HttpClient apiClient = this.factory.CreateClient("TestAPIServer1"); apiClient.SetBearerToken(accessToken); HttpResponseMessage apiResponse = await apiClient.GetAsync("secret"); string content = await apiResponse.Content.ReadAsStringAsync(); return content; } #endregion } } |
■ IHttpClientFactory 인터페이스의 CreateClient 메소드를 사용해 HttpClient 객체를 생성하는 방법을 보여준다. ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace TestClient { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication ( options => { options.DefaultScheme = "Cookie"; options.DefaultChallengeScheme = "oidc"; } ) .AddCookie("Cookie") .AddOpenIdConnect ( "oidc", options => { options.Authority = "https://localhost:44300/"; options.ClientId = "CLIENTID0003"; options.ClientSecret = "CLIENTSECRET0003"; options.SaveTokens = true; options.ResponseType = "code"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("API1" ); } ); services.AddHttpClient(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶ Controllers/HomeController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.IdentityModel.Tokens.Jwt; using System.Net.Http; using System.Threading.Tasks; using IdentityModel.Client; namespace TestClient.Controllers { /// <summary> /// 홈 컨트롤러 /// </summary> public class HomeController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// HTTP 클라이언트 팩토리 /// </summary> private readonly IHttpClientFactory factory; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - HomeController(factory) /// <summary> /// 생성자 /// </summary> /// <param name="factory">HTTP 클라이언트 팩토리</param> public HomeController(IHttpClientFactory factory) { this.factory = factory; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이지 처리하기 - Index() /// <summary> /// 인덱스 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> public IActionResult Index() { return View(); } #endregion #region 비밀 페이지 처리하기 - Secret() /// <summary> /// 비밀 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> [Authorize] public async Task<IActionResult> Secret() { string accessToken = await HttpContext.GetTokenAsync("access_token"); JwtSecurityToken accessJwtSecurityToken = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); string secret = await GetSecret(accessToken); ViewData["Secret"] = secret; return View(); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 비밀 구하기 - GetSecret(accessToken) /// <summary> /// 비밀 구하기 /// </summary> /// <param name="accessToken">액세스 토큰</param> /// <returns>비밀</returns> public async Task<string> GetSecret(string accessToken) { HttpClient apiClient = this.factory.CreateClient(); apiClient.SetBearerToken(accessToken); HttpResponseMessage apiResponse = await apiClient.GetAsync("https://localhost:44310/secret"); string content = await apiResponse.Content.ReadAsStringAsync(); return content; } #endregion } } |
■ IdentityServer4 API를 호출하는 방법을 보여준다. [TestIdentityServer 프로젝트] ▶ Properties/launchSetting.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:50000", "sslPort" : 44300 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestAuthorizationServer" : { "commandName" : "Project", "launchBrowser" : true, "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ Configuration.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
using System.Collections.Generic; using IdentityModel; using IdentityServer4; using IdentityServer4.Models; namespace TestIdentityServer { /// <summary> /// 구성 /// </summary> public static class Configuration { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 신원 리소스 리스트 구하기 - GetIdentityResourceList() /// <summary> /// 신원 리소스 리스트 구하기 /// </summary> /// <returns>신원 리소스 리스트</returns> public static List<IdentityResource> GetIdentityResourceList() { return new List<IdentityResource>() { new IdentityResources.OpenId(), new IdentityResources.Profile() }; } #endregion #region API 범위 리스트 구하기 - GetAPIScopeList() /// <summary> /// API 범위 리스트 구하기 /// </summary> /// <returns>API 범위 리스트</returns> public static List<ApiScope> GetAPIScopeList() { return new List<ApiScope> { new ApiScope("API1", "API 1"), new ApiScope("API2", "API 2") }; } #endregion #region 클라이언트 리스트 구하기 - GetClientList() /// <summary> /// 클라이언트 리스트 구하기 /// </summary> /// <returns>클라이언트 리스트</returns> public static List<Client> GetClientList() { return new List<Client> { new Client { AllowedGrantTypes = GrantTypes.ClientCredentials, ClientId = "CLIENTID0002", ClientSecrets = { new Secret("CLIENTSECRET0002".ToSha256()) }, AllowedScopes = new List<string> { "API1" } }, new Client { AllowedGrantTypes = GrantTypes.Code, ClientId = "CLIENTID0003", ClientSecrets = { new Secret("CLIENTSECRET0003".ToSha256()) }, RedirectUris = { "https://localhost:44330/signin-oidc" }, AllowedScopes = new List<string> { "API1", "API2", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile }, RequireConsent = false, } }; } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TestIdentityServer.Data; namespace TestIdentityServer { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddDbContext<DatabaseContext>(options => { options.UseInMemoryDatabase("MemoryDB"); }); services.AddIdentity<IdentityUser, IdentityRole> ( options => { options.Password.RequiredLength = 4; options.Password.RequireDigit = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; } ) .AddEntityFrameworkStores<DatabaseContext>() .AddDefaultTokenProviders(); services.ConfigureApplicationCookie ( options => { options.Cookie.Name = "IdentityServer.Cookie"; options.LoginPath = "/Auth/Login"; } ); services.AddIdentityServer() .AddAspNetIdentity<IdentityUser>() .AddInMemoryIdentityResources(Configuration.GetIdentityResourceList()) .AddInMemoryApiScopes(Configuration.GetAPIScopeList()) .AddInMemoryClients(Configuration.GetClientList()) .AddDeveloperSigningCredential(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseIdentityServer(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace TestIdentityServer { /// <summary> /// 프로그램 /// </summary> public class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">인자 배열</param> public static void Main(string[] argumentArray) { IHost host = CreateHostBuilder(argumentArray).Build(); using(IServiceScope scope = host.Services.CreateScope()) { UserManager<IdentityUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); IdentityUser user = new IdentityUser("alice"); userManager.CreateAsync(user, "alice").GetAwaiter(); } host.Run(); } #endregion #region 호스트 빌더 생성하기 - CreateHostBuilder(argumentArray) /// <summary> /// 호스트 빌더 생성하기 /// </summary> /// <param name="argumentArray">인자 배열</param> /// <returns>호스트 빌더</returns> public static IHostBuilder CreateHostBuilder(string[] argumentArray) => Host.CreateDefaultBuilder(argumentArray) .ConfigureWebHostDefaults ( builder => { builder.UseStartup<Startup>(); } ); #endregion } } |
[TestAPIServer1 프로젝트]
■ IdentityServer4 클레임/범위를 추가/삭제하는 방법을 보여준다. [TestIdentityServer 프로젝트] ▶ Configuration.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
using System.Collections.Generic; using IdentityModel; using IdentityServer4; using IdentityServer4.Models; namespace TestIdentityServer { /// <summary> /// 구성 /// </summary> public static class Configuration { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 신원 리소스 리스트 구하기 - GetIdentityResourceList() /// <summary> /// 신원 리소스 리스트 구하기 /// </summary> /// <returns>신원 리소스 리스트</returns> public static List<IdentityResource> GetIdentityResourceList() { return new List<IdentityResource>() { new IdentityResources.OpenId(), //new IdentityResources.Profile(), new IdentityResource { Name = "user.scope", UserClaims = { "user.claim1" } } }; } #endregion #region API 범위 리스트 구하기 - GetAPIScopeList() /// <summary> /// API 범위 리스트 구하기 /// </summary> /// <returns>API 범위 리스트</returns> public static List<ApiScope> GetAPIScopeList() { return new List<ApiScope> { new ApiScope("API1", "API 1", new string[] { "user.claim2" }), new ApiScope("API2", "API 2") }; } #endregion #region 클라이언트 리스트 구하기 - GetClientList() /// <summary> /// 클라이언트 리스트 구하기 /// </summary> /// <returns>클라이언트 리스트</returns> public static List<Client> GetClientList() { return new List<Client> { new Client { AllowedGrantTypes = GrantTypes.ClientCredentials, ClientId = "CLIENTID0002", ClientSecrets = { new Secret("CLIENTSECRET0002".ToSha256()) }, AllowedScopes = new List<string> { "API1" } }, new Client { AllowedGrantTypes = GrantTypes.Code, ClientId = "CLIENTID0003", ClientSecrets = { new Secret("CLIENTSECRET0003".ToSha256()) }, RedirectUris = { "https://localhost:44330/signin-oidc" }, AllowedScopes = new List<string> { "API1", "API2", IdentityServerConstants.StandardScopes.OpenId, //IdentityServerConstants.StandardScopes.Profile, "user.scope" }, RequireConsent = false, // ID 토큰에 사용자 클레임을 포함하지 않는다. AlwaysIncludeUserClaimsInIdToken = false } }; } #endregion } } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Security.Claims; namespace TestIdentityServer { /// <summary> /// 프로그램 /// </summary> public class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">인자 배열</param> public static void Main(string[] argumentArray) { IHost host = CreateHostBuilder(argumentArray).Build(); using(IServiceScope scope = host.Services.CreateScope()) { UserManager<IdentityUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); IdentityUser user = new IdentityUser("alice"); userManager.CreateAsync(user, "alice").GetAwaiter(); userManager.AddClaimAsync(user, new Claim("user.claim1", "VALUE0001")).GetAwaiter(); userManager.AddClaimAsync(user, new Claim("user.claim2", "VALUE0002")).GetAwaiter(); // 액세스 토큰 추가용 } host.Run(); } #endregion #region 호스트 빌더 생성하기 - CreateHostBuilder(argumentArray) /// <summary> /// 호스트 빌더 생성하기 /// </summary> /// <param name="argumentArray">인자 배열</param> /// <returns>호스트 빌더</returns> public static IHostBuilder CreateHostBuilder(string[] argumentArray) => Host.CreateDefaultBuilder(argumentArray) .ConfigureWebHostDefaults ( builder => { builder.UseStartup<Startup>(); } ); #endregion } } |
[TestClient 프로젝트] ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace TestClient { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication ( options => { options.DefaultScheme = "Cookie"; options.DefaultChallengeScheme = "oidc"; } ) .AddCookie("Cookie") .AddOpenIdConnect ( "oidc", options => { options.Authority = "https://localhost:44300/"; options.ClientId = "CLIENTID0003"; options.ClientSecret = "CLIENTSECRET0003"; options.SaveTokens = true; options.ResponseType = "code"; // 사용자 정보 엔드포인트에서 클레임을 구한다. // ID 토큰의 크기가 적어지나 쿠키에 클레임을 로드하기 위해 추가로 통신한다. options.GetClaimsFromUserInfoEndpoint = true; // 사용자 정의 클레임을 매핑한다. options.ClaimActions.MapUniqueJsonKey("user.claim1", "user.claim1"); // 클레임을 삭제한다. options.ClaimActions.DeleteClaim("s_hash"); // 범위를 지우고 추가한다. options.Scope.Clear(); options.Scope.Add("openid" ); options.Scope.Add("user.scope"); } ); services.AddHttpClient(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
TestSolution.zip
■ IdentityServer4 커스텀 클레임을 액세스 토큰에 추가하는 방법을 보여준다. [TestIdentityServer 프로젝트] ▶ Configuration.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
using System.Collections.Generic; using IdentityModel; using IdentityServer4; using IdentityServer4.Models; namespace TestIdentityServer { /// <summary> /// 구성 /// </summary> public static class Configuration { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 신원 리소스 리스트 구하기 - GetIdentityResourceList() /// <summary> /// 신원 리소스 리스트 구하기 /// </summary> /// <returns>신원 리소스 리스트</returns> public static List<IdentityResource> GetIdentityResourceList() { return new List<IdentityResource>() { new IdentityResources.OpenId(), new IdentityResources.Profile() }; } #endregion #region API 범위 리스트 구하기 - GetAPIScopeList() /// <summary> /// API 범위 리스트 구하기 /// </summary> /// <returns>API 범위 리스트</returns> public static List<ApiScope> GetAPIScopeList() { return new List<ApiScope> { new ApiScope("API1", "API 1", new string[] { "user.claim" }), new ApiScope("API2", "API 2") }; } #endregion #region 클라이언트 리스트 구하기 - GetClientList() /// <summary> /// 클라이언트 리스트 구하기 /// </summary> /// <returns>클라이언트 리스트</returns> public static List<Client> GetClientList() { return new List<Client> { new Client { AllowedGrantTypes = GrantTypes.ClientCredentials, ClientId = "CLIENTID0002", ClientSecrets = { new Secret("CLIENTSECRET0002".ToSha256()) }, AllowedScopes = new List<string> { "API1" } }, new Client { AllowedGrantTypes = GrantTypes.Code, ClientId = "CLIENTID0003", ClientSecrets = { new Secret("CLIENTSECRET0003".ToSha256()) }, RedirectUris = { "https://localhost:44330/signin-oidc" }, AllowedScopes = new List<string> { "API1", "API2", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile }, RequireConsent = false, } }; } #endregion } } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Security.Claims; namespace TestIdentityServer { /// <summary> /// 프로그램 /// </summary> public class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">인자 배열</param> public static void Main(string[] argumentArray) { IHost host = CreateHostBuilder(argumentArray).Build(); using(IServiceScope scope = host.Services.CreateScope()) { UserManager<IdentityUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); IdentityUser user = new IdentityUser("alice"); userManager.CreateAsync(user, "alice").GetAwaiter(); userManager.AddClaimAsync(user, new Claim("user.claim", "VALUE0001")).GetAwaiter(); } host.Run(); } #endregion #region 호스트 빌더 생성하기 - CreateHostBuilder(argumentArray) /// <summary> /// 호스트 빌더 생성하기 /// </summary> /// <param name="argumentArray">인자 배열</param> /// <returns>호스트 빌더</returns> public static IHostBuilder CreateHostBuilder(string[] argumentArray) => Host.CreateDefaultBuilder(argumentArray) .ConfigureWebHostDefaults ( builder => { builder.UseStartup<Startup>(); } ); #endregion } } |
[TestClient 프로젝트] ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace TestClient { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication ( options => { options.DefaultScheme = "Cookie"; options.DefaultChallengeScheme = "oidc"; } ) .AddCookie("Cookie") .AddOpenIdConnect ( "oidc", options => { options.Authority = "https://localhost:44300/"; options.ClientId = "CLIENTID0003"; options.ClientSecret = "CLIENTSECRET0003"; options.SaveTokens = true; options.ResponseType = "code"; options.Scope.Add("openid" ); options.Scope.Add("profile"); options.Scope.Add("API1" ); options.Scope.Add("API2" ); } ); services.AddHttpClient(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
■ IdentityServer4 커스텀 클레임을 ID 토큰에서 제외하는 방법을 보여준다. [TestIdentityServer 프로젝트] ▶ Configuration.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
using System.Collections.Generic; using IdentityModel; using IdentityServer4; using IdentityServer4.Models; namespace TestIdentityServer { /// <summary> /// 구성 /// </summary> public static class Configuration { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 신원 리소스 리스트 구하기 - GetIdentityResourceList() /// <summary> /// 신원 리소스 리스트 구하기 /// </summary> /// <returns>신원 리소스 리스트</returns> public static List<IdentityResource> GetIdentityResourceList() { return new List<IdentityResource>() { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResource { Name = "user.scope", UserClaims = { "user.claim" } } }; } #endregion #region API 범위 리스트 구하기 - GetAPIScopeList() /// <summary> /// API 범위 리스트 구하기 /// </summary> /// <returns>API 범위 리스트</returns> public static List<ApiScope> GetAPIScopeList() { return new List<ApiScope> { new ApiScope("API1", "API 1"), new ApiScope("API2", "API 2") }; } #endregion #region 클라이언트 리스트 구하기 - GetClientList() /// <summary> /// 클라이언트 리스트 구하기 /// </summary> /// <returns>클라이언트 리스트</returns> public static List<Client> GetClientList() { return new List<Client> { new Client { AllowedGrantTypes = GrantTypes.ClientCredentials, ClientId = "CLIENTID0002", ClientSecrets = { new Secret("CLIENTSECRET0002".ToSha256()) }, AllowedScopes = new List<string> { "API1" } }, new Client { AllowedGrantTypes = GrantTypes.Code, ClientId = "CLIENTID0003", ClientSecrets = { new Secret("CLIENTSECRET0003".ToSha256()) }, RedirectUris = { "https://localhost:44330/signin-oidc" }, AllowedScopes = new List<string> { "API1", "API2", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "user.scope" }, RequireConsent = false, // ID 토큰에 사용자 클레임을 포함하지 않는다. AlwaysIncludeUserClaimsInIdToken = false } }; } #endregion } } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Security.Claims; namespace TestIdentityServer { /// <summary> /// 프로그램 /// </summary> public class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">인자 배열</param> public static void Main(string[] argumentArray) { IHost host = CreateHostBuilder(argumentArray).Build(); using(IServiceScope scope = host.Services.CreateScope()) { UserManager<IdentityUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); IdentityUser user = new IdentityUser("alice"); userManager.CreateAsync(user, "alice").GetAwaiter(); userManager.AddClaimAsync(user, new Claim("user.claim", "VALUE0001")).GetAwaiter(); } host.Run(); } #endregion #region 호스트 빌더 생성하기 - CreateHostBuilder(argumentArray) /// <summary> /// 호스트 빌더 생성하기 /// </summary> /// <param name="argumentArray">인자 배열</param> /// <returns>호스트 빌더</returns> public static IHostBuilder CreateHostBuilder(string[] argumentArray) => Host.CreateDefaultBuilder(argumentArray) .ConfigureWebHostDefaults ( builder => { builder.UseStartup<Startup>(); } ); #endregion } } |
[TestClient 프로젝트] ▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace TestClient { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication ( options => { options.DefaultScheme = "Cookie"; options.DefaultChallengeScheme = "oidc"; } ) .AddCookie("Cookie") .AddOpenIdConnect ( "oidc", options => { options.Authority = "https://localhost:44300/"; options.ClientId = "CLIENTID0003"; options.ClientSecret = "CLIENTSECRET0003"; options.SaveTokens = true; options.ResponseType = "code"; options.ClaimActions.MapUniqueJsonKey("user.claim", "user.claim"); // 사용자 정의 엔드포인트에서 클레임을 구한다. // ID 토큰의 크기가 적어지나 쿠키에 클레임을 로드하기 위해 추가로 통신한다. options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("openid" ); options.Scope.Add("profile" ); options.Scope.Add("API1" ); options.Scope.Add("API2" ); options.Scope.Add("user.scope"); } ); services.AddHttpClient(); services.AddControllersWithViews(); } #endregion #region 구성하기 - Configure(app, environment) /// <summary> /// 구성하기 /// </summary> /// <param name="app">애플리케이션 빌더</param> /// <param name="environment">웹 호스트 환경</param> public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) { if(environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |