是否可以使用IErrorHandler返回与合同中预期匹配的响应DTO

时间:2014-11-24 15:46:42

标签: c# wcf

我希望能够捕获所有未处理的异常并返回预期的DTO,但填写了一些错误信息。例如

public class CreateFooRequest
{
    public string Name { get; set; }
}

public class CreateFooResponse
{
    public Foo Created { get; set; }

    public string Error { get; set; }  // If call was successful then this will be null

    public string Detail { get; set; }
}

public interface IFooService
{
    CreateFooResponse Create(CreateFooRequest request);
}

public ErrorHandler: IErrorHandler
{
    public bool Handle(Exception ex)
    {
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        // Some how figure out that IFooService.Create was called. 
        // Inspect the method signature and see that there is an input called CreateFooRequest
        // Use reflection to initialize response objects that will replace the "Request" with "Response"
        var response = new CreateFooResponse();
        response.Error = error.GetType().Name;
        // I think i need one of the following overloads
        fault = Message.CreateMessage(version, action, response);
    }
}

甚至可以做这样的事情吗?我们正在使用NetTCP作为我们的绑定,如果这会产生影响。

3 个答案:

答案 0 :(得分:1)

在您的位置,我将使用常规try/catch块尽可能简单地实现此功能。使用自定义异常处理程序来观察调用的服务方法并使用反射创建相应的响应对我来说就像是一种过度杀伤。

让您的生活更轻松:

public CreateFooResponse Create(CreateFooRequest request)
{
    try
    {
        // Create Foo
        var foo = CreateFoo();

        // Return successful CreateFooResponse
        return new CreateFooResponse
        {
            Created = foo,
            Error = null,
            Detail = "Created successfully"
        };
    }
    catch (Exception ex)
    {
        // Return CreateFooResponse with an error
        return new CreateFooResponse
        {
            Created = null,
            Error = CreateError(ex),
            Detail = "Unable to create Foo."
        };
    }
}

使用自定义WCF错误处理程序的一个很好的示例是记录错误,将其转换为FaultContract并返回给调用者。你有不同的场景,我建议采用不同的方法。

答案 1 :(得分:1)

IErrorHandler旨在生成错误契约。如果您不想返回错误,最好采用拦截器方法并使用IOperationInvoker扩展点。

操作调用程序是实际调用服务方法的WCF框架的一部分。当你扩展它时,你可以有效地拦截"对服务的呼吁。请注意,Invoker确实有责任。您不能简单地替换WCF调用程序实现。而是将它们链接起来(见下文)。

