首先,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.搞得返回值的类型很被动.

请大侠指点迷津.谢谢.

作者: xling 发表于 2011-07-04 22:11 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架