ASP.NET Core Web API の Tips

ASP.NET Core で Web API を利用する際の注意点や備忘録です。ほぼ箇条書きです。
ASP.NET Web API 版は以前に書きました。

ルーティング、コントローラーなど

ASP.NET Web API と細かい差異はありますが、説明は省略します。
公式解説を参照するとよいでしょう。

CORS

  • NuGet で Microsoft.AspNetCore.Cors をインストールする
  • Startup.cs で AddCors メソッドおよび UseCors メソッドを呼び出すことで機能を有効にする
    • AddMvc メソッドおよび UseMvc メソッドの前で呼び出す必要がある
    • コントローラー、アクションの単位では [EnableCors] を指定する
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace SampleWebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(b => b.AllowAnyOrigin());
app.UseMvc();
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

CORS が機能しているかどうかをテストするには、現在実行中のものとは異なるドメインを Origin ヘッダーに付加して API を呼び出します。
応答に Access-Control-Allow-Origin ヘッダーが含まれていれば OK です。

要求ヘッダー

Origin: https://tempuri.org

応答ヘッダー

Access-Control-Allow-Origin: *

ツールとしては Advanced REST client などを使えばよいでしょう。

公式解説: ASP.NET Core でのクロス オリジン要求 (CORS) を有効にする

ヘルプ ページ

コードの XML ドキュメントから、ユーザー向けのヘルプ ページを自動的に生成する機能です。
ASP.NET Core では、OpenAPI (Swagger) の .NET 向け実装である Swashbuckle を利用します。
API を呼び出すためのテスト UI も含まれていて便利です。

  • NuGet で Swashbuckle.AspNetCore をインストールする
  • プロジェクトのプロパティで、XML ドキュメントの出力を有効にする
  • Startup.cs で AddSwaggerGen メソッド、UseSwagger メソッドおよび UseSwaggerUI メソッドを呼び出すことで機能を有効にする

ソースコード: Startup.cs

  • ヘルプ ページの URI は既定で /swagger となるが、ルートに変更するには、RoutePrefix を空文字列に設定する
  • アクション メソッドの戻り値が IActionResult の場合、
    [ProducesResponseType(200, Type = typeof(string))] のように属性でデータの型を指定する
  • このサンプルではアセンブリ情報の値をタイトルなどに設定している

公式解説: Swashbuckle と ASP.NET Core の概要

フォーマット

ASP.NET Core Web API では、既定でテキスト (text/plain) と JSON が有効になっています。
テキストを無効にして XML を有効にするには、Startup.ConfigureServices メソッド内で次のようにします。

using System;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
namespace SampleWebApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
// テキスト形式を無効化、XML 形式を有効化
services.AddMvc(options =>
{
options.OutputFormatters.RemoveType<StringOutputFormatter>();
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});
// XML 形式を有効化
//services.AddMvc().AddXmlSerializerFormatters();
// 以下略
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

さらに、コントローラーまたはアクションに Produces 属性を指定することで、利用可能な Content-Type を制限することもできます。

[Produces("application/json", "application/xml")]

公式解説: ASP.NET Core Web API の応答データの書式設定

 

前回: dotnet コマンドによるビルド

作成したサンプル

バージョン情報

  • Microsoft.AspNetCore.All 2.0.8
  • Microsoft.AspNetCore.Cors 2.0.3
  • Swashbuckle.AspNetCore 2.5.0

参照

カテゴリー: .NET Core. タグ: , . 3 Comments »

ランダムなデータを生成する JSON API

Random Data Web API というものを作成しました (正確には、2014 年に作成したものを最近改修しました)。
ランダムなデータを生成するための JSON Web API です。以下のデータを生成できます。

  • アルファベット
  • アルファベットと数字
  • バイト列
    • 16進数形式、Base64 形式
  • UUID (GUID)
  • 時刻順の ID
    • 現在の時刻をもとに、並べ替え可能な GUID を生成
    • SQL Server の uniqueidentifier 型にも対応

内容自体はとくに変哲のない API です。時刻順の ID は少し珍しいかもしれませんが。
また、仕様が記述されたヘルプページ、および jQuery を利用したテストページが付属しています。

Test page

