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.)
Related
I want to have a central place to implement exceptions handling logic for specific type of exception.
If specific exception type occurs, I'd like to be able to run one of the following depending on internal configuration:
send message to error queue immediately without further second level retries;
hide the message, not sending it to both processing queue or error queue;
I've found this topic which covers first case but not second one, as a message would be put into error queue if we return TimeSpan.MinValue:
NServiceBus error handling
So how could I implement 2nd case? better both to be be implemented in one place, one class
Prior to version 6 of NServiceBus, you could use IManageMessageFailures to manage message failures. You can handle the case of a serialization exception or - more relevant to your problem at hand - when a message can not be handled gracefully after first-level retries are attempted.
Here's how to implement a custom FaultManager that'd ignore exceptions of certain type or send failed messages with other errors back to the error queue. Note that the First-Level retires still happens and this kicks-in instead of Second-Level retry.
public class IssueOrder : ICommand
{
public bool NotFound { get; set; }
public bool HasFaulted { get; set; }
}
public class OrderHandler : IHandleMessages<IssueOrder>
{
public void Handle(IssueOrder message)
{
if(message.NotFound)
throw new OrderNotFoundException();
if(message.HasFaulted)
throw new ApplicationException();
}
}
public class OrderNotFoundException : Exception
{
}
public class CustomFaultManager : IManageMessageFailures
{
private ISendMessages sender;
private MessageForwardingInCaseOfFaultConfig config;
private BusNotifications notifications;
private static ILog Logger = LogManager.GetLogger<CustomFaultManager>();
public CustomFaultManager(ISendMessages sender, IProvideConfiguration<MessageForwardingInCaseOfFaultConfig> config)
{
this.sender = sender;
this.config = config.GetConfiguration();
}
public void SerializationFailedForMessage(TransportMessage message, Exception e)
{
}
public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e)
{
if (e is OrderNotFoundException)
{
//Ignore the exception;
Logger.WarnFormat("OrderNotFoundException was thrown. Ignoring the message Id {0}.", message.Id);
}
else
{
//Check if you have performed enough retries, ultimately send to error queue
SendToErrorQueue(message, e);
}
}
private void SendToErrorQueue(TransportMessage message, Exception ex)
{
message.TimeToBeReceived = TimeSpan.MaxValue;
sender.Send(message, new SendOptions(config.ErrorQueue));
Logger.WarnFormat("Message {0} will was moved to the error queue.", message.Id);
}
public void Init(Address address)
{
}
}
And to register the custom FaultManager:
var config = new BusConfiguration();
//Other configuration code
config.RegisterComponents(c =>
{
c.ConfigureComponent<CustomFaultManager>(DependencyLifecycle.InstancePerCall);
});
In Version 6 of NServiceBus however, the IManageMessageFailures interface is deprecated. The new Recoverability api in version 6 allows for better customization, althrough there's no direct way of ignoring/muting an exception. For that purpose you need a custom behavior in the NServiceBUs pipeline and run it in a step between one of the known steps (e.g. before a message is moved to the error queue).
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.
Consider the following very basic WCF service implementation:
public enum TransactionStatus
{
Success = 0,
Error = 1
}
public class TransactionResponse
{
public TransactionStatus Status { get; set; }
public string Message { get; set; }
}
[ServiceContract]
[XmlSerializerFormat]
public interface ITestService
{
[OperationContract]
TransactionResponse DoSomething(string data);
}
public class TestService : ITestService
{
public TransactionResponse DoSomething(string data)
{
var result = ProcessData(data); // may throw InvalidOperationException
return new TransactionResponse()
{
Status = TransactionStatus.Success,
Message = result
};
}
private string ProcessData(string data)
{
if (data = "foobar")
throw new InvalidOperationException();
return data;
}
}
In the instance that the DoSomething method does throw an InvalidOperationException, I would like to intercept the fault and return a TransactionResponse object, rather than have WCF raise a FaultException with the client. How can I do this without surrounding each method body in a huge try catch statement? Is there some where I can hook into? Can I do this with some sort of attribute or something? An example of how I would like to handle it can be demonstrated using ASP.NET MVC:
public class ApiController : BaseController
{
protected override void OnException(ExceptionContext filterContext)
{
var ex = filterContext.Exception;
var message = HttpContext.IsDebuggingEnabled ? ex.ToString() : ex.Message;
_logger.Error("Error processing request for controller {0}, action {1}",
filterContext.RequestContext.RouteData.Values["controller"],
filterContext.RequestContext.RouteData.Values["action"]);
_logger.Error(ex.ToString());
filterContext.ExceptionHandled = true;
filterContext.Result = ToXml(new ApiResult(false)
{
Message = message
});
}
// ...
}
Using the above method in MVC, I can ensure that no matter which controller action throws an exception, I can handle it and return an appropriately formatted ActionResult containing the necessary info. Is there a way to do this kind of thing with WCF?
Check out the WCF IErrorHandler interface - it allows you to centrally define one way in your service implementation to catch all exceptions and either swallow them, or convert them to WCF-friendly SOAP exceptions. This will make sure the channel between the client and the server isn't faulted, e.g. it can still be used after this call failed.
I don't understand why you'd want to "catch" the SOAP faults and convert those to something else, though.... nor do I know of any support that WCF would give you. The basic assumption is: catch .NET exceptions and convert them into interoperable SOAP faults