设计模式/ c#重复代码的技巧

本文关键字:代码 设计模式 | 更新日期: 2023-09-27 18:02:01

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

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

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#技巧可以让它更优雅?

设计模式/ c#重复代码的技巧

你说的是AOP - Aspect Oriented Programming

我是这样做的,通过将"work"作为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( ... );
  }
);

然后,你可以把你通常想要应用的所有方面包装起来,创建一个主方面

在我们的一个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中的逻辑。

对于特定于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代码并在那里转换它们。

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

您可能想尝试AspectF(不是我的):http://www.codeproject.com/Articles/42474/AspectF-Fluent-Way-to-Add-Aspects-for-Cleaner-Main。作者是Omar Al-Zabir…Dropthings框架的创建者,以及其他东西。

如果你只是抛出,所以传播异常在调用堆栈的顶部,你可以避免在每个深层处理它,只捕获它在最顶层(可能的)和日志。记住,Exception对象也包含堆栈跟踪。

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

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

对于桌面应用程序,在AppDomain中有一个UnhandledException事件

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

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");
    }
}

如果您的问题是关于如何使您当前的模式更快地为您工作,您可以通过创建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>