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
浙公网安备 33010602011771号