简体   繁体   English

使用IErrorHandler进行WCF异常处理

[英]WCF exception handling using IErrorHandler

I am basically implementing the IErrorHandler interface to catch all kinds of exceptions from the WCF service and sending it to the client by implementing the ProvideFault method. 我基本上实现IErrorHandler接口来捕获来自WCF服务的各种异常,并通过实现ProvideFault方法将其发送到客户端。

I am facing one critical issue however. 然而,我面临一个关键问题。 All of the exceptions are sent to the client as a FaultException but this disables the client to handle specific exceptions that he may have defined in the service. 所有异常都作为FaultException发送到客户端,但这会禁止客户端处理他可能在服务中定义的特定异常。

Consider: SomeException that has been defined and thrown in one of the OperationContract implementations. 考虑: SomeException已在其中一个OperationContract实现中定义和抛出。 When the exception is thrown, its converted to a fault using the following code: 抛出异常时,使用以下代码将其转换为错误:

var faultException = new FaultException(error.Message);
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);

This does send the error as a string, but the client has to catch a general exception like: 这会将错误作为字符串发送,但客户端必须捕获一般异常,例如:

try{...}
catch(Exception e){...}

and not: 并不是:

try{...}
catch(SomeException e){...}

Not only custom exceptions like SomeException, but system exceptions like InvalidOperationException cannot be caught using the above process. 不仅像SomeException这样的自定义异常,而且使用上述过程也无法捕获像InvalidOperationException这样的系统异常。

Any ideas as to how to implement this behavior? 关于如何实现这种行为的任何想法?

In WCF desirable to use special exceptions described as contracts, because your client may not be .NET application which has information about standard .NET exceptions. 在WCF中,希望使用描述为契约的特殊异常,因为您的客户端可能不是.NET应用程序,它具有有关标准.NET异常的信息。 For do this you can define the FaultContract in the your service and then use the FaultException class. 为此,您可以在服务中定义FaultContract ,然后使用FaultException类。

SERVER SIDE 服务器端

[ServiceContract]
public interface ISampleService
{
    [OperationContract]
    [FaultContractAttribute(typeof(MyFaultMessage))]
    string SampleMethod(string msg);
}

[DataContract]
public class MyFaultMessage
{
    public MyFaultMessage(string message)
    {
        Message = message;
    }

    [DataMember]
    public string Message { get; set; }
}

class SampleService : ISampleService
{
    public string SampleMethod(string msg)
    {
        throw new FaultException<MyFaultMessage>(new MyFaultMessage("An error occurred."));
    }        
}

In addition, you can specify in the configuration file that the server returns exception details in it's FaultExceptions, but this is not recommended in a production application: 此外,您可以在配置文件中指定服务器在其FaultExceptions中返回异常详细信息,但不建议在生产应用程序中使用此方法:

<serviceBehaviors>
   <behavior>
   <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
      <serviceDebug includeExceptionDetailInFaults="true"/>
   </behavior>
</serviceBehaviors>

After that, you can rewrite your method for handling exceptions: 之后,您可以重写处理异常的方法:

var faultException = error as FaultException;
if (faultException == null)
{
    //If includeExceptionDetailInFaults = true, the fault exception with details will created by WCF.
    return;
}
MessageFault messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);

CLIENT: 客户:

try
{
    _client.SampleMethod();
}
catch (FaultException<MyFaultMessage> e)
{
    //Handle            
}
catch (FaultException<ExceptionDetail> exception)
{
    //Getting original exception detail if includeExceptionDetailInFaults = true
    ExceptionDetail exceptionDetail = exception.Detail;
}

This article might help: 本文可能有所帮助:

http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/ http://www.olegsych.com/2008/07/simplifying-wcf-using-exceptions-as-faults/

An approach I've used with some success when I didn't want to enumerate the exceptions that might be thrown is to create a PassthroughExceptionHandlingBehavior class that implements an IErrorHandler behavior for the server-side and IClientMessageInspector for the client side. 当我不想枚举可能抛出的异常时,我已经成功使用的一种方法是创建一个PassthroughExceptionHandlingBehavior类,它实现服务器端的IErrorHandler行为和客户端的IClientMessageInspector。 The IErrorHandler behavior serializes the exception into the fault message. IErrorHandler行为将异常序列化为故障消息。 The IClientMessageInspector deserializes and throws the exception. IClientMessageInspector反序列化并抛出异常。

