Note that there are some explanatory texts on larger screens.

plurals
  1. POjQuery success callback called with empty response when WCF method throws an Exception
    text
    copied!<p>I'm tearing my hair out over this one, so bear with me (it's a long post). </p> <h3>Basic Info</h3> <ul> <li>ASP.NET 3.5 with WCF service in ASP.NET compatibility mode</li> <li>Using jQuery with <a href="http://www.west-wind.com/weblog/posts/896411.aspx" rel="nofollow noreferrer">this service proxy</a> for AJAX requests</li> <li>Custom <code>IErrorHandler</code> and <code>IServiceBehavior</code> implementation to trap exceptions and provide Faults, which are serialized to JSON</li> <li>I'm testing locally using Cassini (I've seen some threads that talk about issues that occur when debugging locally but work fine in a production environment).</li> </ul> <p>The issue I'm running into is that whenever an exception is thrown from my WCF service, the <strong>success</strong> handler of the <code>$.ajax</code> call is fired. The response is empty, the status text is "Success" and the response code is 202/Accepted.</p> <p>The <code>IErrorHandler</code> implementation does get used because I can step through it and watch the FaultMessage get created. What happens in the end is that the <code>success</code> callback throws an error because the response text is empty when it is expecting a JSON string. The <code>error</code> callback never fires. </p> <p>One thing that provided a little insight was removing the <code>enableWebScript</code> option from the endpoint behavior. Two things happened when I did this:</p> <ol> <li>The responses were no longer wrapped (i.e. no <code>{ d: "result" }</code>, just <code>"result"</code>).</li> <li>The <code>error</code> callback is fired, but the response is only the HTML for the 400/Bad Request yellow-screen-of-death from IIS, not my serialized fault.</li> </ol> <p>I've tried as many things as show up in the top 10 results or more from Google regarding random combinations of the keywords "jquery ajax asp.net wcf faultcontract json", so if you plan on googling for an answer, don't bother. I'm hoping somebody on SO has run into this issue before.</p> <p>Ultimately what I want to achieve is:</p> <ol> <li>Be able to throw any type of <code>Exception</code> in a WCF method</li> <li>Use a <code>FaultContact</code></li> <li>Trap the exceptions in the <code>ShipmentServiceErrorHandler</code></li> <li>Return a serialized <code>ShipmentServiceFault</code> (as JSON) to the client.</li> <li>Have the <code>error</code> callback invoked so I can handle item 4.</li> </ol> <p>Possibly related to:</p> <ul> <li><a href="https://stackoverflow.com/questions/3703052/wcf-ierrorhandler-extension-not-returning-specified-fault">WCF IErrorHandler Extension not returning specified Fault</a></li> </ul> <hr /> <p><strong>Update 1</strong></p> <p>I examined the output from tracing System.ServiceModel activity, and at one point after calling the UpdateCountry method, an exception is thrown, the message being</p> <blockquote> <p>Server returned an invalid SOAP Fault.</p> </blockquote> <p>and that's it. An inner exception complains about the serializer expecting a different root element, but I can't decipher much else out of it.</p> <hr /> <p><strong>Update 2</strong></p> <p>So with some more messing around, I got something to work, though not the way I would consider ideal. Here's what I did:</p> <ol> <li>Removed the <code>&lt;enableWebScript /&gt;</code> option from the endpoint behavior section of the web.config.</li> <li>Removed the <code>FaultContract</code> attribute from the service method.</li> <li>Implemented a subclass of <code>WebHttpBehavior</code> (called <code>ShipmentServiceWebHttpBehavior</code>) and overrode the <code>AddServerErrorHandlers</code> function to add the <code>ShipmentServiceErrorHandler</code>.</li> <li>Changed the <code>ShipmentServiceErrorHandlerElement</code> to return an instance of type of <code>ShipmentServiceWebHttpBehavior</code> instead of the error handler itself.</li> <li>Moved the <code>&lt;errorHandler /&gt;</code> line from the service behavior section of the web.config to the endpoint behavior section.</li> </ol> <p>It's not ideal because now WCF ignores the <code>BodyStyle = WebMessageBodyStyle.WrappedRequest</code> I want on my service methods (though I can now omit it altogether). I also had to change some of the code in the JS service proxy because it was looking for a wrapper (<code>{ d: ... }</code>) object on the responses.</p> <hr /> <p>Here is all of the relevant code (the <code>ShipmentServiceFault</code> object is pretty self explanatory).</p> <h3>The Service</h3> <p>My service is dead simple (truncated version):</p> <pre><code>[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) } } </code></pre> <h3>Error Handling</h3> <p>The <code>IErrorHandler, IServiceBehavior</code> implementation is as follows:</p> <pre><code>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&lt;ServiceEndpoint&gt; 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 } </code></pre> <h3>The JavaScript</h3> <p>Calling the WCF method begins with:</p> <pre><code> function SaveCountry() { var data = $('#uxCountryEdit :input').serializeBoundControls(); ShipmentServiceProxy.invoke('UpdateCountry', { country: data }, function(html) { $('#uxCountryGridResponse').html(html); }, onPageError); } </code></pre> <p>The service proxy I mentioned earlier takes care of a lot of things, but at the core, we get to here:</p> <pre><code>$.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) { } }); </code></pre> <h3>Configuration</h3> <p>I feel like the problems may lie here, but I've tried just about every combination of settings from everywhere I can find on the 'net that has an example.</p> <pre><code>&lt;system.serviceModel&gt; &lt;serviceHostingEnvironment aspNetCompatibilityEnabled="true"/&gt; &lt;behaviors&gt; &lt;endpointBehaviors&gt; &lt;behavior name="Removed.ShipmentServiceAspNetAjaxBehavior"&gt; &lt;webHttp /&gt; &lt;enableWebScript /&gt; &lt;/behavior&gt; &lt;/endpointBehaviors&gt; &lt;serviceBehaviors&gt; &lt;behavior name="Removed.ShipmentServiceServiceBehavior"&gt; &lt;serviceMetadata httpGetEnabled="true"/&gt; &lt;serviceDebug includeExceptionDetailInFaults="false"/&gt; &lt;errorHandler /&gt; &lt;/behavior&gt; &lt;/serviceBehaviors&gt; &lt;/behaviors&gt; &lt;services&gt; &lt;service name="ShipmentService" behaviorConfiguration="Removed.ShipmentServiceServiceBehavior"&gt; &lt;endpoint address="" behaviorConfiguration="Removed.ShipmentServiceAspNetAjaxBehavior" binding="webHttpBinding" contract="ShipmentService" /&gt; &lt;/service&gt; &lt;/services&gt; &lt;extensions&gt; &lt;behaviorExtensions&gt; &lt;add name="errorHandler" type="Removed.Services.ShipmentServiceErrorHandlerElement, Removed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/&gt; &lt;/behaviorExtensions&gt; &lt;/extensions&gt; &lt;/system.serviceModel&gt; </code></pre> <p><strong>Notes</strong></p> <p>I noticed this questions is getting a few favorites. I <em>did</em> find the solution to this issue and I hope to provide an answer when I find some time. Stay tuned!</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload