JSONP พร้อม ASP.NET Web API


136

ฉันกำลังทำงานเกี่ยวกับการสร้างชุดบริการใหม่ใน ASP.MVC MVC 4 โดยใช้ Web API จนถึงตอนนี้มันเยี่ยมมาก ฉันได้สร้างบริการและทำให้มันใช้งานได้และตอนนี้ฉันกำลังพยายามใช้ JQuery ฉันสามารถเรียกคืนสตริง JSON โดยใช้ Fiddler ได้และดูเหมือนว่าจะโอเค แต่เนื่องจากบริการนี้มีอยู่ในไซต์อื่นพยายามโทรด้วย JQuery error ด้วย "Not Allowed" ดังนั้นนี่เป็นกรณีที่ฉันต้องใช้ JSONP อย่างชัดเจน

ฉันรู้ว่า Web API นั้นใหม่ แต่ฉันหวังว่าจะมีใครบางคนช่วยฉันได้

ฉันจะโทรไปยังวิธี Web API โดยใช้ JSONP ได้อย่างไร


1
เพิ่งดูโครงสร้าง Web API ใหม่หลังจากดูวิดีโอ ScottGu บน Channel9 และอ่านบทความ Scott Hanselman และนี่เป็นหนึ่งในความคิด / คำถามแรกของฉันเกี่ยวกับเรื่องนี้
Tracker1

คำตอบ:


132

หลังจากถามคำถามนี้ในที่สุดฉันก็พบสิ่งที่ฉันต้องการดังนั้นฉันจึงตอบมัน

ฉันวิ่งข้ามJsonpMediaTypeFormatterนี้ เพิ่มเข้าไปในApplication_Startglobal.asax ของคุณโดยทำสิ่งนี้:

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

และคุณก็ดีที่จะใช้ JQuery AJAX call ที่มีลักษณะดังนี้:

$.ajax({
    url: 'http://myurl.com',
    type: 'GET',
    dataType: 'jsonp',
    success: function (data) {
        alert(data.MyProperty);
    }
})

ดูเหมือนว่าจะทำงานได้ดีมาก


ดูเหมือนจะไม่ทำงานในกรณีของฉันซึ่งฉันได้เพิ่มฟอร์แมตเตอร์สำหรับการจัดลำดับ Json.Net แล้ว ความคิดใด ๆ
Justin

4
ฉันเชื่อว่า FormatterContext ถูกลบใน MVC4 RC Version forums.asp.net/post/5102318.aspx
Diganta Kumar

13
รหัสนี้เป็นส่วนหนึ่งของ WebApiContrib ใน NuGet ไม่จำเป็นต้องดึงมันเข้าไปด้วยตนเอง
Jon Onstott

7
ใช่ตอนนี้เพียงแค่: "ติดตั้งแพคเกจ WebApiContrib.Formatting.Jsonp" Doco อยู่ที่นี่: nuget.org/packages/WebApiContrib.Formatting.Jsonp
nootn

4
นี่คือสิ่งที่ฉันต้องใช้โดยการดาวน์โหลด nuget ของวันนี้:GlobalConfiguration.Configuration.AddJsonpFormatter(config.Formatters.JsonFormatter, "callback");
joym8

52

นี่คือ 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);
    }
}

8
ขอบคุณที่น่ากลัว แต่ผมเชื่อว่า WriteToStreamAsync ควรจะ HttpContent ไม่ HttpContentHeaders วัตถุในขณะนี้ในรุ่นสุดท้าย แต่มีการเปลี่ยนแปลงที่หนึ่งทำงานเหมือนเสน่ห์
เบน

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.Net formatter อยู่แล้วซึ่งให้วันที่ json ที่น่ารักและการจัดลำดับที่เร็วขึ้นคุณไม่สามารถเพิ่ม formatter ที่สองสำหรับ jsonp ได้คุณต้องรวมสองตัวนี้เข้าด้วยกัน มันเป็นความคิดที่ดีที่จะใช้มันอย่างไรก็ตาม Scott Hanselman ได้กล่าวว่าการเปิดตัว ASP.NET Web API จะใช้ Json.Net serializer เป็นค่าเริ่มต้น

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 Web API RC ได้อย่างไร
jonperl

สนใจรุ่น RC
โทมัสต็อก


6

