136

Web API を使用して、ASP.MVC MVC 4 で新しいサービス セットの作成に取り組んでいます。これまでのところ、それは素晴らしいです。私はサービスを作成して動作させましたが、今は JQuery を使用してサービスを利用しようとしています。Fiddler を使用して JSON 文字列を取得できます。問題ないようですが、サービスが別のサイトに存在するため、「許可されていません」という JQuery エラーで呼び出そうとします。したがって、これは明らかに JSONP を使用する必要があるケースです。

Web API が新しいことは知っていますが、誰かが助けてくれることを願っています。

JSONP を使用して Web API メソッドを呼び出すにはどうすればよいですか?

4

15 に答える 15

132

この質問をした後、ようやく必要なものを見つけたので、答えています。

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

とてもうまくいくようです。

于 2012-02-23T22:10:49.180 に答える
52

以下は、WebAPI RC で使用するための JsonpMediaTypeFormatter の更新バージョンです。

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);
    }
}
于 2012-06-03T15:37:28.713 に答える
11

確かにブライアンの答えは正しいものですが、すでにJson.Netフォーマッターを使用している場合は、かなりのjson日付とより高速なシリアライゼーションが得られます.jsonp用の2番目のフォーマッターを追加するだけではなく、2つを組み合わせる必要があります。Scott Hanselman が、ASP.NET Web API のリリースではデフォルトで Json.Net シリアライザーを使用する予定であると述べているように、とにかくそれを使用することをお勧めします。

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);
        }
    }
于 2012-05-17T12:57:27.317 に答える
9

Rick Strahl の実装は、RC を使用する場合に最適でした。

于 2012-08-07T12:39:21.197 に答える
6

JSONP は、HTTP GET 要求でのみ機能します。すべての http 動詞で適切に機能する asp.net Web API には CORS サポートがあります。

この記事は役に立つかもしれません。

于 2012-08-24T00:51:05.917 に答える
2

これは、Web API の RTM バージョンで動作する、いくつかの改良を加えた更新バージョンです。

  • Accept-Encodingリクエスト自体のヘッダーに基づいて、正しいエンコーディングを選択します。前のnew StreamWriter()例では、単純に UTF-8 を使用します。への呼び出しbase.WriteToStreamAsyncで別のエンコーディングが使用される可能性があり、結果として出力が破損する可能性があります。
  • JSONP リクエストをapplication/javascript Content-Typeヘッダーにマップします。前の例では JSONP が出力されますが、application/jsonヘッダーが含まれています。この作業はネストされたMappingクラスで行われます (cf. Best content type to serve JSONP? )
  • a の構築とフラッシュのオーバーヘッドを無視しStreamWriter、バイトを直接取得して出力ストリームに書き込みます。
  • タスクを待機する代わりに、Task Parallel Library の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プロパティを表示できません。in を渡すとFunc、プロパティがラムダにバインドされるためMapping、後で でアクセスできるようになりますTryMatchMediaType。もっとエレガントな方法があれば、コメントしてください!

于 2012-09-19T10:06:10.393 に答える
2

残念ながら、コメントするほどの評判がないので、回答を投稿します。@Justin は、標準の JsonFormatter と一緒にWebApiContrib.Formatting.Jsonpフォーマッタを実行するという問題を提起しました。その問題は最新のリリースで解決されています (実際には少し前にリリースされました)。また、最新の Web API リリースで動作するはずです。

于 2014-09-25T15:58:28.047 に答える
1

独自の JSONP フォーマッタ バージョンをホストする代わりに、既に実装されているWebApiContrib.Formatting.Jsonp NuGet パッケージをインストールできます (.NET Framework で動作するバージョンを選択してください)。

このフォーマッタを に追加Application_Start:

GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));
于 2014-01-10T13:44:47.700 に答える
0

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 shim は必要ありません。

C.

于 2013-01-13T22:39:44.470 に答える
0

これをチェックしてください。それが役立つかどうかを確認してください。

Web API を使用した JSONP

于 2013-08-12T16:17:56.610 に答える
0

コンテキストがWeb Apiである場合、 の回答に感謝し、参照する場合、になる価値を010227leo考慮する必要があります。WebContext.Currentnull

そこで、彼のコードを次のように更新しました。

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);
    }
}
于 2015-02-13T14:48:16.307 に答える
0

2 つの方法を使用して、CORS (クロスオリジン リソース共有) の問題を解決できます。

1) Jsonp の使用 2) Cors の有効化

1) Jsonp の使用 - Jsonp を使用するには、WebApiContrib.Formatting.Jsonp nuget パッケージをインストールする必要があり、WebApiConfig.cs に JsonpFormmater を追加する必要があります。スクリーンショットを参照してください。ここに画像の説明を入力

Jクエリコード ここに画像の説明を入力

2) Cors の有効化 -

Cors を有効にするには、Microsoft.AspNet.WebApi.Cors nuget パッケージを追加する必要があり、WebApiConfig.cs で cors を有効にする必要があります。スクリーンショットを参照してください。

ここに画像の説明を入力

詳細については、次のリンクを使用して GitHub のサンプル リポジトリを参照してください。 https://github.com/mahesh353/Ninject.WebAPI/tree/develop

于 2018-03-15T07:54:21.170 に答える