さて、従来の一般的な Web API の問題点として、利用する開発者の意思に反してサービスが終了してしまうということがよくあります。
無償・有償を問わずサービスが永久に提供されるとは限らないため、
なるべく自身のアプリをそれに依存させず、自身でサービスを運用することが望ましいでしょう。

そこでこの Web API では、ソースコードをオープンソース ライセンスのもとで提供し、
それを利用する開発者自身がサービスをホストすることを想定します。
例えば Azure Web App などの PaaS を利用すれば GitHub から直接ビルドおよびデプロイができるため、
簡単な手順でサービスの運用を開始させることができます。
またこの場合は継続的デプロイが構成され、fork したリポジトリが更新されれば Azure Web App も自動的に更新されます。

詳細の方法については Azure Web App にデプロイする手順にまとめてあります。
なお、randomdata.azurewebsites.net は配置例として提供しているものです。このサイトに保証・サポートはありません。

Deployment Option

技術的な特徴としては、以下が挙げられます。

  • ASP.NET Web API
    • XML Formatter を無効化して、JSON 形式のみをサポート
  • ASP.NET Web API Help Page
    • ソースコード内のコメントから自動生成
    • いろいろカスタマイズして利用
  • ASP.NET Web API Cross-Origin Support
    • CORS (Cross-Origin Resource Sharing)
  • HTTPS 必須化

ヘルプページの多言語対応については、ブラウザーの翻訳機能を利用すれば何とかなるでしょう。

Help Translation

バージョン情報

  • .NET Framework 4.5
  • ASP.NET Web API 5.2.3
  • ASP.NET Web API Help Page 5.2.3
  • ASP.NET Web API Cross-Origin Support 5.2.3
  • Blaze 1.1.10

参照

ASP.NET Web API の Tips (2)

ASP.NET Web API を利用する際の注意点や備忘録です。ほぼ箇条書きです。
基本的な説明は省略しています。
(ASP.NET Core Web API 版も書きました。)

例外処理

  • 戻り値が HttpResponseMessage または IHttpActionResult の場合、Request.CreateErrorResponse メソッドなどで HttpResponseMessage を生成する
  • 戻り値が HttpResponseMessage でも IHttpActionResult でもない場合、HttpResponseException を返すことで同様の効果が得られる
  • HttpError を利用して、JSON のエラー メッセージを含めることができる
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace SampleWebApi.Controllers
{
public class ValuesController : ApiController
{
static readonly List<string> values = new List<string> { "value0", "value1" };
public string Get(int id)
{
if (id < 0 || values.Count <= id)
{
var message = $"No value with ID = {id}";
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, message));
// No content:
//throw new HttpResponseException(HttpStatusCode.NotFound);
}
return values[id];
}
}
}

公式解説: Exception Handling in ASP.NET Web API

Help Page

コードの XML ドキュメントから、ユーザー向けのヘルプ ページを自動的に生成する機能です。
Visual Studio でプロジェクトを作成するときに Web API を選択すると、Help Page もインストールされます。
あとから追加するには、NuGet で Microsoft.AspNet.WebApi.HelpPage をインストールします。

