JSONP z ASP.NET Web API

Pracuję nad stworzeniem nowego zestawu usług w ASP.MVC MVC 4 przy użyciu Web API. Jak na razie jest świetnie. Stworzyłem usługę i uruchomiłem ją, a teraz próbuję ją wykorzystać za pomocą JQuery. Mogę odzyskać ciąg JSON za pomocą Fiddler, i wydaje się być ok, ale ponieważ usługa istnieje na oddzielnej stronie, próbując wywołać go z błędami JQuery z "niedozwolone". Tak więc jest to wyraźnie przypadek, w którym muszę użyć JSONP.

Wiem, że Web API jest nowe, ale Mam nadzieję, że ktoś mi pomoże.

Jak wykonać wywołanie metody Web API przy użyciu JSONP?

Author: David Pfeffer, 2012-02-24

15 answers

Po zadaniu tego pytania, w końcu znalazłem to, czego potrzebowałem, więc odpowiadam.

Natknąłem się na to JsonpMediaTypeFormatter . Dodaj go do Application_Start twojego globalnego.asax robiąc to:

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

I dobrze jest przejść z wywołaniem jQuery AJAX, które wygląda tak:

$.ajax({
    url: 'http://myurl.com',
    type: 'GET',
    dataType: 'jsonp',
    success: function (data) {
        alert(data.MyProperty);
    }
})
Wygląda na to, że działa bardzo dobrze.
 126
Author: Brian McCord,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-17 01:48:52

Oto zaktualizowana wersja JsonpMediaTypeFormatter do użytku z WebAPI 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);
    }
}
 51
Author: Peter Moberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-27 14:27:16

Możesz użyć ActionFilterAttribute w następujący sposób:

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

Następnie umieścić go na swojej akcji:

[JsonCallback]
public IEnumerable<User> User()
{
    return _user;
}
 20
Author: 010227leo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-13 10:19:50

Z pewnością odpowiedź Briana jest prawidłowa, jednak jeśli już korzystasz z Json.Net formatter, który daje ładne daty json i szybszą serializację, to nie można po prostu dodać drugiego formatera dla jsonp, trzeba połączyć te dwa. To dobry pomysł, aby go używać tak, jak Scott Hanselman powiedział, że wydanie ASP.NET Web API będzie używać Json.Net serializer domyślnie.

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
Author: Justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-17 12:57:27

Implementacja Ricka Strahla działała najlepiej dla mnie z RC.

 9
Author: Paul G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-07 12:39:21

JSONP działa tylko z żądaniem HTTP GET. Istnieje wsparcie CORS w asp.net web api, które działa dobrze ze wszystkimi czasownikami http.

Ten artykuł może być dla Ciebie pomocny.

 6
Author: user1186065,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-24 00:51:05

Aktualizacja

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
Author: ITXGEN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-29 10:22:30

Oto zaktualizowana wersja z kilkoma ulepszeniami, która działa z wersją RTM interfejsów API sieci Web.

  • wybiera poprawne kodowanie na podstawie własnych nagłówków Accept-Encoding żądania. new StreamWriter() w poprzednich przykładach używałoby po prostu UTF-8. Wywołanie base.WriteToStreamAsync może używać innego kodowania, co skutkuje uszkodzeniem wyjścia.
  • mapuje wnioski JSONP do application/javascript Content-Type nagłówek; poprzedni przykład wyświetli JSONP, ale z nagłówkiem application/json. Praca ta jest wykonywana w Klasa zagnieżdżona Mapping (por. najlepszy typ treści do obsługi JSONP?)
  • przesunął konstrukcję i przepłukał napowietrzną StreamWriter i bezpośrednio pobrał bajty i zapisywał je do strumienia wyjściowego.
  • zamiast czekać na zadanie, użyj mechanizmu Task Parallel Library ContinueWith, aby połączyć kilka zadań ze sobą.

Kod:

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
}