在较高的层面上,步骤是:

  1. 创建一个实现try / catch块的IOperationInvoker.Invoke()。捕获您希望成为响应消息而不是FaultExceptions的异常。
  2. 创建一个IOperationBehavior(可选择也是一个属性)以将该行为应用于您的服务。
  3. 该方法有几个优点:

    1. 在WCF看到异常之前捕获异常。
    2. 在IOperationBehavior.ApplyDispatchBehavior()中,您可以在服务启动时访问OperationDescription。如果将其保存在Invoker中,则不需要使用反射来捕获方法的返回类型。
    3. IOperationBehavior.Validate()允许进行健壮检查,以确保实际可以处理返回类型。
    4. 下面是一个完整的Windows控制台应用程序,演示了该方法。将其粘贴到Visual Studio中,添加明显的程序集引用,然后运行它。

      我为大量代码和过度使用基类道歉。我正在减少实际的生产代码。

      如果您想更好地了解IOperationInvoker扩展点以及示例InvokerBase类正在做什么,请参阅Carlos Figueira's blog

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Reflection;
      using System.Runtime.Serialization;
      using System.ServiceModel;
      using System.ServiceModel.Description;
      using System.ServiceModel.Dispatcher;
      using System.Text;
      using System.Threading.Tasks;
      
      namespace WcfErrorResponse
      {
          /// <summary>
          /// Provides a base IOperationInvoker implementation that stores and passes through calls to the exisiting (old) invoker
          /// </summary>
          public abstract class InvokerBase : IOperationInvoker
          {
              private readonly IOperationInvoker m_OldInvoker;
      
              protected IOperationInvoker OldInvoker
              {
                  get { return m_OldInvoker; }
              }
      
              public InvokerBase(IOperationInvoker oldInvoker)
              {
                  m_OldInvoker = oldInvoker;
              }
      
              public virtual object[] AllocateInputs()
              {
                  return OldInvoker.AllocateInputs();
              }
      
              public virtual object Invoke(object instance, object[] inputs, out object[] outputs)
              {
                  return OldInvoker.Invoke(instance, inputs, out outputs);
              }
      
              public virtual IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
              {
                  return OldInvoker.InvokeBegin(instance, inputs, callback, state);
              }
      
              public virtual object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
              {
                  return OldInvoker.InvokeEnd(instance, out outputs, result);
              }
      
              public virtual bool IsSynchronous
              {
                  get { return OldInvoker.IsSynchronous; }
              }
          }
      
          /// <summary>
          /// Base implementation for a Method level attribte that applies a <see cref="InvokerBase"/> inherited behavior.
          /// </summary>
          [AttributeUsage(AttributeTargets.Method)]
          public abstract class InvokerOperationBehaviorAttribute : Attribute, IOperationBehavior
          {
              protected abstract InvokerBase CreateInvoker(IOperationInvoker oldInvoker, OperationDescription operationDescription, DispatchOperation dispatchOperation);
      
              public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
              { }
      
              public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
              { }
      
              public virtual void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
              {
                  // chain invokers.
                  IOperationInvoker oldInvoker = dispatchOperation.Invoker;
                  dispatchOperation.Invoker = CreateInvoker(oldInvoker, operationDescription, dispatchOperation);
              }
      
              public virtual void Validate(OperationDescription operationDescription)
              {
                  return;
              }
          }
      
          public class ResponseExceptionInvoker : InvokerBase
          {
              private Type returnType;
      
              public ResponseExceptionInvoker(IOperationInvoker oldInvoker, OperationDescription operationDescription)
                  : base(oldInvoker)
              {
                  // save the return type for creating response messages
                  this.returnType = operationDescription.GetReturnType();
      
                  if (this.returnType == null)
                  {
                      throw new InvalidOperationException("The operation '" + operationDescription.SyncMethod.DeclaringType.Name + "' does not define a return type.");
                  }
              }
      
              public override object Invoke(object instance, object[] inputs, out object[] outputs)
              {
                  object returnedValue = null;
                  object[] outputParams = new object[] { };
                  outputs = new object[] { };
      
                  try
                  {
                      returnedValue = OldInvoker.Invoke(instance, inputs, out outputParams);
                      outputs = outputParams;
                      return returnedValue;
                  }
                  catch (Exception ex)
                  {
                      Logger.Debug("ResponseExceptionInvoker() - Caught Exception. A Response Message will be returned. Message='" + ex.Message + "'");
      
                      // there was an excpetion. Do not assign output params... their state is undefined.
                      //outputs = outputParams;
      
                      try
                      {
                          // assumes the behavior only used for return types that inherit from Response, as verified by ResponseExceptionOperationBehaviorAttribute.Validate()
                          Response response = (Response)Activator.CreateInstance(this.returnType);
                          response.Success = false;
                          response.ErrorMessage = ex.Message;
                          return response;
                      }
                      catch (Exception exCreateResponse)
                      {
                          // Log that the Response couldn't be created and throw the original exception.
                          // Probably preferable to wrap and throw.
                          Logger.Error("Caught ResponseException, but unable to create the Response object. Likely indicates a bug or misconfiguration. Exception will be rethrown." + exCreateResponse.Message);
                      }
      
                      throw;
                  }
              }
          }
      
          public class ResponseExceptionOperationBehaviorAttribute : InvokerOperationBehaviorAttribute
          {
              protected override InvokerBase CreateInvoker(IOperationInvoker oldInvoker, OperationDescription operationDescription, DispatchOperation dispatchOperation)
              {
                  return new ResponseExceptionInvoker(oldInvoker, operationDescription);
              }
      
              public override void Validate(OperationDescription operationDescription)
              {
                  // validate that this attribute can be applied to the service behavior.
                  Type returnType = operationDescription.GetReturnType();
                  if (!typeof(Response).IsAssignableFrom(returnType))
                  {
                      throw new InvalidOperationException("'" + returnType.FullName + "' does not inherit from '" + typeof(Response).FullName +
                                                          "'. ImplicitResponse behavior applied to '" + operationDescription.SyncMethod.DeclaringType.Name + "." + operationDescription.Name +
                                                          "' requires the method return type inherit from '" + typeof(Response).FullName);
                  }
              }
          }
      
          static class OperationDescriptionExtensions
          {
              public static Type GetReturnType(this OperationDescription operationDescription)
              {
                  if (operationDescription.SyncMethod == null)
                      throw new InvalidOperationException("These behaviors have only been tested with Sychronous methods.");
      
                  // !! Warning: This does NOT work for Asynch or Task based implementations.
                  System.Reflection.MethodInfo method = operationDescription.SyncMethod ?? operationDescription.EndMethod;
                  return method.ReturnType;
              }
          }
      
          // When not using FaultContracts, return success/fail as a part of all responses via some base class properties.
          [DataContract]
          public class Response
          {
              [DataMember]
              public bool Success { get; set; }
      
              [DataMember]
              public string ErrorMessage { get; set; }
          }
      
          public class ChildResponse : Response
          {
              [DataMember]
              public string Foo { get; set; }
          }
      
          [DataContract]
          public class Request
          {
              [DataMember]
              public string Name { get; set; }
          }
      
          [ServiceContract]
          public interface ISimple
          {
              [OperationContract]
              ChildResponse Work(Request request);
      
              [OperationContract]
              ChildResponse Fail(Request request);
          }
      
          public class SimpleService : ISimple
          {
              public ChildResponse Work(Request request) {
                  return new ChildResponse() { Success = true };
              }
      
              [ResponseExceptionOperationBehavior]
              public ChildResponse Fail(Request request)
              {
                  throw new NotImplementedException("This method isn't done");
              }
          }
      
          class Program
          {
              static void Main(string[] args)
              {
                  ServiceHost simpleHost = new ServiceHost(typeof(SimpleService), new Uri("http://localhost/Simple"));
                  simpleHost.Open();
      
                  ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(simpleHost.Description.Endpoints[0]);
                  ISimple proxy = factory.CreateChannel();
      
                  Logger.Debug("Calling Work...");
                  var response1 = proxy.Work(new Request() { Name = "Foo" });
                  Logger.Debug("Work() returned Success=" + response1.Success + " message='" + response1.ErrorMessage + "'");
      
                  Logger.Debug("Calling Fail...");
                  var response2 = proxy.Fail(new Request() { Name = "FooBar" });
                  Logger.Debug("Fail() returned Success=" + response2.Success + " message='" + response2.ErrorMessage + "'");
      
                  Console.WriteLine("Press ENTER to close the host.");
                  Console.ReadLine();
      
                  ((ICommunicationObject)proxy).Shutdown();
      
                  simpleHost.Shutdown();
              }
          }
      
      
          public static class CommunicationObjectExtensions
          {
              static public void Shutdown(this ICommunicationObject obj)
              {
                  try
                  {
                      obj.Close();
                  }
                  catch (Exception ex)
                  {
                      Console.WriteLine("Shutdown exception: {0}", ex.Message);
                      obj.Abort();
                  }
              }
          }
      
          public static class Logger
          {
              public static void Debug(string message) { Console.WriteLine(message); }
      
              public static void Error(string message) { Console.WriteLine(message); }
          }
      }
      