ただし、既定では機能が有効になっていません。

  • 有効にする手順:
    • プロジェクトのプロパティで、XML ドキュメントの出力を有効にする
    • HelpPageConfig.cs のコメントアウトを解除する ( // を取る)

SetDocumentationProvider

  • アクション メソッドの戻り値が HttpResponseMessage または IHttpActionResult の場合、
    [ResponseType(typeof(string))] のように属性でデータの型を指定する
  • Areas\HelpPage にソースコードがあるため、カスタマイズ可能
  • ヘルプ ページ (Help/Index) を既定のページに設定するには、HelpPageAreaRegistration.cs でルーティングの設定を追加するとよい
  • ASP.NET Core Web API では、Help Page を使えない
    • Swashbuckle (Swagger の .NET 向け実装) を使う

HelpPage

公式解説: Creating Help Pages for ASP.NET Web API

Web API の呼び出し

.NET アプリケーションから Web API を呼び出すには、HttpClient クラスを利用するとよいでしょう。
また、サービス側で実装されたカスタム データ型も、サービス コントラクトとして利用できます。
すなわち、応答メッセージに対して response.Content.ReadAsAsync<T>() を呼び出せば T 型としてデシリアライズできます。

using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace UnitTest.Client
{
public static class HttpHelper
{
public static readonly Uri BaseUri = new Uri("http://localhost:1961/&quot;);
async public static Task<T> GetAsync<T>(string uri)
{
using (var http = new HttpClient { BaseAddress = BaseUri })
{
var response = await http.GetAsync(uri);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<T>();
}
}
}
}
view raw HttpHelper.cs hosted with ❤ by GitHub

公式解説: Call a Web API From a .NET Client (C#)

CORS

  • NuGet で Microsoft.AspNet.WebApi.Cors をインストールする
  • WebApiConfig.cs で config.EnableCors メソッドを呼び出すことで機能を有効にする
    • 引数に EnableCorsAttribute を渡すとグローバルに設定できる
    • コントローラー、アクションのレベルでは [EnableCors] を指定する

CORS が機能しているかどうかをテストする方法については ASP.NET Core Web API の Tips に書きました。

公式解説: Enabling Cross-Origin Requests in ASP.NET Web API 2

JSONP

未検証。

  • MediaTypeFormatter を利用する
    • WebApiContrib.Formatting.Jsonp など

フォーマット

ASP.NET Web API は、既定で JSON と XML をサポートします。
要求の Accept ヘッダーに何が指定されているかで結果のフォーマットが変わります。
Google Chrome 上で Web API を直接呼び出すと結果が XML 形式で返ってきます。
これは、要求の Accept ヘッダーに application/xml が含まれているためと考えられます。
これを JSON 形式にするには、text/html が含まれている場合に JSON を返す設定や XML 形式を無効にする設定が考えられます。

using System;
using System.Net.Http.Formatting;
using System.Web.Http;
using System.Web.Http.Cors;
namespace SampleWebApi
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// CORS の有効化
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
// XML 形式は有効のまま。HTML が要求されたら JSON を返します。
config.Formatters.JsonFormatter.MediaTypeMappings.Add(new RequestHeaderMapping("Accept", "text/html", StringComparison.InvariantCultureIgnoreCase, true, "application/json"));
// XML 形式の無効化 その1
//config.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
// XML 形式の無効化 その2
//config.Formatters.Remove(config.Formatters.XmlFormatter);
// 以下略
}
}
}
view raw WebApiConfig.cs hosted with ❤ by GitHub

解説: How do I get ASP.NET Web API to return JSON instead of XML using Chrome?

 

前回: ASP.NET Web API の Tips (1)

作成したサンプル

バージョン情報

  • .NET Framework 4.5
  • ASP.NET Web API 5.2.3
  • ASP.NET MVC 5.2.3
  • ASP.NET Web API Help Page 5.2.3
  • ASP.NET Web API CORS 5.2.3

参照

カテゴリー: .NET Framework. タグ: , . 1 Comment »

ASP.NET Web API の Tips (1)

ASP.NET Web API を利用する際の注意点や備忘録です。ほぼ箇条書きです。
基本的な説明は省略しています。
(ASP.NET Core Web API 版も書きました。)

ルーティング

WebApiConfig.cs にルーティングの設定が記述されています。
既定のテンプレートは api/{controller}/{id} となっており、REST スタイルを想定したものとなっています。
ただし、ASP.NET MVC と同様に {action} も利用可能であり、RPC スタイルの API も構成できます。

  • [RoutePrefix], [Route], [ActionName] などの属性を利用することで柔軟に構成できる
  • [Route] を複数設定できる

公式解説: Web API Routing

HTTP メソッド

  • 主に REST スタイルの場合、Get、Post などのメソッド名で解決される (CoC)。
    この場合、[HttpGet] などの属性を指定する必要はない
  • 主に RPC スタイルで任意のアクション名を利用するには、[HttpGet] などの属性を指定する

