Web APIを使用して、ASP.MVC MVC 4で新しいサービスセットを作成しています。これまでのところ、それは素晴らしいです。サービスを作成し、機能するようになったので、JQueryを使用して使用しようとしています。 Fiddlerを使用してJSON文字列を取得できますが、問題ないようですが、サービスが別のサイトに存在するため、「許可されていません」というJQueryエラーで呼び出しを試みます。したがって、これは明らかにJSONPを使用する必要がある場合です。
私はWeb APIが新しいことを知っていますが、誰かが助けてくれることを望んでいます。
JSONPを使用してWeb APIメソッドを呼び出すにはどうすればよいですか?
この質問をした後、私は最終的に必要なものを見つけたので、答えています。
私はこれに遭遇しました JsonpMediaTypeFormatter 。これを実行して、global.asaxのApplication_Start
に追加します。
var config = GlobalConfiguration.Configuration;
config.Formatters.Insert(0, new JsonpMediaTypeFormatter());
そして、次のようなJQuery AJAX呼び出しを使用するとよいでしょう。
$.ajax({
url: 'http://myurl.com',
type: 'GET',
dataType: 'jsonp',
success: function (data) {
alert(data.MyProperty);
}
})
とてもうまくいくようです。
Web API RCで使用するJsonMediaTypeFormatterの更新バージョンは次のとおりです。
public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
private string callbackQueryParameter;
public JsonpMediaTypeFormatter()
{
SupportedMediaTypes.Add(DefaultMediaType);
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
}
public string CallbackQueryParameter
{
get { return callbackQueryParameter ?? "callback"; }
set { callbackQueryParameter = value; }
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
{
string callback;
if (IsJsonpRequest(out callback))
{
return Task.Factory.StartNew(() =>
{
var writer = new StreamWriter(stream);
writer.Write(callback + "(");
writer.Flush();
base.WriteToStreamAsync(type, value, stream, content, transportContext).Wait();
writer.Write(")");
writer.Flush();
});
}
else
{
return base.WriteToStreamAsync(type, value, stream, content, transportContext);
}
}
private bool IsJsonpRequest(out string callback)
{
callback = null;
if (HttpContext.Current.Request.HttpMethod != "GET")
return false;
callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
return !string.IsNullOrEmpty(callback);
}
}
次のようなActionFilterAttributeを使用できます。
public class JsonCallbackAttribute : ActionFilterAttribute
{
private const string CallbackQueryParameter = "callback";
public override void OnActionExecuted(HttpActionExecutedContext context)
{
var callback = string.Empty;
if (IsJsonp(out callback))
{
var jsonBuilder = new StringBuilder(callback);
jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);
context.Response.Content = new StringContent(jsonBuilder.ToString());
}
base.OnActionExecuted(context);
}
private bool IsJsonp(out string callback)
{
callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
return !string.IsNullOrEmpty(callback);
}
}
それからあなたのアクションにそれを置きます:
[JsonCallback]
public IEnumerable<User> User()
{
return _user;
}
確かにブライアンの答えは正しいものですが、すでにJson.Netフォーマッターを使用している場合は、かなりのJSON日付とより高速なシリアル化が提供されますので、jsonpに2番目のフォーマッターを追加することはできません.2つを組み合わせる必要があります。いずれにしても、ASP.NET Web APIのリリースではデフォルトでJson.Netシリアライザーを使用する予定だとScott Hanselmanが言っているように、それを使用することをお勧めします。
public class JsonNetFormatter : MediaTypeFormatter
{
private JsonSerializerSettings _jsonSerializerSettings;
private string callbackQueryParameter;
public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
{
_jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();
// Fill out the mediatype and encoding we support
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
Encoding = new UTF8Encoding(false, true);
//we also support jsonp.
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", "application/json"));
}
public string CallbackQueryParameter
{
get { return callbackQueryParameter ?? "jsoncallback"; }
set { callbackQueryParameter = value; }
}
protected override bool CanReadType(Type type)
{
if (type == typeof(IKeyValueModel))
return false;
return true;
}
protected override bool CanWriteType(Type type)
{
return true;
}
protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders,
FormatterContext formatterContext)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task reading the content
return Task.Factory.StartNew(() =>
{
using (StreamReader streamReader = new StreamReader(stream, Encoding))
{
using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
{
return serializer.Deserialize(jsonTextReader, type);
}
}
});
}
protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders,
FormatterContext formatterContext, TransportContext transportContext)
{
string callback;
var isJsonp = IsJsonpRequest(formatterContext.Response.RequestMessage, out callback);
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task writing the serialized content
return Task.Factory.StartNew(() =>
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false })
{
if (isJsonp)
{
jsonTextWriter.WriteRaw(callback + "(");
jsonTextWriter.Flush();
}
serializer.Serialize(jsonTextWriter, value);
jsonTextWriter.Flush();
if (isJsonp)
{
jsonTextWriter.WriteRaw(")");
jsonTextWriter.Flush();
}
}
});
}
private bool IsJsonpRequest(HttpRequestMessage request, out string callback)
{
callback = null;
if (request.Method != HttpMethod.Get)
return false;
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
callback = query[CallbackQueryParameter];
return !string.IsNullOrEmpty(callback);
}
}
Rick Strahlの実装 RCで私に最適です。
JSONPはHttp GETリクエストでのみ機能します。 asp.net Web APIには、すべてのhttp動詞で適切に機能するCORSサポートがあります。
これ 記事が役に立つかもしれません。
更新済み
public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
private string callbackQueryParameter;
public JsonpMediaTypeFormatter()
{
SupportedMediaTypes.Add(DefaultMediaType);
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
}
public string CallbackQueryParameter
{
get { return callbackQueryParameter ?? "callback"; }
set { callbackQueryParameter = value; }
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
string callback;
if (IsJsonpRequest(out callback))
{
return Task.Factory.StartNew(() =>
{
var writer = new StreamWriter(writeStream);
writer.Write(callback + "(");
writer.Flush();
base.WriteToStreamAsync(type, value, writeStream, content, transportContext).Wait();
writer.Write(")");
writer.Flush();
});
}
else
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
}
private bool IsJsonpRequest(out string callback)
{
callback = null;
if (HttpContext.Current.Request.HttpMethod != "GET")
return false;
callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
return !string.IsNullOrEmpty(callback);
}
}
以下に、いくつかの改善が加えられた更新バージョンを示します。これは、Web APIのRTMバージョンで動作します。
Accept-Encoding
ヘッダーに基づいて、正しいエンコードを選択します。前の例のnew StreamWriter()
は、単にUTF-8を使用します。 base.WriteToStreamAsync
への呼び出しは、異なるエンコードを使用する可能性があり、その結果、出力が破損します。application/javascript
Content-Type
ヘッダーにマップします。前の例ではJSONPが出力されますが、application/json
ヘッダーがあります。この作業は、ネストされたMapping
クラスで行われます(cf. JSONPに最適なコンテンツタイプ? )StreamWriter
の構築とフラッシュのオーバーヘッドを無視し、バイトを直接取得して出力ストリームに書き込みます。ContinueWith
メカニズムを使用して、複数のタスクを連結します。コード:
public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
private string _callbackQueryParameter;
public JsonpMediaTypeFormatter()
{
SupportedMediaTypes.Add(DefaultMediaType);
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/javascript"));
// need a lambda here so that it'll always get the 'live' value of CallbackQueryParameter.
MediaTypeMappings.Add(new Mapping(() => CallbackQueryParameter, "application/javascript"));
}
public string CallbackQueryParameter
{
get { return _callbackQueryParameter ?? "callback"; }
set { _callbackQueryParameter = value; }
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
TransportContext transportContext)
{
var callback = GetCallbackName();
if (!String.IsNullOrEmpty(callback))
{
// select the correct encoding to use.
Encoding encoding = SelectCharacterEncoding(content.Headers);
// write the callback and opening paren.
return Task.Factory.StartNew(() =>
{
var bytes = encoding.GetBytes(callback + "(");
writeStream.Write(bytes, 0, bytes.Length);
})
// then we do the actual JSON serialization...
.ContinueWith(t => base.WriteToStreamAsync(type, value, writeStream, content, transportContext))
// finally, we close the parens.
.ContinueWith(t =>
{
var bytes = encoding.GetBytes(")");
writeStream.Write(bytes, 0, bytes.Length);
});
}
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
private string GetCallbackName()
{
if (HttpContext.Current.Request.HttpMethod != "GET")
return null;
return HttpContext.Current.Request.QueryString[CallbackQueryParameter];
}
#region Nested type: Mapping
private class Mapping : MediaTypeMapping
{
private readonly Func<string> _param;
public Mapping(Func<string> discriminator, string mediaType)
: base(mediaType)
{
_param = discriminator;
}
public override double TryMatchMediaType(HttpRequestMessage request)
{
if (request.RequestUri.Query.Contains(_param() + "="))
return 1.0;
return 0.0;
}
}
#endregion
}
私は、内部クラスコンストラクターのFunc<string>
パラメーターの「ハッキング」を知っていますが、それが解決する問題を回避するための最速の方法でした-C#には静的な内部クラスしかないため、 CallbackQueryParameter
プロパティを参照してください。 Func
を渡すと、ラムダのプロパティがバインドされるため、Mapping
は後でTryMatchMediaType
でアクセスできます。もっとエレガントな方法がある場合は、コメントしてください!
残念ながら、コメントするほどの評判がありませんので、回答を投稿します。 @Justinは、標準のJsonFormatterと一緒に WebApiContrib.Formatting.Jsonp フォーマッターを実行する問題を提起しました。この問題は、最新リリース(実際には少し前にリリースされました)で解決されています。また、最新のWeb APIリリースでも動作するはずです。
独自のJSONPフォーマッターバージョンをホストする代わりに、 WebApiContrib.Formatting.Jsonp NuGetパッケージを実装済みのパッケージ(.NET Frameworkで動作するバージョンを選択)をインストールできます。
このフォーマッターをApplication_Start
に追加します。
GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));
ジョーペルル、トーマス。上記のPeter Mobergによる回答は、彼が継承するJsonMediaTypeFormatterがすでにNewtonSoft Jsonシリアライザーを使用しているため、RCバージョンでは正しいはずです。
ただし、なぜ次の操作を実行できるのに、なぜ人々はまだパラメータを使用していますか?
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
{
var isJsonpRequest = IsJsonpRequest();
if(isJsonpRequest.Item1)
{
return Task.Factory.StartNew(() =>
{
var writer = new StreamWriter(stream);
writer.Write(isJsonpRequest.Item2 + "(");
writer.Flush();
base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext).Wait();
writer.Write(")");
writer.Flush();
});
}
return base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext);
}
private Tuple<bool, string> IsJsonpRequest()
{
if(HttpContext.Current.Request.HttpMethod != "GET")
return new Tuple<bool, string>(false, null);
var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback);
}
これをチェックしてください。それが役立つかどうかを確認してください。
2つの方法を使用して、CORS(クロスオリジンリソース共有)の問題を解決できます。
1)Jsonpの使用2)Corsの有効化
1)Jsonp-を使用してJsonpを使用するには、WebApiContrib.Formatting.Jsonp nugetパッケージをインストールし、WebApiConfig.csにJsonpFormmaterを追加する必要がありますスクリーンショットを参照してください
2)Corsの有効化-
corsを有効にするには、Microsoft.AspNet.WebApi.Cors nugetパッケージを追加し、WebApiConfig.csでcorsを有効にする必要がありますスクリーンショットを参照してください
詳細については、次のリンクを使用してGitHubのサンプルリポジトリを参照できます。 https://github.com/mahesh353/Ninject.WebAPi/tree/develop
コンテキストがWeb Api
であり、010227leo
の回答に感謝し、参照している場合、null
になるWebContext.Current
値を考慮する必要があります。
だから私は彼のコードをこれに更新しました:
public class JsonCallbackAttribute
: ActionFilterAttribute
{
private const string CallbackQueryParameter = "callback";
public override void OnActionExecuted(HttpActionExecutedContext context)
{
var callback = context.Request.GetQueryNameValuePairs().Where(item => item.Key == CallbackQueryParameter).Select(item => item.Value).SingleOrDefault();
if (!string.IsNullOrEmpty(callback))
{
var jsonBuilder = new StringBuilder(callback);
jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);
context.Response.Content = new StringContent(jsonBuilder.ToString());
}
base.OnActionExecuted(context);
}
}
HttpSelfHostServerを使用している場合、このコードセクションはHttpContext.Currentで失敗します。これは、自己ホストサーバー上に存在しないためです。
private Tuple<bool, string> IsJsonpRequest()
{
if(HttpContext.Current.Request.HttpMethod != "GET")
return new Tuple<bool, string>(false, null);
var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback);
}
ただし、このオーバーライドを介して自己ホスト「コンテキスト」をインターセプトできます。
public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
_method = request.Method;
_callbackMethodName =
request.GetQueryNameValuePairs()
.Where(x => x.Key == CallbackQueryParameter)
.Select(x => x.Value)
.FirstOrDefault();
return base.GetPerRequestFormatterInstance(type, request, mediaType);
}
Request.Methodは「GET」、「POST」などを提供し、GetQueryNameValuePairsは?callbackパラメーターを取得できます。したがって、修正したコードは次のようになります。
private Tuple<bool, string> IsJsonpRequest()
{
if (_method.Method != "GET")
return new Tuple<bool, string>(false, null);
return new Tuple<bool, string>(!string.IsNullOrEmpty(_callbackMethodName), _callbackMethodName);
}
これがあなたの一部を助けることを願っています。この方法では、HttpContextシムは必ずしも必要ありません。
C.