10

我有一个 WCF 服务,它记录任何异常,然后将它们作为 FaultExceptions 抛出。

我做了很多重复,例如在每种服务方法中。

try { 
   // do some work

}
catch(Exception ex)
{
  Logger.log(ex);

  // actually will be Fault Exception but you get the idea.
  throw ex;
}

我正在寻找一种更优雅的方式来执行此操作,因为我在每个服务中剪切和粘贴 try/catch。

是否有设计模式/C# 技巧可用于使其更优雅?

4

9 回答 9

9

你在谈论AOP -Aspect Oriented Programming

这是我通过将“工作”作为 lambda 传递的方法:

public partial static class Aspect
{
  public static T HandleFaultException<T>( Func<T> fn )
  {
    try
    { 
      return fn();
    }
    catch( FaultException ex )
    {
      Logger.log(ex);
      throw;
    }
  }
}

然后使用它:

return Aspect.HandleFaultException( () =>
  {
    // call WCF
  }
);

还有其他方法可以实现相同的目标,甚至还有一些商业产品,但我发现这种方法是最明确和最灵活的。

例如,您可以编写一个方面来为您创建和处置客户端:

public partial static class Aspect
{
  public static T CallClient<T>( Func<Client, T> fn )
  {
    using ( var client = ... create client ... )
    {
      return fn( client );
    }
  }
}

所以:

return Aspect.CallClient( client =>
  {
    return client.Method( ... );
  }
);

然后,您可以包装您通常想要应用的所有方面并创建一个主方面。

于 2013-03-04T14:15:32.280 回答
5

我们的一个 WCF 服务也有类似的问题,我已经通过使用帮助委托解决了这个问题:

public static void ErrorHandlingWrapper(Action DoWork)
{
    try { 
        DoWork();
    }
    catch(Exception ex)
    {
        Logger.log(ex);

        // actually will be Fault Exception but you get the idea.
        throw;
    }
}

用法:

public void MyMethod1()
{
    ErrorHandlingWrapper(() => {
        // do work
    });
}

public void MyMethod2()
{
    ErrorHandlingWrapper(() => {
        // do work
    });
}

您仍然需要重复包装器,但代码要少得多,您可以try..catch在一个地方修改逻辑。

于 2013-03-04T14:09:37.203 回答
3

对于 WCF 特定的,您可能会考虑添加自己的 ErrorHandler。这允许您“注入”您自己的代码,以便在每次任何方法抛出异常时执行。

你可以这样设置:

serviceHost.Description.Behaviors.Add(new ErrorHandlerBehavior()); //Add your own ErrorHandlerBehaviour

public class ErrorHandlerBehavior : IErrorHandler, IServiceBehavior
{
    private static readonly Logger log = LogManager.GetCurrentClassLogger();

    public bool HandleError(Exception error)
    {
        if (error is CommunicationException)
        {
            log.Info("Wcf has encountered communication exception.");
        }
        else
        {
            // Log
        }

        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
        //Here you can convert any exception to FaultException like this:
        if (error is FaultException)
            return;

        var faultExc = new FaultException(error.Message);
        var faultMessage = faultExc.CreateMessageFault();

        fault = Message.CreateMessage(version, faultMessage, faultExc.Action);
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, 
        BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ChannelDispatcherBase channelDispatcher in serviceHostBase.ChannelDispatchers)
        {
            var channelDisp = channelDispatcher as ChannelDispatcher;

            if (channelDisp != null)
                channelDisp.ErrorHandlers.Add(this);
        }
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }
}

这也允许您抛出各种异常并将它们转换为错误异常,稍后由 WCF 正确处理,而无需修改您的业务层或应用 try/catch 代码并在那里转换它们。

于 2013-03-04T14:08:02.243 回答
1

模板方法模式就是这样做的,您可以使用 C# 的委托(或 lambdas)轻松实现它而无需继承。

于 2013-03-04T14:06:37.240 回答
1

您可能想尝试 AspectF(不是我的): http: //www.codeproject.com/Articles/42474/AspectF-Fluent-Way-to-Add-Aspects-for-Cleaner-Main。它是由 Omar Al-Zabir... Dropthings 框架的创建者,除此之外。希望这对您有所帮助。

于 2013-03-04T14:12:16.493 回答
0

一般来说,您可以编写一些异常处理程序来为您完成冗余工作:

public abstract class ExceptionHandler
{
    /// Returns true if the exception is handled; otherwise returns false.
    public abstract bool Handle(Exception ex);

    protected void Log(Exception ex)
    {
        // Log exception here
    }
}

public class FileExceptionHandler : ExceptionHandler
{
    public override bool Handle(Exception ex)
    {
        this.Log(ex);

        // Tries to handle exceptions gracefully
        if (ex is UnauthorizedAccessException)
        {
            // Add some logic here (for example encapsulate the exception)
            // ...
            return true;
        }
        else if (ex is IOException)
        {
            // Another logic here
            // ...
            return true;
        }

        // Did not handled the exception...
        return false;
    }
}

public class Program
{
    private static void Main(string[] args)
    {
        try
        {
            // File manipulation
            throw new IOException();
        }
        catch (Exception ex)
        {
            if (!new FileExceptionHandler().Handle(ex))
            {
                // Exception not handled, so throw exception
                throw;
            }
        }

        Console.WriteLine("end");
    }
}
于 2013-03-04T14:17:36.130 回答
0

如果您只是抛出异常,因此在调用堆栈的顶部传播异常,您可以避免在每个深层处理它,只需在最顶层(可能的)级别捕获它并记录。请记住,Exception对象也包含堆栈跟踪。

顺便说一句,如果您需要在每个级别上捕获,请记住 use throw,因此调用堆栈不会受到影响,而是以您使用它的方式,它会受到影响。

于 2013-03-04T14:07:19.263 回答
0

您也可以尝试订阅文件中的Application_Error事件global.asax

对于桌面应用程序UnhandledException,在AppDomain.

于 2013-03-04T14:07:41.527 回答
0

如果您的问题是关于如何让您的当前模式更快地使用,您可以通过创建一个重复该样板代码Snippet

<CodeSnippets
    xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>
                trylog
            </Title>
            <Shortcut>
                trylog
            </Shortcut>
        </Header>
        <Snippet>
            <Code Language="CSharp">
                <![CDATA[try { 
   // do some work

}
catch(Exception ex)
{
  Logger.log(ex);

  // actually will be Fault Exception but you get the idea.
  throw ex;
}]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>
于 2013-03-04T14:32:36.893 回答