I have the standard error handing in place in my service:
- I have an IErrorHandler hooked to the service to handle unexpected errors during service execution.
- I have try/catch blocks in all my service methods to handle expected cases.
However, there are cases where exceptions are thrown on the server and neither is called.
Here is a case where the server exception is not sent to the IErrorHandler:
Service1Client sc = new Service1Client();
ICommunicationObject o = sc as ICommunicationObject;
o.Open(); // open channel
sc.GetData(10); // do a first call
Thread.Sleep(10000); // wait longer than the server receiveTimeout
sc.GetData(10); // Attempt another call: server throws a FaulException
In that case, the error is thrown on the server but I cannot find a way to handle it (and log it). I know an error is raised because if I attach a debugger on the server process and break on all exceptions, the debugger breaks.
I have found other similar cases where low level errors are not passed to my program.
Where can I hook my code to ensure that I can handle ALL exceptions that occur on the server before they are returned to the client app? Should I implement my own IChannel or some other low level interface?
Thanks
| | RedHotSly | IErrorHandler should just work for such a scenario. can you please post a small working repro of this problem. Amit Sharma | | Amit Sharma R | Hi, How do I attachmy sampletoa forumpost? Sly
| | RedHotSly | I am hoping that you will have a small repro, if so then you can just paste it here. If not then you can upload your repro in skydrive.live.com and share it
Amit Sharma | | Amit Sharma R | Hi,
I did not know skydrive, here is the project. http://cid-7852cb693be251f4.skydrive.live.com/self.aspx/.Public/UnhandeledErrorSample.zip
Run the program, click the button. As you will see, the client gets an exception on the 2nd call but the IErrorHandler on the server is never called (set a breakpoint there).
Thanks | | RedHotSly | Hi RedHotSly, As you found out, it's possible that IErrorHandler won't catch all the errors on the server. I don't know exactly where the boundary is to where it starts applying or not, but this is an error on the security layer (found it by enabling tracing), and apparently it is one of those who can't be handled by IErrorHandler. The problem is that somehow the security channel is using the ReceiveTimeout as the duration of the token validity (I can't understand why, since the instance mode is per call). I'll send this information to the product team to see if it's a bug in WCF. As far as alternatives to handle that error,I tried using an IDispatchMessageInspector to see if it could be used, but it also wasn't able to catch it - the error was on a level too low for that as well. The final resource I tried was to create a custom channel. At that point, I was able to see the fault message that was sent to the client, and I'm pretty sure you'd be able to modify it prior to having it delivered to the client. That's a lot of work (see code below), but at least it's doable.
public class Post_21b77052_2b6e_439c_80b6_83607393edb2
{
[ServiceContract]
public interface ITest
{
[OperationContract]
string GetData(int value);
}
[ErrorBehavior]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service : ITest
{
public string GetData(int value)
{
Console.WriteLine("Service GetData Invoked");
return value.ToString();
}
}
public class ErrorBehaviorAttribute : Attribute, IServiceBehavior
{
public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { }
public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) { }
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler = new ErrorHandler();
MyInspector inspector = new MyInspector();
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
}
}
public class MyInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
Console.WriteLine("BeforeSendReply: {0}", reply);
}
}
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
Trace.WriteLine("ErrorHandler::HandleError" + error.Message);
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
Trace.WriteLine("ErrorHandler::ProvideFault" + error.Message);
}
}
}
public class MyNewBindingElement : BindingElement
{
public override BindingElement Clone()
{
return new MyNewBindingElement();
}
public override T GetProperty<T>(BindingContext context)
{
return context.GetInnerProperty<T>();
}
public override bool CanBuildChannelListener<TChannel>(BindingContext context)
{
return typeof(TChannel) == typeof(IReplyChannel) && context.CanBuildInnerChannelListener<TChannel>();
}
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
if (!this.CanBuildChannelListener<TChannel>(context)) throw new InvalidOperationException("Unsupported channel type");
return (IChannelListener<TChannel>)new MyNewFactory(context);
}
public class MyNewFactory : ChannelListenerBase<IReplyChannel>
{
IChannelListener<IReplyChannel> innerListener;
public MyNewFactory(BindingContext context)
{
this.innerListener = context.BuildInnerChannelListener<IReplyChannel>();
}
protected override IReplyChannel OnAcceptChannel(TimeSpan timeout)
{
return WrapChannel(this.innerListener.AcceptChannel(timeout));
}
protected override IAsyncResult OnBeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerListener.BeginAcceptChannel(timeout, callback, state);
}
protected override IReplyChannel OnEndAcceptChannel(IAsyncResult result)
{
return WrapChannel(this.innerListener.EndAcceptChannel(result));
}
protected override IAsyncResult OnBeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerListener.BeginWaitForChannel(timeout, callback, state);
}
protected override bool OnEndWaitForChannel(IAsyncResult result)
{
return this.innerListener.EndWaitForChannel(result);
}
protected override bool OnWaitForChannel(TimeSpan timeout)
{
return this.innerListener.WaitForChannel(timeout);
}
public override Uri Uri
{
get { return this.innerListener.Uri; }
}
protected override void OnAbort()
{
this.innerListener.Abort();
}
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerListener.BeginClose(timeout, callback, state);
}
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerListener.BeginOpen(timeout, callback, state);
}
protected override void OnClose(TimeSpan timeout)
{
this.innerListener.Close();
}
protected override void OnEndClose(IAsyncResult result)
{
this.innerListener.EndClose(result);
}
protected override void OnEndOpen(IAsyncResult result)
{
this.innerListener.EndOpen(result);
}
protected override void OnOpen(TimeSpan timeout)
{
this.innerListener.Open(timeout);
}
IReplyChannel WrapChannel(IReplyChannel innerChannel)
{
if (innerChannel == null)
{
return null;
}
else
{
return new MyNewChannel(this, innerChannel);
}
}
}
public class MyNewChannel : ChannelBase, IReplyChannel
{
IReplyChannel innerChannel;
public MyNewChannel(MyNewFactory factory, IReplyChannel innerChannel) :
base(factory)
{
this.innerChannel = innerChannel;
}
protected override void OnAbort()
{
this.innerChannel.Abort();
}
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginClose(timeout, callback, state);
}
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginOpen(timeout, callback, state);
}
protected override void OnClose(TimeSpan timeout)
{
this.innerChannel.Close(timeout);
}
protected override void OnEndClose(IAsyncResult result)
{
this.innerChannel.EndClose(result);
}
protected override void OnEndOpen(IAsyncResult result)
{
this.innerChannel.EndOpen(result);
}
protected override void OnOpen(TimeSpan timeout)
{
this.innerChannel.Open(timeout);
}
#region IReplyChannel Members
public IAsyncResult BeginReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginReceiveRequest(timeout, callback, state);
}
public IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state)
{
return this.innerChannel.BeginReceiveRequest(callback, state);
}
public IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginTryReceiveRequest(timeout, callback, state);
}
public IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginWaitForRequest(timeout, callback, state);
}
public RequestContext EndReceiveRequest(IAsyncResult result)
{
RequestContext requestContext = this.innerChannel.EndReceiveRequest(result);
return new MyNewRequestContext(requestContext);
}
public bool EndTryReceiveRequest(IAsyncResult result, out RequestContext context)
{
bool methodResult = this.innerChannel.EndTryReceiveRequest(result, out context);
context = new MyNewRequestContext(context);
return methodResult;
}
public bool EndWaitForRequest(IAsyncResult result)
{
return this.innerChannel.EndWaitForRequest(result);
}
public EndpointAddress LocalAddress
{
get { return this.innerChannel.LocalAddress; }
}
public RequestContext ReceiveRequest(TimeSpan timeout)
{
RequestContext result = this.innerChannel.ReceiveRequest(timeout);
return new MyNewRequestContext(result);
}
public RequestContext ReceiveRequest()
{
RequestContext result = this.innerChannel.ReceiveRequest();
return result;
}
public bool TryReceiveRequest(TimeSpan timeout, out RequestContext context)
{
bool result = this.innerChannel.TryReceiveRequest(timeout, out context);
context = new MyNewRequestContext(context);
return result;
}
public bool WaitForRequest(TimeSpan timeout)
{
return this.innerChannel.WaitForRequest(timeout);
}
#endregion
}
public class MyNewRequestContext : RequestContext
{
RequestContext innerContext;
public MyNewRequestContext(RequestContext innerContext)
{
this.innerContext = innerContext;
}
public override void Abort()
{
this.innerContext.Abort();
}
public override IAsyncResult BeginReply(Message message, TimeSpan timeout, AsyncCallback callback, object state)
{
TraceIfFault(message);
IAsyncResult result = this.innerContext.BeginReply(message, timeout, callback, state);
return result;
}
public override IAsyncResult BeginReply(Message message, AsyncCallback callback, object state)
{
TraceIfFault(message);
IAsyncResult result = this.innerContext.BeginReply(message, callback, state);
return result;
}
public override void Close(TimeSpan timeout)
{
this.innerContext.Close(timeout);
}
public override void Close()
{
this.innerContext.Close();
}
public override void EndReply(IAsyncResult result)
{
this.innerContext.EndReply(result);
}
public override void Reply(Message message, TimeSpan timeout)
{
TraceIfFault(message);
this.innerContext.Reply(message, timeout);
}
public override void Reply(Message message)
{
TraceIfFault(message);
this.innerContext.Reply(message);
}
public override Message RequestMessage
{
get { return this.innerContext.RequestMessage; }
}
void TraceIfFault(Message message)
{
if (message.IsFault)
{
Console.WriteLine("Fault: " + message);
}
}
}
}
static Binding GetBinding(bool changeTimeouts)
{
Binding result = new WSHttpBinding();
if (changeTimeouts)
{
result.ReceiveTimeout = TimeSpan.FromSeconds(5);
result.OpenTimeout = TimeSpan.FromHours(1);
result.SendTimeout = TimeSpan.FromHours(1);
result.CloseTimeout = TimeSpan.FromHours(1);
CustomBinding custom = new CustomBinding(result);
custom.Elements.Insert(custom.Elements.Count - 1, new MyNewBindingElement());
result = custom;
}
return result;
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), GetBinding(true), "");
host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(GetBinding(false), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
try
{
ICommunicationObject comm = proxy as ICommunicationObject;
comm.Open();
Console.WriteLine(proxy.GetData(10));
Thread.Sleep(10000);
Console.WriteLine("Slept for 10 seconds");
Console.WriteLine(proxy.GetData(10));
comm.Close();
factory.Close();
}
catch (Exception e)
{
Console.WriteLine("{0}: {1}", e.GetType().FullName, e.Message);
Exception inner = e.InnerException;
while (inner != null)
{
Console.WriteLine(" {0}: {1}", inner.GetType().FullName, inner.Message);
inner = inner.InnerException;
}
}
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
- Proposed As Answer byCarlos FigueiraMSFTMonday, September 21, 2009 10:30 PM
-
| | Carlos Figueira | Hi, Thank you very much for the detailed answer. How long will it take before you get a response from the WCF team? Will you please post the answer here. Should I post the issue in Connect? I will start analyzing the impacts of implementing my own channel wrapper as you did. The code your provided will very useful. I think I should keep this question open until you get a response back from the WCF team. Regards, | | RedHotSly |
|