62

我们正在使用出色的ELMAH来处理 ASP.NET 3.5 Web 应用程序中未处理的异常。除了使用 REST 功能使用的 WCF 服务之外,这对于所有站点都非常有效。当应用程序代码未处理的操作方法中发生异常时,WCF 会根据服务协定和配置设置以各种方式处理它。这意味着异常不会最终触发ELMAH使用的 ASP.NET HttpApplication.Error 事件。我知道解决这个问题的两个解决方案是:

  • 将所有方法调用包装在 try { } catch(Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex); 扔; } 在 catch 块中显式调用 Elmah。
  • 使用Will Hughes 的博客文章使 WCF 和 ELMAH 很好地协同工作中所述的IErrorHandler将对 ELMAH 的调用分解为单独的 ErrorHandler。

第一个选项非常简单,但并不完全是DRY。第二个选项只需要你在实现属性和ErrorHandler之后用自定义属性来装饰每个服务。我是根据Will 的工作完成的,但我想在发布代码之前验证这是正确的方法。

我错过了更好的方法吗?

IErrorHandler的 MSDN 文档说HandleError方法是进行日志记录的地方,但ELMAH访问 HttpContext.Current。ApplicationInstance,即使 HttpContext.Current 可用,在此方法中也为 null。在 ProvideFault 方法中调用 Elmah 是一种解决方法,因为 ApplicationInstance 已设置,但这与 API 文档中描述的意图不匹配。我在这里错过了什么吗?该文档确实声明您不应依赖在操作线程上调用的 HandleError 方法,这可能是 ApplicationInstance 在此范围内为 null 的原因。

4

6 回答 6

88

我的博客文章中的解决方案(在 OP 中引用)基于我们在错误状态期间用于更改 HTTP 响应代码的现有解决方案。

因此,对我们来说,将异常传递给 ELMAH 是一项更改。如果有更好的解决方案,我也很想知道。

对于后代/参考和潜在的改进 - 这是当前解决方案的代码。

HttpErrorHandler 和 ServiceErrorBehaviourAttribute 类

using System;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Collections.ObjectModel;
using System.Net;
using System.Web;
using Elmah;
namespace YourApplication
{
    /// <summary>
    /// Your handler to actually tell ELMAH about the problem.
    /// </summary>
    public class HttpErrorHandler : IErrorHandler
    {
        public bool HandleError(Exception error)
        {
            return false;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            if (error != null ) // Notify ELMAH of the exception.
            {
                if (System.Web.HttpContext.Current == null)
                    return;
                Elmah.ErrorSignal.FromCurrentContext().Raise(error);
            }
        }
    }
    /// <summary>
    /// So we can decorate Services with the [ServiceErrorBehaviour(typeof(HttpErrorHandler))]
    /// ...and errors reported to ELMAH
    /// </summary>
    public class ServiceErrorBehaviourAttribute : Attribute, IServiceBehavior
    {
        Type errorHandlerType;

        public ServiceErrorBehaviourAttribute(Type errorHandlerType)
        {
            this.errorHandlerType = errorHandlerType;
        }

        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;
            errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }
        }
    }
}

使用示例

使用 ServiceErrorBehaviour 属性装饰您的 WCF 服务:

[ServiceContract(Namespace = "http://example.com/api/v1.0/")]
[ServiceErrorBehaviour(typeof(HttpErrorHandler))]
public class MyServiceService
{
  // ...
}
于 2009-05-25T12:11:14.800 回答
9

创建 BehaviorExtensionElement 时,甚至可以使用配置激活行为:

public class ErrorBehaviorExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(ServiceErrorBehaviourAttribute); }
    }

    protected override object CreateBehavior()
    {
        return new ServiceErrorBehaviourAttribute(typeof(HttpErrorHandler));
    }
}

配置:

<system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="elmah" type="Namespace.ErrorBehaviorExtensionElement, YourAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <elmah />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

这样也可以将 ELMAH 与 RIA 服务结合使用!

于 2011-06-17T13:42:38.073 回答
3

这对某些人来说可能很明显,但我只是花了很长时间试图弄清楚为什么我的 HttpContext.Current 为空,尽管遵循了 Will Hughes 的所有出色答案。尴尬的是,我意识到这是因为我的 WCF 服务是由 MSMQ 消息激活的。

我最终重写了ProvideFault()方法:

if (HttpContext.Current == null)
{
    ErrorLog.GetDefault(null).Log(new Error(error));
}
else
{
    ErrorSignal.FromCurrentContext().Raise(error);
}
于 2013-12-05T16:47:14.080 回答
2

我是根据 Will 的工作完成的,但我想在发布代码之前验证这是正确的方法。

我认为这是一个很好的方法(感谢 Will 发表这篇文章!)。我不认为威尔或你在这里错过了什么。实现 IErrorHandler 是捕获所有可能的服务器端异常的首选方法,否则这些异常可能会导致通信通道出现故障(拆除),因此它是挂钩某些日志记录(如 ELMAH)的自然场所。

马克

于 2009-05-22T05:25:14.727 回答
1

我无法使用 WCF 数据服务获得建议的答案。我连接了行为属性等,但仍然没有记录任何错误。相反,我最终将以下内容添加到服务实现中:

protected override void HandleException(HandleExceptionArgs args)
{
    Elmah.ErrorSignal.FromCurrentContext().Raise(args.Exception);
    base.HandleException(args);
}
于 2013-12-05T19:40:19.137 回答
0

我没有尝试使用 REST 明确地执行此操作,并且我自己也没有使用 ELMAH,但另一个值得研究的选项可能是使用IDispatchMessageInspector而不是 IErrorHandler 连接到 WCF。

于 2009-05-22T02:11:03.807 回答