I have a WCF REST service which is consumed on the client by the classical :
WebResponse response = request.GetResponse();
I want to intercept any errors that appear in the service and deliver them to the client. By default behavior, when an exception occurs in the service an faultexception is thrown and the channel is faulted, therefore on the client I receive a Bad request.
I want to be able to return the client the stackstrace and override the behaviour not to fault the channel.
For that I have implemented the IErrorHandler
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
fault = Message.CreateMessage(version, string.Empty, String.Format("An unknown error has occurred. The error identifier "), new DataContractJsonSerializer(typeof(string)));
fault.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
fault.Properties.Add(HttpResponseMessageProperty.Name, HttpStatusCode.Accepted);
}
}
the question is even if I register this on the service, I can debug the errorhandler, but the channel is still faulted so I still receive a bad request in the client.
I use the following factory for the client:
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var host = base.CreateServiceHost(serviceType, baseAddresses);
ServiceEndpoint ep = host.AddServiceEndpoint(serviceType, new WebHttpBinding(), "");
host.Description.Endpoints[0].Behaviors.Add(new WebHttpBehavior { HelpEnabled = true });
return host;
}
The question is how can I prevent the channel from getting faulted in the errorhandler.
It should work. I have the same situation and I set the fault also in the ProvideFault method. The only thing I can think of is that I am not seeing you create a FaultException from which you'd call CreateMessageFault().
Here is an example:
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
// we don't want the communication channel to fault, so we'll provide a general purpose fault with the exception provided.
var fe = new FaultException(error.Message);
MessageFault msg = fe.CreateMessageFault();
fault = Message.CreateMessage(version, msg, "YourActionNamespace");
}
Related
My services simply call BusinessLogicLayer methods where entire business logic is put. I want to know what's the best practice for handling exceptions raised by BL?(not only fatal exceptions, also "logic" ApplicationExceptions like UserNotFoundException which my BL throws when can't find user).
Where should I transform these exceptions into FaultExceptions which client will see?
Should I throw my business Exceptions from BL and than catch them into service call and transform to FaultException and return to client? or BL should raise already "client friendly" FaultExceptions?
thanks in advance :)
I would say throw business exception from business logic layer, this would keep your business logic layer decoupled with wcf implementation. In service call you may override applydispatchbehaviour and add error handler there, something like
Overriding IServiceBehavior.ApplyDispatchBehavior
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(new FaultErrorHandler());
}
}
FaultErrorHandler
public class FaultErrorHandler : IErrorHandler
{
void IErrorHandler.ProvideFault(System.Exception error, MessageVersion version, ref Message fault)
{
if (fault == null)
{
FaultException<[ExceptionType]> fe = new
FaultException<[ExceptionType]>([Exception cass],
error.Message, FaultCode.CreateReceiverFaultCode(new FaultCode("ServerException")));
MessageFault mf = fe.CreateMessageFault();
fault = Message.CreateMessage(version, mf, fe.Action);
}
}
}
Standard .Net exceptions are correctly serialized on server side, and deserialized on client side.
By default, not ours. Why ?
It may be a best practise to send business exception to client during debuging sessions:
- without having to put the Exception Data in a [DataMember] object
- having more info than a simple string ( ExceptionFault<ExceptionDetail>)
But take care of not sending exceptions when putting code in production. It may cause security leaks disclosing details to hackers if your service is exposed on internet !
In order to send the business exception to the client, the best (and some mandatory) practises are :
1/ Toggle the serviceDebugBehavior on
ServiceHost host = ...;
var debuggingBehavior = host.Description.Behaviors.Find<ServiceBehaviorAttribute>();
if (debuggingBehavior == null)
debuggingBehavior = new ServiceBehaviorAttribute();
#if DEBUG
debuggingBehavior.IncludeExceptionDetailInFaults = true;
#else
debuggingBehavior.IncludeExceptionDetailInFaults = false;
#endif
It is also pretty easy configurate it in xml
2/ On the service interface, declare some [FaultContract] :
[ServiceContract(Namespace="your namespace")]
public interface IBillingService
{
[OperationContract]
[FaultContract(typeof(BusinessException))]
void RaiseBusinessException();
}
3/ A business exception should be marked as Serializable
[Serializable]
public class BusinessException : Exception
{ ... }
4/ In order to have a business exception correctly deserialized on the client side as FaultException<BusinessException>, it is important to implement a constructor taking care of deserialization. Otherwise you'll get a generic FaultException.
protected BusinessException(SerializationInfo info, StreamingContext context)
: base(info, context)
{}
5/ If you have some extra members in you exception, serialize/deserialize them :
public DateTime CreationTime { get; set; }
protected BusinessException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
CreationTime = (DateTime)info.GetValue("CreationTime", typeof(DateTime));
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("CreationTime", CreationTime);
}
I have a WCF 4 REST service configured to use json. I want to catch exceptions and return a HTTP Status code of 400 with the exception message as a json object. I have followed examples on the web to implement my own IErrorHandler and IService interface to do this.
For example:
http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/
Returning Error Details from AJAX-Enabled WCF Service
http://social.msdn.microsoft.com/Forums/en/wcf/thread/fb906fa1-8ce9-412e-a16a-5d4a2a0c2ac5
However, just as in this post
jQuery success callback called with empty response when WCF method throws an Exception
I get a 202 Accepted response with no data which is due to a serialization error when I try to create my fault. This is logged from my service as follows:
2012-01-31 00:37:19,229 [8] DEBUG JsonWebScriptServiceHostFactory: creating service host
2012-01-31 00:37:19,292 [8] DEBUG JsonErrorHandler.ApplyDispatchBehavior: adding error handler
2012-01-31 00:43:06,995 [10] DEBUG ForemanSvc.GetSessionID
2012-01-31 00:43:39,292 [10] DEBUG ForemanSvc.GetProjects
2012-01-31 00:43:39,448 [10] DEBUG JsonErrorHandler.ProvideFault: creating fault
2012-01-31 00:43:39,635 [10] ERROR ForemanSvc exeption
Type: System.ServiceModel.CommunicationException
Message: Server returned an invalid SOAP Fault. Please see InnerException for more details.
Source: System.ServiceModel
StackTrace:
at System.ServiceModel.Channels.MessageFault.CreateFault(Message message, Int32 maxBufferSize)
at System.ServiceModel.Description.WebScriptEnablingBehavior.JsonErrorHandler.ProvideFault(Exception error, MessageVersion version, Message& fault)
at System.ServiceModel.Dispatcher.ErrorBehavior.ProvideFault(Exception e, FaultConverter faultConverter, ErrorHandlerFaultInfo& faultInfo)
at System.ServiceModel.Dispatcher.ErrorBehavior.ProvideMessageFaultCore(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage8(MessageRpc& rpc)
Type: System.Xml.XmlException
Message: Start element 'Fault' from namespace 'http://schemas.microsoft.com/ws/2005/05/envelope/none' expected. Found element 'root' from namespace ''.
Source: System.Runtime.Serialization
StackTrace:
at System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3)
at System.Xml.XmlExceptionHelper.ThrowStartElementExpected(XmlDictionaryReader reader, String localName, String ns)
at System.Xml.XmlDictionaryReader.ReadStartElement(XmlDictionaryString localName, XmlDictionaryString namespaceUri)
at System.ServiceModel.Channels.ReceivedFault.CreateFault12Driver(XmlDictionaryReader reader, Int32 maxBufferSize, EnvelopeVersion version)
at System.ServiceModel.Channels.MessageFault.CreateFault(Message message, Int32 maxBufferSize)
It's not clear from that post how to fix it. I have tried all sorts - using an attribute, using an endpoint behavior, trying a simple CreateMessage with no json formatting or extra info returned - nothing seems to work. Can anyone help?
Here's some code snippets - the error handler
public class JsonErrorHandler : IServiceBehavior, IErrorHandler
{
private static readonly ILog log =
LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
//Dont do anything
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
//dont do anything
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
log.IfDebug("JsonErrorHandler.ApplyDispatchBehavior: adding error handler");
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = dispatcherBase as ChannelDispatcher;
if (channelDispatcher != null)
{
channelDispatcher.ErrorHandlers.Add(this);
}
}
}
public bool HandleError(Exception error)
{
log.IfError("ForemanSvc exeption", error);
//Tell the system that we handle all errors here.
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
log.IfDebug("JsonErrorHandler.ProvideFault: creating fault");
JsonError msErrObject =
new JsonError
{
Message = error.Message,
Source = error.Source,
Detail = error.InnerException != null ? error.InnerException.Message : null
};
//The fault to be returned
fault = Message.CreateMessage(version, "", msErrObject, new DataContractJsonSerializer(msErrObject.GetType()));
// tell WCF to use JSON encoding rather than default XML
WebBodyFormatMessageProperty wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
// Add the formatter to the fault
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
//Modify response
HttpResponseMessageProperty rmp = new HttpResponseMessageProperty();
if (error is SecurityException &&
(error.Message == "Session expired" || error.Message == "Authentication ticket expired"))
{
rmp.StatusCode = HttpStatusCode.Unauthorized;
rmp.StatusDescription = "Unauthorized";
}
else
{
// return custom error code, 400.
rmp.StatusCode = HttpStatusCode.BadRequest;
rmp.StatusDescription = "Bad request";
}
//Mark the jsonerror and json content
rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
rmp.Headers["jsonerror"] = "true";
//Add to fault
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
}
}
and where I add the custom error handler for the service
public class JsonWebScriptServiceHostFactory : WebScriptServiceHostFactory
{
private static readonly ILog log =
LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
log.IfDebug("JsonWebScriptServiceHostFactory: creating service host");
ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
host.Description.Behaviors.Add(new JsonErrorHandler());
return host;
}
}
and the custom error
[DataContract(Namespace = "VSS.Nighthawk.Foreman", Name = "JsonError")]
public class JsonError
{
[DataMember]
public string Message { get; set; }
[DataMember]
public string Source { get; set; }
[DataMember]
public string Detail { get; set; }
}
What binding and encoder are you using, and what settings do you have configured on them? Also, what behavior have you plugged in? If you've plugged in WebScriptEnablingBehavior (because WebScriptServiceHostFactory plugs it in automatically), your problem may be that WSEB plugs in an error handler of its own, which does a LOT of the same things you're trying to do.
What I'd also do is use Reflector and look at the error handler that is embedded in WebScriptEnablingBehavior, and see what you're doing differently, and whether you could be doing anything else that you're not already doing. It's a very, very tricky and hairy area plagued with many subtleties, so you probably did not get the error handler right the first time.
You may also have to stop using WebScriptEnablingBehavior at all (if you're using it) -- so just make sure you aren't. You may have to re-implement WebScriptEnablingBehavior no your own, from scratch, and plug it in from scratch in your service host factory, instead of plugging in just a JSON error handler.
Hope this helps!
I have a wcf service which has a one way operation and it sends the result in a callback. Here is my service definition
[ServiceContract(CallbackContract = typeof(IIrmCallback), SessionMode = SessionMode.Required)]
public interface IFileService
{
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
[ServiceKnownType(typeof(StatementFile))]
[ServiceKnownType(typeof(InvoiceFile))]
void UploadFile(IFile file);
}
public interface IFileCallback
{
[OperationContract]
void FileProcessed(string result);
}
public MyService : IFileService
{
IFileCallBack callbackchannel;
object result;
public void UploadFile(IFile file)
{
callbackChannel = OperationContext.Current.GetCallbackChannel<IIrmCallback>();
Task.Factory.StartNew(() => ProcessFile(file));
}
private ProcessFile(IFile file)
{
// file gets processed
callbackChannel.FileProcessed(result)
}
}
So now if there is some error during the file processing is there someway I can throw an exception back to the caller in the callback?
You can use FaultException
When an operation throws an exception, a FaultException will be returned to the client.
You need to implement a FaultContract in your service interface specifying the types of faults that are possible in that given method and then throw a new fault that inherits from FaultException within the method.
On the client, make sure to catch and handle FaultException and Exception objects individually because Exceptions will cause the WCF channel to fault and it cannot be reused, while a FaultException will allow you to continue using the existing channel.
How can I get my WCF service to communicate errors in a RESTful manner? Specifically, if the caller passes invalid query string parameters to my method, I'd like to have a 400 or 404 HTTP error returned to the user. When I search for HTTP error status in relation to WCF, all I can find are pages where people are trying to resolve errors they're receiving. I'd rather not just throw a FaultException, because that gets converted to a 500 error, which is not the correct status code.
I found a helpful article here: http://zamd.net/2008/07/08/error-handling-with-webhttpbinding-for-ajaxjson/. Based on that, this is what I came up with:
public class HttpErrorsAttribute : Attribute, IEndpointBehavior
{
public void AddBindingParameters(
ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(
ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(
ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
var handlers = endpointDispatcher.ChannelDispatcher.ErrorHandlers;
handlers.Clear();
handlers.Add(new HttpErrorHandler());
}
public void Validate(ServiceEndpoint endpoint)
{
}
public class HttpErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(
Exception error, MessageVersion version, ref Message fault)
{
HttpStatusCode status;
if (error is HttpException)
{
var httpError = error as HttpException;
status = (HttpStatusCode)httpError.GetHttpCode();
}
else if (error is ArgumentException)
{
status = HttpStatusCode.BadRequest;
}
else
{
status = HttpStatusCode.InternalServerError;
}
// return custom error code.
fault = Message.CreateMessage(version, "", error.Message);
fault.Properties.Add(
HttpResponseMessageProperty.Name,
new HttpResponseMessageProperty
{
StatusCode = status,
StatusDescription = error.Message
}
);
}
}
}
This allows me to add a [HttpErrors] attribute to my service. In my custom error handler, I can ensure that the HTTP status codes I'd like to send are sent.
If you are using standard WCF then FaultException is the correct approach to this. If you do not wish to do that and you want to be RESTful then you should use the REST WCF approach (Here is a quick start template for 4.0 and for 3.5). This fully supports returning HTTP Status Codes to the client.
I wanted to implement the same solution you are asking, the link below worked perfect when you want to play with HTTP status codes.
How can I return a custom HTTP status code from a WCF REST method?
There is a WebOperationContext that you can access and it has a OutgoingResponse property of type OutgoingWebResponseContext which has a StatusCode property that can be set.
WebOperationContext ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
If i got a service definition like this:
[PoisonErrorBehavior]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MsgQueue: IMsgQueue
{
public void ProcessMsg(CustomMsg msg)
{
throw new Exception("Test");
}
}
( where ProcessMsg is the registered method for incoming msmq-messages )
and i want to handle the exception with my error handler ( i took the code from msdn as a template for mine ):
public sealed class PoisonErrorBehaviorAttribute : Attribute, IServiceBehavior
{
MsmqPoisonMessageHandler poisonErrorHandler;
public PoisonErrorBehaviorAttribute()
{
this.poisonErrorHandler = new MsmqPoisonMessageHandler();
}
void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
{
}
void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(poisonErrorHandler);
}
}
}
class MsmqPoisonMessageHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
}
public bool HandleError(Exception error)
{
string test = error.GetType().ToString();
//
// The type of the exception is never MsmqPoisonMessageException !!!
//
MsmqPoisonMessageException poisonException = error as MsmqPoisonMessageException;
if (null != poisonException)
{
long lookupId = poisonException.MessageLookupId;
Console.WriteLine(" Poisoned message -message look up id = {0}", lookupId);
}
}
then i got the problem that the exception is never of type MsmqPoisonMessageException. I would have expected .NET to magically encapsulate my "new Exception("Test")" in a MsmqPoisonMessageException, but the exception catched in my errorhandler is always of the same type as the exception i threw.
Am i missunderstanding this whole poison message behavior? I thought if an unhandled exception was thrown by my message-handling-code then the exception would turn out to be a MsmqPoisonMessageException, because otherwise i would'nt have a chance to get the lookup-id of msg in the queue.
Thank you all.
WCF encapsulates exceptions in a fault exception.
http://msdn.microsoft.com/en-us/library/system.servicemodel.faultexception.aspx
You must also specify which exceptions are to be thrown in the Interface / Contract.
First of all, you need to be retrieving the messages inside of a transaction, otherwise they won't be put back to the queue when there is an exception thrown from your code. Add this to the ProcessMessage function:
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
Also, you need to make sure that binding is set to fault when poison messages are detected, and that the retry count and time are small enough that you'll see it in your testing.
Try these steps (using VS 2008):
Open the WCF Configuration tool for your app.config file
Select Bindings in the tree, and click "New Binding Configuration" in the tasks area
Select the binding type of your endpoint (probably netMsmqBinding or msmqIntegrationBinding)
Set the name of the new binding configuration
Set the ReceiveErrorHandling property to "Fault"
Set the ReceiveRetryCount property to 2
Set the RetryCycleDelay to "00:00:10"
Select the endpoint to your service and set the binding configuration to the name you specified in step 4.
(You will probably want different values for ReceiveRetryCount and RetryCycleDelay for your production configuration.)