WCF RESTful 4.0 Service级别的异常处理(WCF RESTful 4.0 Service level Exception Handling)
众所周知, WCF RESTful 4.0目前没有全局的异常处理方法. 也就是说, 你不能捕获所有的异常.
在ASP.NET里, 你可以在Global.asax.cs的Application_Error
事件中捕获并处理所有之前没有被捕获的异常:
1: public class Global : HttpApplication
2: {
3: protected void Application_Error(object sender, EventArgs e)
4: {
5: Exception unhandledException = Server.GetLastError();
6:
7: //处理异常
8: ...
9: }
10: }
但在WCF RESTful 4.0中, 这种方法实际上只能捕获一些很特殊的异常, 比如你在Application_BeginRequest添加了自定义代码, 这些代码中出现了异常.
而其他绝大多数情况下, 当你的WCF服务中出现错误时, 这个事件是不会被触发的.
要想捕获所有"有用的异常信息", 一个折衷的办法是在每个Service Method中, 用一个try…catch包裹所有整个方法体, 像这样:
1: [WebGet(UriTemplate = "me/{profileIdOrName}")]
2: public string GetMyProfile(string profileIdOrName)
3: {
4: try
5: {
6: //以下是GetMyProfile的方法体
7: RequireLogin();
8: return GetUserProfile(CurrentUser.Id, profileIdOrName);
9: }
10: catch (Exception ex)
11: {
12: //ExceptionHandling.Handle 是异常处理模块
13: if(ExceptionHandling.Handle(ex))
14: throw;
15:
16: return null;
17: }
18: }
这样做虽然不能保证捕获所有的异常, 但是至少能保证不会遗漏任何业务逻辑层面的异常. 其他不能捕获的, 大部分是属于配置错误.
如果你能接受这个解决方法, 那你没有必要再接着读下去了.
这篇文章的其余部分, 介绍了一个不用在每个方法中加try…catch, 却能达到相同效果的解决方案.
为了达到在每个方法中加try…catch相同的效果(捕获所有除配置等错误之外的异常), 我们可以利用IErrorHandler接口.
首先, 实现一个IErrorHandler:
1: using System;
2: using System.ServiceModel.Channels;
3: using System.ServiceModel.Dispatcher;
4: using System.ServiceModel.Web;
5:
6: namespace Test
7: {
8: public class GeneralErrorHandler : IErrorHandler
9: {
10: /// <summary>
11: /// 处理异常的方法代理.
12: /// <para>通常情况下这个方法代理应该返回false, 以使其他处理异常的方法代理能继续处理这个异常.</para>
13: /// </summary>
14: public readonly Func<Exception, bool> FnHandleError;
15:
16: /// <summary>
17: /// 根据异常生成返回到客户端的错误信息的方法代理.
18: /// </summary>
19: public readonly Func<Exception, object> FnGetFaultDetails;
20:
21: /// <summary>
22: /// 创建一个GeneralErrorHandler新实例.
23: /// </summary>
24: /// <param name="fnGetFaultDetails">根据异常生成返回到客户端的错误信息的方法代理.</param>
25: /// <param name="fnHandleError">
26: /// 处理异常的方法代理.
27: /// <para>通常情况下这个方法代理应该返回false, 以使其他处理异常的方法代理能继续处理这个异常.</para>
28: /// </param>
29: public GeneralErrorHandler(Func<Exception, bool> fnHandleError, Func<Exception, object> fnGetFaultDetails)
30: {
31: FnHandleError = fnHandleError;
32: FnGetFaultDetails = fnGetFaultDetails;
33: }
34:
35: #region IErrorHandler Members
36:
37: public bool HandleError(Exception error)
38: {
39: if (FnHandleError == null)
40: return false; //returns false so the other Error Handlers can do sth with the error.
41:
42: return FnHandleError(error);
43: }
44:
45: public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
46: {
47: //如果你只是想捕获并记录所有的异常, 并打算向客户端发送所有的错误信息,
48: //你可以把以下代码用一句fault = null;代替
49:
50:
51: //if we set fault = null, raw error will be sent to client(http status won't be 200 OK);
52: // otherwise, if you provided fault message, http status will be 200 OK, and client will receive the fault message instead an error.
53:
54: var isJson = true;
55: var context = WebOperationContext.Current;
56: if (null != context && null != context.OutgoingResponse && null != context.OutgoingResponse.Format)
57: isJson = context.OutgoingResponse.Format.Value == WebMessageFormat.Json;
58:
59: var faultDetails = null == FnGetFaultDetails ? null : FnGetFaultDetails(error);
60: var bodyWriter = new FaultBodyWriter(faultDetails, isJson);
61: fault = Message.CreateMessage(version, string.Empty, bodyWriter);
62:
63: var bodyFormatProperty = new WebBodyFormatMessageProperty(isJson ? WebContentFormat.Json : WebContentFormat.Xml);
64: fault.Properties[WebBodyFormatMessageProperty.Name] = bodyFormatProperty;
65:
66: if (isJson)
67: context.OutgoingResponse.ContentType = "application/json";
68: }
69:
70: #endregion
71: }
72: }
1: using System.Runtime.Serialization;
2: using System.Runtime.Serialization.Json;
3: using System.ServiceModel.Channels;
4: using System.Xml;
5:
6: namespace Test
7: {
8: public class FaultBodyWriter : BodyWriter
9: {
10: private object FaultDetails;
11: private XmlObjectSerializer Serializer;
12:
13: public FaultBodyWriter(object faultDetails, bool isJson)
14: : base(true)
15: {
16: FaultDetails = faultDetails;
17:
18: var type = faultDetails.GetType();
19: if (isJson)
20: Serializer = new DataContractJsonSerializer(type);
21: else
22: Serializer = new DataContractSerializer(type);
23: }
24:
25: protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
26: {
27: Serializer.WriteObject(writer, FaultDetails);
28: }
29: }
30: }
然后是最关键的一步, 把我们的GeneralErrorHandler"注册"(或者你可以说绑定)到WCF服务中, 我们用实现一个IServiceBehavior接口的ErrorHandlerBehaviorAttribute完成.
1: using System;
2: using System.Collections.ObjectModel;
3: using System.ServiceModel;
4: using System.ServiceModel.Channels;
5: using System.ServiceModel.Description;
6: using System.ServiceModel.Dispatcher;
7:
8: namespace Test
9: {
10: [AttributeUsage(AttributeTargets.Class, Inherited = true)]
11: public class ErrorHandlerBehaviorAttribute : Attribute, IServiceBehavior
12: {
13: public readonly GeneralErrorHandler ErrorHandler;
14:
15: public ErrorHandlerBehaviorAttribute()
16: {
17: ErrorHandler = new GeneralErrorHandler(error =>
18: {
19: //...在这里记录并处理异常
20: return false; //返回false, 以使其他处理异常的方法代理能继续处理这个异常.
21: },
22:
23: error => new SampleItem //在这里, 我们返回异常的Message. 实际情况下的处理可能会很复杂, 取决于你的需求.
24: {
25: Id = 20110509,
26: StringValue = error.Message
27: });
28: }
29:
30: #region IServiceBehavior Members
31:
32: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
33: Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
34: { }
35:
36: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
37: {
38: //在这里注册我们的GeneralErrorHandler
39: foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
40: dispatcher.ErrorHandlers.Add(ErrorHandler);
41: }
42:
43: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
44: { }
45:
46: #endregion
47: }
48: }
最后, 把它注册到Service上.
1: using System;
2: using System.Collections.Generic;
3: using System.ServiceModel;
4: using System.ServiceModel.Activation;
5: using System.ServiceModel.Web;
6:
7: namespace Test
8: {
9: [ServiceContract]
10: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
11: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
12: [ErrorHandlerBehavior] //注册ErrorHandler到基类
13: public class Service1
14: {
15: [WebGet(UriTemplate = "")]
16: public List<SampleItem> GetCollection()
17: {
18: throw new Exception("这是一个演示WCF REST 4.0 Exception Handling的例子");
19: }
20: }
21: }
好了, 一切搞定. 现在你只要在任何新的Service上面添加一个[ErrorHandlerBehavior]属性, 就可以避免在每个方法中都添加try…catch了.
参考: http://tothsolutions.com/Blog/post/WCFSilverlight-Exception-Handling.aspx