WCF 将指定错误做为结果的一部分返回
首先,WCF 我只在最近一年的项目中使用过,其中的奥秘我了解的不多,用到才有时间去想.
我这样想不知道对不对.但是有想法总比没想法好:
比如数据验证这块,我们原来使用的是直接在代码里检查,我准备在新的项目里完全使用 EntLib 的 Validation ,EntLib WCF Validation 是直接将验证不通过抛出为 FaultValidation<ValidationFault> .而我们现有的架构是将验证错误返回为 ReturnCodes.ParameterValidateFailed. 在比如身份验证这块,现有的架构是把没有权限返回为 ReturnCodes.NotHaveEnoughPermission.
像这样:
public class BaseResult { /// <summary> /// /// </summary> public ReturnCodes ReturnCode { get; set; } /// <summary> /// 数据验证结果 /// </summary> public IList<ValidationFault> ValidationResults { get; set; } public BaseResult() { } public BaseResult(IList<ValidationFault> details) { this.ValidationResults = details; this.ReturnCode = ReturnCodes.ParamValidateFailed; } } public class SingleResult<T> : BaseResult where T : class { /// <summary> /// 单笔结果 /// </summary> public T Entity { get; set; } } public class SearchResult<T> : BaseResult where T : class { /// <summary> /// 搜索结果集 /// </summary> public List<T> Entities { get; set; } private int page = 1; /// <summary> /// 当前页 /// </summary> public int Page { get { return page; } set { page = value < 1 ? 1 : value; } } /// <summary> /// 每页记录数 /// </summary> public int PageSize { get; set; } /// <summary> /// 总记录数 /// </summary> public int Total { get; set; } public ReturnCodes ReturnCode { get; set; } }
我试着把WCF的ValidationAttribute 重写,但是在 BeforeCall 这块,不知道怎么处理了.
public object BeforeCall(string operationName , object[] inputs) { List<ValidationDetail> details = new List<ValidationDetail> ( ); for ( int i = 0 ; i < this.inputValidators.Count ; i++ ) { var results = this.inputValidators[i].Validate ( inputs[i] ); if ( !results.IsValid ) { var tmp = results.Select ( r => new ValidationDetail ( r.Message , r.Key , r.Tag ) ); details.AddRange ( tmp ); } } ValidationFault fault = new ValidationFault ( details ); if ( !fault.IsValid ) { //throw new FaultException<ValidationFault> ( fault ); //EntLib 是直接在这里 throw ,这样就不会进入具体的 OperationContract 里, //但是,如果我在这里不抛出错,要怎么处理才能让 WCF 直接返回,而不是进入 OperationContract 里? } else { return null; } }
思考这个问题了好几天,搜了无数文章,奈何没有找到.
后来找到一篇文章,讲的是怎么在 IErrorHandler 里把错误包装成结果返回(报歉,忘了地址,明天补上),我跟据这篇文章写成这样:
public class FaultHandler : IErrorHandler { public bool HandleError(Exception error) { return true; } public void ProvideFault(Exception error , System.ServiceModel.Channels.MessageVersion version , ref System.ServiceModel.Channels.Message fault) { if ( error is FaultException<ValidationFault> ) { var action = OperationContext.Current.IncomingMessageHeaders.Action; var operations = OperationContext.Current.EndpointDispatcher.DispatchRuntime.Operations; var operation = operations.Where ( d => d.Action == action ).FirstOrDefault ( ); if ( operation != null ) { var method = TypeDescriptor.GetProperties ( operation.Invoker )["Method"].GetValue ( operation.Invoker ); var returnType = ( Type )TypeDescriptor.GetProperties ( method )["ReturnType"].GetValue ( method ); var details = ( error as FaultException<ValidationFault> ).Detail.Details; if ( returnType.GetConstructor ( new Type[] { typeof ( IList<ValidationDetail> ) } ) != null ) { var value = Activator.CreateInstance ( returnType , details ); fault = operation.Formatter.SerializeReply ( version , new object[] { } , value ); } } } } }
关键在 ProvideFault 这个方法上.首先找到错误是发生在哪个 OperationContract 上的,并得返回类型,判断这个返回类型是不是存在有一个 IList<ValidationDetail> 参数的构造函数,如果有,把具体的验证错误做为参数实例化一个对象,在将这个对象序列化,填进 ref fault 里.
在 config 文件里要加上这个 FaultHandler:
1 <system.serviceModel>
2 <extensions>
3 <behaviorExtensions>
4 <add name="FaultHandler" type="AsNum.Common.WCFExtends.FaultBehaviorExtension, AsNum.Common.WCFExtends, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
5 </behaviorExtensions>
6 ....
7 ....
8 <behaviors>
9 <serviceBehaviors>
10 <behavior name="">
11 <serviceMetadata httpGetEnabled="true" />
12 <serviceDebug includeExceptionDetailInFaults="false" />
13 <FaultHandler />
[ValidationBehavior("SendMsg")] [FaultContract ( typeof ( ValidationFault ) )] [OperationContract] SingleResult<MessageEntity> SendMsg(SendMsgEntity param);
通过上面的描述,可以看出,这样做有诸多限制和不便,但是确实可以达到我的要求.
我想问的是:
1,怎么在 BeforeCall 里直接返回,而不是在 IErrorHandler 里包装返回?
2,这一点明知是不可能的事情,写出来算发发牢骚:An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.搞得返回值的类型很被动.
请大侠指点迷津.谢谢.