jquery wcf json asp.net-3.5

La devolución de llamada exitosa de jQuery se llama con una respuesta vacía cuando el método WCF lanza una excepción



json asp.net-3.5 (5)

Me estoy arrancando el pelo por esto, así que tengan paciencia (es un post largo).

Información básica

  • ASP.NET 3.5 con servicio WCF en modo de compatibilidad ASP.NET
  • Usando jQuery con este servicio proxy para solicitudes AJAX
  • IErrorHandler personalizada de IErrorHandler e IServiceBehavior para atrapar excepciones y proporcionar fallas, que se serializan a JSON
  • Estoy probando localmente utilizando Cassini (he visto algunos subprocesos que hablan de problemas que ocurren al depurar localmente pero funcionan bien en un entorno de producción).

El problema que estoy encontrando es que cada vez que se lanza una excepción de mi servicio WCF, se desencadena el controlador de éxito de la llamada $.ajax . La respuesta está vacía, el texto de estado es "Correcto" y el código de respuesta es 202 / Aceptado.

La implementación de IErrorHandler se usa porque puedo recorrerla y ver cómo se crea el FaultMessage. Lo que sucede al final es que la devolución de llamada correcta produce un error porque el texto de respuesta está vacío cuando se espera una cadena JSON. El error devolución de llamada nunca se dispara.

Una cosa que proporcionó un poco de información fue eliminar la opción enableWebScript del comportamiento del punto final. Dos cosas pasaron cuando hice esto:

  1. Las respuestas ya no estaban envueltas (es decir, no { d: "result" } , solo "result" ).
  2. La devolución de llamada de error se activa, pero la respuesta es solo el HTML de la pantalla amarilla de 400 / Bad Request de IIS, no mi error serializado.

He intentado tantas cosas como aparecen en los 10 mejores resultados o más de Google con respecto a las combinaciones aleatorias de las palabras clave "jquery ajax asp.net wcf faultcontract json", por lo que si planea buscar en Google para una respuesta, no se moleste. . Espero que alguien en SO se haya encontrado con este problema antes.

En última instancia, lo que quiero lograr es:

  1. Ser capaz de lanzar cualquier tipo de Exception en un método WCF.
  2. Utilice un FaultContact
  3. Atrape las excepciones en ShipmentServiceErrorHandler
  4. Devuelva un ShipmentServiceFault serializado (como JSON) al cliente.
  5. Haga que error invoque la devolución de llamada de error para poder manejar el artículo 4.

Posiblemente relacionado con:

Actualización 1

Examiné la salida de seguimiento de la actividad System.ServiceModel, y en un momento después de llamar al método UpdateCountry, se lanza una excepción, el mensaje es

El servidor devolvió un error de SOAP no válido.

y eso es. Una excepción interna se queja de que el serializador espera un elemento raíz diferente, pero no puedo descifrar muchas otras cosas.

Actualización 2

Así que con algo más de confusión, conseguí algo para trabajar, aunque no de la manera que consideraría ideal. Esto es lo que hice:

  1. Se eliminó la <enableWebScript /> de la sección de comportamiento del punto final de web.config.
  2. Se eliminó el atributo FaultContract del método de servicio.
  3. Implementó una subclase de WebHttpBehavior (llamada ShipmentServiceWebHttpBehavior ) y anuló la función AddServerErrorHandlers para agregar ShipmentServiceErrorHandler .
  4. Se modificó ShipmentServiceErrorHandlerElement para devolver una instancia del tipo de ShipmentServiceWebHttpBehavior lugar del controlador de errores.
  5. Se movió la línea <errorHandler /> de la sección de comportamiento del servicio de web.config a la sección de comportamiento del punto final.

No es ideal porque ahora WCF ignora el BodyStyle = WebMessageBodyStyle.WrappedRequest que quiero en mis métodos de servicio (aunque ahora puedo omitirlo por completo). También tuve que cambiar parte del código en el proxy de servicio JS porque estaba buscando un objeto de envoltura ( { d: ... } ) en las respuestas.

Aquí está todo el código relevante (el objeto ShipmentServiceFault se explica por sí mismo).

El servicio

Mi servicio es muerto simple (versión truncada):

