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 deIErrorHandler
eIServiceBehavior
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:
- Las respuestas ya no estaban envueltas (es decir, no
{ d: "result" }
, solo"result"
). - 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:
- Ser capaz de lanzar cualquier tipo de
Exception
en un método WCF. - Utilice un
FaultContact
- Atrape las excepciones en
ShipmentServiceErrorHandler
- Devuelva un
ShipmentServiceFault
serializado (como JSON) al cliente. - Haga que
error
invoque la devolución de llamada deerror
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:
- Se eliminó la
<enableWebScript />
de la sección de comportamiento del punto final de web.config. - Se eliminó el atributo
FaultContract
del método de servicio. - Implementó una subclase de
WebHttpBehavior
(llamadaShipmentServiceWebHttpBehavior
) y anuló la funciónAddServerErrorHandlers
para agregarShipmentServiceErrorHandler
. - Se modificó
ShipmentServiceErrorHandlerElement
para devolver una instancia del tipo deShipmentServiceWebHttpBehavior
lugar del controlador de errores. - 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);