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.搞得返回值的类型很被动.
请大侠指点迷津.谢谢.