65.9K
CodeProject 正在变化。 阅读更多。
Home

查找调用的操作并更改 WCF 服务中的返回值

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.11/5 (6投票s)

2011年2月10日

CPOL

5分钟阅读

viewsIcon

25914

downloadIcon

247

如何从 WCF 行为/错误处理程序中重新创建操作的返回对象

引言

我目前正在为一个 SOA 项目的基础设施工作,该项目为多个平台提供服务(传统网站 - JSON+SOAP,移动应用程序 - JSON,自助服务终端 - MSMQ)。
我认为到目前为止我们遇到的最大问题是为所有平台提供统一的 API,坦白地说,尽可能地统一。我们在创建通用错误处理程序方面遇到了特别的麻烦,该处理程序将为所有类型的终结点返回相同的自定义结果对象。

JSON,特别是 JavaScript,是弱类型的,所以基本上可以返回任何东西,即使数据不符合声明的契约,只要客户端知道如何处理数据,我们就没事。请记住,这并非易事。

SOAP 则不同,更确切地说,只要客户端实际上检查收到的回复是否完全符合预期。需要说明的是,.NET SOAP 客户端确实会检查接收到的数据(据我所知)。

* 总的来说,只要服务器和客户端知道如何处理,WCF 就可以处理所有类型的数据,某些协议比其他协议更严格,这是提供统一 API 困难的主要原因。

背景

如前所述,我的目标是实现一个通用错误处理程序,以处理所有类型的终结点。WCF 提供了一个名为 IErrorHandler 的接口,它提供了处理异常的入口点

  • bool HandleError(Exception)

    指示 IErrorHandler 是否已处理异常。

  • void ProvideFault(Exception, MessageVersion, ref Message)

    创建在抛出异常时返回的消息。

这正是我想要的,因为它允许我捕获错误并将其转换为我告知客户端我将提供的一个对象。即使操作失败,我也不希望客户端处理我的异常,我只会通知它调用失败了。

要实现这一点,有几个问题需要解决

  • 第一个问题是提供一个描述所有操作的错误(异常)的对象。
    这个解决方案很简单,我只需要让所有 API 返回类型都继承自一个名为 Result 的通用基对象,该对象以友好且可序列化的方式保存了可以描述异常的一些属性以及一些内部内容。
  • 第二个问题是用异常详细信息返回 Result 对象。
    这里开始变得棘手了,第一个(也是天真的)方法是创建一个 Result 对象,并在捕获到异常时创建一个 Message 对象(使用 Message.CreateMessage)。
    这实际上是可行的,好吧,对于不检查他们收到了什么的客户端(如引言中所述),但当一个严格的客户端看到响应时,它只会抛出一个异常,通常意味着——你不遵守契约。

    然后我考虑尝试“伪造”一个消息,所以我使用了 DataContractSerializerBodyWriter 来创建 Message,只要我知道原始返回对象是什么,它就能工作,我硬编码了一些东西来模仿 WCF 序列化。
    我知道我正在走上正确的道路,但我意识到在继续之前还有一些事情需要解决。 
    首先我需要一个序列化器,因为我无法估算 WCF 如何为每次调用中的每个对象序列化数据。我知道它存在于某处,因为 WCF 在每次调用时都在进行这种序列化,所以我看了一下这个 ,我注意到了 Formatter 块。我做了一些研究,发现它实现了 IDispatchMessageFormatter,但遗憾的是,关于它的信息非常少,所有尝试实现的尝试都失败了,我甚至尝试继承 WebHttpBehavior

    我尝试继承 WebHttpBehavior 以便通过调用 GetReplyDispatchFormatter 来使用其格式化程序,但仍然失败了。然后我意识到我实际上不需要创建一个格式化程序,我已经从 WCF 中获得了一个。我只需要了解如何获取它。
    我需要找到当前的操作并使用关联的格式化程序。在 VS 监视窗口中做一些研究,我意识到查找当前操作的最简单方法是按操作名称查找,可以在 OperationContext.Current.IncomingMessageHeaders.Action 中找到,现在我们只需要遍历 OperationContext.Current.EndpointDispatcher.DispatchRuntime.Operations 数组并查找处理此操作名称的操作。
    现在我们有了当前操作,我们可以获取关联的格式化程序(从 Formatter 属性),这对于稍后重新创建原始返回参数也很重要。

  • 查找原始返回参数。
    我不得不深入研究 WCF 的内部机制,也许有些人会说滥用反射来实际找出被调用操作返回的类型。这真的让我感到惊讶,因为,我希望其他人也有同感,这是一个微不足道但合法的、理应提供的功能。
    该操作还有一个名为 Invoker 的属性,由 SyncMethodInvoker 实现,这个 invoker 有一个名为 Method (MethodInfo)private 属性,它又有一个名为 ReturnType (Type) 的属性,该属性保存了操作的返回类型。

