miso_soup3 Blog

主に ASP.NET 関連について書いています。

ASP.NET Web API シンプルな文字列の POST について

ASP.NET Web API にて、シンプルな文字列を POST する方法&受け取る方法についてまとめておきます。

文字列は、HTTP リクエストの Body に格納し、次のいずれかの Content-Type で送るものとします。

  • application/json
  • application/x-www-form-urlencoded
  • text/plain

text/plain で受け取るには、少々手を入れる必要があります。

目次:

  • API コントローラーとアクションの用意
  • application/json
  • application/x-www-form-urlencoded
  • text/plain
    • 方法 1 楽だけど制限のある方法
    • 方法 2 メディアタイプフォーマッタを実装する方法

API コントローラーとアクションの用意

次のように API コントローラーとアクションを用意しました。

using System.Web.Http;

namespace WebApplication74.Controllers
{
	public class ValuesController : ApiController
	{
		// POST api/values
		public string Post([FromBody]string value)
		{
			return value;
		}
	}
}

このメソッドの引数の value に Body に格納された文字列をバインドさせるための、HTTP リクエストを確認します。

application/json で POST する場合

HTTP リクエスト:

Method POST
URL ~/api/values
Content-Type application/json
Body "hello"

次のような Body の値では、正しくバインドできないので注意。

  • 「hello」(" を省略する)
    • " を省略するのは JSON 形式ではありません。
  • 「 { value : "hello" }」
    • このようにプロパティと値を指定してもバインドできません。

また、この HTTP リクエストを jQuery で送信する例は次のようになります。(jQuery の バージョンは v1.10 で確認)

$.ajax({
    type: "POST",
    url: "api/values",
    contentType: "application/json",
    data: JSON.stringify( "hello")
});

contentType の デフォルト値は "application/x-www-form-urlencoded" なので、上のコードのように "application/json" と指定する必要があります。

application/x-www-form-urlencoded で POST する場合

HTTP リクエスト:

Method POST
URL ~/api/values
Content-Type application/x-www-form-urlencoded
Body =hello

次のような Body の値では、正しくバインドできないので注意。

  • 「value=hello」
    • このようにキーと値で指定してもバインドできません。

また、この HTTP リクエストを jQuery で送信する例は次のようになります。

$.ajax({
    type: "POST",
    url: "api/values",
    data: { '' : "hello"}
});

data: { '' : "hello"} と指定すると「=hello」の形で送信できます。

text/plain で POST する場合

次のように text/plain で送信する場合は、ASP.NET Web API 側で少し変更が必要になります。

Method POST
URL ~/api/values
Content-Type text/plain
Body hello

何故なら ASP.NET Web API がデフォルトで対応している Content-Type(メディアタイプフォーマッタ)は次の 4 種類であり、text/plain に対応していないからです。

メディアタイプフォーマッタクラス
JsonMediaTypeFormatter application/json, text/json
XmlMediaTypeFormatter application/xml, text/xml
FormUrlEncodedMediaTypeFormatter application/x-www-form-urlencoded
JQueryMvcFormUrlEncodedFormatter application/x-www-form-urlencoded

text/plain で POST される文字列を受け取るには、2 つの方法―

  • 楽だけど制限のある方法
  • text/plain に対応するメディアタイプフォーマッタを実装する

があります。

方法 1 楽だけど制限のある方法

1 つめは、API コントローラーのメソッドを次のようにする方法です。

using System.Threading.Tasks;
using System.Web.Http;

namespace WebApplication74.Controllers
{
	public class ValuesController : ApiController
	{
		// POST api/values
		public async Task<string> Post()
		{
			return await Request.Content.ReadAsStringAsync();
		}
	}
}

「Request.Content.ReadAsStringAsync();」で、text/plain で POST された Body の値を取得することができます。ただし、次の制限があります。

1. 他の Content-Type(application/json や application/x-www-form-urlencoded)に対応していない

Body の値をそのまま取得するので「"hello"」や「=hello」という文字列を処理することになります。

2. ↓ のように、HTTP リクエストの Content をデシリアライズさせるようなアクションの引数を定義をしないこと。

//例
public async Task<string> Post([FromBody]string value);
public async Task<string> Post(Person model); //Person は独自で作成したクラス例

先のコードが Post() {...} と引数無しになっていることに注意して下さい。ASP.NET Web API では Content を重複して読み取りできません。(ちなみにこれは ASP.NET MVC との違いでもあります。)
他、Post(HttpRequestMessage request); のように HttpRequestMessage を引数にしても OK です。

方法 2 メディアタイプフォーマッタを実装する方法

text/plain に対応するメディアタイプフォーマッタを実装すれば、先のような他の Content-Type に対応できないという問題点が解決できます。

以下、その実装例を記述しますが、どこかのオープンソースには既に作成されたものがあるかもしれません。

BufferedMediaTypeFormatter(System.Net.Http.Formatting 名前空間)を継承した TextPlainMediaTypeFormatter を実装します。

(BufferedMediaTypeFormatter は、少量のデータを同期で読み込むために用意された MediaTypeFormatter を継承したクラスです。 )

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;

namespace WebApplication74
{
	public class TextPlainMediaTypeFormatter : BufferedMediaTypeFormatter
	{
		public TextPlainMediaTypeFormatter()
		{
			SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
			SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true));
			SupportedEncodings.Add(new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true));
		}

		public override bool CanReadType(Type type)
		{
			return type == typeof(string);
		}

		public override bool CanWriteType(Type type)
		{
			return type == typeof(string);
		}

		public override object ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, System.Threading.CancellationToken cancellationToken)
		{
			Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers);
			using (StreamReader sReader = new StreamReader(readStream, effectiveEncoding))
			{
				return sReader.ReadToEnd();
			}
		}

		public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content, System.Threading.CancellationToken cancellationToken)
		{
			Encoding effectiveEncoding = SelectCharacterEncoding(content.Headers);
			using (StreamWriter sWriter = new StreamWriter(writeStream, effectiveEncoding))
			{
				sWriter.Write(value);
			}
		}
	}
}

WebApiConfig.cs 等で、HttpConfiguration に作成した TextPlainMediaTypeFormatter を登録します。

public static class WebApiConfig
{
	public static void Register(HttpConfiguration config)
	{
		config.Formatters.Add(new TextPlainMediaTypeFormatter());
		//他ルーティング等の設定...

手順は以上です。

API コントローラーのアクションは、冒頭のコードと同じになります。

public string Post([FromBody]string value)