1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
using Microsoft.AspNetCore.Mvc; namespace CoinAPI.Controllers { [ApiController] [Route("api2/[controller]")] public class OSIController : Controller { private readonly ILogger<OSIController> _logger; private readonly IConfiguration Configuration; public OSIController(ILogger<OSIController> logger, IConfiguration configuration) { _logger = logger; Configuration = configuration; } // 샘플 데이터 (실제 DB 대신 사용) private static List<OSIModel> _osiList = new List<OSIModel> { new OSIModel { Id = 1, Code = "OSI1", Description = "Special meal request" }, new OSIModel { Id = 2, Code = "OSI2", Description = "Wheelchair assistance" } }; // GET: api/OSI [HttpGet] public IActionResult GetAll() { return Ok(_osiList); } // GET: api/OSI/1 [HttpGet("{id}")] public IActionResult GetById(int id) { var osi = _osiList.FirstOrDefault(o => o.Id == id); if (osi == null) return NotFound(new { message = "해당 OSI 정보를 찾을 수 없습니다." }); return Ok(osi); } // POST: api/OSI [HttpPost] public IActionResult Create([FromBody] OSIModel newOsi) { if (!ModelState.IsValid) return BadRequest(ModelState); newOsi.Id = _osiList.Max(o => o.Id) + 1; _osiList.Add(newOsi); return CreatedAtAction(nameof(GetById), new { id = newOsi.Id }, newOsi); } // PUT: api/OSI/1 [HttpPut("{id}")] public IActionResult Update(int id, [FromBody] OSIModel updatedOsi) { var osi = _osiList.FirstOrDefault(o => o.Id == id); if (osi == null) return NotFound(new { message = "수정할 OSI 정보를 찾을 수 없습니다." }); osi.Code = updatedOsi.Code; osi.Description = updatedOsi.Description; return Ok(osi); } // DELETE: api/OSI/1 [HttpDelete("{id}")] public IActionResult Delete(int id) { var osi = _osiList.FirstOrDefault(o => o.Id == id); if (osi == null) return NotFound(new { message = "삭제할 OSI 정보를 찾을 수 없습니다." }); _osiList.Remove(osi); return NoContent(); } } // 샘플 모델 클래스 public class OSIModel { public int Id { get; set; } public string Code { get; set; } public string Description { get; set; } } } |

실무에서 권장되는 사용 패턴
작업 | 응답 코드 | 응답 메서드 | 응답 Body |
---|---|---|---|
생성(Create) | 201 | CreatedAtAction() | 생성된 데이터 |
조회(Read) | 200 | Ok() | 데이터 |
업데이트(Update) | 200 | Ok() | 업데이트된 데이터 |
삭제(Delete) | 204 | NoContent() | 없음 |
완전한 CRUD 컨트롤러 + Swagger 설정 예제
1) Program.cs (Swagger + MVC 설정)
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 Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "OSI API", Version = "v1", Description = "Sample CRUD with CreatedAtAction / Ok / NoContent" }); }); var app = builder.Build(); app.UseHttpsRedirection(); app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "OSI API v1"); c.RoutePrefix = string.Empty; // https://localhost:5001/ 진입 시 Swagger 열기 }); app.MapControllers(); app.Run(); |
2) 모델 (유효성 검사 포함)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System.ComponentModel.DataAnnotations; public class OSIModel { public int Id { get; set; } [Required, StringLength(60)] public string PassengerName { get; set; } = string.Empty; [Required, StringLength(3, MinimumLength = 2)] public string Airline { get; set; } = string.Empty; // 예: KE, OZ, AA [Required, StringLength(200)] public string Message { get; set; } = string.Empty; // 예: CTCE/EMAIL@DOMAIN.COM public bool IsActive { get; set; } = true; } |
3) 간단한 인메모리 저장소 (데모용)
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 |
using System.Collections.Concurrent; public static class OsiStore { // Thread-safe private static readonly ConcurrentDictionary<int, OSIModel> _map = new(); private static int _seq = 0; public static IEnumerable<OSIModel> GetAll() => _map.Values.OrderBy(x => x.Id); public static OSIModel? Get(int id) => _map.TryGetValue(id, out var v) ? v : null; public static OSIModel Add(OSIModel m) { var id = Interlocked.Increment(ref _seq); m.Id = id; _map[id] = m; return m; } public static bool Update(int id, OSIModel m) { if (!_map.ContainsKey(id)) return false; m.Id = id; _map[id] = m; return true; } public static bool Remove(int id) => _map.TryRemove(id, out _); } |
4) 컨트롤러 (CRUD + 상태코드 명확화)
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.Mvc; [ApiController] [Route("api/[controller]")] public class OSIController : ControllerBase { /// <summary>전체 조회</summary> [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult<IEnumerable<OSIModel>> GetAll() => Ok(OsiStore.GetAll()); /// <summary>단건 조회</summary> [HttpGet("{id:int}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult<OSIModel> GetById(int id) { var item = OsiStore.Get(id); if (item is null) return NotFound(); return Ok(item); // 200 + 본문 } /// <summary>생성 (201 Created + Location 헤더)</summary> [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public ActionResult<OSIModel> Create([FromBody] OSIModel newOsi) { if (!ModelState.IsValid) return ValidationProblem(ModelState); var created = OsiStore.Add(newOsi); // 핵심: 201 Created + Location 헤더 (GET /api/OSI/{id}로 이동 가능) return CreatedAtAction( nameof(GetById), // 대상 액션 new { id = created.Id }, // 라우트 값 (이름 일치 중요!) created // 응답 바디 ); } /// <summary>전체 수정 (성공 시 204 No Content)</summary> [HttpPut("{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult Update(int id, [FromBody] OSIModel updated) { if (!ModelState.IsValid) return ValidationProblem(ModelState); var exist = OsiStore.Get(id); if (exist is null) return NotFound(); // Id는 경로 기준으로 고정 updated.Id = id; OsiStore.Update(id, updated); return NoContent(); // 204, 본문 없이 성공 } /// <summary>부분 수정 예시 (활성화/비활성 같은 상태 토글)</summary> [HttpPatch("{id:int}/activate")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult Activate(int id) { var exist = OsiStore.Get(id); if (exist is null) return NotFound(); exist.IsActive = true; OsiStore.Update(id, exist); return NoContent(); } /// <summary>삭제 (성공 시 204 No Content)</summary> [HttpDelete("{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult Delete(int id) { var ok = OsiStore.Remove(id); if (!ok) return NotFound(); return NoContent(); // 204, 본문 없음 } } |
5) Swagger에서 테스트 흐름 (요약)
POST /api/OSI
- Body 예시:
1 2 3 4 5 6 7 8 |
{ "passengerName": "HONG GIL DONG", "airline": "KE", "message": "CTCE/EMAIL@DOMAIN.COM", "isActive": true } |
응답: 201 Created
- 헤더:
Location: https://localhost:5001/api/OSI/{id}
- 바디: 생성된 객체(JSON)
GET /api/OSI/{id}
- 응답: 200 OK + 해당 객체
PUT /api/OSI/{id}
- 바디에 전체 필드 전달
- 응답: 204 No Content (본문 없음)
DELETE /api/OSI/{id}
- 응답: 204 No Content
StatusCodes 열거형(StatusCodes 클래스)
이 값들은 Microsoft.AspNetCore.Http 네임스페이스에 정의되어 있어요.
열거형 상수 | 숫자 | 의미 |
---|---|---|
StatusCodes.Status200OK | 200 | 요청 성공 |
StatusCodes.Status201Created | 201 | 리소스 생성 성공 |
StatusCodes.Status204NoContent | 204 | 요청 성공, 본문 없음 |
StatusCodes.Status400BadRequest | 400 | 잘못된 요청 |
StatusCodes.Status401Unauthorized | 401 | 인증 필요 |
StatusCodes.Status403Forbidden | 403 | 접근 금지 |
StatusCodes.Status404NotFound | 404 | 리소스 없음 |
StatusCodes.Status500InternalServerError | 500 | 서버 에러 |
실전 패턴
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 |
// 단일 조회 [HttpGet("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult GetById(int id) { ... } // 생성 [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public IActionResult Create([FromBody] OSIModel newOsi) { ... } // 수정 [HttpPut("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult Update(int id, [FromBody] OSIModel updated) { ... } // 삭제 [HttpDelete("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult Delete(int id) { ... } |
[ProducesResponseType]
은 Swagger 문서 품질을 높이고 협업을 원활하게 만드는 핵심 도구입니다.
API 사용자는 Swagger UI에서 “이 API가 어떤 상황에서 어떤 상태 코드와 데이터를 반환하는지” 바로 알 수 있습니다.
실무에서는 모든 컨트롤러 액션마다 꼭 명시하는 것이 좋습니다.
1. CRUD의 뜻
약어 | 의미 | 설명 | 예시 (HTTP 메서드) |
---|---|---|---|
C | Create | 새로운 데이터를 생성 | POST |
R | Read | 저장된 데이터를 조회 | GET |
U | Update | 기존 데이터를 수정 | PUT / PATCH |
D | Delete | 데이터를 삭제 | DELETE |
2. CRUD와 HTTP 메서드의 관계
RESTful API에서는 보통 CRUD → HTTP 메서드를 다음처럼 매핑합니다.
동작 | 설명 | HTTP 메서드 | 예시 URL |
---|---|---|---|
Create | 새 데이터 생성 | POST | POST /api/OSI |
Read (전체) | 전체 목록 조회 | GET | GET /api/OSI |
Read (단일) | 특정 ID 데이터 조회 | GET | GET /api/OSI/3 |
Update | 기존 데이터 수정 | PUT 또는 PATCH | PUT /api/OSI/3 |
Delete | 데이터 삭제 | DELETE | DELETE /api/OSI/3 |