答案 2 :(得分:0)

首先,我想说使用faultcontracts要好得多。下面我将举例说明服务GetDataUsingDataContract:

[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);

[DataContract]
public class CompositeType 
{
    [DataMember]
    public bool BoolValue { get; set; }

    [DataMember]
    public string StringValue { get; set; }
}

然后,您创建一个等同于正常响应的bodyWriter:

public class MyBodyWriter : BodyWriter
{
    public CompositeType CompositeType { get; private set; }
    public MyBodyWriter(CompositeType composite)
        : base(false)
    {
        CompositeType = composite;
    }

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
       writer.WriteStartElement("GetDataUsingDataContractResponse", "http://tempuri.org/");
       writer.WriteStartElement("GetDataUsingDataContractResult");
       writer.WriteAttributeString("xmlns", "a", null, "http://schemas.datacontract.org/2004/07/WcfService1");
       writer.WriteAttributeString("xmlns", "i", null, "http://www.w3.org/2001/XMLSchema-instance");
       writer.WriteStartElement("a:BoolValue");
       writer.WriteString(CompositeType.BoolValue.ToString().ToLower());
       writer.WriteEndElement();
       writer.WriteStartElement("a:StringValue");
       writer.WriteString(CompositeType.StringValue);
       writer.WriteEndElement();
       writer.WriteEndElement();
    }
}

最后,你在IErrorHandler中使用它:

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        // TODO: parse error and gets response
        var response = new CompositeType {BoolValue = true, StringValue = "a"};
        fault = Message.CreateMessage(version, "http://tempuri.org/", new MyBodyWriter(response));
    }

我做了一个测试,如果服务抛出异常或者服务响应正常,我可以得到正确的答案:

    var response1 = client.GetDataUsingDataContract(null);
    var response2 = client.GetDataUsingDataContract(new CompositeType { StringValue = "a", BoolValue = true });