引数

  • DateTime, Guid などの型も扱える
  • 引数の [FromBody] は、エンティティ型なら付ける必要はない
  • ルーティングで {i:int:range(0,100)} のような制約を追加できる
  • 引数に既定値を指定すると、URL のパラメーターを省略可能
    • ルーティングで指定する場合は {i:int?} のようにする
    • range などを使う場合、省略可能にできない
      • int? と range は同時に指定できない
  • 引数で / を使う場合、引数名の前に * を指定する (下の DateTime 型の例)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace SampleWebApi.Controllers
{
[RoutePrefix("api/Random")]
[Route("{action}")]
public class RandomController : ApiController
{
static readonly Random random = new Random();
[HttpGet]
[Route("Echo/{i:int?}")]
public int Echo(int i = 123) => i;
[HttpGet]
[Route("NewDoubles/{count:int:range(0,64)}")]
public double[] NewDoubles(int count) =>
Enumerable.Range(0, count)
.Select(i => random.NextDouble())
.ToArray();
[HttpGet]
[Route(@"NewDateTime/{date:datetime:regex(\d{4}-\d{2}-\d{2})}")]
[Route(@"NewDateTime/{*date:datetime:regex(\d{4}/\d{2}/\d{2})}")]
public DateTime NewDateTime(DateTime date) => date + TimeSpan.FromHours(24 * random.NextDouble());
[HttpGet]
public Guid NewUuid() => Guid.NewGuid();
}
}

公式解説: Attribute Routing in ASP.NET Web API 2

戻り値

戻り値として、通常のデータ型以外に次のものを指定できます。

  • HttpResponseMessage
    • 生の応答データ
  • IHttpActionResult
    • HttpResponseMessage をラップしたインターフェイス

これらを利用することにより、応答データを柔軟に設定できます。

  • JSON や XML に限らず、テキストや画像などの任意の形式のコンテンツを返せる
  • 主な IHttpActionResult の実装は System.Web.Http.Results 名前空間で定義されている

以下は、テキスト (Content-Type: text/plain) を返す例です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
namespace SampleWebApi.Controllers
{
[RoutePrefix("api")]
public class RandomTextController : ApiController
{
static readonly Random random = new Random();
[HttpGet]
[Route("NewBytes1/{count:int:range(0,64)}")]
[ResponseType(typeof(string))]
public HttpResponseMessage NewBytes1(int count)
{
var content = CreateBytesString(count);
var response = Request.CreateResponse();
response.Content = new StringContent(content);
return response;
}
[HttpGet]
[Route("NewBytes2/{count:int:range(0,64)}")]
[ResponseType(typeof(string))]
public IHttpActionResult NewBytes2(int count)
{
var content = CreateBytesString(count);
return new TextResult(content, Request);
}
static string CreateBytesString(int count)
{
var bytes = new byte[count];
random.NextBytes(bytes);
return string.Join("\n", bytes);
}
}
public class TextResult : IHttpActionResult
{
public string Content { get; }
public HttpRequestMessage Request { get; }
public TextResult(string content, HttpRequestMessage request)
{
Content = content;
Request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = Request.CreateResponse();
response.Content = new StringContent(Content);
return Task.FromResult(response);
}
}
}

公式解説: Action Results in Web API 2

 

次回: ASP.NET Web API の Tips (2)

作成したサンプル

バージョン情報

  • .NET Framework 4.5
  • ASP.NET Web API 5.2.3

参照

カテゴリー: .NET Framework. タグ: , . 3 Comments »

JSON データのデシリアライズにおける日付型の変換

(軽めの jQuery Advent Calendar 2012 の 23 日目です。)

次のようなコードで jQuery の getJSON 関数で JSON Web サービスを呼び出すと、
コールバック関数の引数 data には JSON データがデシリアライズ (パース) されたオブジェクトが渡されます。
ここで、data に含まれている値の型について考えてみます。


$.getJSON("Services/Products")
    .done(function (data) {
        // 取得したデータに対する処理
        var value = data[0].prop1; // 何型?
    });


JSON 形式では、オブジェクト、配列、文字列、数値、ブール値、null の記法が定義されており、
それぞれ JavaScript の Object, Array, String, Number, Boolean, null の値に対応します。
しかし、Date に対応する日付型は定義されていません。

