.NET WCF/Web服务异常处理设计模式

5
我正在尝试设计一种简单易用的错误处理设计模式,用于.NET WCF服务(特别是Silverlight启用的WCF服务)。如果服务方法中抛出异常,则Silverlight应用程序将看到一个CommunicationException,指明“远程服务器返回了一个错误:NotFound——>”,以及可能的堆栈跟踪(具体取决于您的设置),但这完全没有用,因为它并没有告诉您实际的错误,通常真正的错误不是“NotFound”。
通过阅读有关Web服务和WCF服务以及异常的资料,您需要抛出SOAP/WCF标准异常,例如FaultException或SoapException。因此,对于WCF服务,您需要在每个方法中包装try catch,捕获每个异常,将其包装在FaultException中并抛出。至少这是我的理解,请纠正我如果我错了。
因此,我创建了自己的设计模式:
[ServiceContract(Namespace = "http://MyTest")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class DataAccess
{
    /// <summary>
    /// Error class, handle converting an exception into a FaultException
    /// </summary>
    [DataContractAttribute]
    public class Error
    {
        private string strMessage_m;
        private string strStackTrace_m;

        public Error(Exception ex)
        {
            this.strMessage_m = ex.Message;
            this.strStackTrace_m = ex.StackTrace;
        }

        [DataMemberAttribute]
        public string Message
        {
            get { return this.strMessage_m; }
            set { this.strMessage_m = value; }
        }

        [DataMemberAttribute]
        public string StackTrace
        {
            get { return this.strStackTrace_m; }
            set { this.strStackTrace_m = value; }
        }

        //Convert an exception into a FaultException
        public static void Throw(Exception ex)
        {
            if (ex is FaultException)
            {
                throw ex;
            }
            else
            {
                throw new FaultException<Error>(new Error(ex));
            }
        }
    }

    [OperationContract]
    [FaultContract(typeof(Error))]
    public void TestException()
    {
        try
        {
            throw new Exception("test");
        }
        catch (Exception ex)
        {
            Error.Throw(ex);
        }
    }
}

长话短说,我的Silverlight应用程序仍然没有正确的错误提示。我检查了AsyncCompletedEventArgs.Error对象,它仍然包含一个通用错误的CommunicationException对象。请帮我设计一个简单的设计模式,以便我可以轻松地从服务中抛出正确的异常,并在应用程序中轻松地捕获它。

3个回答

6

我建议您将WCF服务的错误处理集中起来,而不是在每个方法上放置try/catch。为此,您可以实现IErrorHandler接口:

public class ErrorHandler : IErrorHandler
{
    public bool HandleError(Exception error)
    {
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message msg)
    {
        DataAccessFaultContract dafc = new DataAccessFaultContract(error.Message);
        var fe = new FaultException<DataAccessFaultContract>(dafc);
        Message fault = fe.CreateMessageFault();
        string ns = "http://www.example.com/services/FaultContracts/DataAccessFault";
        msg = Message.CreateMessage(version, fault, ns);
    }
}
ProvideFault方法在你的OperationContract抛出异常时被调用。它将把异常转换为自定义定义的FaultContract并将其发送给客户端。这样,您就不再需要在每个方法中使用try/catch。您还可以根据抛出的异常发送不同的FaultContract
在客户端上,每次调用 Web 服务的方法时,您都需要捕获FaultException<DataAccessFaultContract>

7
那么,你如何使用ErrorHandler类呢?你需要将它与你的服务关联起来吗? - Jeremy

5

好的,我研究了一下IErrorHandler的想法。我不知道你可以这样做,而且这很完美,因为它让你避免了为每个方法编写try-catch语句。你能在标准的Web服务中也这样做吗?我是按以下方式实现的:

/// <summary>
/// Services can intercept errors, perform processing, and affect how errors are reported using the 
/// IErrorHandler interface. The interface has two methods that can be implemented: ProvideFault and
/// HandleError. The ProvideFault method allows you to add, modify, or suppress a fault message that 
/// is generated in response to an exception. The HandleError method allows error processing to take 
/// place in the event of an error and controls whether additional error handling can run.
/// 
/// To use this class, specify it as the type in the ErrorBehavior attribute constructor.
/// </summary>
public class ServiceErrorHandler : IErrorHandler
{
    /// <summary>
    /// Default constructor
    /// </summary>
    public ServiceErrorHandler()
    {
    }

    /// <summary>
    /// Specifies a url of the service
    /// </summary>
    /// <param name="strUrl"></param>
    public ServiceErrorHandler(string strUrl, bool bHandled)
    {
        this.strUrl_m = strUrl;
        this.bHandled_m = bHandled;
    }

    /// <summary>
    ///HandleError. Log an error, then allow the error to be handled as usual. 
    ///Return true if the error is considered as already handled
    /// </summary>
    /// <param name="error"></param>
    /// <returns></returns>
    public virtual bool HandleError(Exception exError)
    {
        System.Diagnostics.EventLog evt = new System.Diagnostics.EventLog("Application", ".", "My Application");
        evt.WriteEntry("Error at " + this.strUrl_m + ":\n" + exError.Message, System.Diagnostics.EventLogEntryType.Error);

        return this.bHandled_m;
    }

    /// <summary>
    ///Provide a fault. The Message fault parameter can be replaced, or set to
    ///null to suppress reporting a fault.
    /// </summary>
    /// <param name="error"></param>
    /// <param name="version"></param>
    /// <param name="msg"></param>
    public virtual void ProvideFault(Exception exError,
        System.ServiceModel.Channels.MessageVersion version,
        ref System.ServiceModel.Channels.Message msg)
    {
        //Any custom message here
        /*
        DataAccessFaultContract dafc = new DataAccessFaultContract(exError.Message);

        System.ServiceModel.FaultException fe = new System.ServiceModel.FaultException<DataAccessFaultContract>(dafc);
        System.ServiceModel.Channels.MessageFault fault = fe.CreateMessageFault();

        string ns = "http://www.example.com/services/FaultContracts/DataAccessFault";
        msg = System.ServiceModel.Channels.Message.CreateMessage(version, fault, ns);
        */
    }

    private string strUrl_m;
    /// <summary>
    /// Specifies a url of the service, displayed in the error log
    /// </summary>
    public string Url
    {
        get
        {
            return this.strUrl_m;
        }
    }

    private bool bHandled_m;
    /// <summary>
    /// Determines if the exception should be considered handled
    /// </summary>
    public bool Handled
    {
        get
        {
            return this.bHandled_m;
        }
    }
}

/// <summary>
/// The ErrorBehaviorAttribute exists as a mechanism to register an error handler with a service. 
/// This attribute takes a single type parameter. That type should implement the IErrorHandler 
/// interface and should have a public, empty constructor. The attribute then instantiates an 
/// instance of that error handler type and installs it into the service. It does this by 
/// implementing the IServiceBehavior interface and then using the ApplyDispatchBehavior 
/// method to add instances of the error handler to the service.
/// 
/// To use this class specify the attribute on your service class.
/// </summary>
public class ErrorBehaviorAttribute : Attribute, IServiceBehavior
{
    private Type typeErrorHandler_m;

    public ErrorBehaviorAttribute(Type typeErrorHandler)
    {
        this.typeErrorHandler_m = typeErrorHandler;
    }

    public ErrorBehaviorAttribute(Type typeErrorHandler, string strUrl, bool bHandled)
        : this(typeErrorHandler)
    {
        this.strUrl_m = strUrl;
        this.bHandled_m = bHandled;
    }

    public virtual void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
    {
        return;
    }

    public virtual void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
    {
        return;
    }

    protected virtual IErrorHandler CreateTypeHandler()
    {
        IErrorHandler typeErrorHandler;

        try
        {
            typeErrorHandler = (IErrorHandler)Activator.CreateInstance(this.typeErrorHandler_m, this.strUrl_m, bHandled_m);
        }
        catch (MissingMethodException e)
        {
            throw new ArgumentException("The ErrorHandler type specified in the ErrorBehaviorAttribute constructor must have a public constructor with string parameter and bool parameter.", e);
        }
        catch (InvalidCastException e)
        {
            throw new ArgumentException("The ErrorHandler type specified in the ErrorBehaviorAttribute constructor must implement System.ServiceModel.Dispatcher.IErrorHandler.", e);
        }

        return typeErrorHandler;
    }

    public virtual void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
    {
        IErrorHandler typeErrorHandler = this.CreateTypeHandler();            

        foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
        {
            ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
            channelDispatcher.ErrorHandlers.Add(typeErrorHandler);
        }
    }

    private string strUrl_m;
    /// <summary>
    /// Specifies a url of the service, displayed in the error log
    /// </summary>
    public string Url
    {
        get
        {
            return this.strUrl_m;
        }
    }

    private bool bHandled_m;
    /// <summary>
    /// Determines if the ServiceErrorHandler will consider the exception handled
    /// </summary>
    public bool Handled
    {
        get
        {
            return this.bHandled_m;
        }
    }
}

服务:

[ServiceContract(Namespace = "http://example.come/test")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ErrorBehavior(typeof(ServiceErrorHandler),"ExceptonTest.svc",false)]
public class ExceptonTest
{
    [OperationContract]
    public void TestException()
    {   
        throw new Exception("this is a test!");
    }
}

5
建议不要在代码中添加大量注释。由于SO允许代码和文本混合,因此可以利用这一特性,避免人们需要滚动浏览相对较小的代码块才能了解你的操作。 - Adam Robinson

-6

对于懒人(像我一样):

using System.ServiceModel;  
using System.ServiceModel.Dispatcher;  
using System.ServiceModel.Description;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接