C#
 Computer >> コンピューター >  >> プログラミング >> C#

C# ASP.NET WebAPIでカスタムメディアタイプを使ったバージョニングの実装方法

メディアタイプ(Media Type)は、APIがクライアントに対してペイロード内のデータをどのように解釈すべきかを伝えるための仕組みです。HTTPプロトコルでは、text/htmlapplication/jsonapplication/xml といった識別子でメディアタイプが指定され、それぞれHTML・JSON・XMLという代表的なWebフォーマットに対応しています。これら以外にも、application/vnd.api+json のようなAPI固有のカスタムメディアタイプが存在します。

本記事では、このカスタムメディアタイプを活用してASP.NET Web APIのバージョニングを実現する方法を解説します。まず、リクエスト時に送信するメディアタイプと、それに対応するコントローラーの関係は以下のようになります。

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

CustomControllerSelectorの作成

デフォルトのコントローラー選択ロジックでは、Acceptヘッダーのカスタムメディアタイプからバージョンを判別できないため、エラーが発生します。そこで、DefaultHttpControllerSelector を継承した独自の 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;
        }
    }
}

このクラスでは、正規表現 application/vnd.demo.([a-z]+).v(?<version>[0-9])+([a-z]+) を使ってAcceptヘッダー内のメディアタイプを解析し、バージョン番号を取得します。取得したバージョンに応じてコントローラー名に「V1」や「V2」を連結することで、対応するコントローラーが選択される仕組みです。

WebApiConfig.csへの登録

次に、作成した CustomControllerSelectorWebApiConfig.csIHttpControllerSelector の差し替えとして登録します。

実装例:WebApiConfig.cs

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 }
        );
    }
}

バージョン別コントローラーの実装

ここでは、v1とv2でモデル構造が異なる例として、v1では「Name」という単一プロパティを持つStudentモデル、v2では「FirstName」「LastName」に分割されたモデルを使用します。同じURLでも、送信されたメディアタイプによって返却されるデータ構造が変化します。

実装例: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;
        }
    }
}

動作確認

以下の出力結果は、カスタムメディアタイプによるバージョニングを適用した際に、StudentV1ControllerとStudentV2Controllerからそれぞれ取得できるレスポンスです。Acceptヘッダーで v1+json を指定すればv1のデータ構造が、v2+json を指定すればv2のデータ構造が返されます。

C# ASP.NET WebAPIでカスタムメディアタイプを使ったバージョニングの実装方法

C# ASP.NET WebAPIでカスタムメディアタイプを使ったバージョニングの実装方法

XML形式でのレスポンスに対応させる

同じデータをXML形式で取得したい場合は、カスタムメディアタイプのXML版をXmlFormatterのサポート対象に追加します。WebApiConfig.cs のRegisterメソッドに以下の記述を加えてください。

実装例: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 }
    );
}

C# ASP.NET WebAPIでカスタムメディアタイプを使ったバージョニングの実装方法

上記の設定により、Acceptヘッダーで application/vnd.demo.student.v1+xmlapplication/vnd.demo.student.v2+xml を指定すると、出力がカスタムメディアタイプで指定した通りのXML形式で返却されることが確認できます。

まとめ

カスタムメディアタイプによるバージョニングは、URLやクエリ文字列を汚さずにAPIのバージョンを管理できるエレガントな手法です。実装のポイントは以下の3点です。

  • DefaultHttpControllerSelector を継承し、Acceptヘッダーからバージョンを解析する CustomControllerSelector を作成する
  • WebApiConfig.csIHttpControllerSelector を差し替えて登録する
  • XML形式にも対応させたい場合は、XmlFormatterのSupportedMediaTypesにカスタムメディアタイプを追加する

この方式を採用すれば、クライアントはAcceptヘッダーを切り替えるだけで任意のバージョンのAPIを利用でき、サーバー側もコントローラーを分離して保守性の高いバージョン管理を実現できます。

  1. C# ASP.NET Web APIのアクションメソッドからカスタム結果タイプを返す方法

    ASP.NET Web APIでは、IHttpActionResultインターフェースを実装することで、独自のカスタムクラスを結果タイプとして作成できます。IHttpActionResultインターフェースには、HttpResponseMessageインスタンスを非同期に生成する単一のメソッド ExecuteAsync が定義されています。public interface IHttpActionResult { Task<HttpResponseMessage> ExecuteAsync( CancellationToken cancellationToke

  2. Zoomでカスタム背景(バーチャル背景)を使う方法|PC・スマホの設定手順を徹底解説

    パンデミックをきっかけに、世界中の何百万人ものユーザーがビデオ会議ツール「Zoom」を使い始めました。その中で特に人気を集めているのが、自分の背後の部屋を好きな画像や動画に置き換えられる「カスタム背景(バーチャル背景)」機能です。Zoomがあらかじめ用意したプリセットから選ぶことも、オリジナルの画像や動画をアップロードすることも可能。火星の写真から本棚の画像まで、アイデア次第で無限の可能性が広がります。Zoomのカスタム背景機能は非常に精度が高く、高価なグリーンスクリーンがなくても十分に機能します。顔の特徴と背景を正しく識別できれば、自然な合成を実現できます。この記事では、Zoomでカスタム背