ASP.NET 웹 API를 사용하는 JSONP
웹 API를 사용하여 ASP.MVC MVC 4에서 새로운 서비스 세트를 작성하고 있습니다. 지금까지는 훌륭합니다. 서비스를 만들고 작동 시켰으며 이제 JQuery를 사용하여 서비스를 사용하려고합니다. Fiddler를 사용하여 JSON 문자열을 다시 가져올 수 있지만 괜찮은 것처럼 보이지만 서비스가 별도의 사이트에 있기 때문에 "허용되지 않음"으로 JQuery 오류로 호출하려고합니다. 따라서 JSONP를 사용해야하는 경우가 분명합니다.
나는 웹 API가 새로운 것을 알고 있지만 누군가 나를 도울 수 있기를 바랍니다.
JSONP를 사용하여 웹 API 메소드를 호출하려면 어떻게해야합니까?
이 질문을 한 후에 마침내 필요한 것을 찾았으므로 대답하고 있습니다.
이 JsonpMediaTypeFormatter에서 실행 되었습니다 . 다음 Application_Start
을 수행하여 global.asax에 추가하십시오 .
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);
}
})
잘 작동하는 것 같습니다.
다음은 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);
}
}
다음과 같이 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;
}
확실히 Brian의 대답은 맞습니다. 그러나 이미 json 날짜와 빠른 직렬화를 제공하는 Json.Net 포맷터를 사용하고 있다면 jsonp에 두 번째 포맷터를 추가 할 수는 없으며 두 가지를 결합해야합니다. 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);
}
}
Rick Strahl의 구현 은 RC와 함께 가장 효과적이었습니다.
JSONP는 Http GET 요청에서만 작동합니다. asp.net 웹 API에는 모든 http 동사와 잘 작동하는 CORS 지원이 있습니다.
업데이트
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 버전의 Web API에서 작동하는 몇 가지 개선 된 업데이트 버전입니다.
- 요청 자체
Accept-Encoding
헤더를 기반으로 올바른 인코딩을 선택합니다 .new StreamWriter()
앞의 예에서 단순히 UTF-8을 사용합니다. 에 대한 호출base.WriteToStreamAsync
이 다른 인코딩을 사용하여 출력이 손상 될 수 있습니다. - JSONP 요청을
application/javascript
Content-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
속성을 볼 수 없습니다 . Func
in을 전달하면 람다의 속성이 바인딩되므로 Mapping
나중에에서에 액세스 할 수 있습니다 TryMatchMediaType
. 더 우아한 방법이 있다면 의견을 말하십시오!
불행히도, 의견을 말할만큼 충분한 평판이 없으므로 답변을 게시 할 것입니다. @Justin 은 표준 JsonFormatter와 함께 WebApiContrib.Formatting.Jsonp 포맷터 를 실행하는 문제를 제기했습니다 . 이 문제는 최신 릴리스 (실제로 얼마 전에 릴리스 됨)에서 해결되었습니다. 또한 최신 웹 API 릴리스와 함께 작동해야합니다.
조퍼, 토마스 Peter Moberg가 위의 대답은 RC 버전에 대해 그가 상속 한 JsonMediaTypeFormatter가 이미 NewtonSoft Json 직렬 변환기를 사용하므로 변경하지 않아도 작동해야하므로 RC 버전에 정확해야합니다.
그러나 왜 지구상에서 사람들이 여전히 매개 변수를 사용하고 있습니까?
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.Formatting.Jsonp NuGet 패키지를 설치할 수 있습니다 (.NET Framework에 적합한 버전 선택).
이 포맷터를 Application_Start
다음에 추가하십시오 .
GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));
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이 필요하지 않습니다.
씨.
이것을 확인하십시오. 도움이되는지 확인하십시오.
문맥이 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.Formatting.Jsonp nuget 패키지를 설치하고 WebApiConfig.cs에 JsonpFormmater를 추가해야합니다.
2) Cors 활성화-
cors를 활성화하려면 Microsoft.AspNet.WebApi.Cors nuget 패키지를 추가하고 WebApiConfig.cs에서 cors를 활성화해야합니다.
자세한 내용은 다음 링크를 사용하여 GitHub에서 샘플 저장소를 참조하십시오. https://github.com/mahesh353/Ninject.WebAPi/tree/develop
참고 URL : https://stackoverflow.com/questions/9421312/jsonp-with-asp-net-web-api
'programing tip' 카테고리의 다른 글
jQuery document.ready 대 자체 호출 익명 함수 (0) | 2020.06.25 |
---|---|
채널을 열어 두어도 되나요? (0) | 2020.06.25 |
Bash에서 마지막 명령의 출력 재사용 (0) | 2020.06.24 |
System.Diagnostics.Debug.Write 출력은 어디에 나타 납니까? (0) | 2020.06.24 |
UML 클래스 다이어그램 열거 형 (0) | 2020.06.24 |