Дизайн шаблона/С# трюк для повторного бит кода

У меня есть служба 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 в каждую службу.

Есть ли шаблон дизайна/С#, который можно использовать, чтобы сделать его более элегантным?

Ответ 1

Вы говорите об АОП - Aspect Oriented Programming

Вот как я это делаю, передавая "работу" как лямбда:

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( ... );
  }
);

И затем вы можете обернуть все аспекты, которые вы обычно хотите применить, и создать один главный аспект.

Ответ 2

У нас есть аналогичная проблема в одной из наших служб 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 в одном месте.

Ответ 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)
    {
    }
}

Это также позволяет вам перебрасывать все виды исключений и преобразовывать их в исключения Fault, которые впоследствии обрабатываются WCF без изменения вашего бизнес-уровня или применения кода try/catch и преобразования их там.

Ответ 4

Шаблон метода шаблона делает именно это, и вы можете легко реализовать его без наследования с помощью делегатов С# (или lambdas).

Ответ 6

Если вы просто бросаете, поэтому распространяйте исключение поверх вызывающего стека, вы можете избежать его обработки на каждом глубоком уровне, просто поймайте его сверху (возможно) и запишите. Помните, что объект Exception содержит также стек-трассировку.

Если вы, между прочим, нуждаетесь в ловле на каждом уровне, помните, что используйте throw, поэтому стек вызовов не будет затронут, а не так, как вы его используете.

Ответ 7

Вы также можете попробовать подписаться на Application_Error событие в global.asax.

Для настольных приложений есть UnhandledException событие в AppDomain.

Ответ 8

Вообще говоря, вы можете написать некоторые обработчики исключений, которые делают избыточную работу для вас:

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

Ответ 9

Если ваш вопрос касается того, как сделать ваш текущий шаблон быстрее, чтобы вы могли работать с ним, вы можете повторить этот код плиты котла, создав 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>