Jestem świadomy "hackiness" parametru Func<string> w konstruktorze klasy wewnętrznej, ale to był najszybszy sposób, aby uzyskać wokół problemu, który rozwiązuje -- ponieważ C# ma tylko statyczne klasy wewnętrzne, nie widzi właściwości CallbackQueryParameter. Podanie Func in wiąże właściwość w lambdzie, więc Mapping będzie mógł uzyskać do niej dostęp później w TryMatchMediaType. Jeśli masz bardziej elegancki sposób, skomentuj!

 2
Author: atanamir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:00

Johperl Tomasz Odpowiedź udzielona przez Petera Moberg powyżej powinna być poprawna dla wersji RC jako jsonmediatypeformatter, że dziedziczy od używa Newtonsoft JSON serializer już, a więc to, co ma, powinno działać bez żadnych zmian.

Jednakże, dlaczego ludzie nadal używają parametrów, kiedy możesz po prostu wykonać następujące czynności

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
Author: stevethethread,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-11 11:34:42

Zamiast hostować własną wersję JSONP formatter można zainstalować WebApiContrib.Formatowanie.Jsonp pakiet NuGet z już zaimplementowanym (wybierz wersję, która działa dla Twojego. NET Framework).

Dodaj ten format do Application_Start:

GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));
 1
Author: Mr. Pumpkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-10 13:44:47

Niestety, nie mam wystarczającej reputacji, aby skomentować, więc napiszę odpowiedź. @Justin podniósł kwestię prowadzenia WebApiContrib.Formatowanie.JSONP formatowanie obok standardowego JsonFormatter. Ten problem został rozwiązany w najnowszej wersji (faktycznie wydany jakiś czas temu). Powinien również działać z najnowszą wersją interfejsu API.

 1
Author: panesofglass,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-25 15:58:28

Dla tych z Was, którzy używają HttpSelfHostServer Ta sekcja kodu nie powiedzie się na HttpContext.Aktualna, ponieważ nie istnieje na serwerze hosta własnego.

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

Można jednak przechwycić "kontekst" hosta własnego za pomocą tego nadpisania.

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

Prośba.Metoda daje "GET"," POST", itp. a GetQueryNameValuePairs może odzyskać ?parametr wywołania zwrotnego. Tak więc mój poprawiony kod wygląda następująco:

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);
}
Mam nadzieję, że to pomoże niektórym z was. W ten sposób nie koniecznie potrzebujesz podkładki HttpContext.

C.

 0
Author: Coyote,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-13 22:39:44

Zobacz też Zobacz, czy to pomoże.

JSONP z Web API

 0
Author: Chorinator,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-12 16:17:56

Jeśli kontekst jest Web Api, dziękując i odwołując się do odpowiedzi 010227leo, musisz wziąć pod uwagę WebContext.Current wartość, która będzie null.

Więc zaktualizowałem jego kod do tego:

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
Author: Rikki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-13 14:48:16

Możemy rozwiązać problem CORS (Cross-origin resource sharing)na dwa sposoby,

1) Korzystanie Z Jsonp 2) Włączanie Kors

1) Korzystanie Z Jsonp- aby użyć Jsonp musimy zainstalować WebApiContrib.Formatowanie.Pakiet Jsonp nuget i trzeba dodać JsonpFormmater w WebApiConfig.cs refer screenshoty,Tutaj wpisz opis obrazka

Kod Jquery Tutaj wpisz opis obrazka

2) Włączanie Kors -

Aby włączyć cors musimy dodać Microsoft.AspNet.WebApi.Cors nuget pakiet i trzeba włączyć cors w WebApiConfig.CS refer screenshot

Tutaj wpisz opis obrazka

Aby uzyskać więcej informacji, możesz odwołać się do mojego przykładowego repo na Githubie, używając poniższego linku. https://github.com/mahesh353/Ninject.WebAPi/tree/develop

 0
Author: Mendax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 07:54:21