ASP.NET 웹 API를 사용하는 JSONP


136

웹 API를 사용하여 ASP.MVC MVC 4에서 새로운 서비스 세트를 작성하고 있습니다. 지금까지는 훌륭합니다. 서비스를 만들고 작동 시켰으며 이제 JQuery를 사용하여 서비스를 사용하려고합니다. Fiddler를 사용하여 JSON 문자열을 다시 가져올 수 있지만 괜찮은 것처럼 보이지만 서비스가 별도의 사이트에 있기 때문에 "허용되지 않음"으로 JQuery 오류로 호출하려고합니다. 따라서 JSONP를 사용해야하는 경우가 분명합니다.

나는 웹 API가 새로운 것을 알고 있지만 누군가 나를 도울 수 있기를 바랍니다.

JSONP를 사용하여 웹 API 메소드를 호출하려면 어떻게해야합니까?


1
Channel9에서 ScottGu 비디오를보고 Scott Hanselman 기사를 읽은 후 새로운 웹 API 구조를 살펴 보았으며, 이것에 대한 나의 첫 생각 / 질문 중 하나였습니다.
추적자 1

답변:


132

이 질문을 한 후에 마침내 필요한 것을 찾았으므로 대답하고 있습니다.

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

잘 작동하는 것 같습니다.


Json.Net 직렬화를 위해 이미 포맷터가 추가되어있는 경우에는 작동하지 않는 것 같습니다. 어떤 아이디어?
Justin

4
FormatterContext가 MVC4 RC 버전 forums.asp.net/post/5102318.aspx
Diganta Kumar

13
코드는 이제 NuGet에서 WebApiContrib의 일부입니다. 수동으로 넣을 필요가 없습니다.
Jon Onstott

7
네, 지금 : "Install-Package WebApiContrib.Formatting.Jsonp"Doco는 다음과 같습니다 : nuget.org/packages/WebApiContrib.Formatting.Jsonp
nootn

4
이것이 오늘의 너겟 다운로드를 사용하여 넣은 것입니다.GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
joym8

52

다음은 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);
    }
}

8
감사합니다. WriteToStreamAsync는 최종 릴리스에서 HttpContentHeaders 객체가 아닌 HttpContent를 가져와야한다고 생각하지만 한 가지 변경 사항이 매력처럼 작동했습니다.
Ben

21

다음과 같이 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;
}

VS2013 U5와 함께 완벽하게 작동, MVC5.2 & WebApi 2
Yarla 문의

11

확실히 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);
        }
    }

ASP .NET 웹 API RC에 대해 어떻게 할 수 있습니까?
jonperl

RC 버전에도 관심이 있습니다
Thomas Stock



5

업데이트

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

다른 버전은 최신 .net 프레임 워크에서 작동하지 않습니다.
djbielejeski

2

다음은 RTM 버전의 웹 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>내부 클래스 생성자에서 매개 변수 의 "hackiness"를 알고 있지만 해결하는 문제를 해결하는 가장 빠른 방법이었습니다 .C #에는 정적 내부 클래스 만 있기 때문에 CallbackQueryParameter속성을 볼 수 없습니다 . Funcin을 전달하면 람다의 속성이 바인딩되므로 Mapping나중에에서에 액세스 할 수 있습니다 TryMatchMediaType. 더 우아한 방법이 있다면 의견을 말하십시오!


2

불행히도, 의견을 말할만큼 충분한 평판이 없으므로 답변을 게시 할 것입니다. @Justin 은 표준 JsonFormatter와 함께 WebApiContrib.Formatting.Jsonp 포맷터 를 실행하는 문제를 제기했습니다 . 이 문제는 최신 릴리스 (실제로 얼마 전에 릴리스 됨)에서 해결되었습니다. 또한 최신 웹 API 릴리스와 함께 작동해야합니다.


1

조퍼, 토마스 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);
        }

1

자체 JSONP 포맷터 버전을 호스팅하는 대신 이미 구현 된 WebApiContrib.Formatting.Jsonp NuGet 패키지를 설치할 수 있습니다 (.NET Framework에 적합한 버전 선택).

이 포맷터를 Application_Start다음에 추가하십시오 .

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

0

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이 필요하지 않습니다.

씨.



0

문맥이 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);
    }
}

0

CORS (Cross-Origin Resource Sharing) 문제는 두 가지 방법으로 해결할 수 있습니다.

1) Jsonp 사용 2) Cors 활성화

1) Jsonp를 사용하여 Jsonp를 사용하려면 WebApiContrib.Formatting.Jsonp nuget 패키지를 설치하고 WebApiConfig.cs에 JsonpFormmater를 추가해야합니다.여기에 이미지 설명을 입력하십시오

jquery 코드 여기에 이미지 설명을 입력하십시오

2) Cors 활성화-

cors를 활성화하려면 Microsoft.AspNet.WebApi.Cors nuget 패키지를 추가하고 WebApiConfig.cs에서 cors를 활성화해야합니다.

여기에 이미지 설명을 입력하십시오

자세한 내용은 다음 링크를 사용하여 GitHub에서 샘플 저장소를 참조하십시오. https://github.com/mahesh353/Ninject.WebAPi/tree/develop

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.