[ServiceContract(Namespace = "http://removed")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class ShipmentService { [OperationContract] [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)] [FaultContract(typeof(ShipmentServiceFault))] public string UpdateCountry(Country country) { var checkName = (country.Name ?? string.Empty).Trim(); if (string.IsNullOrEmpty(checkName)) throw new ShipmentServiceException("Country name cannot be empty."); // Removed: try updating country in repository (works fine) return someHtml; // new country information HTML (works fine) } }

Manejo de errores

La implementación de IErrorHandler, IServiceBehavior es la siguiente:

public class ShipmentServiceErrorHandlerElement : BehaviorExtensionElement { protected override object CreateBehavior() { return new ShipmentServiceErrorHandler(); } public override Type BehaviorType { get { return typeof(ShipmentServiceErrorHandler); } } } public class ShipmentServiceErrorHandler : IErrorHandler, IServiceBehavior { #region IErrorHandler Members public bool HandleError(Exception error) { // We''ll handle the error, we don''t need it to propagate. return true; } public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault) { if (!(error is FaultException)) { ShipmentServiceFault faultDetail = new ShipmentServiceFault { Reason = error.Message, FaultType = error.GetType().Name }; fault = Message.CreateMessage(version, "", faultDetail, new DataContractJsonSerializer(faultDetail.GetType())); this.ApplyJsonSettings(ref fault); this.ApplyHttpResponseSettings(ref fault, System.Net.HttpStatusCode.InternalServerError, faultDetail.Reason); } } #endregion #region JSON Exception Handling protected virtual void ApplyJsonSettings(ref Message fault) { // Use JSON encoding var jsonFormatting = new WebBodyFormatMessageProperty(WebContentFormat.Json); fault.Properties.Add(WebBodyFormatMessageProperty.Name, jsonFormatting); } protected virtual void ApplyHttpResponseSettings(ref Message fault, System.Net.HttpStatusCode statusCode, string statusDescription) { var httpResponse = new HttpResponseMessageProperty() { StatusCode = statusCode, StatusDescription = statusDescription }; httpResponse.Headers[HttpResponseHeader.ContentType] = "application/json"; httpResponse.Headers["jsonerror"] = "true"; fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponse); } #endregion #region IServiceBehavior Members public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { // Do nothing } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { IErrorHandler errorHandler = new ShipmentServiceErrorHandler(); foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers) { ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher; if (channelDispatcher != null) { channelDispatcher.ErrorHandlers.Add(errorHandler); } } } public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { // Do nothing } #endregion }

El JavaScript

Llamar al método WCF comienza con:

function SaveCountry() { var data = $(''#uxCountryEdit :input'').serializeBoundControls(); ShipmentServiceProxy.invoke(''UpdateCountry'', { country: data }, function(html) { $(''#uxCountryGridResponse'').html(html); }, onPageError); }

El proxy de servicio que mencioné anteriormente se encarga de muchas cosas, pero en el núcleo, llegamos hasta aquí:

$.ajax({ url: url, data: json, type: "POST", processData: false, contentType: "application/json", timeout: 10000, dataType: "text", // not "json" we''ll parse success: function(response, textStatus, xhr) { }, error: function(xhr, status) { } });

Configuración

Siento que los problemas pueden estar aquí, pero he intentado casi todas las combinaciones de configuraciones de todos los lugares que puedo encontrar en la red que tiene un ejemplo.

<system.serviceModel> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> <behaviors> <endpointBehaviors> <behavior name="Removed.ShipmentServiceAspNetAjaxBehavior"> <webHttp /> <enableWebScript /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="Removed.ShipmentServiceServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="false"/> <errorHandler /> </behavior> </serviceBehaviors> </behaviors> <services> <service name="ShipmentService" behaviorConfiguration="Removed.ShipmentServiceServiceBehavior"> <endpoint address="" behaviorConfiguration="Removed.ShipmentServiceAspNetAjaxBehavior" binding="webHttpBinding" contract="ShipmentService" /> </service> </services> <extensions> <behaviorExtensions> <add name="errorHandler" type="Removed.Services.ShipmentServiceErrorHandlerElement, Removed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </behaviorExtensions> </extensions> </system.serviceModel>

Notas

Me di cuenta de que esta pregunta está recibiendo algunos favoritos. Encontré la solución a este problema y espero dar una respuesta cuando encuentre algo de tiempo. ¡Manténganse al tanto!


¿Has mirado JSON.NET ? Lo estaba utilizando para convertir objetos en c # en cadenas compatibles con JSON y luego se lo pasé por el cable a mi cliente, donde lo analicé en un objeto JSON. Al final, me deshice de él y fui a JSON2 para hacer una JSON2 de caracteres. Aquí está mi llamada ajax que uso:

function callScriptMethod(url, jsonObject, callback, async) { callback = callback || function () { }; async = (async == null || async); $.ajax({ type: ''POST'', contentType: ''application/json; charset=utf-8'', url: url, data: JSON.stringify(jsonObject), dataType: ''json'', async: async, success: function (jsonResult) { if (''d'' in jsonResult) callback(jsonResult.d); else callback(jsonResult); }, error: function () { alert("Error calling ''" + url + "'' " + JSON.stringify(jsonObject)); callback([]); } }); }


Aquí hay otra oportunidad. Dejaré mi intento original en caso de que la solución ayude a alguien más.

Para activar la condición de error para la llamada $ .ajax, necesitará un código de error en su respuesta.

protected virtual void ApplyHttpResponseSettings(ref Message fault, System.Net.HttpStatusCode statusCode, string statusDescription) { var httpResponse = new HttpResponseMessageProperty() { //I Think this could be your problem, if this is not an error code //The error condition will not fire //StatusCode = statusCode, //StatusDescription = statusDescription //Try forcing an error code StatusCode = System.Net.HttpStatusCode.InternalServerError; }; httpResponse.Headers[HttpResponseHeader.ContentType] = "application/json"; httpResponse.Headers["jsonerror"] = "true"; fault.Properties.Add(HttpResponseMessageProperty.Name, httpResponse); }

¡Aquí está la esperanza de que mi segundo ataque sea más útil para usted!


No estoy familiarizado con ASP o WCF, pero estoy bastante familiarizado con jQuery. Lo único que me viene a la mente sobre su pregunta es que su servicio está devolviendo 202 Success cuando se lanza una excepción. jQuery elige a qué devolución de llamada llamar ( success o error ) en función del código de estado HTTP que se devuelve desde el servidor. 202 se considera una respuesta exitosa y, por lo tanto, jQuery llamará success . Si desea que jQuery llame a la devolución de llamada de error , debe hacer que su servicio devuelva un código de estado 40x o 50x . Consulte http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html para obtener una lista de códigos de estado HTTP.


Tuve los mismos síntomas con un escenario diferente, así que esto puede o no ayudar.

Aquí hay un breve resumen de lo que estaba haciendo y nuestra solución:

Estaba publicando en una implementación REST de un servicio WCF que alojamos desde una página ASP clásica. Descubrí que tenía que establecer la entrada como un flujo y leer de eso, eliminando el flujo cuando termine. Creo que fue en este punto que estaba recibiendo la respuesta 202 con el texto de "éxito" como lo describió. Descubrí que al no deshacerme del flujo recibía la respuesta que esperaba por las condiciones de error.

Aquí hay un resumen del código final:

[WebHelp(Comment="Expects the following parameters in the post data:title ...etc")] public int SaveBook(Stream stream) { NameValueCollection qString; StreamReader sr = null; string s; try { /************************************************************************************** * DO NOT CALL DISPOSE ON THE STREAMREADER OR STREAM * * THIS WILL CAUSE THE ERROR HANDLER TO RETURN A PAGE STATUS OF 202 WITH NO CONTENT * * IF THERE IS AN ERROR * * ***********************************************************************************/ sr = new StreamReader(stream); s = sr.ReadToEnd(); qString = HttpUtility.ParseQueryString(s); string title = qString["title"]; //Do what we need //Then Return something int retRecieptNum = UtilitiesController.SubmitClientEntryRequest(entryReq); return retRecieptNum; } catch (Exception ex) { throw new WebProtocolException(System.Net.HttpStatusCode.Forbidden, ex.Message, this.GetExceptionElement(true, "BookRequest", ex.Message), false, ex); } finally { } }

Esperemos que esto sea de alguna ayuda para usted, tal vez intente usar una transmisión y vea cómo va.


Tuve un problema similar con WCF y el uso de la compatibilidad con ASP.NET al integrar MVC y WCF en mi solución. Lo que haría sería lanzar una excepción WebFaultException y luego verificar el estado de la respuesta en el extremo receptor (ya sea java u otro cliente .NET). Su error personalizado podría lanzar eso si el WebOperationContext.Current no es nulo. Probablemente ya estés al tanto de esto, pero solo pensé que lo tiraría por ahí.

throw new WebFaultException(HttpStatusCode.BadRequest);