最后,我们有了所有需要的东西,所以现在剩下要做的就是

  • 创建返回类型。
  • 用异常详细信息填充它。
  • 使用操作格式化程序将创建的对象序列化为消息。

Using the Code

我正在编写一个 IErrorHandler 的简化实现。

这是一个基本的 IErrorHandler

public class CustomErrorHandler : IServiceBehavior, IErrorHandler 
{
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
        ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)
        {
            chDisp.ErrorHandlers.Add(this);
        } 
    }
    
    public void Validate(ServiceDescription serviceDescription, 
		ServiceHostBase serviceHostBase)
    {
    
    }
    
    public void AddBindingParameters(ServiceDescription serviceDescription, 
	ServiceHostBase serviceHostBase, 
        	Collection<serviceendpoint /> 
		endpoints, BindingParameterCollection bindingParameters)
    {
    
    }
    
    public bool HandleError(Exception error)
    {
        return true;
    }
    
    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
    }
}

我们将修改 ProvideFault 以重新创建原始 return 对象

public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
    try
    {
        // find operation invoker
        DispatchOperation operation = OperationByAction(CurrentAction);
        Type returnType = GetOperationReturnType(operation);

        // create result type
        object value = Activator.CreateInstance(returnType, error);

        try
        {
            // serialize the created object
            fault = operation.Formatter.SerializeReply(version, new object[] { }, value);
        }
        catch (Exception ex)
        {
            throw new Exception("failed to create error result");
        }
    }
    catch (Exception ex)
    {
        fault = Message.CreateMessage(
            version,
            MessageFault.CreateFault(FaultCode.CreateSenderFaultCode
		("error handler", ""), "failed to create custom error Result object\n" 
		+ ex.Message),
            OperationContext.Current.IncomingMessageHeaders.Action
        );
    }
}

加入一些辅助方法使代码更具可读性

private static string CurrentAction
{
    get { return OperationContext.Current.IncomingMessageHeaders.Action; }
}

private static DispatchRuntime CurrentDispatchRuntime
{
    get { return OperationContext.Current.EndpointDispatcher.DispatchRuntime; }
}

private static string CurrentEndpoint
{
    get { return OperationContext.Current.
    	EndpointDispatcher.EndpointAddress.Uri.AbsoluteUri; }
}

private Type GetOperationReturnType(DispatchOperation operation)
{
    string key = CurrentEndpoint + CurrentAction;
    Type returnType;

    if (typeCache.ContainsKey(key))
    {
        returnType = typeCache[key];
    }
    else
    {
        // find original return type
        object method = GetPropertyValue(operation.Invoker, "Method");
        returnType = GetPropertyValue(method, "ReturnType") as Type;

        typeCache.Add(key, returnType);
    }

    return returnType;
}

private object GetPropertyValue(object obj, string property)
{
    PropertyDescriptor pd = TypeDescriptor.GetProperties(obj)[property];

    return pd.GetValue(obj);
}

private DispatchOperation OperationByAction(string action)
{
    SynchronizedKeyedCollection operations = CurrentDispatchRuntime.Operations;

    for (int i = 0; i < operations.Count; i++)
        if (operations[i].Action == action)
                return operations[i];

    throw new Exception("operation not found in DispatchRuntime operations");
}

就是这样。附带的文件包含了完整的实现。

关注点

如果您使用此代码,请记住,反射部分可能无法在 WCF 的未来版本中正常工作。

历史

  • 2011 年 2 月 9 日:初始版本
  • 2011 年 2 月 12 日:代码优化
© . All rights reserved.