现实世界中的WCF故障契约

14

假设我们有一个服务方法,它执行一些安全检查,从数据库和第三方Web服务中检索数据,构造MyDataDTO,并将审核条目写回到数据库。 我们希望有良好结构化的、细粒度的错误代码,不是吗?我们是好孩子,按照标准的WCF错误处理指南操作:

[FaultContract(typeof(AccessDenied))]
[FaultContract(typeof(KeyNotFound))]
[FaultContract(typeof(WsFault))]
[FaultContract(typeof(DbFault))]
MyDataDTO GetData(string key);

现在我们正在添加一个更新数据的新方法。该方法内部调用GetData()(或其主要部分),执行验证并更新数据。因此,它必须具有GetData()的所有错误的副本以及自身的错误:

[FaultContract(typeof(InvalidState))]
[FaultContract(typeof(DataNotValid))]
[FaultContract(typeof(AccessDenied))]
[FaultContract(typeof(KeyNotFound))]
[FaultContract(typeof(WsFault))]
[FaultContract(typeof(DbFault))]
void UpdateData(MyDataDTO data);
到目前为止还不错。这甚至允许我们生成XML文档,以便我们可以向服务的消费者提供错误代码的信息。
现在想象一下,我们有十个类似上述(或更复杂)的方法的10个服务。定义所有这些故障契约变成了噩梦,因为这是一个容易出错的过程:
1. 没有办法为整个服务定义一般故障(例如DbFault)。 2. 无法保证在操作契约中定义的故障确实会被返回(复制粘贴问题)。 3. 无法保证您没有错过添加到操作契约中的某些故障。
暂且不考虑接口版本控制的问题。
这就是您在生产中支持WCF服务时遇到的情况。我们应该完全放弃故障契约并使用老式的C样式(例如使用具有 ErrorCode 属性的基本DTOBase类)吗?降低错误粒度?如何确保文档正确/更新?我对一些最佳实践很感兴趣。

4
请问您的客户是否真正会“使用”这些详细错误代码?他们是否会基于返回的不同故障采取不同的行动,还是仅仅显示错误信息呢? - John Saunders
4
那么您只需要一个包含“Message”字符串的单个FaultContract。 - John Saunders
FaultContracts 是如何返回 SOAP 错误的。它们旨在将带外信息传递回调用者,告知发生了错误。在您的情况下,您需要传递的唯一信息是消息,因此我建议使用 FaultException<string>FaultException<MessageFault>,其中 MessageFault 将是具有单个 string 属性的 FaultContract。请注意,我不会返回任何错误代码,只返回要显示的字符串。 - John Saunders
1
抛出新的 FaultException("您无权执行此操作", new FaultCode("AccessDenied")),而且我不需要在操作中定义任何类型的 FaultException 或 FaultContract。这解决了你描述的问题,仍然保持100%的SOAP兼容性。 - UserControl
1
这是在信封中的结果: <s:Fault> <faultcode>s:AccessDenied</faultcode> <faultstring xml:lang="en-US">你没有权限进行此操作</faultstring> </s:Fault> - UserControl
显示剩余3条评论
2个回答

9
你原来的方法存在一个问题,就是试图复制可能出现的各种系统错误/异常。随着每个新功能的增加,系统的复杂性也会呈指数级增长(或更多!)。我建议采用以下方法:由于您正在创建一组服务和访问调用的“系统”,因此仅定义与该系统相关的FaultContracts。客户端只关心以下问题:
1. 这是我想要的数据还是我提出的方式有问题? 2. 如果不是,这是我的问题还是IT相关问题(系统崩溃、网络错误、数据库问题等)?
通过一些改进,可以减少需要提供的故障合同数量。例如(这是我临时想到的):
//Base class to define general problems
[DataContract]
public class SysFault
{
  //MyDataDTO-specific general error.
  [DataMember]
  public string SysMsg {get;set;}

  //boolean for "this is a problem with me or the hosting system?"
  [DataMember]
  public bool IsSystemic {get;set;}
}

//Subclass to expose synchronization issues--if that's one you want to define
[DataContract]
public class SyncFault : SysFault
{
  [DataMember]
  public string SyncMsg { get;set; }
}

//Subclass to expose validation issues
[DataContract]
public class ValFault : SysFault
{
  [DataMember]
  public string ValMsg { get;set; }
}

现在,您可以使用故障类型来分离出您的服务中正在发生的情况。 例如:
[ServiceContract]
public interface IRecordSys
{
    [OperationContract]
    [FaultContract(typeof(SysFault))]  //Raised for underlying problem
    [FaultContract(typeof(ValFault))]  //Raised if there is an issue with the key value
    MyDataDTO getData(string key);

    [OperationContract]
    [FaultContract(typeof(SysFault))]  //Raised for underlying problem elsewhere
    //Raised for some issue, such as unable to get two subsystems to update properly
    //with the given data
    [FaultContract(typeof(SyncFault))]
    void update(MyDataDTO data); 
}

您的具体实现方法可能会有所不同,但是想法是传递与您的系统有关的消息,而不是每个可能出现的系统问题。


1

这会更新WSDL以返回每个操作要返回的故障吗? - John Saunders

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接