JSONP с ASP.NET Web API
Я работаю над созданием нового набора сервисов в ASP.MVC MVC 4 с помощью веб-API. До сих пор, это здорово. Я создал сервис и заставил его работать, и теперь я пытаюсь использовать его с помощью JQuery. Я могу вернуть строку JSON с помощью Fiddler, и, похоже, все в порядке, но поскольку служба существует на отдельном сайте, пытаясь вызвать ее с ошибками JQuery с "не разрешено". Итак, это явно тот случай, когда мне нужно использовать JSONP.
Я знаю, что веб-API новый, но я надеюсь, что кто-то там может мне помочь.
Как сделать вызов метода Web API с помощью JSONP?
15 ответов:
задав этот вопрос, я, наконец, нашел то, что мне нужно, поэтому я отвечаю на него.
я наткнулся на это JsonpMediaTypeFormatter. Добавьте его в
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); } })Это, кажется, работает очень хорошо.
вот обновленная версия JsonpMediaTypeFormatter для использования с 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); } }
Вы можете использовать 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, вы должны объединить их. Это хорошая идея, чтобы использовать его в любом случае, как Скотт Хансельман сказал, что выпуск ASP.NET веб-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); } }
реализация Рика Штрала лучше всего работал для меня с RC.
JSONP работает только с HTTP GET запросом. Существует поддержка CORS в asp.net веб-api, который хорошо работает со всеми http-глаголами.
этой статья может быть полезной для вас.
Обновлено
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); } }
вот обновленная версия с несколькими улучшениями, которая работает с RTM-версией веб-API.
- выбирает правильную кодировку, основываясь на собственном запросе
Accept-Encodingзаголовки. Элементnew StreamWriter()в предыдущих примерах будет просто использовать UTF-8. Звонок вbase.WriteToStreamAsyncможет использоваться другая кодировка,что приводит к повреждению вывода.- сопоставляет запросы JSONP с
application/javascriptContent-Typeзаголовок; предыдущий пример будет выводить JSONP, но сapplication/jsonзаголовок. Эта работа выполняется во вложенномMappingкласса (ср. МФ. лучший тип контента для обслуживания 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собственность. Проходя мимоFuncin связывает свойство в лямбде, поэтомуMappingбудет иметь доступ к нему позже вTryMatchMediaType. Если у вас есть более элегантный способ, пожалуйста, прокомментируйте!
johperl, Thomas. Ответ, данный Питером Мобергом выше, должен быть правильным для версии RC как JsonMediaTypeFormatter, который он наследует от использования сериализатора NewtonSoft Json, и поэтому то, что у него есть, должно работать с любыми изменениями.
однако, почему на Земле люди все еще используют параметры, когда вы можете просто сделать следующее
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); }
вместо того, чтобы свой собственный хостинг JSONP в праматерии версия вы можете установить WebApiContrib.Форматирование.Jsonp пакет NuGet с уже реализованным (выберите версию, которая работает для вашего .NET Framework).
добавьте этот форматер в
Application_Start:GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));
к сожалению, у меня недостаточно репутации, чтобы комментировать, поэтому я опубликую ответ. @Justin поднял вопрос о запуске WebApiContrib.Форматирование.Jsonp форматер наряду со стандартным JsonFormatter. Эта проблема решена в последнем выпуске (фактически выпущенном некоторое время назад). Кроме того, он должен работать с последней версией Web API.
для тех из вас, кто использует HttpSelfHostServer этот раздел кода не будет работать на HttpContext.Текущий, так как он не существует на сервере self host.
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); }запрос.Метод даст вам "GET", "POST" и т. д. и GetQueryNameValuePairs может получить ?параметр обратного вызова. Таким образом мой пересмотренный код выглядит так:
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.
если контекст
Web Api, благодаря и ссылаясь на010227leoответ, вы должны рассмотретьWebContext.Currentзначение, которое будетnull.поэтому я обновил его код следующим образом:
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); } }
мы можем решить проблему CORS (Cross-origin resource sharing) двумя способами,
1) Использование Jsonp 2) включение Cors
1) Использование Jsonp- для использования Jsonp нам необходимо установить WebApiContrib.Форматирование.Пакет NuGet JSONP в и нужно добавить JsonpFormmater в WebApiConfig.СЅ см. скриншоты
2) включение Cors -
чтобы включить cors нам нужно добавить Microsoft.сеть САШ.Веб-API.CORS nuget пакет и необходимо включить cors в WebApiConfig.СЅ см. скриншот
для получения дополнительной информации вы можете сослаться на мой образец РЕПО на GitHub, используя следующую ссылку. https://github.com/mahesh353/Ninject.WebAPi/tree/develop



Comments