You have to attach this behavior to both the WCF client and the WCF server. 您必须将此行为附加到WCF客户端和WCF服务器。 You can attach the behaviors using the config file or by applying the [PassthroughExceptionHandlingBehavior] attribute to your contract. 您可以使用配置文件或通过将[PassthroughExceptionHandlingBehavior]属性应用于合同来附加行为。

Here's the behavior class: 这是行为类:

public class PassthroughExceptionHandlingBehavior : Attribute, IClientMessageInspector, IErrorHandler,
    IEndpointBehavior, IServiceBehavior, IContractBehavior
{
    #region IClientMessageInspector Members

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
        if (reply.IsFault)
        {
            // Create a copy of the original reply to allow default processing of the message
            MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
            Message copy = buffer.CreateMessage();  // Create a copy to work with
            reply = buffer.CreateMessage();         // Restore the original message

            var exception = ReadExceptionFromFaultDetail(copy) as Exception;
            if (exception != null)
            {
                throw exception;
            }
        }
    }

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
    {
        return null;
    }

    private static object ReadExceptionFromFaultDetail(Message reply)
    {
        const string detailElementName = "detail";

        using (XmlDictionaryReader reader = reply.GetReaderAtBodyContents())
        {
            // Find <soap:Detail>
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element && 
                    detailElementName.Equals(reader.LocalName, StringComparison.InvariantCultureIgnoreCase))
                {
                    return ReadExceptionFromDetailNode(reader);
                }
            }
            // Couldn't find it!
            return null;
        }
    }

    private static object ReadExceptionFromDetailNode(XmlDictionaryReader reader)
    {
        // Move to the contents of <soap:Detail>
        if (!reader.Read())
        {
            return null;
        }

        // Return the deserialized fault
        try
        {
            NetDataContractSerializer serializer = new NetDataContractSerializer();
            return serializer.ReadObject(reader);
        }
        catch (SerializationException)
        {
            return null;
        }
    }

    #endregion

    #region IErrorHandler Members

    public bool HandleError(Exception error)
    {
        return false;
    }

    public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
    {
        if (error is FaultException)
        {
            // Let WCF do normal processing
        }
        else
        {
            // Generate fault message manually including the exception as the fault detail
            MessageFault messageFault = MessageFault.CreateFault(
                new FaultCode("Sender"),
                new FaultReason(error.Message),
                error,
                new NetDataContractSerializer());
            fault = Message.CreateMessage(version, messageFault, null);
        }
    }

    #endregion

    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        ApplyClientBehavior(clientRuntime);
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        ApplyDispatchBehavior(dispatchRuntime.ChannelDispatcher);
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

    #endregion

    #region IEndpointBehavior Members

    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        ApplyClientBehavior(clientRuntime);
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        ApplyDispatchBehavior(endpointDispatcher.ChannelDispatcher);
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    #endregion

    #region IServiceBehavior Members

    public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
        {
            ApplyDispatchBehavior(dispatcher);
        }
    }

    public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
    {
    }

    #endregion

    #region Behavior helpers

    private static void ApplyClientBehavior(System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        foreach (IClientMessageInspector messageInspector in clientRuntime.MessageInspectors)
        {
            if (messageInspector is PassthroughExceptionHandlingBehavior)
            {
                return;
            }
        }

        clientRuntime.MessageInspectors.Add(new PassthroughExceptionHandlingBehavior());
    }

    private static void ApplyDispatchBehavior(System.ServiceModel.Dispatcher.ChannelDispatcher dispatcher)
    {
        // Don't add an error handler if it already exists
        foreach (IErrorHandler errorHandler in dispatcher.ErrorHandlers)
        {
            if (errorHandler is PassthroughExceptionHandlingBehavior)
            {
                return;
            }
        }

        dispatcher.ErrorHandlers.Add(new PassthroughExceptionHandlingBehavior());
    }

    #endregion
}

#region PassthroughExceptionHandlingElement class

public class PassthroughExceptionExtension : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(PassthroughExceptionHandlingBehavior); }
    }

    protected override object CreateBehavior()
    {
        System.Diagnostics.Debugger.Launch();
        return new PassthroughExceptionHandlingBehavior();
    }
}

#endregion

FaultException has a Code property which you can use fore exception handling. FaultException有一个Code属性,您可以使用前面的异常处理。

try
{
...
}
catch (FaultException ex)
{
   switch(ex.Code)
   {
       case 0:
          break;
       ...
   }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM