Cómo manejar la solicitud AJAX JQUERY POST con autocaptura WCF

Hay muchas razones para crear un servidor WCF RESTful (es fácil) y aún mejor si puede evitar el ASP y su caja de seguridad (si todo lo que hace son simples solicitudes para devolver información). Vea: http://msdn.microsoft.com/en-us/library/ms750530.aspx sobre cómo hacer esto.

Lo que encontré es que manejar solicitudes AJAX (JQUERY) GET es fácil. Pero lidiar con JSON en un POST es complicado.

Aquí hay un ejemplo de un simple contrato de solicitud GET:

[OperationContract] [WebGet(ResponseFormat = WebMessageFormat.Json)] String Version(); 

Y la implementación está aquí (que devuelve un JSON)

  public partial class CatalogService : ICatalogService { public String Version() { mon.IsActive = true; this.BypassCrossDomain(); ViewModel.myself.TransactionCount++; return ViewModel.myself.VersionString; } } 

Ah, pero qué pasa si quieres PUBLICAR un JSON. Encontrará muchos artículos sobre desbordamiento de stack que le dicen que todo lo que tiene que hacer es esto:

  [OperationContract] [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] BuildResponse BuildToby(BuildRequest request); 

que recibirá un mensaje JSON, deserializar en un objeto .NET puro (PONO) y le permitirá trabajar con él. Y de hecho, esto funcionó bien cuando construí la solicitud en Fiddler.

 POST /BuildToby HTTP/1.1 User-Agent: Fiddler Content-Type: application/json Host: localhost:4326 Content-Length: 1999 

Sin embargo, cuando utiliza el siguiente AJAX en JQUERY 1.8, encontrará una SORPRESA:

Al especificar el tipo de contenido de “aplicación / json”, encontrará que hay una verificación de “verificación previa” que se activa por el navegador para ver si puede enviar algo que no sea un mensaje de correo www-url-encloded. (hay notas en desbordamiento de stack sobre esto ).

  var request = JSON.stringify({ FrameList: ExportData.buildList }); var jqxhr = $.ajax({ type: "POST", url: "http://localhost:4326/BuildToby", data: request, contentType: "application/json; charset=utf-8", dataType: "json" }); 

y aquí está lo que informa el violinista: (Tenga en cuenta que no es un mensaje POST, sino un mensaje de OPCIONES).

 OPTIONS http://localhost:4326/BuildToby HTTP/1.1 Host: localhost:4326 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Origin: http://ysg4206 Access-Control-Request-Method: POST Access-Control-Request-Headers: content-type Connection: keep-alive Pragma: no-cache Cache-Control: no-cache 

Lo que sucedió es que un navegador (en este caso Firefox) tiene que hacer una llamada adicional al servidor, con un mensaje OPTIONS HTTP, para ver si se permite un POST (de este tipo de contenido).

Todos los artículos sobre cómo solucionar esto se tratan de editar GLOBAL.ASAX, lo cual está bien si estás en ASP.NET pero no sirve para nada si estás haciendo un WCF autohospedado.

Entonces ahora ves la pregunta (disculpa por ser tan larga, pero quería hacer de esto un artículo completo para que otros puedan seguir los resultados).

Bien, ahora hay algunos gurús reales de MSDN que han escrito soluciones, pero no puedo entenderlas: http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support -in-wcf.aspx

Pero he encontrado una solución simple. Al menos en WCF 4.5 puede agregar su propio OperationContract para tratar con solicitudes OPTIONS:

  [OperationContract] [WebInvoke(Method = "OPTIONS", UriTemplate = "*")] void GetOptions(); 

Tenga en cuenta que la firma del método es nula y no tiene argumentos. Esto se llamará primero, y luego se llamará al mensaje POST.

La implementación de GetOptions es:

  public partial class CatalogService : ICatalogService { public void GetOptions() { mon.IsActive = true; WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*"); WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type"); } } 

y eso es realmente todo lo que tienes que hacer.

Es posible que también desee agregar este atributo a su clase de servicio, para que pueda serializar JSON grande:

 //This defines the base behavior of the CatalogService. All other files are partial classes that extend the service [ServiceBehavior(MaxItemsInObjectGraph = 2147483647)] // Allows serialization of very large json structures public partial class CatalogService : ICatalogService { PgSqlMonitor mon = new PgSqlMonitor(); private void BypassCrossDomain() { WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*"); } } 

Tenga en cuenta que tengo un pequeño método de ayuda llamado BypassCrossDomain () al que llamo en todos mis métodos POST y GET, para que pueda manejar llamadas de dominio cruzado.

Pasé mucho tiempo de investigación aquí (en foros de MSDN, desbordamiento de stack, blogs) y espero que esto ayude a otros a tratar de hacer este tipo de proyectos.

Otra adición a la respuesta de Dr.YSG, si necesita admitir el método OPTIONS en puntos finales que toman POSTS a ID individuales, tendrá que implementar múltiples métodos de GetOptions:

  [WebInvoke(Method = "OPTIONS", UriTemplate = "")] void GetOptions(); [WebInvoke(Method = "OPTIONS", UriTemplate = "{id}")] void GetOptions(string id); 

Es realmente decepcionante que WCF / Microsoft no pueda generar automáticamente la respuesta correcta de OPCIONES basada en la firma del punto final de forma automática, pero al menos puede manejarse manualmente.

Además de lo que Dr.YSG enumeró como su respuesta, descubrí que recibía un aviso de Firefox de que se estaba produciendo una redirección y un error del “Método 405 no permitido”. Añadiendo

 WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*"); WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Max-Age", "1728000"); 

a la clase GetOptions parece haber abordado el problema.

Después de muchos días buscando y leyendo muchos mensajes y soluciones propuestas, creo que esta pregunta del Dr. YSG fue el mejor recurso para entender y resolver mi problema con angular / wcf / post / CORS, etc.

Pero lo que realmente funcionó para mí fue esto:

 protected void Application_BeginRequest(object sender, EventArgs e) { if (Request.HttpMethod == "OPTIONS") { Response.End(); } } 

Entiendo que no es una solución completa (ni bella), ya que estoy usando global.asax y hay muchos escenarios posibles, pero solo quiero compartir esta alternativa, ya que puede ayudar a alguien más eventualmente.

(Además de agregar los encabezados)