JSONP ใช้ได้กับคำขอ Http GET เท่านั้น มีการรองรับ CORS ใน asp.net web api ซึ่งทำงานได้ดีกับคำกริยา http ทั้งหมด

นี้บทความอาจจะเป็นประโยชน์กับคุณ


1
ตอนนี้มีการรองรับ CORS ใน Web API บทความนี้มีประโยชน์มาก - asp.net/web-api/overview/security/…
Ilia Barahovski

5

Updated

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 เวอร์ชันของ Web APIs

  • เลือกการเข้ารหัสที่ถูกต้องตามAccept-Encodingส่วนหัวของคำขอ new StreamWriter()ในตัวอย่างก่อนหน้านี้ก็จะใช้ UTF-8 การเรียกไปยังbase.WriteToStreamAsyncอาจใช้การเข้ารหัสที่แตกต่างกันทำให้เกิดผลลัพธ์ที่เสียหาย
  • Maps JSONP ร้องขอไปยังapplication/javascript Content-Typeส่วนหัว; ตัวอย่างก่อนหน้านี้จะส่งออก JSONP แต่มีapplication/jsonส่วนหัว งานนี้ทำในMappingคลาสที่ซ้อนกัน(เปรียบเทียบกับประเภทเนื้อหาที่ดีที่สุดเพื่อให้บริการ JSONP? )
  • นำหน้าการก่อสร้างและการล้างค่าใช้จ่ายของ a StreamWriterและรับไบต์โดยตรงและเขียนลงในเอาต์พุตสตรีม
  • แทนที่จะรองานให้ใช้ContinueWithกลไกของ Task Parallel Library เพื่อเชื่อมโยงภารกิจต่างๆเข้าด้วยกัน

รหัส:

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ในผูกอสังหาริมทรัพย์ในแลมบ์ดาจึงจะสามารถเข้าถึงได้ในภายหลังในMapping TryMatchMediaTypeหากคุณมีวิธีที่สง่างามมากขึ้นโปรดแสดงความคิดเห็น!


2

น่าเสียดายที่ฉันไม่มีชื่อเสียงพอที่จะแสดงความคิดเห็นดังนั้นฉันจะโพสต์คำตอบ @Justin แจ้งปัญหาในการเรียกใช้WebApiContrib.Formatting.Jsonpฟอร์แมตเตอร์พร้อมกับ JsonFormatter มาตรฐาน ปัญหาดังกล่าวได้รับการแก้ไขในรุ่นล่าสุด นอกจากนี้ควรทำงานกับ Web API รุ่นล่าสุด


1

johperl โทมัส คำตอบที่ Peter Moberg ได้กล่าวไว้ข้างต้นควรจะถูกต้องสำหรับรุ่น RC เนื่องจาก JsonMediaTypeFormatter ที่เขาสืบทอดมาจากการใช้ serializer 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);
        }

1

แทนที่จะโฮสติ้ง JSONP ของคุณเอง formatter รุ่นที่คุณสามารถติดตั้ง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);
        }

การร้องขอวิธีการจะให้ "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.Currentnull

ดังนั้นฉันจึงปรับปรุงรหัสของเขาเป็น:

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 (การแบ่งปันทรัพยากรข้ามแหล่ง) โดยใช้สองวิธี

1) การใช้ Jsonp 2) การเปิดใช้งาน Cors

1) การใช้ Jsonp- เพื่อใช้ Jsonp เราจำเป็นต้องติดตั้งแพคเกจ WebApiContrib.Formatting.Jsonp และต้องการเพิ่ม JsonpFormmater ใน WebApiConfig.cs อ้างอิงภาพหน้าจอป้อนคำอธิบายรูปภาพที่นี่

รหัส jquery ป้อนคำอธิบายรูปภาพที่นี่

2) การเปิดใช้งาน Cors -

เพื่อเปิดใช้งาน cors เราจำเป็นต้องเพิ่มแพคเกจ nuget Microsoft.AspNet.WebApi.Cors และต้องเปิดใช้งาน cors ใน WebApiConfig.cs ดูภาพหน้าจอ

ป้อนคำอธิบายรูปภาพที่นี่

สำหรับการอ้างอิงเพิ่มเติมคุณสามารถอ้างอิง repo ตัวอย่างของฉันบน GitHub โดยใช้ลิงค์ต่อไปนี้ https://github.com/mahesh353/Ninject.WebAPi/tree/develop

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.