WCF FaultContract Fails with NamedPipe
Asked Answered
T

1

1

I have a simple IPC mechanism that uses WCF and named pipes. My goal is to propagate exception details (including the stacktrace) to the client for logging purposes (the rest of the application logging is located on the client).

If I use the following code I am able to catch FaultException<Exception> on the client and see exception details:

Contract:

[ServiceContract]
public interface IService
{
    [OperationContract]
    [FaultContract(typeof(Exception))]
    void DoSomething();
}

Implementation:

public class Service : IService
{
    public void DoSomething()
    {
        try
        {
            ThisWillThrowAnException();
        }
        catch (Exception e)
        {
            throw new FaultException<Exception>(e);
        }
    }
 }

Client:

public void CallServer()
{
    try
    {
        proxy.DoSomething();
    }
    catch (FaultException<Exception> e)
    {
        Console.WriteLine("Caught fault exception!");
    }
}

This works fine and I see the message printed on the console. However, if I want to use my own derived exception instead of the base Exception class, it fails.

Custom Exception:

[Serializable]
public class MyException : Exception
{
    public MyException () { }
    public MyException (string message) : base(message) { }
    public MyException (string message, Exception inner) : base(message, inner) { }
    protected MyException (
      SerializationInfo info,
      StreamingContext context)
        : base(info, context) { }
}

Change the FaultContract on IService.DoSomething to

typeof(MyException).

Change the throw clause in Service to

new FaultException<MyException>(new MyException(e.Message, e);

Change the catch clause in the client to

catch (FaultException<MyException> e)

When I execute this, a CommunicationException is caught on the client with the error: System.ServiceModel.CommunicationException: There was an error reading from the pipe: The pipe has been ended. (109, 0x6d).

The MyException class is in a shared library available to both the client and server.

This question is very similar to this question, but that did not help me.

Tempe answered 3/2, 2011 at 16:25 Comment(0)
T
2

I resolved this by writing my own fault DataContract which contained a serialized list of StackFrames.

Apparently this MSDN article is not exactly accurate?

http://msdn.microsoft.com/en-us/library/ff649840.aspx

[DataContract]
public class MyFault
{
    [DataMember]
    public string Message { get; set; }

    [DataMember]
    public IList<SerializableMiniStackFrame> StackTrace { get; set; }


    public static MyFault CreateFault(Exception e)
    {
        MyFault fault = new MyFault();
        fault.Message = e.Message;
        fault.InitTrace(e);
        return fault;
    }

    /// <summary>
    /// Initializes the stack trace based on when the inner exception was thrown.
    /// </summary>
    /// <param name="inner">The inner exception.</param>
    private void InitTrace(Exception inner)
    {
        StackTrace trace = new StackTrace(inner, true);
        InitTrace(trace);
    }

    /// <summary>
    /// Initializes the internal serializable stack frames based on the given
    /// stack trace.
    /// </summary>
    /// <param name="stackTrace">The stack trace.</param>
    private void InitTrace(StackTrace stackTrace)
    {
        // Create a new list of serializable frames.
        this.StackTrace = new List<SerializableMiniStackFrame>();
        // Iterate over each frame in the stack trace.
        foreach (StackFrame frame in stackTrace.GetFrames())
        {
            string type = "";
            Type declaringType = frame.GetMethod().DeclaringType;
            if (null != declaringType)
            {
                type = declaringType.FullName;
            }

            MethodBase method = frame.GetMethod();
            string methodName = method.Name;
            string parameters = string.Empty;
            string delimiter = string.Empty;
            foreach (ParameterInfo parameter in method.GetParameters())
            {
                parameters += string.Format("{0}{1} {2}", delimiter, parameter.ParameterType.Name, parameter.Name);
                delimiter = ", ";
            }
            string file = Path.GetFileName(frame.GetFileName());
            int line = frame.GetFileLineNumber();

            // Create a serializable frame and add it to the list.
            SerializableMiniStackFrame miniFrame = new SerializableMiniStackFrame(type, methodName, parameters, file, line);
            this.StackTrace.Add(miniFrame);
        }
    }
}

/// <summary>
/// This class encapsulates basic stack frame information into a serializable
/// object.
/// </summary>
[DataContract]
public class SerializableMiniStackFrame
{
    public SerializableMiniStackFrame() { }
    public SerializableMiniStackFrame(string type, string method, string parameters, string file, int line)
    {
        this.Type = type;
        this.Method = method;
        this.Parameters = parameters;
        this.File = file;
        this.Line = line;
    }

    [DataMember]
    public string Type { get; set; }
    [DataMember]
    public string Method { get; set; }
    [DataMember]
    public string Parameters { get; set; }
    [DataMember]
    public string File { get; set; }
    [DataMember]
    public int Line { get; set; }
}
Tempe answered 4/2, 2011 at 18:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.