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?
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.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);
}
}
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;
}
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);
}
}
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.
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.
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);
}
}
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łaniebase.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łówkiemapplication/json
. Praca ta jest wykonywana w Klasa zagnieżdżonaMapping
(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!
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);
}
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()));
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.
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.
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.
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);
}
}
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,
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
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
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