Design pattern / C# trick for repeated bit of code

2019-04-05 23:12发布

I have a WCF service which logs any exceptions and then throws them as FaultExceptions.

I am doing a lot of repetition e.g. in each service method.

try { 
   // do some work

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

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

I am looking for a more elegant way to do this as I am cutting and pasting the try/catch throughout each service.

Is there a design pattern /C# trick that could be used to make this more elegant?

9条回答
该账号已被封号
2楼-- · 2019-04-05 23:21

You're talking about AOP - Aspect Oriented Programming

Here's how I do it by passing the "work" as a lambda:

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

Then to use it:

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

There are other ways to achieve the same goal, and even some commercial products, but I find this way to be the most explicit and flexible.

For example, you can write an aspect that creates and disposes the client for you:

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

and so:

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

And then, you can wrap all the aspects you normally want to apply and create one master aspect.

查看更多
仙女界的扛把子
3楼-- · 2019-04-05 23:22

Template method pattern is doing just this and you can easily implement it without inheritence using C#'s delegates (or lambdas).

查看更多
对你真心纯属浪费
4楼-- · 2019-04-05 23:26

You could also try to subscribe to the Application_Error event in your global.asax file.

For desktop applications there is an UnhandledException event in the AppDomain.

查看更多
Rolldiameter
5楼-- · 2019-04-05 23:28

Generally speaking, you could write some exception handlers that does the redundant job for you:

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");
    }
}
查看更多
走好不送
6楼-- · 2019-04-05 23:29

For WCF specific, you might look into adding own ErrorHandler. This allows you to "inject" your own code to be executed each time any method throws an exception.

You can set it up like this:

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)
    {
    }
}

This also allows you to throw all kinds of exceptions and converting them to Fault Exceptions, which are properly handled by WCF later on, without modifying your business layer or applying try/catch code and converting them there.

查看更多
何必那么认真
7楼-- · 2019-04-05 23:30

If you simply throw, so propagate exception on top of the calling stack, you can avoid of handling it on every deep level, just catch it on top most (possible) level and log. Remember that Exception object contains stack-trace too.

If you, by the way, need catching, on every level, remember use throw, so call stack will not be affected, instead in a way you use it, it will.

查看更多
登录 后发表回答