Выделить повторяющийся блок 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 шт):
Если важен стектрейс, то можно использовать 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)
{
// ...
}
}