web-dev-qa-db-fra.com

JSONP avec API Web ASP.NET

Je travaille actuellement à la création d'un nouvel ensemble de services dans ASP.MVC MVC 4 à l'aide de l'API Web. Jusqu'ici, c'est génial. J'ai créé le service et je l'ai mis au travail. J'essaie maintenant de le consommer avec JQuery. Je peux récupérer la chaîne JSON en utilisant Fiddler, et cela semble être correct, mais parce que le service existe sur un site séparé, essayez de l'appeler avec des erreurs JQuery avec le message "Non autorisé". Donc, c’est clairement un cas où j’ai besoin d’utiliser JSONP.

Je sais que l'API Web est nouvelle, mais j'espère que quelqu'un pourra m'aider.

Comment appeler une méthode API Web à l'aide de JSONP?

136
Brian McCord

Après avoir posé cette question, j'ai finalement trouvé ce dont j'avais besoin, alors je réponds.

Je suis tombé sur cette JsonpMediaTypeFormatter . Ajoutez-le dans le Application_Start de votre global.asax en faisant ceci:

var config = GlobalConfiguration.Configuration;
config.Formatters.Insert(0, new JsonpMediaTypeFormatter());

et vous pouvez aller avec un appel JQuery AJAX) qui ressemble à ceci:

$.ajax({
    url: 'http://myurl.com',
    type: 'GET',
    dataType: 'jsonp',
    success: function (data) {
        alert(data.MyProperty);
    }
})

Cela semble fonctionner très bien.

131
Brian McCord

Voici une version mise à jour de JsonMediaTypeFormatter à utiliser avec Web API RC:

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);
    }
}
52
Peter Moberg

Vous pouvez utiliser un ActionFilterAttribute comme ceci:

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

Alors mets-le sur ton action:

[JsonCallback]
public IEnumerable<User> User()
{
    return _user;
}
21
010227leo

Certes, la réponse de Brian est la bonne, mais si vous utilisez déjà le formateur Json.Net, qui vous donne de jolies dates json et une sérialisation plus rapide, vous ne pouvez pas simplement ajouter un deuxième formateur pour jsonp, vous devez combiner les deux. Quoi qu'il en soit, c'est une bonne idée de l'utiliser, car Scott Hanselman a déclaré que la version de l'API Web ASP.NET utilisera le sérialiseur Json.Net par défaut.

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

implémentation de Rick Strahl a fonctionné le mieux pour moi avec RC.

9
Paul G

JSONP ne fonctionne qu'avec la requête Http GET. Asp.net web supporte CORS et fonctionne bien avec tous les verbes http.

This article peut vous être utile.

6
user1186065

Mise à jour

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

Voici une version mise à jour avec plusieurs améliorations, qui fonctionne avec la version des API Web RTM).

  • Sélectionne le bon codage, basé sur les en-têtes propres de la demande Accept-Encoding. La new StreamWriter() dans les exemples précédents utilisera simplement UTF-8. L'appel à base.WriteToStreamAsync Peut utiliser un codage différent, entraînant une sortie corrompue.
  • Mappe les requêtes JSONP à l'en-tête application/javascriptContent-Type; l'exemple précédent afficherait JSONP, mais avec l'en-tête application/json. Ce travail est effectué dans la classe imbriquée Mapping (cf. meilleur type de contenu pour servir JSONP? )
  • Renonce à la construction et à la surcharge d'un StreamWriter, récupère directement les octets et les écrit dans le flux de sortie.
  • Au lieu d'attendre une tâche, utilisez le mécanisme ContinueWith de la bibliothèque parallèle de tâches pour enchaîner plusieurs tâches.

Code:

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
}

Je suis conscient de la "rigidité" du paramètre Func<string> Dans le constructeur de la classe interne, mais c'était le moyen le plus rapide de contourner le problème qu'il résolvait - puisque C # n'a que des classes internes statiques, il peut ' ne voyez pas la propriété CallbackQueryParameter. Passer le Func dans lie la propriété dans le lambda, donc Mapping pourra y accéder ultérieurement dans TryMatchMediaType. Si vous avez une manière plus élégante, commentez s'il vous plaît!

2
atanamir

Malheureusement, je n'ai pas assez de réputation pour commenter, alors je posterai une réponse. @Justin a soulevé la question de l'exécution du programme de formatage WebApiContrib.Formatting.Jsonp aux côtés de JsonFormatter standard. Ce problème est résolu dans la dernière version (publiée il y a quelque temps). En outre, cela devrait fonctionner avec la dernière version de l'API Web.

2
panesofglass

Au lieu d’héberger votre propre version du formateur JSONP, vous pouvez installer WebApiContrib.Formatting.Jsonp Package NuGet avec celui déjà implémenté (choisissez la version qui convient à votre .NET Framework).

Ajouter ce formateur dans Application_Start:

GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));
1
Mr. Pumpkin

johperl, Thomas. La réponse donnée par Peter Moberg ci-dessus devrait être correcte pour la version RC, car le JsonMediaTypeFormatter dont il hérite utilise déjà le sérialiseur NewtonSoft Json.

Cependant, pourquoi diable les gens utilisent-ils encore des paramètres alors que vous pouvez simplement faire ce qui suit

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

Vérifiez celui-ci. Voyez si ça aide.

JSONP avec Web API

0
Chorinator

Nous pouvons résoudre le problème CORS (Cross-Origin Resource Sharing) de deux manières:

1) Utilisation de Jsonp 2) Activation du Cors

1) Pour utiliser Jsonp, nous devons installer le package WebApiContrib.Formatting.Jsonp et ajouter JsonpFormmater dans WebApiConfig.cs en vous référant aux screenshots, enter image description here

Jquery code enter image description here

2) Activer le Cors -

pour activer les cors, nous devons ajouter le package de nuget Microsoft.AspNet.WebApi.Cors et nous devons les activer dans WebApiConfig.cs.

enter image description here

Pour plus de référence, vous pouvez consulter mon exemple de dépôt sur GitHub en utilisant le lien suivant. https://github.com/mahesh353/Ninject.WebAPi/tree/develop

0
Mendax

Si le contexte est Web Api, En remerciant et en vous référant à la réponse de 010227leo, Vous devez prendre en compte la valeur WebContext.Current Qui va être null.

J'ai donc mis à jour son code pour ceci:

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

Pour ceux d'entre vous qui utilisent HttpSelfHostServer, cette section de code échouera sur HttpContext.Current, car elle n'existe pas sur le serveur hôte autonome.

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

Cependant, vous pouvez intercepter le "contexte" de votre hôte par ce remplacement.

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 vous donnera "GET", "POST", etc. et GetQueryNameValuePairs peut récupérer le paramètre? Callback. Ainsi, mon code révisé ressemble à:

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

J'espère que cela aide certains d'entre vous. De cette façon, vous n’avez pas nécessairement besoin d’une cale HttpContext.

C.

0
Coyote