getJSON 関数では、JSON 形式の文字列をデシリアライズするために
既定では $.parseJSON 関数 (JSON.parse 関数を IE7 以前のために拡張したもの) を使用します。
この場合、たとえ ISO 8601 の日付形式である "2012-12-24T14:59:59.999Z" のような文字列であっても、
自動的に Date 型に変換されることはありません。

しかし jQuery の Ajax 機能の設定値の中に converters というものがあり、
デシリアライズ方法をカスタマイズできるようになっています。
この機能は、jQuery 1.5で追加されたようです。

今回は、この converters を利用して日付を表す文字列を Date 型に変換してみます。

■ ISO 8601 の日付形式の場合

converters を設定するには、Ajax で通信を開始する前に $.ajaxSetup 関数を呼び出します。
キー "text json" に対してデシリアライズ関数を指定します。
次のように実装します。


$.ajaxSetup({
    converters: {
        "text json": function (text) {
            return JSON.parse(text, reviveDate);
        }
    }
});

function reviveDate(key, value) {
    if (value == null ||
        value.constructor !== String ||
        value.search(/^\d{4}-\d{2}-\d{2}/g) === -1)
        return value;
    return new Date(value);
}


実は JSON.parse 関数の第 2 引数には、キーと値を受け取って任意の値に変換する関数を指定できます。
この reviveDate 関数の中で、正規表現で文字列の先頭部分が YYYY-MM-DD に一致した場合は Date 型に変換しています。

次のように $.ajaxSettings.converters に直接上書きしても同様の効果です。

$.ajaxSettings.converters["text json"] = function (text) {
    return JSON.parse(text, reviveDate);
};

また、$.getJSON 関数ではなく $.ajax 関数を使う場合は、引数 settings に converters を指定できます。

では実際に次の JSON データをデシリアライズして、確認のためにブラウザーで表示してみます。

JSON データ (ISO 8601 形式)

表示サンプル

ここでは、先頭部分が YYYY-MM-DD の形式かどうかを判定しましたが、
他にも例えば「key がサフィックスとして "Date" を持つ (例: startDate)」という判定条件も考えられるでしょう。

なお、IE で Date のコンストラクターに ISO 8601 の日付形式を指定できるのは、IE9 以降です。

■ ASP.NET の場合

JSON Web サービスを WCF サービスなどで実装する場合、
.NET Framework の DateTime 型のオブジェクトは
"\/Date(1356361199999)\/" または "\/Date(1356361199999+0900)\/" のような形式にシリアライズされます。

したがって、先ほどの reviveDate 関数を次のように実装します。


function reviveDate(key, value) {
    if (value == null ||
        value.constructor !== String)
        return value;
    var m = /^\/Date\((\d+)(.+)?\)\/$/g.exec(value);
    if (!m) return value;
    return new Date(parseInt(m[1]));
}


先ほどと同じ意味のデータを WCF サービスで返すと次のようになります。表示結果は同じです。

JSON データ (ASP.NET 形式)

■ その他の場合

その他にも、日付の形式が案件で指定されている場合や、ISO 8601 形式を IE8 でも使いたい場合などが考えられますが、
同様に変換ロジックをカスタマイズすれば実現できると思います。

例えば、"2012年12月24日" という形式の場合は、次のようにして実現できます。


function reviveDate(key, value) {
    if (value == null ||
        value.constructor !== String)
        return value;
    var m = /^(\d{4})年(\d{1,2})月(\d{1,2})日$/g.exec(value);
    if (!m) return value;
    return new Date(parseInt(m[1]), parseInt(m[2]) – 1, parseInt(m[3]));
}


これで、次のようなデータを変換できます。

JSON データ (カスタム形式)

作成したサンプル
JSON Converter Sample 1 (ISO 8601 形式)
JSON Converter Sample 2 (ASP.NET 形式)
JSON Converter Sample 3 (カスタム形式)

バージョン情報
JSON.parse 関数: Internet Explorer 8 以降、その他の主要なブラウザー
ISO 8601 形式を受け取る Date のコンストラクター: Internet Explorer 9 以降、その他の主要なブラウザー

参照
jQuery.ajax()
Extending Ajax: Prefilters, Converters, and Transports
JSON の紹介
Date and Time Formats (W3C)
jQuery parseJSON automatic date conversion for Asp.net and ISO date strings