ASP.NET Core Web API와 REST
최근 웹 개발 추세에 따라 Blazor를 포함한 Angular, React, Vue, jQuery와 같은 자바스크립트 프레임워크 및 라이브러리가 널리 사용되고 있습니다. 이런 자바스크립트 라이브러리와 연동하기 위해 ASP.NET Core Web API를 구현하여 클라이언트 측 스크립트인 자바스크립트나 C#과 데이터를 주고받을 수 있습니다. 이 강의에서는 Web API와 JSON을 활용한 프로그래밍에 대해 설명합니다.
강의 소개
Web API는 인기 있는 웹과 모바일 앱뿐만 아니라, 냉장고에서 전화기까지 인터넷 지원 장치에서 사용할 수 있습니다. 자신만의 Web API를 구축하는 방법에 관심이 있으신가요? 이 과정에서 박용준 강사는 Windows, macOS, Linux용 오픈 소스 프레임워크인 ASP.NET Core 8.0을 사용해 Web API 작성 과정을 단계별로 안내합니다. 라우팅 실행 방식과 ASP.NET Core 8.0에서의 데이터 모델링 방법을 설명하며, Entity Framework Core를 사용해 프로젝트의 데이터 모델이 기본 데이터베이스에 연결되는 방식을 알려드립니다. 또한 API 버전 관리, CRUD 작업, API 보안 등 필수 주제를 다룹니다.
선수 학습
ASP.NET Core Web API는 박용준 강사의 ASP.NET Core 강의를 필수 선수 학습으로 필요합니다.
- C#
- SQL Server
- ASP.NET Core
- Web(HTML, CSS, JavaScript, Bootstrap)
- 권장 선수학습: jQuery, Angular, React, Vue 등의 자바스크립트
필수 프로그램
ASP.NET Core를 사용하여 Web API를 구축할 때에는 Visual Studio, Postman, Visual Studio Code 등이 사용됩니다.
- Visual Studio 2022 최신 버전
- ASP.NET 워크로드 필수 설치
- Postman
Postman 옵션 변경
Postman의 기본 옵션에서 다음 2개는 해제를 하고 이 강의의 내용을 따라하면 좋습니다.
Postman으로 GitHub API 호출 테스트
이 그림은 Postman으로 GitHub의 기본 API 사이트를 요청해 본 내용입니다.
ASP.NET Core 8.0 Web API 기본 템플릿 살펴보기
강사 주도로 ASP.NET Core 8.0의 Web API 기본 템플릿으로 프로젝트 생성 후 기본 실행 코드를 살펴봅니다.
본격적으로 Web API를 학습하기에 앞서 다음 경로의 동영상 강의를 먼저 학습하세요.
닷넷 6 Web API 기본 템플릿을 사용하여 Get, Post, Put, Delete 메서드 테스트
ASP.NET Core Web API를 사용한 ValuesController.cs를 통한 CRUD 구현
ASP.NET Core Web API를 사용하여 웹 서비스를 구축하면 강력한 API를 빠르게 만들 수 있습니다. 이 아티클에서는 기본 제공 템플릿을 사용하여 ValuesController.cs 파일로 Web API를 만들고 CRUD(Create, Read, Update, Delete) 메서드를 구현하는 방법을 설명합니다.
프로젝트 생성
먼저 Visual Studio에서 새로운 프로젝트를 만들어야 합니다. 이를 위해 "Create a new project"를 선택한 다음 "ASP.NET Core Web Application" 템플릿을 선택하고 프로젝트명과 저장 위치를 지정합니다.
템플릿 선택 화면에서 "API"를 선택하고 "Create"를 클릭하여 프로젝트를 생성합니다.
ValuesController 생성 및 수정
프로젝트가 생성되면 "Controllers" 폴더에서 "ValuesController.cs" 파일을 찾습니다. 이 파일에는 기본적인 CRUD 메서드가 포함되어 있습니다.
필요한 경우 파일을 열어
ValuesController
클래스를 수정하고, 필요한 CRUD 메서드를 추가합니다.
CRUD 메서드 구현
- Create: 새로운 값을 추가하는 메서드를 구현합니다.
[HttpPost]
어트리뷰트를 사용하여 HTTP POST 요청을 처리하도록 설정합니다.
[HttpPost]
public ActionResult<string> Post([FromBody] string value)
{
_values.Add(value);
return Ok(value);
}
- Read: 값을 검색하는 메서드를 구현합니다.
[HttpGet]
어트리뷰트를 사용하여 HTTP GET 요청을 처리하도록 설정합니다.
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return Ok(_values);
}
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return Ok(_values[id]);
}
- Update: 값을 수정하는 메서드를 구현합니다.
[HttpPut]
어트리뷰트를 사용하여 HTTP PUT 요청을 처리하도록 설정합니다.
[HttpPut("{id}")]
public ActionResult<string> Put(int id, [FromBody] string value)
{
_values[id] = value;
return Ok(value);
}
- Delete: 값을 삭제하는 메서드를 구현합니다.
[HttpDelete]
어트리뷰트를 사용하여 HTTP DELETE 요청을 처리하도록 설정합니다.
[HttpDelete("{id}")]
public ActionResult<string> Delete(int id)
{
_values.RemoveAt(id);
return Ok("Deleted successfully.");
}
이제 ValuesController.cs 파일에 CRUD 메서드가 구현되었습니다. 프로젝트를 빌드하고 실행하여 API를 테스트할 수 있습니다. Postman과 같은 도구를 사용하여 HTTP 요청을 보내고 응답을 확인할 수 있습니다.
이 아티클에서는 ASP.NET Core Web API를 사용하여 기본 제공 템플릿으로 ValuesController.cs를 만들고 CRUD 메서드를 구현하는 방법을 간략하게 살펴보았습니다. 이를 통해 간단한 웹 API를 빠르게 구축할 수 있습니다. 그러나 실제 프로젝트에서는 데이터 모델링, 데이터베이스 연동, API 보안 등 고려해야 할 사항이 더 많습니다. 이러한 내용을 포함한 더 복잡한 웹 API 개발에 대한 자세한 내용을 학습하려면 공식 문서나 관련 자료를 참조하시기 바랍니다.
Web API 기초
ASP.NET Core Web API는 ASP.NET Web API와 같은 내용을 구현합니다. ASP.NET Web API를 사용하면 서버 측의 데이터를 클라이언트 측으로 JSON(또는 XML) 형태로 내려 보내고 반대로 쿼리스트링 또는 JSON 형태로 데이터를 전송할 수 있는 구조를 제공합니다. 이론적인 내용은 간단히 몇 가지만 정리하고 바로 실습해보겠습니다.
- Web API는 HTTP 서비스를 쉽게 만드는 기능을 제공합니다.
HTTP
HTTP 사양은 아래 링크에서 볼 수 있습니다. HTTP의 창시자인 팀 버너스리와 REST의 창시자인 로이 필딩의 이름을 확인할 수 있습니다.
리소스: URIs
Web API는 웹 브라우저 주소를 통해서 들어오는 내용을 리소스(Resources)로 봅니다. 리소스는 다음과 같이 API 주소로 표현합니다. 일반적으로 URI는 GetCustomers
가 아닌 Customers
로 동사가 아닌 명사 형태로 구성합니다. 그리고 복수형을 권장합니다.
/api/Values
/api/Products
/api/Products/1234
/BoardServices
/api/Instructors/{instructorId}
/api/employees
/api/authors/{authorId}/courses
HTTP 메서드(HTTP 동사)
HTTP 메서드(Verbs, HTTP 동사가 MS 공식 용어임)는 기본 키워드 네 가지 GET
, POST
, PUT
, DELETE
와 그 외 PATCH
, COPY
, HEAD
등 많은 키워드가 있습니다. 이 강의는 네 가지 기본 HTTP 메서드만 다룹니다. HTTP 동사에 대한 더 많은 정보의 앞에서 소개한 HTTP 사양을 참고하세요.
GET
: 출력POST
: 입력PUT
: 수정(전체)PATCH
: 수정(부분)DELETE
: 삭제
HTTP 상태 코드
HTTP 상태 코드는 Web API 호출의 결과를 나타내는 코드입니다. 각 코드는 특정 상황을 설명하며, 가장 흔히 사용되는 코드는 200
, 400
, 500
등입니다.
자주 사용되는 HTTP 상태 코드
- 200 OK: 요청이 성공적으로 처리됨.
- 201 Created: 새로운 리소스가 생성됨.
- 202 Accepted: 요청이 수락되었으나 아직 처리되지 않음.
- 302 Found(Redirect): 요청된 리소스가 일시적으로 다른 URI로 이동됨.
- 400 Bad Request: 서버가 요청을 이해할 수 없음.
- 401 Unauthorized: 요청이 인증을 필요로 함.
- 404 Not Found: 서버가 요청한 리소스를 찾을 수 없음.
- 500 Internal Server Error: 서버 내부 오류.
- 503 Service Unavailable: 서비스 이용 불가.
- 504 Gateway Timeout: 게이트웨이 타임아웃.
추가적인 상태 코드
- 304 Not Modified: 리소스가 수정되지 않았음.
- 307 Temporary Redirect: 일시적인 리다이렉션.
- 308 Permanent Redirect: 영구적인 리다이렉션.
- 403 Forbidden: 서버가 요청을 거부함.
- 405 Method Not Allowed: 허용되지 않는 메소드.
- 409 Conflict: 요청이 서버의 현재 상태와 충돌.
ASP.NET Core Web API에서의 주요 HTTP 상태 코드
HTTP 상태 코드 | 설명 |
---|---|
200 | 성공적으로 처리됨. |
201 | 새로운 리소스가 생성됨. |
204 | 성공적인 요청이지만, 반환할 내용 없음. |
400 | 잘못된 요청, 클라이언트 수정 필요. |
401 | 인증 필요. |
403 | 접근 금지, 권한 필요. |
404 | 요청한 리소스를 찾을 수 없음. |
409 | 충돌 발생, 클라이언트 수정 필요. |
500 | 서버 내부 오류 발생. |
HTTP 상태 코드에 해당하는 액션 반환 값
ASP.NET Core에서 상태 코드를 반환하는 액션 반환 값은 다음 메서드를 사용합니다. 더 많은 액션 반환 값은 뒤에서 실습하면서 다룹니다.
Ok()
BadRequest()
NoContent()
엔드포인트(EndPoint, 끝점)
Web API에서 제공하는 하나의 메서드를 엔드포인트
라고도 합니다. GET
, POST
, PUT
, DELETE
를 담당하는 액션 메서드를 끝점
으로 표현합니다. 강의에서는 엔드포인트
로 발음합니다.
REST와 ASP.NET Core MVC
REST
REST는 HTTP 아키텍처 모델을 의미합니다.
- REST 아키텍처 모델
- 모든 장치에서 접근 가능
- XML 또는 JSON 제공
참고로, SOAP와 REST에 대한 비교는 다음 아티클을 참고하세요. 이제 더 이상 박용준 강사의 강의에서는 SOAP는 다루지 않습니다.
33.4.2 ASP.NET Core MVC
ASP.NET은 REST를 표현하기 위해서 다음과 같은 기능을 제공합니다.
- 리소스 = 컨트롤러
- HTTP 메서드 = 액션
- 상태 코드 기본 제공
33.4.3 MVC 컨트롤러에서 API 컨트롤러로 변경하는 방법
Web API는 MVC와 동일한 환경으로 시작합니다. MVC 컨트롤러를 Web API 컨트롤러로 변경하는 방법은 다음 순서를 따르면 됩니다.
Controller
또는ControllerBase
클래스 상속[ApiController]
특성 적용[HttpGet]
특성 적용
33.4.4 Web API 라우팅
Web API 라우팅도 MVC 라우팅과 마찬가지로 [Route]
특성 라우팅을 사용합니다.
[Route("/instructors")]
[Route("/instructors/{id}")]
[Route("/instructors/{id?}")]
[Route("/[controller]")]
ASP.NET Core Web API에서는 애트리뷰트(Attribute)를 사용하여 라우팅을 설정할 수 있습니다. 이를 "Routing with Attributes"라고도 합니다. Routing with Attributes는 라우팅 규칙을 컨트롤러 클래스 또는 액션 메서드의 애트리뷰트로 지정합니다. 이를 통해 개발자는 코드의 가독성을 높이고, 라우팅 규칙을 더 명확하게 지정할 수 있습니다.
Routing with Attributes를 사용하기 위해서는 다음과 같은 절차가 필요합니다.
[ApiController]
애트리뷰트를 컨트롤러 클래스에 추가합니다. 이 애트리뷰트를 사용하면 컨트롤러 클래스의 모든 액션 메서드에 대해 일반적인 라우팅 규칙을 적용할 수 있습니다.[Route]
애트리뷰트를 액션 메서드에 추가하여 특정 URL 경로를 바인딩할 수 있습니다.[HttpGet]
,[HttpPost]
,[HttpPut]
,[HttpDelete]
등의 애트리뷰트를 사용하여 HTTP 메서드를 지정할 수 있습니다.
예를 들어, 다음과 같은 코드는 "api/products" 경로에 대한 GET 요청을 처리합니다.
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "product1", "product2" };
}
}
위 코드에서, [Route]
애트리뷰트는 "api/[controller]"
를 지정합니다. 이는 "/api/products"
경로를 나타냅니다. [HttpGet]
애트리뷰트는 이 메서드가 GET 요청에 응답한다는 것을 나타냅니다.
총괄적으로, Routing with Attributes는 ASP.NET Core Web API에서 라우팅을 설정하는 간단하고 직관적인 방법입니다. 이를 사용하면 라우팅 규칙을 코드에서 명확하게 지정할 수 있으며, 가독성과 유지 보수성을 높일 수 있습니다.
33.4.5. [FromBody]
, [FromRoute]
, [FromQuery]
특성
Web API의 액션 메서드에서 데이터를 전송받는 모델 바인딩에 사용되는 주요 특성들은 다음과 같습니다.
[FromBody]
특성: POST와 PUT으로 전송된 데이터 받기[FromRoute]
특성: 라우트 템플릿으로 전송된 데이터 받기[FromQuery]
특성: URL의 쿼리스트링으로 전송된 데이터 받기
더 많은 정보는 Microsoft Learn의 "ASP.NET Core로 Web API 만들기" 아티클을 참고하세요.
[FromBody]
특성
ASP.NET Core Web API에서 [FromBody]
특성(attribute)은 HTTP 요청 본문(body)에서 데이터를 읽어오기 위한 특성입니다.
HTTP 요청은 헤더와 본문으로 구성되어 있습니다. 헤더는 요청에 대한 메타데이터 정보를 포함하고, 본문은 클라이언트가 서버로 보내는 데이터를 포함합니다. [FromBody]
특성은 HTTP 요청의 본문에서 데이터를 읽어와 해당 파라미터에 매핑(mapping)합니다.
[FromBody]
특성을 사용하면 다양한 데이터 형식의 본문 데이터를 읽어올 수 있습니다. 예를 들어, JSON
, XML
, 텍스트 등의 데이터 형식을 지원합니다. 이를 통해 클라이언트에서 서버로 다양한 형식의 데이터를 보낼 수 있습니다.
[FromBody]
특성은 일반적으로 POST
, PUT
, PATCH
와 같은 요청에 사용됩니다. 이러한 요청에서는 클라이언트가 데이터를 요청 본문에 넣어서 서버로 보냅니다. 서버는 [FromBody]
특성을 사용하여 이러한 데이터를 읽어와 파라미터로 전달합니다.
다음은 [FromBody]
특성이 적용된 예시 코드입니다.
[HttpPost]
public IActionResult Create([FromBody] MyModel myModel)
{
// myModel에는 HTTP 요청의 본문에서 읽어온 데이터가 매핑됩니다.
// 이후에는 이 데이터를 사용하여 적절한 처리를 수행할 수 있습니다.
}
위 코드에서 MyModel
클래스는 클라이언트에서 보낸 데이터를 담을 수 있는 모델 클래스입니다. [FromBody]
특성은 HTTP 요청의 본문에서 데이터를 읽어와 이를 MyModel
인스턴스에 매핑합니다. 이후에는 myModel
인스턴스를 사용하여 적절한 처리를 수행할 수 있습니다.
33.4.6 포맷팅(Formatting)
ASP.NET Core 2.0 이상부터는 미디어 형식 접미사를 변경해서 사용할 수 있습니다. 다만, 이 강의에서는 다루지 않습니다. 예를들어, application/json
처럼 application/foo+json
를 만들 수 있습니다.
XML로 결과 출력하기
Web API의 결과를 XML로 출력할 수 있습니다. JSON만 써도 되기에 이 강의에서는 다루지 않습니다.
services.AddMvc().AddXmlDataContractSerializerFormatters();
ASP.NET Core Web API에서는 기본적으로 JSON 형식으로 데이터를 반환합니다. 그러나 XML 형식으로 데이터를 반환하려면 다음과 같은 작업을 수행해야 합니다.
Microsoft.AspNetCore.Mvc.Formatters.Xml
NuGet 패키지를 프로젝트에 추가합니다.
Install-Package Microsoft.AspNetCore.Mvc.Formatters.Xml
Startup.cs 파일의 ConfigureServices 메서드에서 XML 지원을 추가합니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
})
.AddXmlDataContractSerializerFormatters(); // XML 형식 지원 추가
}
XML 형식을 사용하도록 HTTP 요청의 Accept 헤더를 설정합니다.
XML 형식을 사용하도록 요청 헤더를 설정하지 않으면 기본적으로 JSON 형식으로 데이터가 반환됩니다. 따라서 HTTP 요청 헤더의 Accept 헤더를 application/xml
로 설정하여 XML 형식을 사용하도록 지정할 수 있습니다.
GET /api/cities HTTP/1.1
Host: localhost:5000
Accept: application/xml
위와 같이 HTTP 요청을 보내면, ASP.NET Core Web API는 XML 형식으로 데이터를 반환합니다.
참고로, ASP.NET Core Web API에서 XML 형식을 사용할 경우 기본적으로 Data Contract Serializer를 사용합니다. 만약 XML Serializer를 사용하고 싶다면, AddXmlSerializerFormatters
메서드를 사용하여 XML Serializer를 추가할 수 있습니다.
특성으로 JSON 지정
[Produces]
와 [Consumes]
특성을 사용하여 JSON 반환값을 지정할 수 있습니다.
[Produces("application/json")]
[Produces("application/redplus+json")]
33.4.7 Web API 사용 모양 미리보기: JSON 데이터 전달
Web API는 웹을 통해서 Text, JSON, XML 등의 데이터를 전달하는데 사용됩니다.
코드: DotNetNote.Apis/Controllers/24_WebAPI/WebApiCamps.cs
using Microsoft.AspNetCore.Mvc;
namespace DotNetNote.Controllers
{
/// <summary>
/// WebApiCampsController 대신에 WebApiCamps만 지정해도 사용 가능합니다.
/// </summary>
[Route("api/[controller]")]
public class WebApiCamps : Controller
{
[HttpGet]
public IActionResult Get()
{
return Ok(new { Id = 1, Name = "Web API" });
}
}
}
{"id":1,"name":"Web API"}
33.4.8 Web API 반환값: 개체, 컬렉션, 복합 형식으로 전달
Web API의 반환값은 단순 문자열을 포함하여 개체, 컬렉션, 복합 형식으로 반환됩니다.
- 파일: Controllers\34_WebAPI\05_MyRankingServiceController\MyRankingServiceController.cs
[1]
개체 형태로 전달
코드: MyRankingServiceController.cs
[Route("api/[controller]")]
public class MyRankingServiceController : Controller
{
//[1] 개체 형태로 전달
[HttpGet]
public Subject Get()
{
return new Subject { Kor = 95, Eng = 100, Total = 195 };
}
}
public class Subject
{
public int Kor { get; set; }
public int Eng { get; set; }
public int Total { get; set; }
}
{"kor":95,"eng":100,"total":195}
[2]
컬렉션 형태로 전달
코드: MyRankingServiceController.cs
[Route("api/[controller]")]
public class MyRankingServiceController : Controller
{
//[2] 컬렉션 형태로 전달
[HttpGet]
public List<Student> Get()
{
var students = new List<Student> {
new Student { Id = 1, Name = "홍길동", Score = 3 },
new Student { Id = 2, Name = "백두산", Score = 2 },
new Student { Id = 3, Name = "임꺽정", Score = 1 },
};
return students;
}
}
public class Subject
{
public int Kor { get; set; }
public int Eng { get; set; }
public int Total { get; set; }
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Score { get; set; }
}
[
{
"id": 1,
"name": "홍길동",
"score": 3
},
{
"id": 2,
"name": "백두산",
"score": 2
},
{
"id": 3,
"name": "임꺽정",
"score": 1
}
]
[3]
복합 형식(Complex Type)으로 전달
코드: MyRankingServiceController.cs
/// <summary>
/// 성적 정보: 복합 형식(Complex Type): 과목(개체) + 학생들(컬렉션)
/// </summary>
public class MyRankingDto
{
public Subject Subject { get; set; }
public List<Student> Students { get; set; }
}
//[3] 복합 형식(Complex Type): 하나 이상의 다른 개체를 포함
[HttpGet]
public MyRankingDto Get()
{
var subject = new Subject { Kor = 95, Eng = 100, Total = 195 };
var students = new List<Student>
{
new Student { Id = 1, Name = "홍길동", Score = 3 },
new Student { Id = 2, Name = "백두산", Score = 2 },
new Student { Id = 3, Name = "임꺽정", Score = 1 },
};
return new MyRankingDto { Subject = subject, Students = students };
}
{"subject":{"kor":95,"eng":100,"total":195},"students":[{"id":1,"name":"홍길동","score":3},{"id":2,"name":"백두산","score":2},{"id":3,"name":"임꺽정","score":1}]}
도시 정보를 제공하는 Web API 예시
ASP.NET Core Web API를 사용하면 다양한 종류의 애플리케이션에서 사용되는 RESTful API를 쉽게 개발할 수 있습니다. 이번 글에서는 ASP.NET Core Web API를 사용하여 CRUD(Create, Read, Update, Delete) 작업을 수행하는 예시 코드를 살펴보겠습니다.
City
클래스
먼저, City
클래스를 정의해보겠습니다. City
클래스는 간단한 POCO(Plain Old CLR Object) 클래스로서, ASP.NET Core Web API에서 자주 사용되는 모델 클래스의 예시입니다.
public class City
{
public int Id { get; set; }
public string Name { get; set; }
}
위 City
클래스는 Id
와 Name
이라는 두 개의 속성을 가지고 있습니다. Id
속성은 각 도시에 대한 고유한 식별자를 나타내며, Name
속성은 도시의 이름을 나타냅니다.
CitiesController
클래스
이제, City
클래스를 사용하여 CRUD 기능을 수행하는 CitiesController
클래스를 정의해보겠습니다.
코드: CitiesController.cs
#nullable disable
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace VisualAcademy.Apis
{
public class City
{
public int Id { get; set; }
public string Name { get; set; }
}
[Authorize(Roles = "Administrators")]
[ApiController]
[Route("api/[controller]")]
public class CitiesController : ControllerBase
{
private readonly List<City> _cities;
public CitiesController()
{
_cities = new List<City>
{
new City { Id = 1, Name = "Seoul" },
new City { Id = 2, Name = "New York" },
new City { Id = 3, Name = "London" },
new City { Id = 4, Name = "Paris" },
new City { Id = 5, Name = "Tokyo" },
};
}
// GET api/cities
[HttpGet]
public ActionResult<IEnumerable<City>> GetCities()
{
return _cities;
}
// GET api/cities/5
[HttpGet("{id}")]
public ActionResult<City> GetCity(int id)
{
var city = _cities.FirstOrDefault(c => c.Id == id);
if (city == null)
{
return NotFound();
}
return city;
}
// POST api/cities
[HttpPost]
public ActionResult<City> CreateCity([FromBody] City city)
{
city.Id = _cities.Max(c => c.Id) + 1;
_cities.Add(city);
return CreatedAtAction(nameof(GetCity), new { id = city.Id }, city); // 201 Created
}
// PUT api/cities/5
[HttpPut("{id}")]
public ActionResult UpdateCity(int id, [FromBody] City city)
{
var existingCity = _cities.FirstOrDefault(c => c.Id == id);
if (existingCity == null)
{
return NotFound();
}
existingCity.Name = city.Name;
return NoContent();
}
// DELETE api/cities/5
[HttpDelete("{id}")]
public ActionResult DeleteCity(int id)
{
var city = _cities.FirstOrDefault(c => c.Id == id);
if (city == null)
{
return NotFound();
}
_cities.Remove(city);
return NoContent();
}
}
}
위 코드에서는 City
클래스를 사용하여 CityWebAPI.Controllers
네임스페이스에 CitiesController
클래스를 정의하고 있습니다. CitiesController
클래스는 ApiController
와 Route
특성을 사용하여 웹 API 컨트롤러를 정의하고 있습니다. CitiesController
클래스는 리스트 형식의 _cities
필드를 가지고 있으며, 이 필드는 CitiesController
클래스의 생성자에서 초기화되고 있습니다.
CitiesController
클래스는 다음과 같은 CRUD 기능을 제공합니다.
GET api/cities
: 모든 도시 정보를 반환합니다.GET api/cities/{id}
: 특정 도시 정보를 반환합니다.POST api/cities
: 새로운 도시 정보를 추가합니다.PUT api/cities/{id}
: 특정 도시 정보를 수정합니다.DELETE api/cities/{id}
: 특정 도시 정보를 삭제합니다.
GetCities
메서드는 _cities
필드를 반환하는 메서드입니다. _cities
필드는 CitiesController
클래스의 생성자에서 초기화되며, 미리 정의된 도시 정보를 담고 있습니다.
GetCity
메서드는 id
매개변수로 지정된 도시 정보를 반환합니다. FirstOrDefault
확장 메서드를 사용하여 _cities
필드에서 Id
속성이 id
매개변수와 일치하는 도시 정보를 찾아 반환합니다. 도시 정보를 찾을 수 없는 경우에는 NotFound
메서드를 호출하여 HTTP 404 오류 응답을 반환합니다.
CreateCity
메서드는 City
객체를 전달받아 _cities
필드에 새로운 도시 정보를 추가합니다. 새로운 도시 정보는 _cities
필드에서 가장 큰 Id
속성의 값에 1을 더한 값을 사용하여 생성됩니다. 도시 정보를 추가한 후에는 CreatedAtAction
메서드를 호출하여 HTTP 201 Created 응답을 반환합니다.
UpdateCity
메서드는 id
매개변수로 지정된 도시 정보를 업데이트합니다. FromBody
특성을 사용하여 City
객체를 전달받아 Id
속성이 id
매개변수와 일치하는 도시 정보를 찾아 Name
속성을 업데이트합니다. 도시 정보를 업데이트한 후에는 NoContent
메서드를 호출하여 HTTP 204 No Content 응답을 반환합니다.
DeleteCity
메서드는 id
매개변수로 지정된 도시 정보를 삭제합니다. FirstOrDefault
확장 메서드를 사용하여 _cities
필드에서 Id
속성이 id
매개변수와 일치하는 도시 정보를 찾아 _cities
필드에서 삭제합니다. 도시 정보를 삭제한 후에는 NoContent
메서드를 호출하여 HTTP 204 No Content 응답을 반환합니다.
요약
ASP.NET Core Web API를 사용하면 간단하게 RESTful API를 구현할 수 있습니다. 이번 글에서는 City
클래스를 사용하여 CRUD 기능을 수행하는 CitiesController 클래스를 구현하는 예시 코드를 살펴보았습니다. CitiesController 클래스는 _cities 필드를 사용하여 도시 정보를 관리하며, HttpGet, HttpPost, HttpPut, HttpDelete 메서드를 사용하여 CRUD 작업을 수행합니다. 이러한 방식으로 ASP.NET Core Web API를 사용하면 다양한 종류의 애플리케이션에서 사용되는 RESTful API를 쉽게 개발할 수 있습니다.
33.5. Web API 프로젝트
ASP.NET Core에서의 Web API는 ASP.NET Web API와 달리 ApiController
클래스를 상속하지 않고, 일반 컨트롤러와 똑같이 Controller
클래스 또는 ControllerBase
클래스를 상속해서 만듭니다. 즉, MVC와 Web API가 하나의 컨트롤러에 통합해서 사용될 수 있는 구조입니다. ASP.NET Core가 MVC와 Web API를 통합했다는 대목이 이를 의미합니다.
33.6. [실습]
Web API 프로젝트 템플릿으로 Web API 만들기
33.6.1 소개
ASP.NET Core 프로젝트는 그 자체로서 ASP.NET Web API를 포함하고 있으나 Web API 전용으로 하나의 프로젝트를 만들 수도 있습니다. 이번 실습을 통해서 Web API 프로젝트 템플릿을 사용해봅니다.
NOTE
이번 실습 과정을 "처음으로 작성해보는 ASP.NET Core Web API - Hello World 예제 "라는 제목의 동영상 강좌로도 마련하였으니 참고하기 바랍니다.
https://youtu.be/MDrtsWS500Q
33.6.2 따라하기 1: Web API 프로젝트 템플릿으로 프로젝트 생성
(1) Visual Studio를 열고 C:\ASP.NET\ 폴더에 ApiHelloWorld 이름으로 ASP.NET Core 웹 응용 프로그램(.NET Core) 프로젝트를 생성합니다. 참고로, 완성된 소스는 DotNetNote 또는 DotNetNote.Apis 프로젝트로 이동됩니다.
- 프로젝트 이름: ApiHelloWorld
템플릿 선택에서는 <Web API>
를 선택합니다. ASP.NET Core 새 프로젝트 생성 시 다음과 같이 <Web API>
를 선택하면 Web API 전용 프로젝트가 됩니다. 다음은 ApiHelloWorld 이름으로 Web API 프로젝트를 만드는 모습입니다.
그림: ASP.NET Core Web API 템플릿
(2) Web API 프로젝트 템플릿으로 만든 프로젝트의 기본 구조는 다음과 같습니다.
그림: Web API 템플릿으로 생성된 프로젝트
(3) Web API 템플릿을 사용하여 프로젝트를 생성하면 Controllers 폴더에 샘플로 ValuesController.cs 파일이 생성됩니다. 코드 내용은 다음과 같습니다.
코드: ApiHelloWorld/Controllers/ValuesController.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace ApiHelloWorld.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
(4) 이렇게 만들어진 프로젝트를 Ctrl+F5를 눌러 실행한 후 주소창에 /api/values
경로를 요청합니다. 문자열이 화면에 출력되거나 JSON 파일 형태로 다운로드됩니다.
그림: Values 컨트롤러 실행
(5) /api/values/1234
형태로 숫자 형태를 매개변수로 전달하면 Values
컨트롤러에 있는 Get 액션 메서드 중에서 id
매개변수가 포함된 액션 메서드가 실행되어 화면에 "value"를 출력합니다.
그림: Web API에 매개변수로 값 전달
33.6.3 따라하기 2: Web API 컨트롤러 클래스 만들기
(1) 또 다른 Web API를 테스트해보겠습니다. Controllers 폴더에 ApiHelloWorldControllers.cs 파일명으로 클래스 파일을 생성하거나 <추가 > 새 항목>
영역에서 Web API 파일을 선택한 후 생성한 후 다음과 같이 코드를 작성합니다.
그림: Web API 컨트롤러 클래스
코드: ApiHelloWorld/Controllers/ApiHelloWorldController.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace ApiHelloWorld.Controllers
{
// [!] 애트리뷰트(특성) 라우팅
[Route("api/[controller]")]
public class ApiHelloWorldController : Controller
{
// api/ApiHelloWorld
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "안녕하세요.", "반갑습니다." };
}
// api/ApiHelloWorld/id
// [!] 라우트 매개변수
// [HttpGet("{id}")]
// [!] 모델 바인딩 + 인라인 제약 조건(:)
[HttpGet("{id:int}")]
public string Get(int id)
{
return $"넘어온 값: {id}";
}
}
}
HTTP URL을 통해서 매개변수를 전달받을 때 사용하는 코드는 [HttpGet("{id}")]
와 같이 HttpGet 특성에서 중괄호({}
) 기호를 사용하여 특정 매개변수를 지정합니다. {id:int}
와 같이 매개변수의 값을 int 형으로 제약을 걸 수도 있습니다.
(2) Ctrl+F5를 눌러 실행한 후 경로에 /api/values
대신에 /api/ApiHelloWorld
를 요청하면 다음과 같이 위 코드의 Get 액션 메서드에서 작성한 문자열 배열이 출력됩니다.
그림: Web API 실행
(3) 마찬가지로 뒤에 매개변수로 숫자를 입력하면 이 값이 id
매개변수에 전달되어 출력됩니다.
그림: Web API에 매개변수 전달
(4) 만약 매개변수로 정수형 값이 아닌 문자열 값이 넘어오면 앞서 코드에서 콜론(:
) 기호를 사용해 int 타입으로 설정한 Web API 매개변수 인라인 제약 조건에 의해 다음과 같이 404 에러가 발생합니다.
그림: Web API 매개변수 제약
33.6.3.1 참고: 최소한의 코드로 Web API에서 JSON 데이터 반환하기
public IActionResult Get()
{
return Ok(new { Id = 1, Note = "안녕하세요." });
}
33.6.4 따라하기 3: JSON을 반환하는 Web API 생성
(1) Controllers 폴더에 ApiHelloWorldWithValueController.cs 이름으로 Web API 컨트롤러 클래스를 생성합니다.
(2) ApiHelloWorldWithValueController.cs 파일에 다음과 같이 코드를 작성합니다. 만약 단순한 문자열 배열이 아니라 JSON 형태로 값을 반환하고자 한다면 Web API의 반환값으로 string 배열이 아닌 특정 타입으로 반환하면 됩니다. 예를 들어 다음 코드와 같이 새로운 Web API를 생성하고, 새로 Value
타입을 만들어 이 타입으로 값을 반환시켜 보는 코드를 작성하면 됩니다.
***코드: ApiHelloWorld/Controllers/ApiHelloWorldWithValueController.cs ***
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace ApiHelloWorld.Controllers
{
[Route("api/[controller]")]
public class ApiHelloWorldWithValueController : Controller
{
[HttpGet]
public IEnumerable<Value> Get()
{
return new Value[] {
new Value { Id = 1, Text = "안녕하세요" },
new Value { Id = 2, Text = "반갑습니다" },
new Value { Id = 3, Text = "또 만나요" }
};
}
[HttpGet("{id:int}")]
public Value Get(int id)
{
return new Value { Id = id, Text = $"넘어온 값: {id}" };
}
[HttpPost]
[Produces("application/json", Type = typeof(Value))]
[Consumes("application/json")]
public IActionResult Post([FromBody]Value value)
{
// 모델 유효성 검사
if (!ModelState.IsValid)
{
return BadRequest(ModelState); // 400 에러 출력
}
return CreatedAtAction("Get", new { id = value.Id }, value); // 201
}
}
// 모델 유효성 검사
public class Value
{
public int Id { get; set; }
[Required(ErrorMessage = "Text 속성은 필수입력값입니다.")]
public string Text { get; set; }
}
}
ASP.NET Core Web API에서 ModelState.IsValid
속성은 입력 모델의 유효성 검사를 수행합니다. 이 속성은 데이터 유효성 검사 결과에 대한 정보를 포함합니다.
올바른 모델 상태는 ModelState.IsValid
가 true
를 반환할 때 나타납니다. 이는 데이터 유효성 검사가 모두 통과되어 검증된 모델이 만들어졌음을 의미합니다.
예를 들어, 웹 API 컨트롤러에서 HTTP POST 요청의 본문에서 전달받은 JSON 데이터를 입력 모델로 바인딩한 후, 해당 모델의 데이터 유효성 검사를 수행하려고 할 때, ModelState.IsValid
속성을 사용합니다.
올바른 데이터를 입력하면 ModelState.IsValid
가 true
를 반환하고, 유효하지 않은 데이터가 입력되면 false
를 반환합니다. 이후 이에 대한 적절한 처리가 가능합니다. 이를 통해 웹 API에서 모델 상태를 확인하여 유효성 검사 오류를 처리하고, 알맞은 응답을 반환할 수 있습니다.
참고: Content Type
- JSON :
application/json
- XML :
text/xml
- JSONP :
application/javascript
- RSS :
application/xml+rss
- ATOM :
application/xml+atom
(3) 웹 브라우저로 실행 후 ApiHelloWorldWithValue
컨트롤러를 요청하면 다음과 같이 Id
와 Text
를 쌍으로 갖는 JSON 형태의 데이터가 클라이언트로 전송됩니다. ASP.NET Core에서 JSON 렌더링 시 자동으로 카멜 표기법(첫 글자 소문자)으로 출력됩니다.
그림: Web API 결과를 JSON으로 받기
(4) Web API에 매개변수로 특정 번호를 넘겨주면 단일 데이터에 대한 JSON 데이터가 출력됩니다.
그림: Web API 호출 시 매개변수 전달
33.6.5 마무리
Web API 프로젝트 템플릿을 사용하여 간단한 Web API를 만들고 실행해보았습니다. ASP.NET Core에서는 모든 영역에서 MVC 컨트롤러와 Web API 컨트롤러를 사용할 수 있기에 앞으로 진행되는 실습에서 Web API는 모두 DotNetNote 웹 프로젝트의 Controllers 폴더에 작성해도 됩니다. 참고로 이번 실습에서 생성한 Web API 컨트롤러 클래스 세 개는 DotNetNote 프로젝트의 Controllers 폴더에도 똑같이 존재합니다.
33.7 [실습] HTTP POST 메서드 테스트
33.7.1 소개
이번에는 Web API를 만든 후 Post 방식의 메서드를 만들고 Get과 Post를 함께 테스트해보겠습니다. 단, Get 메서드와 달리 Web API의 Post 메서드는 웹 브라우저로 바로 테스트할 수 없습니다. 따라서 이번 실습에서는 Postman을 사용할 것입니다. Postman 사용 환경이 준비되지 않은 독자들은 이번 실습은 따라하기가 아닌 읽어보기 식으로 학습을 진행해도 상관없습니다.
Web API 디버깅 도구
웹 디버깅 도구로는 브라우저의 F12 개발자 도구, 텔레릭의 Fiddler 또는 크롬 웹 브라우저의 확장 도구로 시작된 Postman이 많이 사용됩니다. 이 책에서는 Postman이 사용됩니다.
참고 - Postman 설치
이번 실습과 관련해서 "구글 크롬 웹 브라우저 다운로드 및 설치 그리고 크롬 확장 도구인 Postman 설치"라는 제목의 동영상 강좌로도 마련하였으니 참고하기 바랍니다.
33.7.2 따라하기
(1) Visual Studio를 열고 C:\ASP.NET\DotNetNote 프로젝트를 실행합니다.
(2) 또 다른 Web API를 테스트해보겠습니다. 데모용 프로젝트인 DotNetNote 프로젝트의 Controllers 폴더에 마우스 오른쪽 버튼 클릭 후 <추가 > 새 항목>
메뉴를 선택합니다. 새 항목 추가 화면에서 Web API 컨트롤러 클래스를 선택하고, 이름은 WebApiDemoController.cs로 설정 후 "추가" 버튼을 클릭하여 Web API 컨트롤러를 추가합니다. 기본 생성 코드를 모두 지운 후 다음과 같이 코드를 작성합니다. [Route]
특성을 사용해서 /api/컨트롤러
형태로 호출되게 하는 방식으로 바로 특정 컨트롤러 안에서 Web API를 생성할 수 있습니다. 물론 /api/
경로 없이 일반 라우팅을 통해서 Web API를 서비스해도 전혀 상관없습니다.
- DotNetNote.Apis\Controllers\34_WebAPI\00_WebAPI\WebApiDemoController.cs
***코드: /Controllers/WebApiDemoController.cs ***
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Net;
namespace DotNetNote.Controllers
{
[Route("api/[controller]")]
public class WebApiDemoController : Controller
{
[HttpGet]
public JsonResult Get()
{
return Json(new { Name = "박용준" });
}
[HttpPost]
public JsonResult Post([FromBody]WebApiDemoClass name)
{
if (ModelState.IsValid)
{
Response.StatusCode = (int)HttpStatusCode.Created;
return Json(true);
}
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json("실패");
}
}
public class WebApiDemoClass
{
public int Id { get; set; }
[Required]
[StringLength(50, MinimumLength = 3, ErrorMessage = "3자 이상")]
public string Name { get; set; }
}
}
NOTE
Json ActionResult 액션 반환값
return Json(new { name = "RedPlus", value = "1234" }, JsonRequestBehavior.AllowGet);
(3) 위에서 생성한 Web API는 DotNetNote 프로젝트를 실행 후 다음과 같이 /api/WebApiDemo
경로를 요청하면 간단한 JSON 문자열이 반환됩니다.
그림: Web API 실행
NOTE
JSON 렌더링 옵션 지정
C# 코드의 속성명은 대문자로 시작하지만, JSON으로 출력되는 결괏값은 소문자로 시작하려면 Startup.cs 파일에 다음과 같이 AddJsonOptions() 메서드 항목을 추가해주면 됩니다. 다만, ASP.NET Core에서는 이 설정값이 기본으로 Web API를 통한 JSON 렌더링 시 소문자로 출력됩니다. 이러한 방식은 C#은 대문자 속성을 사용하는 반면에 JavaScript 프로그래밍 영역은 소문자로 시작하는 특성을 전체 프로젝트 단위로 적용하기 위해서 한 번에 설정할 수 있는 방법입니다.
```cs
using Newtonsoft.Json.Serialization;
services.AddMvc()
.AddJsonOptions(options =>
{
// JSON 속성 첫 글자 소문자
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
});
```
(4) WebApiDemo 컨트롤러의 코드 중에서 [HttpPost]
특성이 적용된 Post 메서드는 Web API 호출 시 Get 방식이 아닌 Post 방식으로 호출될 때 실행됩니다. Post 메서드는 모델 바인딩에 의해서 WebApiDemoClass
형태로 Id
와 Name
을 묶어서 전송하는 모양을 표현하였습니다.
(5) Post 메서드에 다음과 같이 중단점을 설정합니다. Visual Studio에서 F5를 눌러 디버깅 모드로 프로젝트를 실행합니다.
그림: Web API 코드에 중단점 설정
(6) Postman을 직접 실행하거나 크롬 웹 브라우저를 실행시키고 주소란에 chrome://apps
경로를 요청 후 Postman 확장 기능을 클릭해서 실행합니다. 크롬 웹 브라우저는 설치되어 있으나, Postman 확장 도구가 나타나지 않으면 웹 스토어 링크에서 Postman 검색 후 Postman을 크롬에 설치할 수 있습니다. 단, 2023년 이후로는 따로 Postman을 설치하면 됩니다.
그림: 크롬 웹 브라우저 확장 기능 보기
(7) Postman이 실행된 모습입니다.
그림: Postman 실행
(8) 먼저 앞서 실행한 웹 프로젝트 주소에 /api/WebApiDemo
경로를 입력 후 <Send>
버튼을 클릭하여 Get 메서드를 먼저 테스트합니다.
그림: Postman에서 Get 메서드 실행
(9) 이번에는 Postman의 드롭다운리스트에서 GET
대신 POST를 선택 후 같은 경로를 입력합니다. 그리고 <Body > raw > JSON(application/json)
을 선택하고 아래 입력란에 { "name", "홍길동" } 형태로 raw 데이터를 입력합니다. <Send>
버튼을 클릭하여 Web API의 Post 메서드에 데이터를 전달합니다.
전송 버튼을 클릭하면 Web API에 JSON 데이터가 전송됩니다. Postman 확장 기능에서 Body 탭에서 raw와 JSON(application/json)을 선택 후 POST를 전송해야 제대로 실행됩니다.
그림: Web API에 데이터 전송하기
(10) 위에서 전송된 데이터는 그림과 같이 중단점이 설정된 부분에서 잠시 멈춥니다. 그림의 17번 라인의 name 매개변수에 마우스 커서를 올려보면 넘어온 값을 확인할 수 있습니다. Id는 따를 값을 넘겨주지 않아 기본 값인 0으로 Name은 넘어온 값으로 설정된 것을 알 수 있습니다.
그림: 넘어 온 데이터 확인
(11) Post 메서드의 중단점 설정을 해제하고, 디버그 모드 실행을 멈춘 후 다시 Ctrl+F5를 눌러 DotNetNote 프로젝트를 실행합니다. 실행 후 다시 Postman으로 가서 아까 입력한 name을 3자 미만으로 입력하고 <Send>
버튼을 클릭하면 하단에 상태 코드로 "400 Bad Request"가 출력되고, "실패" 메시지가 반환될 것입니다.
그림: Web API에 대한 유효성 검사 진행
33.7.3 마무리
Web API를 생성한 후 Angular와 jQuery 등의 자바스크립트를 사용하여 코드를 작성해서 Get, Post, Put, Delete를 사용할 수 있습니다. 하지만 코드 작성 전에 Web API를 테스트하기에는 Postman, Fiddler 등의 외부 프로그램 도구를 사용하면 훨씬 빠르고 편리합니다. 이상으로 Postman을 사용하여 Web API의 Get과 Post 메서드를 직접 테스트해보았습니다.
33.8 [데모] 모델 클래스, Web API, jQuery까지 전체 단계 테스트
33.8.1 따라하기 1: Web API 생성 및 GET과 POST 테스트
Web API의 액션 메서드의 반환값이 List<T>
또는 IEnumerable<T>
형태인 코드를 살펴보겠습니다.
(1) PersonService Web API를 만들고 Get 및 Post를 테스트합니다.
코드: /DotNetNote/Models/PersonModel.cs
namespace DotNetNote.Models
{
public class PersonModel
{
public int Id { get; set; }
public string Name { get; set; }
}
}
코드: /DotNetNote/Controllers/PersonServiceController.cs
using DotNetNote.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace DotNetNote.Controllers
{
[Route("api/PersonService")]
public class PersonServiceController : Controller
{
[HttpGet]
public IEnumerable<PersonModel> Get()
{
return new List<PersonModel> {
new PersonModel { Id = 1, Name = "김태영" },
new PersonModel { Id = 2, Name = "박용준" },
new PersonModel { Id = 3, Name = "한상훈" }
};
}
[HttpPost]
public PersonModel Post([FromBody]PersonModel model)
{
// 중단점 설정 후 크롬 확장 도구인 Postman으로 테스트
return model;
}
}
}
33.8.2 따라하기 2: jQuery Ajax를 사용하여 Web API의 GET
및 POST 테스트
(1) PersonService Web API를 jQuery Ajax를 사용하여 호출합니다.
코드: /DotNetNote/Controllers/SamplesController.cs 코드 일부
/// <summary>
/// PersonServiceController 테스트
/// </summary>
public IActionResult PersonSerivceTest()
{
return View();
}
/DotNetNote/Views/Samples/PersonServiceTest.cshtml
@{
Layout = null;
}
<h1>jQuery Ajax를 사용하여 Web API의 GET 및 POST 테스트</h1>
<ul id="lstPerson">
</ul>
<input type="button" name="btnPost" id="btnPost" value="POST 테스트" />
<script src="~/lib/jquery/dist/jquery.js"></script>
<script>
$(function () {
var API_URL = "/api/PersonService";
// GET 메서드 테스트
$.getJSON(API_URL, function (data) {
$.each(data, function (key, val) {
var str = val.id + ", " + val.name;
$("<li />", { html: str }).appendTo("#lstPerson");
});
});
// POST 메서드 테스트
$("#btnPost").click(function () {
var json = '{ "id": "1234", "name": "홍길동"}';
$.ajax({
url: API_URL,
cache: false,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: json,
statusCode: {
200: function (data) {
var str = data.id + ", " + data.name;
$("<li />", { html: str }).appendTo("#lstPerson");
}
}
});
});
});
</script>
(2) 앞의 코드를 웹브라우저로 실행하면 다음과 같이 실행됩니다.
33.9 [실습] 컬렉션 형태의 데이터를 JSON으로 출력하기
33.9.1 소개
Web API를 사용하여 컬렉션 형태의 데이터를 JSON으로 출력하는 예제를 만들어보겠습니다. 여러 개의 클래스 파일과 뷰 페이지를 따로 만들지 않고, 클래스 파일 하나에서 모두 만들어보겠습니다.
33.9.2 따라하기
(1) ASP.NET Core 프로젝트인 DotNetNote 프로젝트를 열고, Controllers 폴더에 BoardSummaryApiController.cs 이름으로 클래스 파일을 생성합니다.
(2) BoardSummaryApiController.cs 파일에 다음과 같이 코드를 입력합니다.
***코드: /Controllers/BoardSummaryApiController.cs ***
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
namespace DotNetNote.Controllers
{
// [1] 모델 클래스
public class BoardSummaryModel
{
public int Id { get; set; }
public string Alias { get; set; } // Notice, Free, Photo, ...
public string Title { get; set; }
public string Name { get; set; }
public DateTime PostDate { get; set; }
}
// [2] 리포지토리 클래스
public class BoardSummaryRepository
{
public List<BoardSummaryModel> GetAll()
{
// 인 메모리 데이터베이스 => 실제 데이터베이스
var lists = new List<BoardSummaryModel>() {
new BoardSummaryModel {
Id = 1, Alias = "Notice", Name = "홍길동",
Title = "공지사항입니다.", PostDate = DateTime.Now },
new BoardSummaryModel {
Id = 2, Alias = "Free", Name = "백두산",
Title = "자유게시판입니다.", PostDate = DateTime.Now },
new BoardSummaryModel {
Id = 3, Alias = "Photo", Name = "임꺽정",
Title = "사진게시판입니다.", PostDate = DateTime.Now },
new BoardSummaryModel {
Id = 4, Alias = "Notice", Name = "홍길동",
Title = "공지사항입니다.", PostDate = DateTime.Now }
};
return lists;
}
public List<BoardSummaryModel> GetByAlias(string alias)
{
return GetAll().Where(b => b.Alias == alias).ToList();
}
}
// [3] Web API
[Produces("application/json")]
[Consumes("application/json")]
[Route("api/BoardSummaryApi")]
public class BoardSummaryApiController : Controller
{
private BoardSummaryRepository _repository;
public BoardSummaryApiController()
{
_repository = new BoardSummaryRepository();
}
[HttpGet]
public IEnumerable<BoardSummaryModel> Get()
{
return _repository.GetAll();
}
[HttpGet("{alias}", Name = "Get")]
public IEnumerable<BoardSummaryModel> Get(string alias)
{
return _repository.GetByAlias(alias);
}
}
// [4] 컨트롤러
public class BoardSummaryDemoController : Controller
{
public IActionResult Index()
{
string html = @"
<div id='print'></div>
<script src='https://code.jquery.com/jquery-2.2.4.min.js'></script>
<script>
$(function() {
$.getJSON('/api/BoardSummaryApi', function(data) {
var str = '<dl>';
$.each(data, function (index, entry) {
str += '<dt>' + entry.title + '</dt><dd>' + entry.name + '</dd>';
});
str += '</dl>';
$('#print').html(str);
});
});
</script>
";
ContentResult cr = new ContentResult() {
ContentType = "text/html", Content = html };
return cr;
}
}
}
[1]번 주석 영역은 컬렉션 형태를 구성할 모델 클래스로 BoardSummaryModel
클래스를 생성합니다. 일반적으로 이러한 모델 클래스는 Models 폴더에 하나의 클래스 파일로 관리합니다.
[2]번 주석 영역은 실제 데이터베이스가 아닌 가상으로 인 메모리 데이터베이스를 사용했습니다. 약 네 개의 테스트 데이터를 담아서 반환시켜 주는 메서드 두 개를 리포지토리 클래스로 구성했습니다.
[3]번 주석 영역은 Web API를 사용하여 전체 레코드를 JSON으로 반환하거나 특정 별칭(Alias)에 따른 JSON 데이터를 출력하는 HTTP GET 메서드를 두 개 만들었습니다.
[4]번 주석 영역은 따로 뷰 페이지를 생성하지 않고 컨트롤러의 액션 메서드에서 HTML 코드와 자바스크립트 코드를 동적으로 생성해서 앞서 생성한 Web API를 jQuery로 호출하여 그 결과를 문자열로 출력하는 내용을 만들어 보았습니다. 여기서 주의할 점은 Web API의 값을 JSON으로 출력할 때 따로 첫 글자 소문자 규칙이 적용되었기에 entry.Title 부분을 entry.title처럼 소문자로 사용해야 합니다.
(3) BoardSummaryApiController.cs 파일의 내용을 모두 작성한 후 웹 브라우저로 실행하여 /api/BoardSummaryApi 경로를 요청하면 JSON 형태로 전체 레코드가 출력됩니다.
그림: Web API 실행 후 JSON 렌더링
(4) 만약 API 호출 시 매개변수로 Notice, Free 등의 문자열을 입력하면 해당 문자열의 별칭(Alias)을 갖는 데이터만을 JSON을 출력합니다.
그림: Web API에 매개변수 전달 후 JSON 렌더링
(5) 다음은 Web API가 아닌 /BoardSummaryDemo 경로를 요청한 BoardSummaryDemo 컨트롤러를 실행한 결과다. 따로 뷰 페이지를 만들어서 출력하는 게 기본이지만, 이번 예제에서는 컨트롤러에서 직접 HTML과 JavaScript 코드를 생성한 후 이를 실행하는 형태로 구성해보았습니다.
*그림: Web API를 jQuery로 읽어다 출력하는 BoardSummaryDemo 컨트롤러 실행
33.9.3 마무리
ASP.NET Web API는 서버 측의 데이터를 자바스크립트 같은 클라이언트 측에서 사용하기 편하도록 JSON 형태로 데이터를 전달하고자 할 때 사용되는 기술입니다. 이번 실습에서는 실제 데이터베이스 사용 전에 인 메모리 데이터베이스를 사용하여 컬렉션 형태의 데이터를 JSON으로 출력하는 내용을 다뤄 보았습니다.
33.10 CORS (Cross Origin Resource Sharing)
소개
이번 강좌에서는 CORS (Cross Origin Resource Sharing, 원본 간 리소스 공유)에 대해 설명하고, 실제로 이를 구현하는 방법을 데모를 통해 살펴볼 예정입니다. CORS에 대한 기본적인 정보는 Microsoft의 공식 문서를 참조했습니다:
웹 브라우저는 기본적으로 '동일 출처 정책(Same-Origin Policy)'을 적용하여, 한 도메인에서 로드된 웹 페이지가 다른 도메인의 리소스를 요청하는 것을 제한합니다. 이 정책은 악성 사이트가 사용자 데이터를 읽거나 조작하는 것을 방지합니다. 하지만 때때로 안전하고 허용된 출처에서 API 등의 리소스 요청이 필요할 수 있습니다. 예를 들어, 독립적으로 호스팅되는 Web API가 다른 웹 애플리케이션과 통신해야 할 경우 CORS를 활성화하여 동일 출처 정책을 완화할 수 있습니다.
동일 출처 정책에 대한 보다 자세한 설명은 MDN 웹 문서에서 확인할 수 있습니다:
ASP.NET Core에서의 CORS 설정
기본 CORS 설정
ASP.NET Core에서는 Web API를 통해 데이터를 제공할 때, 다른 도메인의 웹 애플리케이션에서도 해당 데이터에 접근할 수 있도록 CORS를 설정할 수 있습니다. Startup.cs
파일의 ConfigureServices
메서드에서 CORS 정책을 정의하고, Configure
메서드에서 이 정책을 활성화시켜야 합니다.
다음은 모든 요청을 허용하는 CORS 설정 예시입니다:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors("AllowAll");
}
이 설정은 모든 원본(origin), 모든 HTTP 메서드, 모든 헤더를 허용합니다. 이는 개발 초기 단계에서 편리할 수 있지만, 보안상의 이유로 출시 전에는 더 제한적인 정책을 설정하는 것이 좋습니다.
특정 도메인 허용 설정
보다 제한적인 CORS 설정을 위해, 특정 도메인만 요청을 허용하도록 설정할 수 있습니다:
services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder.WithOrigins("https://example.com")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
app.UseCors();
위 코드에서 WithOrigins
메서드를 사용하여 특정 도메인을 명시하며, AllowAnyMethod()
와 AllowAnyHeader()
는 해당 도메인에서 모든 종류의 HTTP 요청과 헤더를 허용하도록 설정합니다.
참고 동영상
"CORS 설정 방법"에 관한 더 자세한 내용을 이해하고자 하신다면, "Web API에 CORS(Cross Origin Resource Sharing) 설정하기"라는 제목의 동영상 강좌를 참조하시기 바랍니다. 이 강좌는 DotNetNote 프로젝트 내 ApiHelloWorldWithValueController.cs
파일을 예로 사용하여 CORS의 구현 방법을 설명합니다.
CORS 설정 방식 종류
CORS 설정을 구현하는 방법은 다양합니다:
- 컨트롤러/액션 단위 설정: 특정 컨트롤러나 액션에만 CORS를 적용하려면
[EnableCors]
어노테이션을 사용합니다. - 전체 프로젝트 설정: 전체 애플리케이션에 걸쳐 CORS를 적용하려면
app.UseCors()
미들웨어를 사용합니다.
ASP.NET Core 프로젝트에 CORS 설정하기
1. 필요한 패키지 추가
ASP.NET Core 프로젝트에 CORS를 설정하기 전에, 필요한 NuGet 패키지를 추가합니다. 대부분의 현대 프로젝트에는 기본적으로 포함되어 있습니다:
Microsoft.AspNetCore.Cors
2. CORS 정책 정의
Startup.cs
파일의 ConfigureServices()
메서드에서 원하는 CORS 정책을 추가합니다. 다음 예시에서는 모든 원본에서 모든 종류의 요청을 허용하는 정책을 설정합니다:
services.AddCors(options =>
{
options.AddPolicy("AllowAnyOrigin", builder =>
{
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
3. CORS 정책 적용
설정한 CORS 정책을 Startup.cs
파일의 Configure()
메서드에서 활성화합니다:
app.UseCors("AllowAnyOrigin");
4. 특정 컨트롤러에 CORS 적용
특정 API 컨트롤러에서 다음과 같이 EnableCors
어노테이션을 사용하여 CORS를 적용할 수 있습니다:
[Route("api/[controller]")]
[EnableCors("AllowAnyOrigin")]
public class HeroesController : Controller
{
// Controller methods here
}
CORS 설정 다양성
ASP.NET Core에서는 다양한 방식으로 CORS를 설정할 수 있습니다. Startup.cs
파일에 다음과 같은 설정 코드 조각을 참고하십시오:
// CORS 정책을 추가하고, 특정 출처만 허용하는 설정
services.AddCors(options =>
{
options.AddPolicy("EntriesPolicy", builder =>
{
builder.WithOrigins("https://example.com").AllowAnyHeader().AllowAnyMethod();
});
});
// 정책 적용
app.UseCors("EntriesPolicy");
위 설정은 https://example.com
도메인으로부터의 요청만을 허용하도록 CORS 정책을 구성합니다. 이러한 방식으로, 보안을 강화하며 필요에 따라 유연하게 CORS를 설정할 수 있습니다.
ASP.NET Core Web API와 MVC 프로젝트를 이용한 CORS 설정 실습
이 튜토리얼에서는 ASP.NET Core를 사용하여 Web API를 생성하고, 별도의 ASP.NET Core MVC 프로젝트에서 JavaScript fetch()
를 이용해 해당 API를 호출하는 과정을 설명합니다. 이 과정에서 CORS 정책으로 인해 실패하는 상황과 이를 해결하는 방법을 단계별로 살펴보겠습니다.
1. ASP.NET Core Web API 프로젝트 생성
첫 단계로 DotNetNote.Apis
라는 이름의 Web API 프로젝트를 생성합니다. 이 프로젝트는 간단한 데이터를 반환하는 API 엔드포인트를 포함합니다.
dotnet new webapi -n DotNetNote.Apis
ValuesController.cs 파일을 다음과 같이 수정하여 테스트 데이터를 반환하도록 설정합니다:
[ApiController]
[Route("[controller]")]
public class ValuesController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new string[] { "value1", "value2" });
}
}
2. ASP.NET Core MVC 프로젝트 생성
다음으로, DotNetNote
라는 이름의 ASP.NET Core MVC 프로젝트를 생성합니다. 이 프로젝트에서는 HTML 페이지를 통해 JavaScript fetch()
를 사용하여 Web API를 호출합니다.
dotnet new mvc -n DotNetNote
3. JavaScript fetch()를 사용한 API 호출 실패
MVC 프로젝트의 wwwroot 폴더 내에 index.html
파일을 생성하고, 다음과 같이 작성하여 API를 호출합니다:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test API Call</title>
</head>
<body>
<h1>API Data</h1>
<ul id="data"></ul>
<script>
fetch('http://localhost:5000/values')
.then(response => response.json())
.then(data => {
const list = document.getElementById('data');
data.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
list.appendChild(li);
});
})
.catch(error => console.error('Unable to get items.', error));
</script>
</body>
</html>
이 단계에서는 Web API 프로젝트가 CORS를 허용하지 않으므로, 브라우저는 보안 정책을 위반한다고 판단하여 요청을 차단합니다.
4. Web API 프로젝트에서 CORS 설정
DotNetNote.Apis
프로젝트의 Startup.cs
파일을 수정하여 CORS를 허용하도록 설정합니다:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://localhost:5002")); // MVC 프로젝트 URL
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
// CORS 정책 사용
app.UseCors("AllowSpecificOrigin");
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
5. JavaScript에서 fetch() 성공
이제 index.html
페이지를 다시 로드하면, CORS 정책에 따라 DotNetNote.Apis
프로젝트에서 API 호출을 성공적으로 수행할 수 있습니다. 이로써, Web API와 MVC 프로젝트 간의 상호작용에서 CORS가 중요한 역할을 하는 것을 확인할 수 있습니다.
이 튜토리얼을 통해 웹 개발에서 CORS 설정의 중요성과 기본적인 구현 방법을 이해하고 실습할 수 있습니다.
33.11 [실습]
Teches 테이블부터 Angular 앱까지
33.11.1 소개
SQL Server에 테이블을 하나 생성하고, 이를 모델 클래스와 리포지토리 클래스를 거쳐 Web API로 CRUD를 구현해보겠습니다. 이를 Angular, React, Vue, jQuery 같은 자바스크립트 라이브러리 또는 Blazor를 사용하여 웹 브라우저에 표현합니다. 이러한 과정으로 전체 계층을 구성하는 간단한 웹 응용 프로그램을 작성해보겠습니다. 계속 같은 패턴대로 진행되는 데이터베이스 프로그래밍을 다시 복습하는 시간이 될 것입니다.
33.11.2 따라하기 1: SQL Server 데이터베이스 프로젝트에 테이블 생성
(1) C:\ASP.NET\DotNetNote.Database 프로젝트를 실행합니다.
(2) dbo\Tables 폴더에 Teches 테이블을 다음과 같이 생성하고, 로컬 DB인 DotNetNote에 게시합니다. 지금까지 같은 절차로 테이블과 저장 프로시저를 만들고 게시를 해왔기에 자세한 내용은 생략합니다. 테이블 구조는 간결하지만, 좀 더 복잡한 테이블 및 조인이 걸린 뷰에 대한 결과를 출력하는 웹 앱도 복잡도가 증가할 뿐 내용은 비슷합니다. 당연한 얘기겠지만, DB에 중점을 둔다면 다음 테이블에 데이터 입출력을 위한 저장 프로시저를 만들고, 이를 통해서 접근하도록 설정하면 더욱 좋은 방법이 될 것입니다.
***코드: DotNetNote.Database 프로젝트 – /dbo/Tables/Teches.sql ***
-- 기술 이름을 저장할 테이블
CREATE TABLE [dbo].[Teches]
(
[Id] INT NOT NULL PRIMARY KEY Identity(1, 1),
[Title] NVarChar(50) Not Null -- 기술 이름
)
Go
(3) 게시 완료 후 SQL Server 개체 탐색기를 열고 LocalDb에 접속하여 다음과 같이 Teches
테이블이 생성된 것을 확인합니다.
그림: Teches 테이블 생성 확인
33.11.3 따라하기 2: 데이터베이스 연결 문자열 설정 및 모델 클래스 구현하기
(1) ASP.NET Core 프로젝트인 C:\ASP.NET\DotNetNote 웹 프로젝트를 실행합니다.
(2) 데이터베이스 연결 문자열을 DotNetNote 프로젝트 루트에 있는 appsettings.json 파일에 기록합니다. 앞서 생성 또는 테이블을 추가한 로컬DB의 DotNetNote 데이터베이스에 대한 정보를 프로젝트 루트에 있는 appsettings.json 파일에 ConnectionStrings:DefaultConnection
항목으로 기록합니다.
코드: appsettings.json 파일에 데이터베이스 연결 문자열 추가
{
"ConnectionStrings": {
"DefaultConnection":
"Server=(localdb)\\mssqllocaldb;Database=DotNetNote;Trusted_Connection=True;"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
데이터베이스 연결 문자열은 ConnectionStrings:DefaultConnection
, ConnectionString
, …
형태로 원하는만큼 계층을 두고 관리할 수 있습니다.
(3) 이번에 제작하는 웹 앱도 데이터베이스 처리는 Micro ORM인 Dapper를 사용할 것입니다.
Visual Studio의 <보기 > 다른 창 > 패키지 관리자 콘솔>
메뉴로 패키지 관리자 콘솔을 실행합니다.
패키지 관리자 콘솔에서 다음과 같이 명령을 입력하고 엔터를 누른다. 아래 옵션에서 버전은 강의(책) 작성 시점의 버전을 표시하지만, -Version
옵션을 제거해서 가장 최신의 버전으로 사용해도 무관할 것입니다. 2023년 현재 Dapper 최신 버전은 3.0 버전대입니다.
PM> Install-Package Dapper -Version 1.50.2
패키지 관리자 콘솔에서 Dapper의 특정 버전을 내려받아 설치하는 과정입니다.
그림: Dapper 참조 추가
만약 최신 버전의 Dapper를 사용하려면 버전 정보를 빼고 실행해도 됩니다.
PM> Install-Package Dapper
(4) 프로젝트의 참조에서 Dapper가 등록되었는지 확인합니다. 만약 따로 프로젝트를 만들어 연습한다면 NuGet 패키지 관리자를 사용하여 Dapper의 최신 버전을 프로젝트에 추가합니다. 최신 버전을 사용한다면 다음 그림의 Dapper 버전이 책과 다를 수 있습니다.
그림: DotNetNote 프로젝트에 참조 추가된 Dapper
33.11.4 따라하기 3: 모델 클래스와 리포지토리 클래스 생성
(1) Teches 테이블과 일대일로 매핑되는 클래스인 Tech 클래스가 다음과 같이 Models
폴더에 생성되어 있는지 확인합니다. 만약 Tech.cs
클래스가 없으면 생성하고 다음과 같이 작성합니다(이 책에서 테이블의 이름은 복수형으로 구성하고, 클래스 이름은 단수형으로 만들었습니다. 또한, 모델 클래스 이름은 Tech
, TechModel
, TechViewModel
, TechDto
식으로 Model
또는 ViewModel
, Dto
등을 접미사로 붙이기도 합니다).
코드: /Models/Tech.cs
namespace DotNetNote.Models
{
/// <summary>
/// Teches 테이블과 일대일로 연결되는 클래스
/// </summary>
public class Tech
{
public int Id { get; set; }
public string Title { get; set; }
}
}
(2) Models 폴더에 ITechRepository.cs 파일을 생성하고 다음과 같이 작성합니다. 리포지토리 클래스에서 사용될 메서드에 대한 규약을 ITechRepository
에서 구현하는데 AddTech
와 GetTeches
메서드 두 개만 구현합니다.
연습이므로 입력과 출력 메서드만 구현하도록 합니다.
***코드: /Models/ITechRepository.cs ***
using System.Collections.Generic;
namespace DotNetNote.Models
{
public interface ITechRepository
{
void AddTech(Tech model); // 입력
List<Tech> GetTechs(); // 출력
}
}
(3) Models 폴더에 TechRepository
클래스를 생성하고 다음과 같이 작성합니다. 생성자에서 IConfiguration
인터페이스 형식의 매개변수를 받습니다. IConfiguration
인터페이스 개체로 데이터베이스 연결 문자열을 appsettings.json 파일로부터 읽어오는 코드가 포함되어 있습니다. SqlConnection
개체에 전달된 데이터베이스 연결 문자열을 바탕으로 데이터베이스 연결 개체를 생성하고, 이를 사용하여 데이터 입력과 출력 메서드를 구현하였습니다.
코드: /Models/TechRepository.cs
using Dapper;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
namespace DotNetNote.Models
{
public class TechRepository : ITechRepository
{
private IConfiguration _config;
private SqlConnection db;
public TechRepository(IConfiguration config)
{
_config = config;
// IConfiguration 개체를 통해서
// appsettings.json의 데이터베이스 연결 문자열을 읽어옵니다.
db = new SqlConnection(
_config.GetSection("ConnectionStrings").GetSection(
"DefaultConnection").Value);
}
// 입력
public void AddTech(Tech model)
{
string sql = "Insert Into Teches (Title) Values (@Title)";
var id = this.db.Execute(sql, model);
}
// 출력
public List<Tech> GetTechs()
{
string sql = "Select Id, Title From Teches Order By Id Asc";
return this.db.Query<Tech>(sql).ToList();
}
}
}
이번 리포지토리 클래스에서도 인라인 SQL 구문을 사용하여 Insert와 Select를 구성하였습니다. 그러나 SQL Server에 원하는 이름으로 저장 프로시저를 생성하고, 이를 통해서 접근하는 코드를 만들 수도 있습니다.
(4) (3)번 코드의 생성자에서 IConfiguration
인터페이스형 매개변수를 받습니다. 이에 대한 인스턴스는 Startup.cs 파일에서 자동으로 생성해주는데, Startup.cs 파일을 열고 ConfigureServices
메서드 하단에 다음 코드 한 줄을 추가합니다. 다른 나머지 코드는 생략된 모습입니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// [DNN][!] Configuration 개체 주입:
// IConfiguration 또는 IConfigurationRoot에 Configuration 개체 전달
// appsettings.json 파일의 데이터베이스 연결 문자열을
// 리포지토리 클래스에서 사용할 수 있도록 설정
services.AddSingleton<IConfiguration>(Configuration);
}
(5) 마찬가지로 Startup.cs 파일을 열고 ConfigureServices
메서드의 제일 하단에 다음 코드를 추가합니다. 이 코드를 추가하면 컨트롤러 클래스 또는 Web API 컨트롤러의 생성자에서 ITechRepository
인터페이스 매개변수에 대한 인스턴스를 TechRepository
클래스의 인스턴스로 자동으로 생성해줍니다. AddTransient
, AddSingleton
, AddScoped
, AddInstance의
네 가지 메서드 중 하나를 사용하여 의존성을 해결할 수 있는 것입니다. 참고로 AddInstance()
메서드는 최근 사용되지 않습니다. Startup.cs 파일에는 다음 한 줄 코드 이외에 DotNetNote 프로젝트에서 사용하는 모든 의존성 주입 관련 코드가 나열되어 있습니다.
***코드: Startup.cs 파일의 ConfigureServices() 메서드에 코드 추가 ***
using DotNetNote.Models;
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IConfiguration>(Configuration);
//[Tech] 기술 목록
services.AddTransient<ITechRepository, TechRepository>();
}
참고: TechContext
이름의 DbContext 클래스를 DotNetNote.Models에 생성
참고로 EF Core를 사용하기 위한 공식과 같은 코드는 다음과 같습니다.
***코드: /DotNetNote.Models/TechContext.cs ***
using Microsoft.EntityFrameworkCore;
using System.Configuration;
namespace DotNetNote.Models
{
public class TechContext : DbContext
{
public TechContext()
{
// Empty
}
public TechContext(DbContextOptions<TechContext> options)
: base(options)
{
// 공식과 같은 코드
}
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
string connectionString = ConfigurationManager
.ConnectionStrings["ConnectionString"].ConnectionString;
optionsBuilder.UseSqlServer(connectionString);
}
}
/// <summary>
/// 기술 리스트: [실습] Teches 테이블부터 Angular 앱 또는 Blazor 앱까지
/// </summary>
public DbSet<Tech> Teches { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Tech>().HasData(
new Tech { Id = 1, Title = "ASP.NET" },
new Tech { Id = 2, Title = "Blazor" });
}
}
}
DotNetNote 프로젝트에서 TechContext를 사용하기위해서는 다음 코드가 추가되어야 합니다.
코드: /DotNetNote/Startup.cs
// 새로운 DbContext 추가
services.AddEntityFrameworkSqlServer().AddDbContext<TechContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
33.11.5 따라하기 4: 기술 목록을 JSON으로 출력해주는 Web API 만들기
(1) DotNetNote 프로젝트의 Controllers 폴더에 TechesApiController.cs 파일을 생성한 후 Web API를 사용하여 RESTful 서비스를 다음과 같이 구현합니다. Web API를 구성하는 컨트롤러에 리포지토리 클래스인 ITechRepository
인터페이스를 생성자에 매개변수로 전달받아서 사용하는 모습을 볼 수 있습니다. Web API 코드의 상단에는 [Produces]
특성이 사용되었는데 옵션으로 application/json
을 주었습니다. 따라서 Web API 호출 시 데이터 형태가 JSON으로 출력됩니다. 추가로 [Route]
특성을 통해 /api/TechesApi/
경로로 Web API가 서비스되도록 설정하였습니다.
코드: /Controllers/TechesApiController.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using DotNetNote.Models;
namespace DotNetNote.Controllers
{
[Produces("application/json")]
[Route("api/TechesApi")]
public class TechesApiController : Controller
{
private ITechRepository _repo;
// 의존성 주입: ITechRepository의 인스턴스를 TechRepository의 인스턴스로
public TechesApiController(ITechRepository repo)
{
_repo = repo;
}
[HttpGet]
public IEnumerable<Tech> GetTech()
{
return _repo.GetTechs();
}
[HttpPost]
public Tech PostTech([FromBody] Tech tech)
{
_repo.AddTech(tech);
return tech;
}
}
}
참고로, 아래 코드는 컨트롤러 생성 스캐폴드에 의해서 자동으로 생성된 코드입니다.
코드: /DotNetNote/Controllers/Tech/TechServicesController.cs
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using DotNetNote.Models;
namespace DotNetNote.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TechServicesController : ControllerBase
{
private readonly TechContext _context;
public TechServicesController(TechContext context)
{
_context = context;
}
// GET: api/TechServices
[HttpGet]
public async Task<ActionResult<IEnumerable<Tech>>> GetTeches()
{
return await _context.Teches.ToListAsync();
}
// GET: api/TechServices/5
[HttpGet("{id}")]
public async Task<ActionResult<Tech>> GetTech(int id)
{
var tech = await _context.Teches.FindAsync(id);
if (tech == null)
{
return NotFound();
}
return tech;
}
// PUT: api/TechServices/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTech(int id, Tech tech)
{
if (id != tech.Id)
{
return BadRequest();
}
_context.Entry(tech).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TechExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/TechServices
[HttpPost]
public async Task<ActionResult<Tech>> PostTech(Tech tech)
{
_context.Teches.Add(tech);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTech", new { id = tech.Id }, tech);
}
// DELETE: api/TechServices/5
[HttpDelete("{id}")]
public async Task<ActionResult<Tech>> DeleteTech(int id)
{
var tech = await _context.Teches.FindAsync(id);
if (tech == null)
{
return NotFound();
}
_context.Teches.Remove(tech);
await _context.SaveChangesAsync();
return tech;
}
private bool TechExists(int id)
{
return _context.Teches.Any(e => e.Id == id);
}
}
}
(2) 프로젝트를 실행 후 웹 브라우저를 통해서 /api/techesapi
경로로 실행하면 다음과 같이 JSON 데이터가 출력됩니다.
그림: Web API 테스트
(3) SQL Server 개체 탐색기 등을 사용하여 Teches
테이블에 샘플로 데이터를 입력 후 출력하면 다음과 같이 JSON 데이터가 출력됩니다.
그림: Tech API Web API 테스트
33.11.6 따라하기 5: AngularJS 모듈과 컨트롤러 생성하기
이번 순서는 오래된 버전이기에 따라하기를 할 필요가 없습니다. 사실상 없어진 기술입니다.
(1) DotNetNote 프로젝트에는 이미 Bower를 통해서 angularjs가 추가된 상태입니다. wwwroot 폴더에 app 폴더를 만들고 이곳에 Tech 폴더를 생성하여 구분을 짓습니다. Tech 폴더에 TechApp.js 파일을 만들고 다음과 같이 Angular의 모듈을 작성합니다. 참고로 다음 코드 조각은 Visual Studio의 확장 기능인 Sidewaffle Template Pack에 의해서 자동 타이핑된 코드를 사용하였습니다. Sidewaffle에 대한 내용은 http://sidewaffle.com 사이트를 참고하기 바랍니다.
코드: /wwwroot/app/Tech/TechApp.js
(function () {
'use strict';
angular.module('TechApp', [
// Angular modules
// Custom modules
// 3rd Party Modules
]);
})();
(2) 같은 경로에 TechController.js 파일을 생성하고 Angular의 컨트롤러를 하나 생성합니다. 이곳에 Web API로부터 데이터를 읽어오고 전송하는 코드를 작성합니다. TechApp 이름의 모듈에 TechController 이름의 Angular 컨트롤러를 만들었습니다. $http 서비스를 주입 후 이를 통해서 get()과 post() 함수를 사용해서 데이터에 대한 입출력 코드를 Web API를 사용해서 할 수 있도록 구현한 코드입니다.
코드: /wwwroot/app/Tech/TechController.js
(function () {
'use strict';
angular
.module('TechApp')
.controller('TechController', TechController);
TechController.$inject = ['$scope', '$http'];
function TechController($scope, $http) {
$scope.title = 'Tech List';
// Web API의 GET 메서드 호출
$http.get("/api/techesapi").success(function (data) {
$scope.teches = data;
});
// Web API의 POST 메서드 호출
$scope.add = function () {
$http.post("/api/techesapi", this.NewTech).success(function (data) {
$scope.teches.push(data);
});
};
activate();
function activate() { }
}
})();
참고: Blazor TechPage.razor 페이지 구현
아래 샘플코드는 Blazor Preview를 사용하여 TechApp을 꾸며본 내용입니다.
코드: /BlazorNote/Pages/TechPage.razor
@*// Blazor Client 데모*@
@page "/techpage"
@inject HttpClient Http
@using DotNetNote.Models
@using Dul.Board
<h1>기술 리스트</h1>
<p>@*<BlazorLinkOnBuild>false</BlazorLinkOnBuild>*@</p>
<h2>출력</h2>
@if (teches == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
</tr>
</thead>
<tbody>
@foreach (var tech in teches)
{
<tr onclick="@(() => Details(tech.Id))">
<td>@tech.Id</td>
<td>@tech.Title</td>
</tr>
}
</tbody>
</table>
}
@if (teches != null)
{
<h3>입력</h3>
<div>
기술명: <input type="text" bind="@txtTitle" class="form-control"
style="width: 200px; display: inline;" />
@if (formType != BoardWriteFormType.Modify)
{
<input type="button" name="btnSave" value="저장"
class="btn btn-primary" onclick="@Add" />
}
@if (formType == BoardWriteFormType.Modify)
{
<input type="button" name="btnUpdate" value="수정"
class="btn btn-primary" onclick="@Update" />
<input type="button" name="btnUpdate" value="삭제"
class="btn btn-primary" onclick="@Delete" />
}
</div>
}
@functions {
private BoardWriteFormType formType = BoardWriteFormType.None;
}
@functions {
private string txtTitle;
private string baseUri = "http://localhost:57368/";
protected async Task Add()
{
formType = BoardWriteFormType.Write;
string postApi = $"{baseUri}api/TechServices";
Tech t = new Tech() { Title = txtTitle };
await Http.SendJsonAsync(HttpMethod.Post, postApi, t);
await OnInitAsync();
txtTitle = "";
}
}
@functions {
private Tech[] teches;
protected override async Task OnInitAsync()
{
formType = BoardWriteFormType.None;
string apiUrl = $"{baseUri}/api/TechServices";
teches = await Http.GetJsonAsync<Tech[]>(apiUrl);
}
}
@functions {
private int techId;
protected async Task Update()
{
formType = BoardWriteFormType.Modify;
string updateUri = $"{baseUri}api/TechServices/{techId}";
Tech t = new Tech { Id = techId, Title = txtTitle };
await Http.SendJsonAsync(HttpMethod.Put, updateUri, t);
await OnInitAsync();
txtTitle = "";
}
}
@functions {
protected async Task Delete()
{
formType = BoardWriteFormType.Modify;
string deleteUri = $"{baseUri}api/TechServices/{techId}";
await Http.SendJsonAsync(HttpMethod.Delete, deleteUri, null);
txtTitle = "";
await OnInitAsync();
}
}
@functions {
private Tech tech { get; set; }
protected async Task Details(int id)
{
formType = BoardWriteFormType.Modify;
string detailsUri = $"{baseUri}api/TechServices/{id}";
tech = await Http.GetJsonAsync<Tech>(detailsUri);
techId = tech.Id;
txtTitle = tech.Title;
}
}
33.11.7 따라하기 6: 전체 기능 테스트 페이지 구현
(1) 순수 HTML로 만들어서 입출력을 구현해도 되지만, MVC의 뷰 페이지에서 출력되도록 DotNetNote 프로젝트의 Controllers 폴더에 TechController.cs 파일로 컨트롤러 클래스를 생성합니다.
***코드: ~/Controllers/TechController.cs ***
using Microsoft.AspNet.Mvc;
namespace DotNetNote.Controllers
{
public class TechController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
(2) Views 폴더에 Tech 폴더를 생성 후 Tech
컨트롤러의 Index
액션에 해당하는 Index.cshtml 뷰 페이지를 다음과 같이 작성합니다. 레이아웃을 따로 사용하지 않기에 angular.js와 앞서 생성한 TechApp.js와 TechController.js 파일을 직접 추가합니다. 나머지 코드는 AngularJS의 전형적인 데이터 출력과 입력 관련 코드를 나타냅니다.
***코드: /Views/Tech/Index.cshtml ***
@{
Layout = null;
}
<div ng-app="TechApp">
<div ng-controller="TechController">
<h2>{{ title }}</h2>
<h3>출력</h3>
<ul ng-repeat="t in teches">
<li>{{ t.title }}</li>
</ul>
<h3>입력</h3>
<div>
기술명: <input type="text" name="Title" ng-model="NewTech.Title" />
<input type="button" name="name" value="저장"
class="btn btn-primary" ng-click="add()" />
</div>
</div>
</div>
<script src="~/lib/angular/angular.js"></script>
<script src="~/app/Tech/TechApp.js"></script>
<script src="~/app/Tech/TechController.js"></script>
(3) 프로젝트를 실행 후 /Tech/Index 경로를 요청하면 다음과 같이 Angular와 Web API를 사용하여 데이터 입력과 출력 페이지가 출력됩니다. 텍스트박스에 내용 입력 후 저장 버튼을 클릭하거나 현재 페이지가 새로고침되면 데이터베이스에 저장된 데이터가 출력됩니다.
그림: Angular와 Web API를 사용한 간단한 데이터 입출력 페이지 구현
33.11.8 마무리
이번 실습에서 서버 측 기능은 ASP.NET Web API로 클라이언트 측 기능은 Angular로 구현해보았습니다. 가장 일반적인 웹 개발 패턴입니다. 이를 진행하면서 부족한 부분이나 추가할 부분이 있다면 해당 부분을 좀 더 깊이 있게 연습해보면 좋을 것입니다. 이보다 좀 더 복잡한 형태는 뒤에서 진행할 게시판 프로젝트를 통해서 계속 살펴볼 것입니다. 참고로 모든 소스는 Models, Controllers 등의 기본 폴더에서 작성하고, 각각의 폴더에 Tech 폴더를 생성 후 이곳에 따로 모아서 관리해도 됩니다.
33.12. HttpClient
클래스를 사용하여 C#에서 Web API 호출하기
33.12.1 소개
앞서 작성한 Web API를 Windows Forms와 같은 클라이언트에서 접근할 때 HttpClient를 사용하여 Get과 Post 메서드를 진행하는 예제를 만들어보겠습니다.
참고용 Microsoft Docs는 다음 경로에 있습니다.
33.12.2 따라하기: HttpClient 클래스로 Web API 호출하기
(1) 프로젝트에 NuGet 패키지를 추가합니다.
- Microsoft.AspNet.WebApi.Client 패키지 추가
(2) Controller/Tech/TechController.cs 파일을 열고 GetTechAllWithHttpClient() 액션 메서드를 다음과 같이 추가합니다.
코드: TechController.cs
using DotNetNote.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace DotNetNote.Controllers
{
public class TechController : Controller
{
public IActionResult Index()
{
return View();
}
public async Task<IActionResult> GetTechAllWithHttpClient()
{
List<Tech> teches = new List<Tech>();
teches = await GetAll();
return View(teches);
}
/// <summary>
/// [1] GET: List of Tech 형태의 데이터를 JSON으로 받아오는 공식 코드
/// </summary>
private static async Task<List<Tech>> GetAll()
{
List<Tech> teches = new List<Tech>();
var baseUri = "http://localhost:16929/";
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUri);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
// 데이터 가져오기
HttpResponseMessage response = await httpClient.GetAsync("api/TechesApi");
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
teches = JsonConvert.DeserializeObject<List<Tech>>(json);
}
return teches;
}
}
}
(3) /Views/Tech/GetTechAllWithHttpClient.cshtml 뷰 페이지를 만들고 다음과 같이 코드를 작성합니다.
코드: GetTechAllWithHttpClient.cshtml
@model List<Tech>
@{
ViewData["Title"] = "TechWithHttpClient";
}
<h2>HttpClient 클래스를 사용하여 Web API 호출하기</h2>
<ul>
@foreach (var tech in Model)
{
<li>@tech.Id - @tech.Title</li>
}
</ul>
(4) 웹 프로젝트 실행 후 /Tech/GetTechAllWithHttpClient 경로를 요청하면 다음과 같이 출력이 됩니다. 출력 데이터는 앞선 작업에서 Teches 테이블에 데이터가 몇 개 추가되어 있다고 가정합니다.
33.12.3 마무리
이번에 사용한 예제 소스 코드는 웹, 모바일, 데스크톱에서 모두 사용할 수 있는 공통 코드입니다.
ASP.NET Core Web API를 사용하여 파일 다운로드
ASP.NET Core Web API를 사용하여 파일을 다운로드하는 방법은 간단합니다. 다음은 간단한 예제 코드입니다.
[ApiController]
[Route("[controller]")]
public class FileDownloadController : ControllerBase
{
private readonly IWebHostEnvironment _hostingEnvironment;
public FileDownloadController(IWebHostEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpGet]
[Route("download")]
public async Task<IActionResult> DownloadFile()
{
var filePath = Path.Combine(_hostingEnvironment.ContentRootPath, "file.txt");
var memory = new MemoryStream();
using (var stream = new FileStream(filePath, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var contentType = "application/octet-stream";
var fileName = Path.GetFileName(filePath);
return File(memory, contentType, fileName);
}
}
위 코드에서 FileDownloadController
는 IWebHostEnvironment
인터페이스를 사용하여 파일을 다운로드하는 기능을 제공합니다.
DownloadFile
메서드는 GET
요청을 처리하고, 서버에 있는 file.txt
파일을 다운로드합니다. File
메서드를 사용하여 파일을 반환하고 있습니다. File
메서드는 파일의 이름, 콘텐츠 유형 및 파일 데이터를 포함하는 FileContentResult
개체를 반환합니다. File
메서드에 전달되는 인수는 다음과 같습니다.
- 첫 번째 매개변수는 파일 데이터를 포함하는 스트림입니다.
- 두 번째 매개변수는 파일의 콘텐츠 유형입니다.
- 세 번째 매개변수는 다운로드될 파일의 이름입니다.
위 코드에서는 File
메서드의 첫 번째 매개변수로 파일 데이터를 포함하는 MemoryStream
객체를 전달하고 있습니다. 파일 데이터를 스트림에 복사한 후에는 스트림의 위치를 0
으로 재설정하여 스트림의 시작 부분에서 데이터를 읽도록 지정합니다. 콘텐츠 유형은 application/octet-stream
으로 지정되며, 이는 모든 유형의 파일을 지원하는 일반적인 유형입니다. 파일 이름은 file.txt
입니다.
이렇게 구현된 FileDownloadController
클래스를 사용하여 GET
요청을 수신하면 file.txt
파일이 다운로드되며, 클라이언트는 이 파일을 저장할 수 있습니다.
파일명에 따른 다운로드 타입 결정
ASP.NET Core Web API에서 파일 확장자에 따른 컨텐츠 타입을 자동으로 만들어주는 함수를 작성하기 위해서는 FileExtensionContentTypeProvider
클래스를 사용할 수 있습니다. 이 클래스는 파일 이름의 확장자에 따라 컨텐츠 유형을 찾을 수 있습니다.
다음은 FileExtensionContentTypeProvider
를 사용하여 파일 확장자에 따른 컨텐츠 타입을 자동으로 생성하는 함수입니다.
private string GetContentType(string fileName)
{
var provider = new FileExtensionContentTypeProvider();
if (!provider.TryGetContentType(fileName, out string contentType))
{
contentType = "application/octet-stream";
}
return contentType;
}
위 함수에서는 FileExtensionContentTypeProvider
클래스의 인스턴스를 만들고, TryGetContentType
메서드를 사용하여 파일 이름의 확장자에 따른 컨텐츠 유형을 가져옵니다. 확장자에 대한 컨텐츠 유형을 찾을 수 없는 경우, 기본값으로 "application/octet-stream"
값을 반환합니다.
이렇게 구현된 GetContentType
함수를 사용하여 파일의 컨텐츠 유형을 가져올 수 있습니다. 다음은 GetContentType
함수를 사용하여 파일의 컨텐츠 유형을 가져오는 예제입니다.
[HttpGet]
public IActionResult DownloadFile(string fileName)
{
var filePath = Path.Combine(_hostingEnvironment.WebRootPath, "files", fileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound();
}
var contentType = GetContentType(fileName);
var fileContentResult = new FileContentResult(System.IO.File.ReadAllBytes(filePath), contentType)
{
FileDownloadName = fileName
};
return fileContentResult;
}
위 예제에서 DownloadFile
메서드는 GET
요청을 처리하고, 파일 이름을 받아들입니다. Path.Combine
메서드를 사용하여 파일 경로를 만들고, 파일이 존재하지 않는 경우 NotFound
결과를 반환합니다.
GetContentType
함수를 사용하여 파일의 컨텐츠 유형을 가져오고, FileContentResult
클래스를 사용하여 파일 내용과 컨텐츠 유형을 설정합니다. 마지막으로, FileDownloadName
속성을 사용하여 파일을 다운로드 할 때 사용될 이름을 설정합니다.
이렇게 구현된 DownloadFile
메서드를 사용하여 파일을 다운로드할 수 있습니다.
33.13 ASP.NET Core Web API를 사용하여 파일 업로드
다음 코드는 Web API를 사용하여 파일을 업로드하는 공식과 같은 코드입니다. ASP.NET Core MVC 또는 JavaScript(앵귤러 등)에서 파일 업로드를 할 때에는 아래 코드를 활용하면 됩니다.
코드: /DotNetNoteCom/Controllers/WebAPI/FileUpload/FileUploadTestFinalController.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.IO;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace DotNetNoteCom.Controllers
{
[Route("api/[controller]")]
public class FileUploadTestFinalController : Controller
{
private readonly IWebHostEnvironment _environment;
public FileUploadTestFinalController(IWebHostEnvironment environment)
{
_environment = environment;
}
[HttpPost]
[Consumes("application/json", "multipart/form-data")]
// files 매개변수 이름은 <input type="file" name="files" />
public async Task<IActionResult> Post(ICollection<IFormFile> imgFile)
{
// 파일을 업로드할 폴더
var uploadFolder = Path.Combine(_environment.WebRootPath, "files");
foreach (var file in imgFile)
{
if (file.Length > 0)
{
// 파일명
var fileName = Path.GetFileName(
ContentDispositionHeaderValue.Parse(
file.ContentDisposition).FileName.Trim('"'));
using (var fileStream = new FileStream(
Path.Combine(uploadFolder, fileName), FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
}
}
return Ok(new { message = "OK" });
}
}
}
ASP.NET Core Web API를 사용하여 wwwroot 폴더의 files 폴더에 파일을 업로드하는 방법은 간단합니다. 다음은 간단한 예제 코드입니다.
[ApiController]
[Route("[controller]")]
public class FileUploadController : ControllerBase
{
private readonly IWebHostEnvironment _hostingEnvironment;
public FileUploadController(IWebHostEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpPost]
[Route("upload")]
public async Task<IActionResult> UploadFile(IFormFile file)
{
if (file == null || file.Length == 0)
{
return BadRequest("No file is selected.");
}
var fileName = Path.GetFileName(file.FileName);
var filePath = Path.Combine(_hostingEnvironment.WebRootPath, "files", fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return Ok();
}
}
위 코드에서 FileUploadController
는 IWebHostEnvironment
인터페이스를 사용하여 파일을 업로드하는 기능을 제공합니다.
UploadFile
메서드는 POST
요청을 처리하고, IFormFile
인터페이스를 사용하여 파일을 받아들입니다. 파일이 제대로 선택되지 않은 경우에는 BadRequest
를 반환합니다.
파일 이름을 가져오기 위해 file.FileName
속성을 사용하고, 파일 경로를 만들기 위해 Path.Combine
메서드를 사용합니다. 업로드 된 파일을 FileStream
으로 열어 파일 시스템에 저장합니다.
이렇게 구현된 FileUploadController
클래스를 사용하여 POST
요청을 수신하면, 클라이언트가 전송한 파일이 wwwroot/files
폴더에 저장되며, 저장된 파일의 경로는 클라이언트에게 반환되지 않습니다. 클라이언트는 파일 업로드 작업이 완료된 후에 파일을 다운로드하거나 업로드된 파일의 메타데이터를 사용하여 다른 작업을 수행할 수 있습니다.
jQuery로 파일 업로드 예시
다음은 HTML 코드와 jQuery 코드 예제입니다. 이 코드는 input 태그를 사용하여 파일을 선택한 후, jQuery 코드를 사용하여 선택한 파일을 서버로 전송합니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File Upload Example</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<input type="file" id="fileInput" />
<button id="uploadButton">Upload</button>
<div id="status"></div>
<script>
$(document).ready(function() {
$('#uploadButton').on('click', function() {
var fileInput = $('#fileInput')[0];
var file = fileInput.files[0];
var formData = new FormData();
formData.append('file', file);
$.ajax({
url: '/fileupload/upload',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function() {
$('#status').text('File uploaded successfully.');
},
error: function() {
$('#status').text('Error occurred while uploading the file.');
}
});
});
});
</script>
</body>
</html>
위 코드에서 input
태그를 사용하여 파일 선택기를 만듭니다. id
속성을 사용하여 파일 입력 요소에 대한 참조를 가져옵니다. 사용자가 파일을 선택하면 jQuery
코드가 선택한 파일을 가져와서 FormData
개체에 추가합니다. 그런 다음 ajax
메서드를 사용하여 파일을 업로드합니다.
ajax
메서드에 전달되는 옵션은 다음과 같습니다.
url
: 업로드할 파일을 처리하는 ASP.NET Core Web API 컨트롤러 메서드의 URL입니다.type
: 요청 메서드입니다. 파일을 업로드하려면POST
요청을 사용합니다.data
: 서버로 보낼 데이터입니다. 이 경우,FormData
개체를 전달합니다.processData
: 데이터를 처리할 것인지 여부를 지정합니다.false
로 설정하면FormData
개체를 직접 전송합니다.contentType
: 전송된 데이터의 콘텐츠 유형입니다.false
로 설정하면jQuery
가 이 값을 자동으로 설정합니다.success
: 요청이 성공적으로 완료되면 호출될 콜백 함수입니다.error
: 요청이 실패하면 호출될 콜백 함수입니다.
이렇게 구현된 jQuery
코드를 사용하여 파일을 업로드하면, 선택한 파일이 서버로 전송되고 FileUploadController
클래스의 UploadFile
메서드에서 파일이 처리됩니다. 파일 업로드 작업이 성공적으로 완료되면 success
함수가 호출되어, 업로드 작업에 대한 메시지가 화면에 출력됩니다.
API 버전 관리(Versioning)
ASP.NET Core Web API Versioning은 웹 API의 다양한 버전을 관리하는 기술입니다. 이를 통해 여러 버전의 웹 API를 안정적으로 지원하고, 이전 버전과의 호환성을 유지하며 새로운 버전을 추가할 수 있습니다.
Web API를 버전 관리하려면 다음 2개의 패키지를 설치합니다.
Microsoft.AspNetCore.Mvc.Versioning
Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
왜 버전 관리가 필요한가?
일반적으로 웹 API는 계속해서 업데이트되고 변경됩니다. 이에 따라 기존 API를 사용하는 클라이언트 애플리케이션이 영향을 받을 수 있습니다. 새로운 API를 출시하고 기존 API와의 호환성을 유지하려면, API를 버전별로 분리하여 버전을 관리해야 합니다.
예를 들어, 기존 API에 새로운 필드를 추가하는 경우, 이전 버전을 사용하는 클라이언트는 새로운 필드를 인식하지 못하고 오류가 발생할 수 있습니다. 이를 방지하기 위해 새로운 필드를 포함하는 새로운 API 버전을 만들고, 기존 API와 새로운 API 버전을 분리하여 버전 관리를 수행합니다.
버전 관리 방법
ASP.NET Core Web API에서는 다음과 같은 방법으로 버전 관리를 수행합니다.
URL 기반 버전 관리
URL 기반 버전 관리는 API 버전을 URL 경로에 포함하여 관리하는 방식입니다. 즉, 클라이언트는 요청하는 API 버전을 URL 경로에 포함하여 전송합니다. URL 기반 버전 관리 방법은 API 버전 관리를 쉽게 할 수 있는 방법 중 하나입니다.
// API 버전 관련 설정
services.AddApiVersioning(options =>
{
// API 버전 정보를 보고할 것인지 여부를 설정합니다.
options.ReportApiVersions = true;
// 기본 API 버전을 1.0으로 설정합니다.
options.DefaultApiVersion = new ApiVersion(1, 0);
// API 버전 정보가 지정되지 않은 경우 기본 버전을 사용할 것인지 여부를 설정합니다.
options.AssumeDefaultVersionWhenUnspecified = true;
// URL 세그먼트를 사용하여 API 버전 정보를 가져올 API 버전 판독기를 설정합니다.
options.ApiVersionReader = new UrlSegmentApiVersionReader();
// ValuesController 클래스가 버전 1.0을 사용하도록 지정합니다.
options.Conventions.Controller<ValuesController>().HasApiVersion(new ApiVersion(1, 0));
// ValuesV2Controller 클래스가 버전 2.0을 사용하도록 지정합니다.
options.Conventions.Controller<ValuesV2Controller>().HasApiVersion(new ApiVersion(2, 0));
});
HTTP 헤더 기반 버전 관리
HTTP 헤더 기반 버전 관리는 API 버전을 HTTP 요청 헤더에 포함하여 관리하는 방식입니다. 클라이언트는 HTTP 요청 헤더에 API 버전 정보를 포함하여 전송합니다.
// API 버전 관련 설정
services.AddApiVersioning(options =>
{
// API 버전 정보를 보고할 것인지 여부를 설정합니다.
options.ReportApiVersions = true;
// 기본 API 버전을 1.0으로 설정합니다.
options.DefaultApiVersion = new ApiVersion(1, 0);
// API 버전 정보가 지정되지 않은 경우 기본 버전을 사용할 것인지 여부를 설정합니다.
options.AssumeDefaultVersionWhenUnspecified = true;
// HTTP 헤더에서 API 버전 정보를 가져올 API 버전 판독기를 설정합니다.
options.ApiVersionReader = new HeaderApiVersionReader("api-version");
// ValuesController 클래스가 버전 1.0을 사용하도록 지정합니다.
options.Conventions.Controller<ValuesController>().HasApiVersion(new ApiVersion(1, 0));
// ValuesV2Controller 클래스가 버전 2.0을 사용하도록 지정합니다.
options.Conventions.Controller<ValuesV2Controller>().HasApiVersion(new ApiVersion(2, 0));
});
HTTP 헤더 기반 버전 관리는 API 버전을 HTTP 요청 헤더에 포함하여 관리하는 방식입니다. 클라이언트는 HTTP 요청 헤더에 API 버전 정보를 포함하여 전송합니다. 위 예제에서는 HTTP 요청 헤더의 api-version 키를 사용하여 API 버전을 전달합니다.
Query 문자열 기반 버전 관리
Query 문자열 기반 버전 관리는 API 버전을 요청 쿼리 문자열에 포함하여 관리하는 방식입니다. 클라이언트는 API 버전 정보를 요청 쿼리 문자열에 포함하여 전송합니다.
// API 버전 관련 설정
services.AddApiVersioning(options =>
{
// API 버전 정보를 보고할 것인지 여부를 설정합니다.
options.ReportApiVersions = true;
// 기본 API 버전을 1.0으로 설정합니다.
options.DefaultApiVersion = new ApiVersion(1, 0);
// API 버전 정보가 지정되지 않은 경우 기본 버전을 사용할 것인지 여부를 설정합니다.
options.AssumeDefaultVersionWhenUnspecified = true;
// Query 문자열에서 API 버전 정보를 가져올 API 버전 판독기를 설정합니다.
options.ApiVersionReader = new QueryStringApiVersionReader("api-version");
// ValuesController 클래스가 버전 1.0을 사용하도록 지정합니다.
options.Conventions.Controller<ValuesController>().HasApiVersion(new ApiVersion(1, 0));
// ValuesV2Controller 클래스가 버전 2.0을 사용하도록 지정합니다.
options.Conventions.Controller<ValuesV2Controller>().HasApiVersion(new ApiVersion(2, 0));
});
버전 관리 구성
ASP.NET Core Web API에서는 다음과 같은 구성을 통해 버전 관리를 설정할 수 있습니다.
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true; // 클라이언트에게 사용 가능한 API 버전을 알려주는 기능 활성화
options.DefaultApiVersion = new ApiVersion(1, 0); // 기본 API 버전 설정
options.AssumeDefaultVersionWhenUnspecified = true; // 클라이언트가 API 버전을 지정하지 않았을 때 기본 API 버전 사용 여부 설정
options.ApiVersionReader = new UrlSegmentApiVersionReader(); // 버전 관리 방식 설정
options.Conventions.Controller<ValuesController>().HasApiVersion(new ApiVersion(1, 0)); // API 버전 지정
options.Conventions.Controller<ValuesV2Controller>().HasApiVersion(new ApiVersion(2, 0));
});
- ReportApiVersions : true로 설정하면 응답 헤더에 사용 가능한 API 버전 정보가 포함됩니다.
- DefaultApiVersion : API 버전을 지정하지 않았을 때 기본적으로 사용할 API 버전을 설정합니다.
- AssumeDefaultVersionWhenUnspecified : 클라이언트가 API 버전을 지정하지 않았을 때 기본 API 버전을 사용할지 여부를 설정합니다.
- ApiVersionReader : URL, HTTP 헤더, Query 문자열 중 어떤 방법으로 API 버전을 지정할지 설정합니다.
- Conventions : API 버전을 지정하는 방법을 설정합니다. 예제에서는 Controller 단위로 API 버전을 지정합니다.
마무리
ASP.NET Core Web API Versioning을 사용하면 다양한 API 버전을 관리하고 이전 버전과의 호환성을 유지하면서 새로운 버전을 추가할 수 있습니다. 이를 통해 클라이언트의 요구에 맞는 최신 API 버전을 제공하여 개발자와 사용자 모두에게 좋은 경험을 제공할 수 있습니다.
로깅
ASP.NET Core Web API에서는 애플리케이션의 상태와 동작 정보를 수집하기 위해 Logging 기능을 제공합니다. Logging 기능을 사용하면, 애플리케이션에서 발생한 이벤트 및 오류 등의 정보를 수집하고, 이를 분석하여 애플리케이션을 개선할 수 있습니다.
Logging은 다양한 레벨의 로그를 생성할 수 있습니다. 로그 레벨은 메시지의 중요도에 따라 다양한 단계로 구성됩니다. ASP.NET Core Web API에서는 다음과 같은 로그 레벨을 제공합니다.
- Trace
- Debug
- Information
- Warning
- Error
- Critical
위에서부터 순서대로, 가장 낮은 중요도인 Trace 레벨에서 가장 높은 중요도인 Critical 레벨까지 존재합니다. 애플리케이션에서는 로그 레벨을 설정하여, 설정된 레벨 이상의 중요도를 가진 로그 메시지만 수집할 수 있습니다.
Logging 기능을 사용하기 위해서는, 다음과 같은 단계를 거쳐야 합니다.
- ASP.NET Core Web API 프로젝트에 필요한 패키지를 설치합니다.
- 로그를 구성합니다.
- 로그를 사용하여 메시지를 기록합니다.
ASP.NET Core Web API에서는 다양한 Logging Provider를 제공하며, 대표적으로 다음과 같은 Logging Provider를 사용할 수 있습니다.
- Console Logging Provider
- Debug Logging Provider
- EventSource Logging Provider
- TraceSource Logging Provider
ASP.NET Core Web API에서는 Microsoft.Extensions.Logging
패키지를 사용하여 Logging 기능을 구현합니다. Logging 기능을 사용하기 위해서는, 이 패키지가 설치되어 있어야 합니다.
다음은 Console Logging Provider를 사용하는 Logging 구성 예시입니다.
using Microsoft.Extensions.Logging;
public class MyClass
{
private readonly ILogger<MyClass> _logger;
public MyClass(ILogger<MyClass> logger)
{
_logger = logger;
}
public void DoSomething()
{
// Trace
_logger.LogTrace("This is a trace message.");
// Debug
_logger.LogDebug("This is a debug message.");
// Information
_logger.LogInformation("This is an information message.");
// Warning
_logger.LogWarning("This is a warning message.");
// Error
_logger.LogError("This is an error message.");
// Critical
_logger.LogCritical("This is a critical message.");
}
}
위 코드에서는 ILogger<T>
인터페이스를 사용하여 Logging을 구성합니다. ILogger<T>
인터페이스는 로깅 메시지에 대한 범주를 나타내는 타입 매개변수 T
를 사용합니다.
위에서 구현한 Logging 구성은 Console Logging Provider를 사용하기 때문에, 로그 메시지는 콘솔에 출력됩니다.
Logging Provider를 변경하려면, Logging Provider를 구성하는 방법에 따라서 달라질 수 있습니다. 대부분의 Logging Provider는 Microsoft.Extensions.Logging 패키지에서 제공되기 때문에, 이 패키지에 대한 이해가 필요합니다.
또한, Logging 메시지는 기본적으로 Microsoft.Extensions.Logging 패키지에서 제공하는 ILogger 인터페이스를 사용하여 작성합니다. 로그 메시지를 작성할 때는, 로그 레벨과 함께 로그 메시지를 기록합니다.
다음은 Logging 기능을 사용하는 예시 코드입니다.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
private readonly ILogger<MyController> _logger;
public MyController(ILogger<MyController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
_logger.LogInformation("Get 메서드 호출됨");
return Ok("Get 메서드 호출됨");
}
}
위 코드에서는 ILogger<T>
인터페이스를 사용하여 Logging을 구성하고 있습니다. ILogger<T>
인터페이스는 로깅 메시지에 대한 범주를 나타내는 타입 매개변수 T를 사용합니다.
Get
메서드에서는, ILogger
인터페이스를 사용하여 Logging 메시지를 작성하고 있습니다. Information
로그 레벨을 사용하여 "Get 메서드 호출됨"이라는 메시지를 기록하고 있습니다.
위 예제에서는 Microsoft.Extensions.Logging.Console
패키지를 사용하여 Console Logging Provider를 구성하고 있습니다. 이를 사용하지 않거나, 다른 Logging Provider를 사용하고자 할 경우, Microsoft.Extensions.Logging
패키지에서 제공하는 다른 Logging Provider를 사용하면 됩니다.
Logging 기능은 애플리케이션의 상태 및 동작 정보를 수집하여 분석할 수 있는 중요한 기능입니다. 이를 통해 애플리케이션의 성능을 개선하거나, 애플리케이션에 발생하는 이벤트를 실시간으로 모니터링할 수 있습니다.
Serilog.AspNetCore
로깅에 대한 결과를 파일로 저장할 때에는 Serilog를 검색해서 사용해보세요.
API 보안
[실습] 강사 리스트 앱 만들기: 테이블부터 Web API까지
퀴즈
퀴즈) 다양한 종류의 클라이언트에 웹 API를 제공하면 일반적으로 어떤 이점이 있습니까?
- 동일한 인터페이스
- 서버에서 사용되는 소프트웨어에 대한 독립성
- XML을 데이터 형식으로 사용
- 최적화된 네트워크 성능
정답: 1. 동일한 인터페이스
JWT 토큰과 ASP.NET Core
JWT 소개
JWT(JSON Web Token) 토큰이란?
JWT(JSON Web Token) 토큰은 인증과 권한 부여 등의 용도로 사용되는 자체 포함형 인증 토큰입니다. JWT 토큰은 헤더, 페이로드, 서명으로 구성됩니다.
- 헤더(Header): 토큰의 유형 및 서명 알고리즘을 정의합니다.
- 페이로드(Payload): 클레임(claim)이라는 데이터 조각으로 구성됩니다. 클레임은 사용자 정보, 권한 등의 정보를 포함합니다.
- 서명(Signature): 헤더와 페이로드의 조합을 기반으로 생성되는 서명은 토큰이 유효하고 변조되지 않았음을 보장합니다.
JWT 토큰은 안전하게 인코딩되어 있으며, 서버와 클라이언트 간에 전송될 때 HTTPS를 사용하여 보안을 유지해야 합니다. 또한 JWT 토큰은 한 번 발급되면 수정할 수 없으므로, 보안을 유지하기 위해 토큰의 유효 기간을 제한하고 필요에 따라 재발급해야 합니다.
JWT(JSON Web Token) 토큰 사용 방법
인증(Authentication)
서버에서는 로그인이나 인증에 성공한 사용자에 대해 JWT 토큰을 발급합니다. 이 JWT 토큰은 클라이언트 측에서 로컬 스토리지 등에 저장하며, 이후 모든 API 요청에서 헤더에 토큰을 전송하여 인증합니다.
권한 부여(Authorization)
JWT 토큰은 사용자의 권한 정보를 포함할 수 있으므로, 권한 부여에도 사용됩니다. 예를 들어, JWT 토큰의 페이로드에 "role" 클레임을 포함하여 사용자가 관리자인지 확인할 수 있습니다.
JWT(JSON Web Token) 토큰의 장단점
장점
- JWT 토큰은 클라이언트 측에서 인증을 처리할 수 있으므로, 서버 부하가 감소합니다.
- JWT 토큰은 안전하게 인코딩되어 있으며, 서명을 통해 토큰의 변조를 방지할 수 있습니다.
- JWT 토큰은 발급자가 정보를 포함할 수 있으므로, 권한 부여에도 사용될 수 있습니다.
단점
- JWT 토큰은 한 번 발급되면 수정할 수 없으므로, 토큰의 유효 기간을 제한하고 필요에 따라 재발급해야 합니다.
- JWT 토큰이 크기가 크다는 점이 단점으로 지적될 수 있습니다.
JWT(JSON Web Token) 토큰의 보안 문제
JWT 토큰의 보안 문제는 주로 서명 시 사용되는 알고리즘과 암호화 키의 관리에 대한 문제입니다.
알고리즘의 경우, 서버 측에서는 안전한 알고리즘을 사용해야 하며, 클라이언트 측에서는 불필요한 정보를 포함하지 않도록 주의해야 합니다. 또한, JWT 토큰은 서명을 통해 변조를 방지할 수 있지만, 암호화 키가 유출되면 서명도 무력화됩니다. 따라서, 암호화 키는 반드시 안전하게 관리되어야 합니다.
결론
JWT(JSON Web Token) 토큰은 자체 포함형 인증 토큰으로, 인증과 권한 부여에 사용됩니다. JWT 토큰은 클라이언트 측에서 인증을 처리할 수 있으므로, 서버 부하가 감소합니다. 하지만, 알고리즘과 암호화 키의 관리가 중요하며, 토큰의 유효 기간을 제한하고 재발급해야 합니다. 보안에 대한 주의를 기울여 안전하게 사용하면 유용한 도구가 될 수 있습니다.
JWT(JSON Web Token) 토큰 구성 요소
JWT(JSON Web Token) 토큰은 세 가지 구성 요소로 이루어져 있습니다.
헤더(Header)
JWT 토큰의 헤더는 다음과 같이 JSON 객체 형태로 작성됩니다.
{
"alg": "HS256",
"typ": "JWT"
}
- alg(Algorithm): 서명 알고리즘을 지정합니다. 대표적으로 HMAC SHA256 알고리즘이 사용됩니다.
- typ(Type): 토큰의 타입을 지정합니다. JWT 토큰은 대부분 "JWT"로 지정됩니다. 헤더는 base64Url로 인코딩되어 페이로드와 함께 토큰을 구성하는데 사용됩니다.
페이로드(Payload)
JWT 토큰의 페이로드는 다음과 같이 JSON 객체 형태로 작성됩니다.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
페이로드는 클레임(Claim)이라는 데이터 조각으로 구성됩니다. 클레임은 사용자 정보, 권한 등의 정보를 포함합니다.
클레임은 크게 다음과 같이 세 가지 유형으로 나눌 수 있습니다.
- 등록된 클레임(Registered claims): 토큰에서 사용되는 정보를 정의합니다.
- 공개 클레임(Public claims): 클라이언트와 서버가 서로 합의하고 사용할 수 있는 클레임으로, 사용자 정의 클레임을 포함할 수 있습니다.
- 비공개 클레임(Private claims): 서버와 클라이언트가 사전에 약속한 클레임으로, 서버와 클라이언트 간에만 교환됩니다.
클레임은 base64Url로 인코딩되어 헤더와 함께 토큰을 구성하는데 사용됩니다.
서명(Signature)
JWT 토큰의 서명은 헤더와 페이로드의 조합을 기반으로 생성됩니다. 서명은 다음과 같은 순서로 생성됩니다.
헤더와 페이로드를 base64Url로 인코딩합니다.
인코딩된 헤더와 페이로드를 마침표(.)로 연결합니다.
연결된 문자열을 서명 알고리즘으로 서명합니다.
서명은 다음과 같은 형식으로 표현됩니다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
서명은 토큰의 유효성을 검증할 때 사용되며, 서버 측에서는 서명 알고리즘과 암호화 키를 사용하여 서명을 검증합니다.
JWT(JSON Web Token) 토큰 구성 요소의 활용
JWT 토큰의 구성 요소는 인증과 권한 부여에 활용됩니다.
인증(Authentication)
JWT 토큰은 인증에 사용됩니다. 서버에서는 로그인이나 인증에 성공한 사용자에 대해 JWT 토큰을 발급합니다. 클라이언트 측에서는 JWT 토큰을 로컬 스토리지 등에 저장하며, 이후 모든 API 요청에서 헤더에 토큰을 전송하여 인증합니다.
권한 부여(Authorization)
JWT 토큰은 권한 부여에 사용됩니다. JWT 토큰의 페이로드에는 사용자 정보뿐만 아니라 권한 정보도 포함될 수 있습니다. 예를 들어, JWT 토큰의 페이로드에 "role" 클레임을 포함하여 사용자가 관리자인지 확인할 수 있습니다.
결론
JWT(JSON Web Token) 토큰은 헤더, 페이로드, 서명의 세 가지 구성 요소로 이루어져 있습니다. 각 구성 요소는 base64Url로 인코딩되어 토큰을 구성하는데 사용됩니다. JWT 토큰은 인증과 권한 부여에 사용되며, 서버에서는 안전한 알고리즘과 암호화 키를 사용하여 서명을 검증합니다. JWT 토큰을 활용하면 클라이언트 측에서 인증을 처리할 수 있으므로, 서버 부하가 감소하고, 사용자 정보와 권한을 안전하게 전달할 수 있습니다.
ASP.NET Core 6.0 Web API에서 JWT 토큰 사용
ASP.NET Core 6.0 Web API에서 JWT(JSON Web Token) 토큰을 사용하는 절차는 다음과 같습니다.
NuGet 패키지 설치
ASP.NET Core 6.0에서 JWT 토큰을 사용하려면 NuGet 패키지를 설치해야 합니다. Visual Studio에서는 NuGet 패키지 관리자를 사용하여 Microsoft.AspNetCore.Authentication.JwtBearer 패키지를 설치할 수 있습니다.
서비스 구성
JWT 토큰을 사용하기 위해 ConfigureServices 메서드에서 서비스를 구성해야 합니다. 다음과 같이 코드를 작성합니다.
public void ConfigureServices(IServiceCollection services)
{
// 인증 서비스 추가
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
// JWT 토큰 인증 서비스 추가
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false; // HTTPS를 사용하지 않는 경우 true로 설정
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("보안 키")),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
위 코드에서는 AddAuthentication 메서드를 사용하여 인증 서비스를 추가하고, AddJwtBearer 메서드를 사용하여 JWT 토큰 인증 서비스를 추가합니다. 이때 TokenValidationParameters를 설정하여 발급한 JWT 토큰을 검증합니다.
- RequireHttpsMetadata: HTTPS를 사용하는 경우 false로 설정합니다.
- SaveToken: 토큰을 저장할지 여부를 지정합니다.
- TokenValidationParameters: JWT 토큰의 검증을 위한 구성을 지정합니다.
인증 구성
Configure 메서드에서 인증 구성을 설정해야 합니다. 다음과 같이 코드를 작성합니다.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
위 코드에서는 UseAuthentication 메서드를 사용하여 인증을 구성합니다. 또한 UseAuthorization 메서드를 사용하여 권한 부여를 구성합니다.
UserDto 클래스는 사용자 정보를 전달하기 위한 데이터 전송 객체(DTO) 클래스입니다. 다음은 UserDto 클래스의 예제 코드입니다.
public class UserDto
{
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
}
위 코드에서는 Username, Password, Email 프로퍼티를 사용하여 사용자 정보를 전달합니다. 이 클래스는 사용자 인증을 위한 API 엔드포인트에서 사용됩니다.
JWT 토큰 발급
JWT 토큰을 발급하는 방법은 매우 다양합니다. 일반적으로는 로그인 시 사용자 정보를 인증하여 JWT 토큰을 발급합니다.
다음은 JWT 토큰을 발급하는 예제 코드입니다.
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody] UserDto userDto)
{
var user = _userService.Authenticate(userDto.Username, userDto.Password);
if (user == null)
{
return BadRequest(new { message = "Username or password is incorrect" });
}
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("보안 키");
var token = new JwtSecurityToken(
expires: DateTime.UtcNow.AddDays(7),
claims: new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
},
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new { token = tokenString });
}
위 코드에서는 인증 정보를 검증한 후, JwtSecurityToken 클래스를 사용하여 JWT 토큰을 발급합니다. expires 매개 변수를 사용하여 토큰의 유효 기간을 설정하고, claims 매개 변수를 사용하여 토큰에 추가할 클레임을 설정합니다. signingCredentials 매개 변수를 사용하여 JWT 토큰을 서명합니다.
- JWT 토큰 사용
JWT 토큰을 사용하는 방법은 각각의 API 엔드포인트에서 Authenticate 속성을 추가하는 것입니다.
다음은 JWT 토큰을 사용하는 예제 코드입니다.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public IActionResult Get()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var username = User.FindFirst(ClaimTypes.Name)?.Value;
var email = User.FindFirst(ClaimTypes.Email)?.Value;
return Ok(new { userId, username, email });
}
위 코드에서는 Authorize 속성을 사용하여 JWT 토큰을 사용함을 지정합니다. 이후 User 객체를 사용하여 클레임 정보에 접근할 수 있습니다.
JWT(JSON Web Token) 토큰을 사용하여 사용자 인증을 처리하는 ASP.NET Core 6.0 Web API의 전체 코드 예제입니다.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
namespace MyWebApi.Controllers
{
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly ILogger<UserController> _logger;
private readonly IUserService _userService;
public UserController(ILogger<UserController> logger, IUserService userService)
{
_logger = logger;
_userService = userService;
}
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody] UserDto userDto)
{
var user = _userService.Authenticate(userDto.Username, userDto.Password);
if (user == null)
{
return BadRequest(new { message = "Username or password is incorrect" });
}
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("보안 키");
var token = new JwtSecurityToken(
expires: DateTime.UtcNow.AddDays(7),
claims: new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
},
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new { token = tokenString });
}
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public IActionResult Get()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var username = User.FindFirst(ClaimTypes.Name)?.Value;
var email = User.FindFirst(ClaimTypes.Email)?.Value;
return Ok(new { userId, username, email });
}
}
}
위 코드에서는 UserController 클래스를 정의하고, Authenticate와 Get API 엔드포인트를 정의합니다. Authenticate 메서드에서는 UserDto 객체를 전달받아 사용자 인증을 처리하고, JWT 토큰을 발급합니다. Get 메서드에서는 Authorize 속성을 사용하여 JWT 토큰을 사용함을 지정하고, 클레임 정보를 반환합니다. 이 클래스에서는 IUserService 인터페이스를 사용하여 사용자 정보를 검색하며, 이 인터페이스는 DI를 사용하여 구현합니다.
결론
ASP.NET Core 6.0 Web API에서 JWT(JSON Web Token) 토큰을 사용하는 절차는 위와 같습니다. JWT 토큰을 사용하여 인증과 권한 부여를 처리하면, 클라이언트 측에서 인증을 처리할 수 있으므로, 서버 부하가 감소하고, 사용자 정보와 권한을 안전하게 전달할 수 있습니다.
ASP.NET Core Web API의 Authorization Policy
ASP.NET Core Web API에서 Authorization Policy는 요청을 인증하고 권한 부여를 제어하는 데 사용되는 일련의 규칙입니다. 이를 사용하면 특정 요청이 허용되는지 여부를 결정하고, 요청을 처리하기 전에 사용자가 해당 권한을 가지고 있는지 확인할 수 있습니다.
Authorization Policy는 애플리케이션의 권한 부여 규칙을 정의하고, 각 규칙은 하나 이상의 요구사항을 포함합니다. 이러한 요구사항은 보통 클레임, 역할 또는 요청의 특성을 나타내며, 이러한 요구사항 중 하나 이상이 충족되어야 해당 규칙을 따르는 요청이 허용됩니다.
ASP.NET Core Web API는 다양한 방법으로 Authorization Policy를 구성할 수 있습니다. 일반적으로는 Startup.cs 파일에서 ConfigureServices 메서드를 사용하여 권한 부여 서비스를 등록하고, Configure 메서드에서 인증 및 권한 부여 미들웨어를 설정합니다.
예를 들어, 클레임 기반 권한 부여를 사용하려면, 인증 및 권한 부여 미들웨어를 등록하고 클레임 기반 권한 부여 정책을 구성할 수 있습니다. 이 경우, 클레임에 대한 요구사항을 정의하는 정책을 만들고, 각 요청에 대해 해당 클레임이 있는지 확인합니다. 다른 권한 부여 방식도 사용할 수 있지만, 모든 경우에 요구사항을 정의하고, 이러한 요구사항이 충족되는지 확인하는 방법은 비슷합니다.
ASP.NET Core Web API에서는 [Authorize]
특성을 사용하여 요청에 대한 인증 및 권한 부여를 지정할 수 있습니다. 이 특성은 필터를 사용하여 Authorization Policy를 적용합니다. 또한 [AllowAnonymous]
특성을 사용하여 인증을 건너뛸 수 있습니다.
요약하면, ASP.NET Core Web API의 Authorization Policy는 요청에 대한 인증과 권한 부여를 제어하는 데 사용되는 규칙입니다. 각 규칙은 요구사항을 포함하며, 요청이 허용되는지 여부는 이러한 요구사항이 충족되는지 여부에 따라 결정됩니다. 이러한 규칙은 서비스 등록 및 미들웨어 설정을 통해 구성할 수 있으며, [Authorize]
특성을 사용하여 요청에 대한 인증 및 권한 부여를 지정할 수 있습니다.
또한, Authorization Policy를 조합하여 더 복잡한 권한 부여 규칙을 생성할 수도 있습니다. 예를 들어, [Authorize(Roles="Admin")]
특성을 사용하면 "Admin" 역할을 가진 사용자만 해당 요청에 대해 승인되도록 권한 부여 규칙을 만들 수 있습니다. 이러한 방식으로 권한 부여 정책을 설정하면, 해당 API 엔드포인트를 사용하려는 모든 요청이 해당 규칙을 따르도록 강제할 수 있습니다.
마지막으로, ASP.NET Core Web API에서는 사용자 지정 요구사항을 작성하여 Authorization Policy를 만들 수 있습니다. 이를 통해 기본적으로 제공되지 않는 특정 권한 요구사항을 구현할 수 있습니다. 예를 들어, 자체 데이터베이스에 저장된 사용자 정의 역할에 기반한 권한 부여를 구현할 수 있습니다. 이러한 요구사항은 IAuthorizationRequirement 인터페이스를 구현하여 만들 수 있습니다.
이러한 기능을 활용하면 ASP.NET Core Web API를 보다 안전하고 보안성 높은 애플리케이션으로 만들 수 있습니다. 그러나, 권한 부여 규칙을 잘못 설정하면 사용자에게 예상치 못한 오류가 발생하거나, 애플리케이션 보안이 침해될 수 있으므로 신중하게 구성해야 합니다.
ASP.NET Core Web API의 Authorization Policy를 사용하여 인증 및 권한 부여를 구성하는 방법을 자세히 설명했습니다. 이러한 기능을 활용하여 애플리케이션의 보안성을 강화하고 중요한 API 엔드포인트에 대한 접근을 통제할 수 있습니다.
ASP.NET Core 6.0 Web API에서 Open API를 사용하기
ASP.NET Core 6.0 Web API에서 Open API를 사용하기 위해서는 Swashbuckle.AspNetCore 패키지를 설치해야 합니다. 이 패키지는 Swagger UI와 함께 Open API를 자동으로 생성하므로 API 엔드포인트에 대한 API 문서를 생성하고 테스트할 수 있습니다.
아래는 ASP.NET Core 6.0 Web API에서 Open API를 사용하는 절차입니다.
- Swashbuckle.AspNetCore 패키지 설치
Swashbuckle.AspNetCore 패키지를 프로젝트에 설치합니다. 패키지 매니저 콘솔에서 다음 명령어를 입력하여 패키지를 설치할 수 있습니다.
Install-Package Swashbuckle.AspNetCore
- Swagger 미들웨어 등록
Swagger 미들웨어를 등록하여 Open API 문서를 제공합니다.
Startup.cs
파일의Configure
메서드에서 다음과 같이 미들웨어를 추가합니다.
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
UseSwagger 메서드는 Open API 문서를 제공하는 미들웨어를 추가합니다. UseSwaggerUI 메서드는 Swagger UI를 제공하는 미들웨어를 추가합니다. SwaggerEndpoint 메서드를 사용하여 Swagger UI에서 사용할 Open API 문서의 경로와 이름을 지정합니다.
- SwaggerGen 서비스 등록
SwaggerGen 서비스를 DI 컨테이너에 등록합니다. Startup.cs 파일의 ConfigureServices 메서드에서 다음과 같이 서비스를 추가합니다.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
AddSwaggerGen 메서드는 SwaggerGen 서비스를 DI 컨테이너에 등록합니다. SwaggerDoc 메서드를 사용하여 Open API 문서에 대한 정보를 지정합니다.
- API 문서 생성
API 컨트롤러 클래스에 대한 XML 주석을 작성하여 SwaggerGen이 API 문서를 생성하도록 합니다. 주석을 작성하려면 프로젝트의 속성에서 빌드 탭으로 이동하여 "XML 문서 파일" 옵션을 선택하고, XML 파일의 이름을 지정해야 합니다.
- 실행 및 확인
애플리케이션을 실행하고 https://localhost:<port>/swagger
에 접속하여 Swagger UI를 확인합니다. Open API 문서를 확인하고 API를 테스트해볼 수 있습니다.
이러한 방법을 통해 ASP.NET Core 6.0 Web API에서 Open API를 사용할 수 있습니다. Swashbuckle.AspNetCore 패키지를 사용하여 쉽게 API 문서를 생성하고 테스트할 수 있습니다.
REST 제약 조건 읽어보기
ASP.NET Core Web API에서 RESTful 웹 서비스를 구현할 때, REST 제약 조건은 중요한 역할을 합니다. REST 제약 조건은 아래와 같이 6가지로 구성됩니다.
클라이언트/서버 분리 (Client/Server separation)
- 클라이언트와 서버는 독립적으로 개발되어야 하며, 서로 간의 종속성이 없어야 합니다.
상태 없음 (Statelessness)
- 클라이언트의 각 요청은 서버에 대한 모든 필요한 정보를 포함해야 하며, 서버는 클라이언트의 상태를 유지하지 않아야 합니다.
캐시 가능 (Cacheability)
- 클라이언트는 응답을 캐시할 수 있어야 합니다. 서버는 캐시 제어 정책을 제공하여 클라이언트가 캐시의 효율성을 개선할 수 있어야 합니다.
계층화 (Layered system)
- 서버는 중개자(proxy)와 같은 계층을 통해 확장될 수 있습니다. 이렇게 하면 서버와 클라이언트 간의 직접적인 연결이 없어지므로, 보안, 로드 밸런싱, 공유 캐시 등을 구현하기 쉬워집니다.
인터페이스 일관성 (Uniform interface)
- 인터페이스는 클라이언트와 서버 간의 상호 작용에 대한 일관성을 유지해야 합니다. 이를 위해서는 자원 식별, 메시지를 통한 자원 조작, 자기 서술적 메시지 등의 방법을 사용해야 합니다.
코드 온 디맨드 (Code on demand, optional)
- 서버에서 클라이언트로 코드를 전송해 실행할 수 있는 기능을 제공하는 것입니다. 이는 선택적인 제약 조건이며, 대부분의 RESTful 웹 서비스에서는 사용되지 않습니다.
이러한 제약 조건들은 RESTful 웹 서비스의 유연성, 확장성, 보안성을 향상시키며, ASP.NET Core Web API에서 이러한 제약 조건을 준수하도록 구현할 수 있습니다.
[JsonIgnore]
특성 사용하기
ASP.NET Core Web API에서 [JsonIgnore]
특성은 JSON 직렬화 시 필드를 무시하는 데 사용됩니다. 이 특성을 속성에 적용하면, 해당 속성은 JSON으로 직렬화할 때 무시됩니다. 이를 통해 개발자는 필요하지 않은 데이터를 제외하고 필요한 데이터만 반환할 수 있으며, API의 응답 크기를 줄일 수 있습니다.
예를 들어, 다음과 같은 모델 클래스가 있다고 가정해 봅시다.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public decimal Price { get; set; }
}
위 코드에서, [JsonIgnore]
특성이 Price 속성에 적용되어 있습니다. 따라서 Price 속성은 JSON 직렬화 시 무시됩니다.
예를 들어, 다음과 같은 GET 메서드가 있다고 가정해 봅시다.
[HttpGet("{id}")]
public ActionResult<Product> Get(int id)
{
var product = _repository.GetProduct(id);
if (product == null)
{
return NotFound();
}
return product;
}
위 코드에서, Get 메서드가 Product 객체를 반환합니다. 하지만, Product 클래스에서 [JsonIgnore]
특성이 적용된 Price 속성은 반환되지 않습니다. 즉, API 응답에는 Id와 Name 속성만 포함됩니다.
총괄적으로, [JsonIgnore]
특성을 사용하면 ASP.NET Core Web API에서 JSON 직렬화 시 필요하지 않은 데이터를 제외할 수 있습니다. 이를 통해 API의 응답 크기를 줄이고, 필요한 데이터만 반환할 수 있습니다.