Computer >> 컴퓨터 >  >> 프로그래밍 >> C#

C# ASP.NET Web API에서 커스텀 미디어 타입으로 버전 관리 구현하는 방법

미디어 타입(Media Type)은 API가 클라이언트에게 페이로드의 데이터를 어떻게 해석해야 하는지 알려주는 중요한 수단입니다. HTTP 프로토콜에서 미디어 타입은 text/html, application/json, application/xml과 같은 식별자로 지정되며, 각각 HTML, JSON, XML이라는 가장 널리 사용되는 웹 포맷에 대응됩니다. 이 외에도 application/vnd.api+json처럼 API 전용으로 설계된 미디어 타입도 존재합니다.

미디어 타입 기반 버전 관리에서는 아래와 같이 버전 정보를 미디어 타입에 담아 전송합니다.

application/vnd.demo.students.v1+json → StudentsV1Controller
application/vnd.demo.students.v2+json → StudentsV2Controller

커스텀 컨트롤러 셀렉터(CustomControllerSelector) 작성하기

기본 설정 상태에서 위와 같은 커스텀 미디어 타입을 사용하면 라우팅 오류가 발생할 수 있습니다. 이를 해결하려면 자체적인 CustomControllerSelector를 추가해야 합니다.

아래는 요청의 Accept 헤더를 분석하여 버전 번호를 추출하고, 그에 맞는 컨트롤러를 선택하는 CustomControllerSelector 구현 예제입니다.

using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
namespace WebAPI.Custom{
    public class CustomControllerSelector : DefaultHttpControllerSelector{
        private HttpConfiguration _config;
        public CustomControllerSelector(HttpConfiguration config) : base(config){
            _config = config;
        }
        public override HttpControllerDescriptor SelectController(HttpRequestMessage request){
            var controllers = GetControllerMapping();
            var routeData = request.GetRouteData();
            var controllerName = routeData.Values["controller"].ToString();
            string versionNumber = "";
            string regex = @"application\/vnd\.demo\.([a-z]+)\.v(?<version>[0-9]+)\+([a-z]+)";
            var acceptHeader = request.Headers.Accept
                .Where(a => Regex.IsMatch(a.MediaType, regex,
                RegexOptions.IgnoreCase));
            if (acceptHeader.Any()){
                var match = Regex.Match(acceptHeader.First().MediaType,
                regex, RegexOptions.IgnoreCase);
                versionNumber = match.Groups["version"].Value;
            }
            HttpControllerDescriptor controllerDescriptor;
            if (versionNumber == "1"){
                controllerName = string.Concat(controllerName, "V1");
            }
            else if (versionNumber == "2"){
                controllerName = string.Concat(controllerName, "V2");
            }
            if (controllers.TryGetValue(controllerName, out controllerDescriptor)){
                return controllerDescriptor;
            }
            return null;
        }
    }
}

WebApiConfig.cs 등록

작성한 커스텀 셀렉터를 사용하려면 WebApiConfig.cs에서 기본 IHttpControllerSelector를 교체해야 합니다.

public static class WebApiConfig{
    public static void Register(HttpConfiguration config){
        config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

버전별 컨트롤러 구현

이제 버전 1과 버전 2에 해당하는 컨트롤러를 각각 작성합니다. V1 모델은 단일 Name 속성을, V2 모델은 FirstName과 LastName으로 분리된 구조를 가집니다.

StudentV1Controller

using DemoWebApplication.Models;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace DemoWebApplication.Controllers{
    public class StudentV1Controller : ApiController{
        List<StudentV1> students = new List<StudentV1>{
            new StudentV1{
                Id = 1,
                Name = "Mark"
            },
            new StudentV1{
                Id = 2,
                Name = "John"
            }
        };
        public IEnumerable<StudentV1> Get(){
            return students;
        }
        public StudentV1 Get(int id){
            var studentForId = students.FirstOrDefault(x => x.Id == id);
            return studentForId;
        }
    }
}

StudentV2Controller

using DemoWebApplication.Models;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace DemoWebApplication.Controllers{
    public class StudentV2Controller : ApiController{
        List<StudentV2> students = new List<StudentV2>{
            new StudentV2{
                Id = 1,
                FirstName = "Roger",
                LastName = "Federer"
            },
            new StudentV2{
                Id = 2,
                FirstName = "Tom",
                LastName = "Bruce"
            }
        };
        public IEnumerable<StudentV2> Get(){
            return students;
        }
        public StudentV2 Get(int id){
            var studentForId = students.FirstOrDefault(x => x.Id == id);
            return studentForId;
        }
    }
}

실행 결과 확인

아래 출력 결과는 커스텀 미디어 타입 기반 버전 관리를 적용했을 때 StudentV1 및 StudentV2 컨트롤러에서 반환되는 응답입니다. 클라이언트가 보낸 미디어 타입의 버전 정보에 따라 서로 다른 컨트롤러가 호출되는 것을 확인할 수 있습니다.

XML 형식 지원 추가하기

동일한 데이터를 XML 형식으로도 받고 싶다면, WebApiConfig.cs에 아래와 같이 XML용 커스텀 미디어 타입을 추가합니다.

public static void Register(HttpConfiguration config){
    config.MapHttpAttributeRoutes();
    config.Services.Replace(typeof(IHttpControllerSelector), new
    CustomControllerSelector(config));
    config.Formatters.XmlFormatter.SupportedMediaTypes
        .Add(new MediaTypeHeaderValue("application/vnd.demo.student.v1+xml"));
    config.Formatters.XmlFormatter.SupportedMediaTypes
        .Add(new MediaTypeHeaderValue("application/vnd.demo.student.v2+xml"));
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
}

위 설정을 적용한 후 요청을 다시 보내면, Accept 헤더에 지정한 커스텀 미디어 타입(+xml)에 따라 응답이 XML 형식으로 반환되는 것을 확인할 수 있습니다. 이처럼 커스텀 미디어 타입을 활용하면 URL 구조를 변경하지 않고도 깔끔하게 API 버전을 관리할 수 있습니다.