Выделить повторяющийся блок try-catch в отдельный метод

Делаю серверную часть на NancyFramework. Выделил базовый модуль, от которого наследуются все остальные.

BaseModule. В нём я описал методы логирования и конвертации json в понятные мне классы, т.к. этот код постоянно повторяется:

public abstract class BaseModule : NancyModule
{
    public BaseModule() : base("/") { }

    protected Response CreateExceptionResponse(string json, string typeException, HttpStatusCode statusCode = HttpStatusCode.InternalServerError) =>
        new()
        {
            ContentType = "application/json",
            Contents = stream =>
            {
                var writer = new StreamWriter(stream) { AutoFlush = true };
                writer.Write(json);
            },
            ReasonPhrase = typeException,
            StatusCode = statusCode
        };

    protected Response BadRequest(string exceptionMessage) =>
        new()
        {
            StatusCode = HttpStatusCode.BadRequest,
            Contents = stream =>
            {
                var writer = new StreamWriter(stream) { AutoFlush = true };
                writer.Write(exceptionMessage);
            },
        };

    protected string CreateLogByContext(NancyContext context)
    {
        Dictionary<string, string> dic = new()
        {
            { nameof(Context.Request.Body), context.Request.Body.AsString() }
        };
        return $"The server received a request. Request body:\n" +
            $"{JsonSerializer.Serialize(dic, CreateSerializerOptions()).Replace(@"\", string.Empty)}";
    }

    protected string CreateReturnLog(string json) =>
        $"The server successfully processed the request. Server response to client:\n{json}";

    protected JsonSerializerOptions CreateSerializerOptions()
    {
        var options = new JsonSerializerOptions()
        {
            WriteIndented = true,
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            AllowTrailingCommas = true,
            NumberHandling = JsonNumberHandling.AllowReadingFromString,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            PropertyNameCaseInsensitive = true
        };
        return options;
    }
}

Далее создал OrderModule, который как раз-таки занимается обработкой запросов:

public class OrderModule : BaseModule
{
    private readonly IConfigCache _configCache;
    private readonly OrderController _orderController;

    public OrderModule(IOrderCache orderCache, IConfigCache configCache) : base()
    {
        _configCache = configCache;
        _orderController = new(orderCache);

        Get("/orders/{orderId}", parameters =>
        {
            try
            {
                Log.Information(CreateLogByContext(Context));
                var order = _orderController.GetOrderById(parameters.orderId);

                var returnJson = JsonSerializer.Serialize<OrderDto>(order);
                Log.Information(CreateReturnLog(returnJson));
                return returnJson;
            }
            catch (EntityNotFoundException ex)
            {
                var json = JsonSerializer.Serialize(ex.CreateDictionary(), CreateSerializerOptions());
                Log.Error(ex, json);
                return CreateExceptionResponse(json, nameof(EntityNotFoundException));
            }
            catch (Exception ex)
            {
                Log.Error(ex, ex.Message);
                return BadRequest(ex.Message);
            }
        });

        Get("/orders", parameters =>
        {
            Log.Information(CreateLogByContext(Context));
            var orders = _orderController.GetOrders();

            var returnJson = JsonSerializer.Serialize(orders);
            Log.Information(CreateReturnLog(returnJson));
            return returnJson;
        });

        Get("/order/create/{waiterId}/{tableId}", parameters =>
        {
            try
            {
                Log.Information(CreateLogByContext(Context));
                var waiterId = parameters.waiterId;
                var tableId = parameters.tableId;
                var order = _orderController.CreateOrder(waiterId, tableId);

                var returnJson = JsonSerializer.Serialize<OrderDto>(order);
                Log.Information(CreateReturnLog(returnJson));
                return returnJson;
            }
            catch (Exception ex)
            {
                Log.Error(ex, ex.Message);
                return BadRequest(ex.Message);
            }
        });

        Post("/order/submitChanges", parameters =>
        {
            try
            {
                Log.Information(CreateLogByContext(Context));
                var json = Request.Body.AsString();
                var obj = JsonSerializer.Deserialize<SessionDto>(json);
                var order = _orderController.SubmitChanges(obj);

                var returnJson = JsonSerializer.Serialize(order);
                Log.Information(CreateReturnLog(returnJson));
                return returnJson;
            }
            catch (EntityNotFoundException ex)
            {
                var json = JsonSerializer.Serialize(ex.CreateDictionary(), CreateSerializerOptions());
                Log.Error(ex, json);
                return CreateExceptionResponse(json, nameof(EntityNotFoundException));
            }
            catch (InvalidSessionException ex)
            {
                var json = JsonSerializer.Serialize(ex.CreateDictionary(), CreateSerializerOptions());
                Log.Error(ex, json);
                return CreateExceptionResponse(json, nameof(InvalidSessionException));
            }
            catch (Exception ex)
            {
                Log.Error(ex, ex.Message);
                return BadRequest(ex.Message);
            }
        });
    }
}

Вставил не весь код, т.к. он гораздо длиннее.

Собственно, сама проблема - мне крайне сильно не нравится то, что в начале каждого запроса я пишу код для логирования тела запроса, логирование перед тем, как отправить ответ и try-catch. В OrderModule это ярко видно, что код повторяется постоянно. Можно ли как-нибудь это вынести в отдельный метод, в который вынести всю эту обработку исключений и тд? Чтоб я просто передавал в него нужный метод, который должен отработать и получал ответ. Может как-нибудь реализовать это через дженерики и Func<out T>?

Логирую с помощью Serilog.


Ответы (1 шт):

Автор решения: aepot

Если важен стектрейс, то можно использовать ExceptionDispatchInfo.

try
{
    // ...
}
catch (Exception ex)
{
    var edi = ExceptionDispatchInfo.Capture(ex);
    LogException(edi);
}
private void LogException(ExceptionDispatchInfo edi)
{
    try
    {
        edi.Throw();
    }
    catch (EntityNotFoundException ex)
    {
        // ...
    }
    catch (InvalidSessionException ex)
    {
        // ...
    }
    catch (Exception ex)
    {
        // ...
    }
}

Окей если надо завернуть метод в делегат (хотя вы и так в делегате), то как-то так

private T WithException<T>(Func<T> func)
{
    try
    {
        return func();
    }
    catch (EntityNotFoundException ex)
    {
        // ...
    }
    catch (InvalidSessionException ex)
    {
        // ...
    }
    catch (Exception ex)
    {
        // ...
    }
}
return WithException(() => 42 / 0);

Асинхронно вот так

private async Task<T> WithException<T>(Func<Task<T>> func)
{
    try
    {
        return await func();
    }
    catch (EntityNotFoundException ex)
    {
        // ...
    }
    catch (InvalidSessionException ex)
    {
        // ...
    }
    catch (Exception ex)
    {
        // ...
    }
}
→ Ссылка