[C#/WEB API] Microsoft.AspNet.WebApi.SelfHost 누겟 설치하기
■ Microsoft.AspNet.WebApi.SelfHost 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ Microsoft.AspNet.WebApi.SelfHost 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ JWT 토큰을 생성하는 방법을 보여준다. ▶ 예제 코드 (C#)
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 |
using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; #region JWT 토큰 생성하기 - GenerateJWTToken(claimEnumerable, secretKey, issuer, audience) /// <summary> /// JWT 토큰 생성하기 /// </summary> /// <param name="claimEnumerable">클레임 열거 가능형</param> /// <param name="secretKey">비밀 키</param> /// <param name="issuer">발행자</param> /// <param name="audience">청취자</param> /// <returns>JWT 토큰</returns> public string GenerateJWTToken(IEnumerable<Claim> claimEnumerable, string secretKey, string issuer, string audience) { byte[] keyByteArray = Encoding.UTF8.GetBytes(secretKey); SymmetricSecurityKey symmetricSecurityKey = new(keyByteArray); SigningCredentials credentials = new(symmetricSecurityKey, SecurityAlgorithms.HmacSha256); JwtSecurityToken token = new ( issuer : issuer, audience : audience, claims : claimEnumerable, expires : DateTime.Now.AddHours(1), signingCredentials : credentials ); string tokenString = new JwtSecurityTokenHandler().WriteToken(token); return tokenString; } #endregion |
■ JWT 토큰에서 클레임 주체를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
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; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; #region 클레임 주체 구하기 - GetClaimsPrincipal(token, secretKey) /// <summary> /// 클레임 주체 구하기 /// </summary> /// <param name="token">토큰</param> /// <param name="secretKey">비밀 키</param> /// <returns>클레임 주체</returns> public ClaimsPrincipal GetClaimsPrincipal(string token, string secretKey) { TokenValidationParameters parameters = new() { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)), }; JwtSecurityTokenHandler tokenHandler = new(); ClaimsPrincipal principal = tokenHandler.ValidateToken(token, parameters, out SecurityToken securityToken); if ( securityToken is not JwtSecurityToken jwtSecurityToken || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase) ) { throw new SecurityTokenException("Invalid token"); } return principal; } #endregion |
■ WEB API에서 JWT(Json Web Token) 인증을 사용하는 방법을 보여준다. [TestLibrary 프로젝트] ▶ User.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 |
namespace TestLibrary { /// <summary> /// 사용자 /// </summary> public class User { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 사용자명 - UserName /// <summary> /// 사용자명 /// </summary> public string UserName { get; set; } #endregion #region 패스워드 - Password /// <summary> /// 패스워드 /// </summary> public string Password { get; set; } #endregion #region 사용자 역할 - UserRole /// <summary> /// 사용자 역할 /// </summary> public string UserRole { get; set; } #endregion } } |
▶ WeatherForecast.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 |
using System; namespace TestLibrary { /// <summary> /// 기상 예보 /// </summary> public class WeatherForecast { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 일자 - Date /// <summary> /// 일자 /// </summary> public DateTime Date { get; set; } #endregion #region 섭씨 온도 - TemperatureC /// <summary> /// 섭씨 온도 /// </summary> public int TemperatureC { get; set; } #endregion #region 화씨 온도 - TemperatureF /// <summary> /// 화씨 온도 /// </summary> public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); #endregion #region 요약 - Summary /// <summary> /// 요약 /// </summary> public string Summary { get; set; } #endregion } } |
[TestServer 프로젝트] ▶ appsettings.json
■ 스트림을 사용해 대용량 파일을 업로드하는 방법을 보여준다. [TestServer 프로젝트] ▶ launchSettings.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 37 38 39 40 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:41483", "sslPort" : 44362 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "swagger", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestServer" : { "commandName" : "Project", "dotnetRunMessages" : "true", "launchBrowser" : true, "launchUrl" : "swagger", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ appsettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "Logging" : { "LogLevel" : { "Default" : "Information", "Microsoft" : "Warning", "Microsoft.Hosting.Lifetime" : "Information" } }, "AllowedHosts" : "*", "APIKey" : "A7A54AD3-91E1-4C3A-98C6-A17E39C9EA3D" } |
▶ web.config
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="2147483648" /> </requestFiltering> </security> </system.webServer> </configuration> |
※ IIS Express
■ 대용량 파일을 업로드하는 방법을 보여준다. (2GB 제한) [TestServer 프로젝트] ▶ launchSettings.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 37 38 39 40 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:41483", "sslPort" : 44362 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "swagger", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestServer" : { "commandName" : "Project", "dotnetRunMessages" : "true", "launchBrowser" : true, "launchUrl" : "swagger", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ appsettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "Logging" : { "LogLevel" : { "Default" : "Information", "Microsoft" : "Warning", "Microsoft.Hosting.Lifetime" : "Information" } }, "AllowedHosts" : "*", "APIKey" : "A7A54AD3-91E1-4C3A-98C6-A17E39C9EA3D" } |
▶ web.config
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="2147483648" /> </requestFiltering> </security> </system.webServer> </configuration> |
▶ APIKeyAttribute.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 |
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; namespace TestServer { /// <summary> /// API 키 어트리뷰트 /// </summary> [AttributeUsage(validOn: AttributeTargets.Class)] public class APIKeyAttribute : Attribute, IAsyncActionFilter { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// API 키명 /// </summary> public static readonly string API_KEY_NAME = "APIKey"; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 액션 실행시 처리하기 (비동기) - OnActionExecutionAsync(context, nextAction) /// <summary> /// 액션 실행시 처리하기 (비동기) /// </summary> /// <param name="context">액션 실행 컨텍스트</param> /// <param name="nextAction">다음 액션 실행 대리자</param> /// <returns>태스크</returns> public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate nextAction) { if(!context.HttpContext.Request.Headers.TryGetValue(API_KEY_NAME, out var sourceAPIKey)) { context.Result = new ContentResult() { StatusCode = 401, Content = "API Key was not provided." }; return; } IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>(); string targetAPIKey = configuration.GetValue<string>(API_KEY_NAME); if(!targetAPIKey.Equals(sourceAPIKey)) { context.Result = new ContentResult() { StatusCode = 401, Content = "API Key is not valid." }; return; } await nextAction(); } #endregion } } |
■ 대용량 데이터를 스트리밍 방식으로 전달받는 방법을 보여준다. (PostgreSQL 연동) [TestClient 프로젝트] ▶ 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 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 179 |
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using Newtonsoft.Json; namespace TestClient { /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> static void Main() { string url = "https://localhost:44352/api/data/inquirylist"; DataSearchConditionModel searchCondition = new DataSearchConditionModel(); searchCondition.MENU_ID = "A007A6D5-35E6-46A2-A3BD-1B84843BB76C"; searchCondition.MEMBER_GRADE = "99"; searchCondition.LIMIT_AGE = "99"; searchCondition.WRITE_DATE1 = new DateTime(2021, 7 , 1 ); searchCondition.WRITE_DATE2 = new DateTime(2021, 7 , 25); searchCondition.CREATE_TIME1 = new DateTime(1 , 1 , 1 ); searchCondition.CREATE_TIME2 = new DateTime(9999, 12, 31); int i = 0; var result = GetPOSTData1<DataModel>(url, searchCondition); foreach(DataModel photo in result) { Console.WriteLine("{0} {1} {2}", i++, photo.ID, photo.SUBJECT); } } #endregion #region POST 스트림 구하기 - POSTGetStream(serverURL, contentType, encoding, argument, timeOut) /// <summary> /// POST 스트림 구하기 /// </summary> /// <param name="serverURL">서버 URL</param> /// <param name="contentType">컨텐츠 타입</param> /// <param name="encoding">인코딩</param> /// <param name="argument">인자 문자열</param> /// <param name="timeOut">타임 아웃</param> /// <returns>POST 스트림</returns> private static Stream POSTGetStream(string serverURL, string contentType, Encoding encoding, string argument, int? timeOut = null) { byte[] argumentArray = encoding.GetBytes(argument); HttpWebRequest request = WebRequest.Create(serverURL) as HttpWebRequest; request.ProtocolVersion = HttpVersion.Version11; request.AllowAutoRedirect = true; request.AllowWriteStreamBuffering = true; request.Method = WebRequestMethods.Http.Post; request.ContentType = contentType; request.ContentLength = argumentArray.Length; if(timeOut.HasValue) { request.Timeout = timeOut.Value; } using(Stream requestStream = request.GetRequestStream()) { requestStream.Write(argumentArray, 0, argumentArray.Length); } HttpWebResponse response = request.GetResponse() as HttpWebResponse; Stream responseStream = response.GetResponseStream(); return responseStream; } #endregion #region POST 데이터 구하기 1 - GetPOSTData1<TResultItem>(url, source) /// <summary> /// POST 데이터 구하기 1 /// </summary> /// <typeparam name="TResultItem">결과 항목 타입</typeparam> /// <param name="url">URL</param> /// <param name="source">소스 객체</param> /// <returns>결과 항목 타입 열거 가능형</returns> private static IEnumerable<TResultItem> GetPOSTData1<TResultItem>(string url, object source) { string sourceJSON = JsonConvert.SerializeObject(source); HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, url); requestMessage.Content = new StringContent(sourceJSON, Encoding.UTF8, "application/json"); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpClient client = new HttpClient(); HttpResponseMessage responseMessage = client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).Result; responseMessage.EnsureSuccessStatusCode(); using(Stream stream = responseMessage.Content.ReadAsStreamAsync().Result) { using(StreamReader streamReader = new StreamReader(stream)) { JsonSerializer serializer = new JsonSerializer(); using(JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { while(jsonTextReader.Read()) { if(jsonTextReader.TokenType != JsonToken.StartArray && jsonTextReader.TokenType != JsonToken.EndArray) { yield return serializer.Deserialize<TResultItem>(jsonTextReader); } } } } } } #endregion #region POST 데이터 구하기 2 - GetPOSTData2<TResultItem>(url, source) /// <summary> /// POST 데이터 구하기 2 /// </summary> /// <typeparam name="TResultItem">결과 항목 타입</typeparam> /// <param name="url">URL</param> /// <param name="source">소스 객체</param> /// <returns>결과 항목 타입 열거 가능형</returns> private static IEnumerable<TResultItem> GetPOSTData2<TResultItem>(string url, object source) { string sourceJSON = JsonConvert.SerializeObject(source); using(Stream stream = POSTGetStream(url, "application/json; charset=utf-8", Encoding.UTF8, sourceJSON)) { using(StreamReader streamReader = new StreamReader(stream)) { using(JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { JsonSerializer serializer = new JsonSerializer(); while(jsonTextReader.Read()) { if(jsonTextReader.TokenType != JsonToken.StartArray && jsonTextReader.TokenType != JsonToken.EndArray) { yield return serializer.Deserialize<TResultItem>(jsonTextReader); } } } } } } #endregion } } |
[TestServer 프로젝트] ▶ launchSettings.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 37 38 39 40 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:35043", "sslPort" : 44352 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "swagger", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestServer" : { "commandName" : "Project", "dotnetRunMessages" : "true", "launchBrowser" : true, "launchUrl" : "swagger", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ appsettings.json
■ PostgreSQL 데이터베이스를 사용하는 방법을 보여준다. ▶ launchSettings.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 37 38 39 40 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:35043", "sslPort" : 44352 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "swagger", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestProject" : { "commandName" : "Project", "dotnetRunMessages" : "true", "launchBrowser" : true, "launchUrl" : "swagger", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ appsettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{ "Logging" : { "LogLevel" : { "Default" : "Information", "Microsoft" : "Warning", "Microsoft.Hosting.Lifetime" : "Information" } }, "AllowedHosts" : "*", "ConnectionStrings" : { "DefaultConnection" : "Server=localhost;Port=5432;Database=arca;User Id=admin;Password=1234;" } } |
▶ Models/CodeModel.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 |
namespace TestProject.Models { /// <summary> /// 코드 모델 /// </summary> public class CodeModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region ID - ID (NOT NULL) /// <summary> /// ID /// </summary> public string ID { get; set; } #endregion #region 그룹 ID - GROUP_ID (NOT NULL) /// <summary> /// 그룹 ID /// </summary> public string GROUP_ID { get; set; } #endregion #region 명칭 - NAME (NOT NULL) /// <summary> /// 명칭 /// </summary> public string NAME { get; set; } #endregion #region 값 - VALUE (NOT NULL) /// <summary> /// 값 /// </summary> public string VALUE { get; set; } #endregion #region 표시 순서 - DISPLAY_ORDER (NOT NULL) /// <summary> /// 표시 순서 /// </summary> public int DISPLAY_ORDER { get; set; } #endregion #region 상태 코드 - STATUS_CODE (NOT NULL) /// <summary> /// 상태 코드 /// </summary> /// <remarks> /// A : 활성 /// D : 삭제 /// </remarks> public string STATUS_CODE { get; set; } #endregion } } |
▶ Models/CodeKeyModel.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 |
namespace TestProject.Models { /// <summary> /// 코드 키 모델 /// </summary> public class CodeKeyModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region ID - ID /// <summary> /// ID /// </summary> public string ID { get; set; } #endregion #region 그룹 ID - GROUP_ID /// <summary> /// 그룹 ID /// </summary> public string GROUP_ID { get; set; } #endregion } } |
▶ CodeController.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 |
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Data; using System.Linq; using Npgsql; using Dapper; using TestProject.Models; namespace TestProject.Controllers { [ApiController] [Route("[controller]")] public class CodeController : ControllerBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region INQUIRY_LIST_SQL /// <summary> /// INQUIRY_LIST_SQL /// </summary> private static string INQUIRY_LIST_SQL = @" SELECT A.ID AS ID ,A.GROUP_ID AS GROUP_ID ,A.NAME AS NAME ,A.VALUE AS VALUE ,A.DISPLAY_ORDER AS DISPLAY_ORDER ,A.STATUS_CODE AS STATUS_CODE FROM COM_CODE A WHERE A.GROUP_ID = @GROUP_ID ORDER BY A.DISPLAY_ORDER ASC "; #endregion #region INQUIRY_ITEM /// <summary> /// INQUIRY_ITEM_SQL /// </summary> private static string INQUIRY_ITEM_SQL = @" SELECT A.ID AS ID ,A.GROUP_ID AS GROUP_ID ,A.NAME AS NAME ,A.VALUE AS VALUE ,A.DISPLAY_ORDER AS DISPLAY_ORDER ,A.STATUS_CODE AS STATUS_CODE FROM COM_CODE A WHERE A.ID = @ID "; #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 구성 /// </summary> private readonly IConfiguration configuration; /// <summary> /// 로거 /// </summary> private readonly ILogger<CodeController> logger; /// <summary> /// 연결 문자열 /// </summary> private readonly string connectionString; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - CodeController(configuration, logger) /// <summary> /// 생성자 /// </summary> /// <param name="configuration">구성</param> /// <param name="logger">로거</param> public CodeController(IConfiguration configuration, ILogger<CodeController> logger) { this.configuration = configuration; this.logger = logger; this.connectionString = configuration.GetConnectionString("DefaultConnection"); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 리스트 조회하기 - InquiryList(groupID) /// <summary> /// 리스트 조회하기 /// </summary> /// <param name="groupID">그룹 ID</param> /// <returns>리스트</returns> [HttpGet] [Route("InquiryList")] public IEnumerable<CodeModel> InquiryList(string groupID) { using(IDbConnection connection = CreateConnection()) { var result = connection.Query<CodeModel>(INQUIRY_LIST_SQL, new CodeKeyModel { GROUP_ID = groupID }); return result; } } #endregion #region 항목 조회하기 - InquiryItem(id) /// <summary> /// 항목 조회하기 /// </summary> /// <param name="id">ID</param> /// <returns></returns> [HttpGet] [Route("InquiryItem")] public CodeModel InquiryItem(string id) { using(IDbConnection connection = CreateConnection()) { var result = connection.Query<CodeModel>(INQUIRY_ITEM_SQL, new CodeKeyModel { ID = id }); return result.FirstOrDefault(); } } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 연결 생성하기 - CreateConnection() /// <summary> /// 연결 생성하기 /// </summary> /// <returns>연결</returns> private IDbConnection CreateConnection() { return new NpgsqlConnection(this.connectionString); } #endregion } } |
▶
■ 커스텀 미들웨어를 사용해 API 키를 인증하는 방법을 보여준다. [TestServer 프로젝트] ▶ Properteis/launchSettings.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 37 38 39 40 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:41483", "sslPort" : 44362 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "swagger", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestServer" : { "commandName" : "Project", "dotnetRunMessages" : "true", "launchBrowser" : true, "launchUrl" : "swagger", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ appsettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "Logging" : { "LogLevel" : { "Default" : "Information", "Microsoft" : "Warning", "Microsoft.Hosting.Lifetime" : "Information" } }, "AllowedHosts" : "*", "APIKey" : "A7A54AD3-91E1-4C3A-98C6-A17E39C9EA3D" } |
▶ APIKeyMiddleware.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 |
using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Threading.Tasks; namespace TestServer { /// <summary> /// API 키 미들웨어 /// </summary> public class APIKeyMiddleware { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// API 키명 /// </summary> private static readonly string API_KEY_NAME = "APIKey"; #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 다음 액션 /// </summary> private readonly RequestDelegate nextAction; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - APIKeyMiddleware(nextAction) /// <summary> /// 생성자 /// </summary> /// <param name="nextAction">다음 액션</param> public APIKeyMiddleware(RequestDelegate nextAction) { this.nextAction = nextAction; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 호출하기 (비동기) - InvokeAsync(context) /// <summary> /// 호출하기 (비동기) /// </summary> /// <param name="context">HTTP 컨텍스트</param> /// <returns>태스크</returns> public async Task InvokeAsync(HttpContext context) { if(!context.Request.Headers.TryGetValue(API_KEY_NAME, out var sourceAPIKey)) { context.Response.StatusCode = 401; await context.Response.WriteAsync("API Key was not provided."); return; } IConfiguration configuration = context.RequestServices.GetRequiredService<IConfiguration>(); string targetAPIKey = configuration.GetValue<string>(API_KEY_NAME); if(!targetAPIKey.Equals(sourceAPIKey)) { context.Response.StatusCode = 401; await context.Response.WriteAsync("Unauthorized client."); return; } await nextAction(context); } #endregion } } |
▶ WeatherForecast.cs
■ IAsyncActionFilter 인터페이스를 사용해 API 키를 인증하는 방법을 보여준다. [TestServer 프로젝트] ▶ Properteis/launchSettings.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 37 38 39 40 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:41483", "sslPort" : 44362 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "swagger", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestServer" : { "commandName" : "Project", "dotnetRunMessages" : "true", "launchBrowser" : true, "launchUrl" : "swagger", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ appsettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "Logging" : { "LogLevel" : { "Default" : "Information", "Microsoft" : "Warning", "Microsoft.Hosting.Lifetime" : "Information" } }, "AllowedHosts" : "*", "APIKey" : "A7A54AD3-91E1-4C3A-98C6-A17E39C9EA3D" } |
▶ APIKeyAttribute.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 |
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Threading.Tasks; namespace TestServer { /// <summary> /// API 키 어트리뷰트 /// </summary> [AttributeUsage(validOn: AttributeTargets.Class)] public class APIKeyAttribute : Attribute, IAsyncActionFilter { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// API 키명 /// </summary> public static readonly string API_KEY_NAME = "APIKey"; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 액션 실행시 처리하기 (비동기) - OnActionExecutionAsync(context, nextAction) /// <summary> /// 액션 실행시 처리하기 (비동기) /// </summary> /// <param name="context">액션 실행 컨텍스트</param> /// <param name="nextAction">다음 액션 실행 대리자</param> /// <returns>태스크</returns> public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate nextAction) { if(!context.HttpContext.Request.Headers.TryGetValue(API_KEY_NAME, out var sourceAPIKey)) { context.Result = new ContentResult() { StatusCode = 401, Content = "API Key was not provided." }; return; } IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>(); string targetAPIKey = configuration.GetValue<string>(API_KEY_NAME); if(!targetAPIKey.Equals(sourceAPIKey)) { context.Result = new ContentResult() { StatusCode = 401, Content = "API Key is not valid." }; return; } await nextAction(); } #endregion } } |
▶ WeatherForecast.cs
■ ControllerBase 클래스의 StatusCode 메소드를 사용해 검증 오류를 처리하는 방법을 보여준다. ▶ 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.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; 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.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.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapControllers(); } ); } #endregion } } |
▶ Controllers/TestController.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 |
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Docs.Samples; namespace TestProject.Controllers { /// <summary> /// 테스트 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] public class TestController : ControllerBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public // GET /api/test/3 #region 구하기 - Get(id) /// <summary> /// 구하기 /// </summary> /// <param name="id">ID</param> /// <returns>액션 결과</returns> [HttpGet("{id}")] public IActionResult Get(string id) { if(id.Contains('0')) { return StatusCode(StatusCodes.Status406NotAcceptable); } return ControllerContext.MyDisplayRouteInfo(id); } #endregion } } |
TestProject.zip
■ IRouteConstraint 인터페이스를 사용해 커스텀 경로 제약 조건을 설정하는 방법을 보여준다. ▶ CustomNameConstraint.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.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using System; using System.Globalization; using System.Text.RegularExpressions; namespace TestProject { /// <summary> /// 커스텀 명칭 제약 조건 /// </summary> public class CustomNameConstraint : IRouteConstraint { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 정규식 /// </summary> private Regex regex; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - CustomNameConstraint() /// <summary> /// 생성자 /// </summary> public CustomNameConstraint() { this.regex = new Regex ( @"^[1-9]*$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(100) ); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 매칭 여부 구하기 - Match(httpContext, route, routeKey, valueDictionary, routeDirection) /// <summary> /// 매칭 여부 구하기 /// </summary> /// <param name="httpContext">HTTP 컨텍스트</param> /// <param name="router">라우터</param> /// <param name="routeKey">경로 키</param> /// <param name="valueDictionary">경로 값 딕셔너리</param> /// <param name="routeDirection">경로 방향</param> /// <returns>매칭 여부</returns> public bool Match ( HttpContext httpContext, IRouter router, string routeKey, RouteValueDictionary valueDictionary, RouteDirection routeDirection ) { if(valueDictionary.TryGetValue(routeKey, out object value)) { string parameterValue = Convert.ToString(value, CultureInfo.InvariantCulture); if(parameterValue == null) { return false; } return regex.IsMatch(parameterValue); } return 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 95 96 97 98 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; 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.AddControllersWithViews(); services.AddRouting ( options => { options.ConstraintMap.Add("customName", typeof(CustomNameConstraint)); } ); } #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.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapControllers(); } ); } #endregion } } |
▶ Controllers/TestController.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 |
using Microsoft.AspNetCore.Mvc; using Microsoft.Docs.Samples; namespace TestProject.Controllers { /// <summary> /// 테스트 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] public class TestController : ControllerBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public // GET /api/test/3 #region 구하기 - Get(id) /// <summary> /// 구하기 /// </summary> /// <param name="id">ID</param> /// <returns>액션 결과</returns> [HttpGet("{id:customName}")] public IActionResult Get(string id) { return ControllerContext.MyDisplayRouteInfo(id); } #endregion // GET /api/test/my/3 #region 구하기 - Get(id) /// <summary> /// 구하기 /// </summary> /// <param name="id">ID</param> /// <returns>액션 결과</returns> [HttpGet("my/{id:customName}")] public IActionResult Get(int id) { return ControllerContext.MyDisplayRouteInfo(id); } #endregion } } |
TestProject.zip
■ 스캐폴딩 기능을 사용해 WEB API 앱을 만드는 방법을 보여준다. ▶ Models/TodoModel.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 |
namespace TestProject.Models { /// <summary> /// 할일 모델 /// </summary> public class TodoModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region ID - ID /// <summary> /// ID /// </summary> public long ID { get; set; } #endregion #region 제목 - Name /// <summary> /// 제목 /// </summary> public string Name { get; set; } #endregion #region 완료 여부 - IsComplete /// <summary> /// 완료 여부 /// </summary> public bool IsComplete { get; set; } #endregion } } |
▶ Data/DatabaseContext.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 |
using Microsoft.EntityFrameworkCore; using TestProject.Models; namespace TestProject.Data { /// <summary> /// 데이터베이스 컨텍스트 /// </summary> public class DatabaseContext : DbContext { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 할일 - Todo /// <summary> /// 할일 /// </summary> public DbSet<TodoModel> Todo { get; set; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - DatabaseContext(options) /// <summary> /// 생성자 /// </summary> /// <param name="options">옵션</param> public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } #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 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TestProject.Data; 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.AddDbContext<DatabaseContext>(options => options.UseInMemoryDatabase("TodoList")); services.AddControllers(); } #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.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints ( endpointRouterBuilder => { endpointRouterBuilder.MapControllers(); } ); } #endregion } } |
▶ 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 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 179 180 181 182 183 184 |
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; 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<ActionResult<IEnumerable<TodoModel>>> Get() { return await this.context.Todo.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<ActionResult<TodoModel>> Post(TodoModel todo) { this.context.Todo.Add(todo); await this.context.SaveChangesAsync(); return CreatedAtAction("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(id != todo.ID) { return BadRequest(); } this.context.Entry(todo).State = EntityState.Modified; try { await this.context.SaveChangesAsync(); } catch(DbUpdateConcurrencyException) { if(!TodoExists(id)) { return NotFound(); } else { throw; } } return NoContent(); } #endregion #region DELETE 처리하기 - Delete(id) /// <summary> /// DELETE 처리하기 /// </summary> /// <param name="id">ID</param> /// <returns>액션 결과 태스크</returns> [HttpDelete("{id}")] public async Task<ActionResult<TodoModel>> 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 todo; } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 할일 존재 여부 구하기 - TodoExists(id) /// <summary> /// 할일 존재 여부 구하기 /// </summary> /// <param name="id"></param> /// <returns>할일 존재 여부</returns> private bool TodoExists(long id) { return this.context.Todo.Any(e => e.ID == id); } #endregion } } |
■ 대용량 데이터를 스트리밍 방식으로 전달받는 방법을 보여준다. [TestLibrary 프로젝트] ▶ ValueModel.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 |
namespace TestLibrary { /// <summary> /// 값 모델 /// </summary> public class ValueModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region ID - ID /// <summary> /// ID /// </summary> public int ID { get; set; } #endregion #region 데이터 - Data /// <summary> /// 데이터 /// </summary> public string Data { get; set; } #endregion #region 태그 - Tag /// <summary> /// 태그 /// </summary> public string Tag { get; set; } #endregion } } |
[TestClient 프로젝트] ▶ 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 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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using Newtonsoft.Json; using TestLibrary; namespace TestClient { /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> private static void Main() { Console.Title = "대용량 데이터를 스트리밍 방식으로 전달받기"; Console.WriteLine("PRESS ANY KEY TO CONTINUE..."); Console.ReadKey(true); //TestPOSTData1(); //TestPOSTData2(); TestGETData(); Console.WriteLine("PRESS ANY KEY TO EXIT..."); Console.ReadKey(true); } #endregion #region POST 데이터 구하기 1 - GetPOSTData1<TResultItem>(url, source) /// <summary> /// POST 데이터 구하기 1 /// </summary> /// <typeparam name="TResultItem">결과 항목 타입</typeparam> /// <param name="url">URL</param> /// <param name="source">소스 객체</param> /// <returns>결과 항목 타입 열거 가능형</returns> private static IEnumerable<TResultItem> GetPOSTData1<TResultItem>(string url, object source) { string sourceJSON = JsonConvert.SerializeObject(source); HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, url); requestMessage.Content = new StringContent(sourceJSON, Encoding.UTF8, "application/json"); requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); HttpClient client = new HttpClient(); HttpResponseMessage responseMessage = client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).Result; responseMessage.EnsureSuccessStatusCode(); using(Stream stream = responseMessage.Content.ReadAsStreamAsync().Result) { using(StreamReader streamReader = new StreamReader(stream)) { JsonSerializer serializer = new JsonSerializer(); using(JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { while(jsonTextReader.Read()) { if(jsonTextReader.TokenType != JsonToken.StartArray && jsonTextReader.TokenType != JsonToken.EndArray) { yield return serializer.Deserialize<TResultItem>(jsonTextReader); } } } } } } #endregion #region POST 데이터 테스트하기 1 - TestPOSTData1() /// <summary> /// POST 데이터 테스트하기 1 /// </summary> private static void TestPOSTData1() { string url = "http://localhost:5569/api/test/postdata"; ValueModel source = new ValueModel(); source.ID = -1; source.Data = "NODATA"; source.Tag = "POST3"; foreach(ValueModel value in GetPOSTData1<ValueModel>(url, source)) { Console.WriteLine("{0} {1} {2}", value.ID, value.Data.Length, value.Tag); } } #endregion #region POST 스트림 구하기 - POSTGetStream(serverURL, contentType, encoding, argument, timeOut) /// <summary> /// POST 스트림 구하기 /// </summary> /// <param name="serverURL">서버 URL</param> /// <param name="contentType">컨텐츠 타입</param> /// <param name="encoding">인코딩</param> /// <param name="argument">인자 문자열</param> /// <param name="timeOut">타임 아웃</param> /// <returns>POST 스트림</returns> private static Stream POSTGetStream(string serverURL, string contentType, Encoding encoding, string argument, int? timeOut = null) { byte[] argumentArray = encoding.GetBytes(argument); HttpWebRequest request = WebRequest.Create(serverURL) as HttpWebRequest; request.ProtocolVersion = HttpVersion.Version11; request.AllowAutoRedirect = true; request.AllowWriteStreamBuffering = true; request.Method = WebRequestMethods.Http.Post; request.ContentType = contentType; request.ContentLength = argumentArray.Length; if(timeOut.HasValue) { request.Timeout = timeOut.Value; } using(Stream requestStream = request.GetRequestStream()) { requestStream.Write(argumentArray, 0, argumentArray.Length); } HttpWebResponse response = request.GetResponse() as HttpWebResponse; Stream responseStream = response.GetResponseStream(); return responseStream; } #endregion #region POST 데이터 구하기 2 - GetPOSTData2<TResultItem>(url, source) /// <summary> /// POST 데이터 구하기 2 /// </summary> /// <typeparam name="TResultItem">결과 항목 타입</typeparam> /// <param name="url">URL</param> /// <param name="source">소스 객체</param> /// <returns>결과 항목 타입 열거 가능형</returns> private static IEnumerable<TResultItem> GetPOSTData2<TResultItem>(string url, object source) { string sourceJSON = JsonConvert.SerializeObject(source); using(Stream stream = POSTGetStream(url, "application/json; charset=utf-8", Encoding.UTF8, sourceJSON)) { using(StreamReader streamReader = new StreamReader(stream)) { using(JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { JsonSerializer serializer = new JsonSerializer(); while(jsonTextReader.Read()) { if(jsonTextReader.TokenType != JsonToken.StartArray && jsonTextReader.TokenType != JsonToken.EndArray) { yield return serializer.Deserialize<TResultItem>(jsonTextReader); } } } } } } #endregion #region POST 데이터 테스트하기 2 - TestPOSTData2() /// <summary> /// POST 데이터 테스트하기 2 /// </summary> private static void TestPOSTData2() { string url = "http://localhost:5569/api/test/postdata"; ValueModel source = new ValueModel(); source.ID = -1; source.Data = "NODATA"; source.Tag = "POST3"; foreach(ValueModel value in GetPOSTData2<ValueModel>(url, source)) { Console.WriteLine("{0} {1} {2}", value.ID, value.Data.Length, value.Tag); } } #endregion #region GET 데이터 구하기 - GetGETData<TResultItem>(url) /// <summary> /// GET 데이터 구하기 /// </summary> /// <typeparam name="TResultItem">결과 항목 타입</typeparam> /// <param name="url">URL</param> /// <returns>결과 항목 열거 가능형</returns> private static IEnumerable<TResultItem> GetGETData<TResultItem>(string url) { HttpClient client = new HttpClient(); MediaTypeWithQualityHeaderValue headerValue = new MediaTypeWithQualityHeaderValue("application/json"); client.DefaultRequestHeaders.Accept.Add(headerValue); using(Stream stream = client.GetStreamAsync(url).Result) { using(StreamReader streamReader = new StreamReader(stream)) { JsonSerializer serializer = new JsonSerializer(); using(JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { while(jsonTextReader.Read()) { if(jsonTextReader.TokenType != JsonToken.StartArray && jsonTextReader.TokenType != JsonToken.EndArray) { yield return serializer.Deserialize<TResultItem>(jsonTextReader); } } } } } } #endregion #region GET 데이터 테스트하기 - TestGETData() /// <summary> /// GET 데이터 테스트하기 /// </summary> private static void TestGETData() { string url = "http://localhost:5569/api/test/getdata?tag=GET"; foreach(ValueModel value in GetGETData<ValueModel>(url)) { Console.WriteLine("{0} {1} {2}", value.ID, value.Data.Length, value.Tag); } } #endregion } } |
[TestServer 프로젝트] ▶ Controllers/TestController.cs
■ 대용량 데이터를 스트리밍 방식으로 전달받는 방법을 보여준다. [TestClient 프로젝트] ▶ 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 78 79 80 81 82 |
using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Net.Http.Headers; using Newtonsoft.Json; namespace TestClient { /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> private static void Main() { Console.Title = "대용량 데이터를 스트리밍 방식으로 전달받기"; Console.WriteLine("PRESS ANY KEY TO CONTINUE..."); Console.ReadKey(true); foreach(ValueModel value in GetValueEnumerable()) { Console.WriteLine("{0}\t{1}", value.SequenceID, value.Data); } Console.WriteLine("PRESS ANY KEY TO EXIT..."); Console.ReadKey(true); } #endregion #region 값 열거 가능형 구하기 - GetValueEnumerable() /// <summary> /// 값 열거 가능형 구하기 /// </summary> /// <returns>값 열거 가능형</returns> private static IEnumerable<ValueModel> GetValueEnumerable() { JsonSerializer serializer = new JsonSerializer(); HttpClient client = new HttpClient(); MediaTypeWithQualityHeaderValue headerValue = new MediaTypeWithQualityHeaderValue("application/json"); client.DefaultRequestHeaders.Accept.Add(headerValue); using(Stream stream = client.GetStreamAsync("http://localhost:63025/api/value").Result) { using(StreamReader streamReader = new StreamReader(stream)) { using(JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) { while(jsonTextReader.Read()) { if(jsonTextReader.TokenType != JsonToken.StartArray && jsonTextReader.TokenType != JsonToken.EndArray) { yield return serializer.Deserialize<ValueModel>(jsonTextReader); } } } } } } #endregion } } |
[TestServer 프로젝트] ▶ Models/ValueModel.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 |
using System.Runtime.Serialization; namespace TestServer.Models { /// <summary> /// 값 모델 /// </summary> [DataContract] public class ValueModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 시퀀스 ID - Sequence /// <summary> /// 시퀀스 ID /// </summary> [DataMember] public int SequenceID { get; set; } #endregion #region 데이터 - Data /// <summary> /// 데이터 /// </summary> [DataMember] public string Data { get; set; } #endregion } } |
▶ Handlers/ValueHandler.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 |
using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace TestServer.Handlers { /// <summary> /// 값 핸들러 /// </summary> public class ValueHandler : DelegatingHandler { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 비동기 송신하기 - SendAsync(requestMessage, cancellationToken) /// <summary> /// 비동기 송신하기 /// </summary> /// <param name="requestMessage">HTTP 요청 메시지</param> /// <param name="cancellationToken">취소 토큰</param> /// <returns>HTTP 응답 메시지 태스크</returns> protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken) { Task<HttpResponseMessage> responseMessageTask = base.SendAsync(requestMessage, cancellationToken); responseMessageTask.Result.Headers.TransferEncodingChunked = true; return responseMessageTask; } #endregion } } |
▶
■ ControllerBase 클래스의 Response 속성을 사용해 파일 다운로드시 스트리밍을 처리하는 방법을 보여준다. ▶ Controllers/TestController.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 Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; namespace TestProject.Controllers { /// <summary> /// 테스트 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] public class TestController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 값 리스트 구하기 - GetValueList() // GET api/test /// <summary> /// 값 리스트 구하기 /// </summary> /// <returns>값 리스트</returns> [HttpGet] public List<string> GetValueList() { return new List<string> { "value1", "value2" }; } #endregion #region 파일 다운로드 하기 - DownloadFile() // GET api/test/download /// <summary> /// 파일 다운로드 하기 /// </summary> /// <returns>태스크</returns> [HttpGet] [Route("download")] public async Task DownloadFile() { string filePath = @"d:\The Best of New Age Music Collection.mp4"; Response.Headers.Add(HeaderNames.ContentType , "application/octet-stream"); Response.Headers.Add(HeaderNames.ContentDisposition, $"attachment; filename=\"{Path.GetFileName(filePath)}\"" ); Response.StatusCode = 200; Stream sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); Stream targetStream = Response.Body; int bufferSize = 1024; byte[] bufferByteArray = new byte[bufferSize]; while(true) { int readCount = await sourceStream.ReadAsync(bufferByteArray, 0, bufferSize); if(readCount == 0) { break; } await targetStream.WriteAsync(bufferByteArray, 0, readCount); } await targetStream.FlushAsync(); } #endregion } } |
TestProject.zip
■ ConsumesAttribute/ProducesAttribute 클래스를 사용해 요청/응답 컨텐트 타입을 설정하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using Microsoft.AspNetCore.Mvc; namespace TestProject.Controllers { /// <summary> /// 테스트 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] [Consumes("application/json")] [Produces("application/json")] public class TestController : Controller { ... } } |
■ Controller 클래스의 Json 메소드를 사용해 JSON 데이터를 받는 방법을 보여준다. [TestServer 프로젝트] ▶ Models/TestModel.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 |
using System.ComponentModel.DataAnnotations; namespace TestServer.Models { /// <summary> /// 테스트 모델 /// </summary> public class TestModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region ID - ID /// <summary> /// ID /// </summary> public int ID { get; set; } #endregion #region 성명 - Name /// <summary> /// 성명 /// </summary> [Required] [StringLength(50, MinimumLength = 3, ErrorMessage = "성명은 3자 이상 입력해 주시기 바랍니다.")] public string Name { get; set; } #endregion } } |
▶ Models/TestRepository.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 |
using System.Collections.Generic; namespace TestServer.Models { /// <summary> /// 테스트 저장소 /// </summary> public class TestRepository { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// 테스트 딕셔너리 /// </summary> public static Dictionary<int, TestModel> TestDictionary; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Static #region 생성자 - TestRepository() /// <summary> /// 생성자 /// </summary> static TestRepository() { TestDictionary = new Dictionary<int, TestModel>(); TestDictionary.Add(1, new TestModel { ID = 1, Name = "홍길동" }); TestDictionary.Add(2, new TestModel { ID = 2, Name = "김철수" }); TestDictionary.Add(3, new TestModel { ID = 3, Name = "이용희" }); } #endregion } } |
▶ Controllers/TestController.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 |
using Microsoft.AspNetCore.Mvc; using System.Linq; using System.Net; using TestServer.Models; namespace TestServer.Controllers { /// <summary> /// 테스트 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] public class TestController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 리스트 구하기 - GetList() /// <summary> /// 리스트 구하기 /// </summary> /// <returns>리스트</returns> [HttpGet] public JsonResult GetList() { Response.StatusCode = (int)HttpStatusCode.OK; return Json(TestRepository.TestDictionary.Values.ToList()); } #endregion #region 항목 구하기 - GetItem(id) /// <summary> /// 항목 구하기 /// </summary> /// <returns>항목</returns> [HttpGet("{id:int}")] public JsonResult GetItem(int id) { if(TestRepository.TestDictionary.ContainsKey(id)) { Response.StatusCode = (int)HttpStatusCode.OK; return Json(TestRepository.TestDictionary[id]); } else { Response.StatusCode = (int)HttpStatusCode.BadRequest; return Json(null); } } #endregion #region 항목 추가하기 - AddItem(test) /// <summary> /// 항목 추가하기 /// </summary> /// <param name="test">테스트</param> /// <returns>처리 결과</returns> [HttpPost] public JsonResult Post([FromBody]TestModel test) { if(ModelState.IsValid) { if(TestRepository.TestDictionary.ContainsKey(test.ID)) { Response.StatusCode = (int)HttpStatusCode.BadRequest; return Json("FAILURE"); } TestRepository.TestDictionary.Add(test.ID, test); Response.StatusCode = (int)HttpStatusCode.Created; return Json("SUCCESS"); } Response.StatusCode = (int)HttpStatusCode.BadRequest; return Json("FAILURE"); } #endregion } } |
■ WEB API 애플리케이션을 만드는 방법을 보여준다. ▶ appsettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "Logging" : { "LogLevel" : { "Default" : "Information", "Microsoft" : "Warning", "Microsoft.Hosting.Lifetime" : "Information" } }, "AllowedHosts" : "*" } |
▶ launchSettings.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 37 38 39 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:5569", "sslPort" : 44320 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "api/test", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestProject" : { "commandName" : "Project", "launchBrowser" : true, "launchUrl" : "api/test", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ TestController.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 |
using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; namespace TestProject.Controllers { /// <summary> /// 테스트 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] public class TestController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 구하기 - Get() // GET api/test /// <summary> /// 구하기 /// </summary> /// <returns>문자열 리스트</returns> [HttpGet] public List<string> Get() { return new List<string> { "value1", "value2" }; } #endregion #region 구하기 - Get(id) // GET api/test/10 /// <summary> /// 구하기 /// </summary> /// <param name="id">ID</param> /// <returns>값</returns> [HttpGet("{id}")] public string Get(int id) { return "value"; } #endregion #region 추가하기 - Post(value) // POST api/test /// <summary> /// 추가하기 /// </summary> /// <param name="value">값</param> [HttpPost] public void Post([FromBody]string value) { } #endregion #region 수정하기 - Put(id, value) // PUT api/test/5 /// <summary> /// 수정하기 /// </summary> /// <param name="id"></param> /// <param name="value"></param> [HttpPut("{id}")] public void Put(int id, [FromBody]string value) { } #endregion #region 삭제하기 - Delete(id) // DELETE api/test/5 /// <summary> /// 삭제하기 /// </summary> /// <param name="id">ID</param> [HttpDelete("{id}")] public void Delete(int id) { } #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 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; 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.AddControllers(); } #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.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapControllers(); } ); } #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 |
using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace TestProject { /// <summary> /// 프로그램 /// </summary> public class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">배열 인자</param> public static void Main(string[] argumentArray) { CreateHostBuilder(argumentArray).Build().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 } } |
■ Microsoft.AspNet.WebApi.Client 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ CRUD 구현 WEB API 애플리케이션을 만드는 방법을 보여준다. [TestServer 프로젝트] ▶ Models/ValueRepository.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 |
using System.Collections.Generic; namespace TestServer { /// <summary> /// 값 저장소 /// </summary> public static class ValueRepository { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// 값 리스트 /// </summary> public static List<string> ValueList = new List<string>() { "Console", "WinForm", "WPF" }; #endregion } } |
▶ Models/TransferItem.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 |
namespace TestServer.Models { /// <summary> /// 전송 항목 /// </summary> public class TransferItem { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 상태 - Status /// <summary> /// 상태 /// </summary> public string Status { get; set; } #endregion #region 컨텐트 - Content /// <summary> /// 컨텐트 /// </summary> public string Content { get; set; } #endregion } } |
▶ Controllers/ValueController.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 |
using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using TestServer.Models; namespace TestServer.Controllers { /// <summary> /// 값 컨트롤러 /// </summary> [ApiController] [Route("api/[controller]")] public class ValueController : ControllerBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 값 추가하기 - AddValue(value) /// <summary> /// 값 추가하기 /// </summary> /// <param name="value">값</param> /// <returns>전송 항목</returns> [HttpPost] public TransferItem AddValue([FromBody]string value) { TransferItem item = new TransferItem(); if(ValueRepository.ValueList.Contains(value)) { item.Status = "FAILURE"; item.Content = null; return item; } else { ValueRepository.ValueList.Add(value); item.Status = "OK"; item.Content = null; return item; } } #endregion #region 값 수정하기 - UpdateValue(index, value) /// <summary> /// 값 수정하기 /// </summary> /// <param name="index">인덱스</param> /// <param name="value">값</param> /// <returns>전송 항목</returns> [HttpPut("{index:int}")] public TransferItem UpdateValue(int index, [FromBody]string value) { TransferItem item = new TransferItem(); if(index < 0 || index >= ValueRepository.ValueList.Count) { item.Status = "FAILURE"; item.Content = null; return item; } ValueRepository.ValueList[index] = value; item.Status = "OK"; item.Content = null; return item; } #endregion #region 값 삭제하기 - DeleteValue(index) /// <summary> /// 값 삭제하기 /// </summary> /// <param name="index">인덱스</param> /// <returns>전송 항목</returns> [HttpDelete("{index:int}")] public TransferItem DeleteValue(int index) { TransferItem item = new TransferItem(); if(index < 0 || index >= ValueRepository.ValueList.Count) { item.Status = "FAILURE"; item.Content = null; return item; } ValueRepository.ValueList.RemoveAt(index); item.Status = "OK"; item.Content = null; return item; } #endregion #region 값 리스트 구하기 - GetValueList() /// <summary> /// 값 리스트 구하기 /// </summary> /// <returns>전송 항목</returns> [HttpGet] public TransferItem GetValueList() { TransferItem item = new TransferItem(); item.Status = "OK"; item.Content = JsonConvert.SerializeObject(ValueRepository.ValueList); return item; } #endregion #region 값 구하기 - GetValue(index) /// <summary> /// 값 구하기 /// </summary> /// <param name="index">인덱스</param> /// <returns>전송 항목</returns> [HttpGet("{index:int}")] public TransferItem GetValue(int index) { TransferItem item = new TransferItem(); if(index < 0 || index >= ValueRepository.ValueList.Count) { item.Status = "FAILURE"; item.Content = null; return item; } item.Status = "OK"; item.Content = JsonConvert.SerializeObject(ValueRepository.ValueList[index]); return item; } #endregion } } |
[TestClient 프로젝트]
■ 템플리트 프로젝트를 사용해 WEB API 애플리케이션을 만드는 방법을 보여준다. ▶ appsettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "Logging" : { "LogLevel" : { "Default" : "Information", "Microsoft" : "Warning", "Microsoft.Hosting.Lifetime" : "Information" } }, "AllowedHosts" : "*" } |
▶ Properties/launchSettings.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 37 38 39 |
{ "$schema" : "http://json.schemastore.org/launchsettings.json", "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:5569", "sslPort" : 44320 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "launchUrl" : "weatherforecast", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestProject" : { "commandName" : "Project", "launchBrowser" : true, "launchUrl" : "weatherforecast", "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ Models/WeatherForecastModel.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 |
using System; namespace TestProject.Models { /// <summary> /// 기상 예보 모델 /// </summary> public class WeatherForecastModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 일자 - Date /// <summary> /// 일자 /// </summary> public DateTime Date { get; set; } #endregion #region 온도 (섭씨) - TemperatureC /// <summary> /// 온도 (섭씨) /// </summary> public int TemperatureC { get; set; } #endregion #region 온도 (화씨) - TemperatureF /// <summary> /// 온도 (화씨) /// </summary> public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); #endregion #region 요약 - Summary /// <summary> /// 요약 /// </summary> public string Summary { get; set; } #endregion } } |
▶ Controllers/WeatherForecastController.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 |
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using TestProject.Models; namespace TestProject.Controllers { /// <summary> /// 기상 예보 컨트롤러 /// </summary> [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 요약 배열 /// </summary> private static readonly string[] _symmaryArray = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; #endregion ////////////////////////////////////////////////////////////////////////////////////////// Instance //////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 로그 기록기 /// </summary> private readonly ILogger<WeatherForecastController> logger; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - WeatherForecastController(logger) /// <summary> /// 생성자 /// </summary> /// <param name="logger">로그 기록기</param> public WeatherForecastController(ILogger<WeatherForecastController> logger) { this.logger = logger; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 열거 가능형 목록 구하기 - Get() /// <summary> /// 열거 가능형 목록 구하기 /// </summary> /// <returns>열거 가능형 목록</returns> [HttpGet] public IEnumerable<WeatherForecastModel> Get() { Random random = new Random(); return Enumerable.Range(1, 5).Select ( index => new WeatherForecastModel { Date = DateTime.Now.AddDays(index), TemperatureC = random.Next(-20, 55), Summary = _symmaryArray[random.Next(_symmaryArray.Length)] } ) .ToArray(); } #endregion } } |
■ WEB API 애플리케이션을 만드는 방법을 보여준다. ▶ HelloController.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 |
using System.Collections.Generic; using System.Web.Http; namespace TestProject.Controllers { /// <summary> /// 헬로우 컨트롤러 /// </summary> public class HelloController : ApiController { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 리스트 구하기 - GetList() /// <summary> /// 리스트 구하기 /// </summary> /// <returns>리스트</returns> [HttpGet] public List<string> GetList() { List<string> list = new List<string>(); list.Add("테스트 문자열 1"); list.Add("테스트 문자열 2"); return list; } #endregion } } |
▶ DefaultController.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 |
using System.Collections.Generic; using System.Web.Http; namespace TestProject.Controllers { /// <summary> /// 디폴트 컨트롤러 /// </summary> public class DefaultController : ApiController { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 조회하기 - Get() [GET : api/Default] /// <summary> /// 조회하기 /// </summary> /// <returns>문자열 열거 가능형 목록</returns> public IEnumerable<string> Get() { return new string[] { "테스트 문자열 3", "테스트 문자열 4" }; } #endregion #region 조회하기 - Get(id) [GET : api/Default/5] /// <summary> /// 조회하기 /// </summary> /// <param name="id">ID</param> /// <returns>문자열</returns> public string Get(int id) { return "입력값 : " + id.ToString(); } #endregion #region 추가하기 - Post(value) [POST : api/Default] /// <summary> /// 추가하기 /// </summary> /// <param name="value">값</param> public void Post([FromBody]string value) { } #endregion #region 수정하기 - Put(id, value) [PUT : api/Default/5] /// <summary> /// 수정하기 /// </summary> /// <param name="id">ID</param> /// <param name="value">값</param> public void Put(int id, [FromBody]string value) { } #endregion #region 삭제하기 - Delete(id) [DELETE : api/Default/5] /// <summary> /// 삭제하기 /// </summary> /// <param name="id">ID</param> public void Delete(int id) { } #endregion } } |
TestProject.zip
■ WEB API 2 셀프 호스팅을 사용하는 방법을 보여준다. [TestServer 프로젝트 실행 결과] [TestClient 프로젝트 실행 결과] [TestCommon 프로젝트] ▶ Product.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 |
namespace TestCommon { /// <summary> /// 제품 /// </summary> public class Product { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region ID - ID /// <summary> /// ID /// </summary> public int ID { get; set; } #endregion #region 제품명 - Name /// <summary> /// 제품명 /// </summary> public string Name { get; set; } #endregion #region 카테고리 - Category /// <summary> /// 카테고리 /// </summary> public string Category { get; set; } #endregion #region 가격 - Price /// <summary> /// 가격 /// </summary> public decimal Price { get; set; } #endregion } } |