public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { using (CancellationTokenSource cts = new CancellationTokenSource()) { string method = context.HttpContext.Request.Method; if (Guid.TryParse(_correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId)) { (bool requestCreated, ApiRequest request) = await _apiRequestTracker.GetOrCreateRequestAsync(correlationId, method, cts.Token); if (!requestCreated) { ApiProblemDetails apiError = new ApiProblemDetails("https://tools.ietf.org/html/rfc7231#section-6.5.8", StatusCodes.Status409Conflict, context.HttpContext.Request.HttpContext.Request.Path.Value, "Api call is already exist.", null, request.ApiRequestId.ToString(), context.HttpContext.Request.HttpContext.TraceIdentifier); context.Result = new ConflictObjectResult(apiError); return; } } else { context.Result = new BadRequestResult(); return; } } await next.Invoke(); }
public static T ApiProblemDetails <T>( Func <ApiProblemDetails, T> objectResultFactory, HttpStatusCode statusCode, IDescriptiveError?error = null, ModelStateDictionary?modelState = null, IReadOnlyDictionary <string, string[]>?additionalInfo = null) where T : ObjectResult { if (modelState == null && error == null) { throw new ArgumentException( $"At least one of '{nameof(modelState)}' and " + $"{nameof(error)} parameters must be non-null"); } modelState ??= new ModelStateDictionary(); if (error != null) { modelState.AddModelError(error); } ApiProblemDetails problemDetails = new ApiProblemDetails( modelState, statusCode: statusCode, additionalInfo: additionalInfo ); T result = objectResultFactory(problemDetails); result.ContentTypes.Clear(); result.ContentTypes.Add("application/problem+json"); return(result); }
private void HandleApiException(ExceptionContext context) { var exception = context.Exception as ApiException; var details = new ApiProblemDetails() { Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3", Title = exception.Message, Detail = exception.Message, Errors = exception.Errors }; context.Result = new BadRequestObjectResult(details); context.ExceptionHandled = true; }
public string Serialize(IEnumerable <ValidationFailure> Errors) { var problemDetails = new ApiProblemDetails(); foreach (var failure in Errors) { var propName = failure.PropertyName; if (!problemDetails.Errors.ContainsKey(propName)) { problemDetails.Errors.Add(propName, new List <Error>()); } problemDetails.Errors[propName].Add(new Error(failure.ErrorCode, failure.ErrorMessage)); } problemDetails.Title = "One or more validation errors occurred."; problemDetails.Status = (int)System.Net.HttpStatusCode.BadRequest; problemDetails.Extensions.Add("traceId", httpContextAccessor.HttpContext.TraceIdentifier); var errors = JsonConvert.SerializeObject(problemDetails); return(errors); }
/// <summary> /// Handles the exception asynchronously. /// </summary> /// <param name="context">The exception handler context</param> /// <param name="cancellationToken">The token to monitor for cancellation requests</param> /// <returns>A Task representing the asynchronous exception handling operation</returns> public override Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken) { // First of all, check if the exception should be handled. if (!ShouldHandle(context)) { return(Task.FromResult(0)); } // Create the API Problem Details model. var problemDetails = new ApiProblemDetails(); if (context.Exception is UnauthorizedAccessException) { // Unauthorised. problemDetails = ApiErrorFactory.Create(new ApiException(HttpStatusCode.Unauthorized)); } else if (context.Exception is ApiException) { // Custom API error. problemDetails = ApiErrorFactory.Create(context.Exception as ApiException); } else { // An unhandled exception occurred. problemDetails = ApiErrorFactory.Create(new ApiException(HttpStatusCode.InternalServerError)); } // Log the error. Trace.TraceError(problemDetails.Instance); // Create and return the error response. var response = context.Request.CreateResponse((HttpStatusCode)problemDetails.Status, problemDetails); context.Result = new ResponseMessageResult(response); return(Task.FromResult(0)); }
public async Task InvokeAsync(HttpContext httpContext, IHostEnvironment env, ICorrelationContextAccessor correlationContextAccessor) { HttpContext context = httpContext ?? throw new ArgumentNullException(nameof(httpContext)); LogContext.PushProperty(Logs.LogType, LogTypes.BackendApiRequest.ToString()); httpContext.Request.EnableBuffering(); Stream body = httpContext.Request.Body; byte[] buffer = new byte[Convert.ToInt32(httpContext.Request.ContentLength)]; await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length); string initialRequestBody = Encoding.UTF8.GetString(buffer); body.Seek(0, SeekOrigin.Begin); httpContext.Request.Body = body; //logz.io/logstash fields can accept only 32k strings so request/response bodies are cut if (initialRequestBody.Length > General.MaxLogFieldLength) { initialRequestBody = initialRequestBody.Substring(0, General.MaxLogFieldLength); } Log.ForContext(Logs.RequestHeaders, context.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), true) .ForContext(Logs.RequestBody, initialRequestBody) .Information("Request information {RequestMethod} {RequestPath} information", context.Request.Method, context.Request.Path); using (MemoryStream responseBodyMemoryStream = new MemoryStream()) { Stream originalResponseBodyReference = context.Response.Body; context.Response.Body = responseBodyMemoryStream; try { await _next(context); } catch (Exception ex) { string errorMessage = ex.Message; Log.Error(ex.Demystify(), "{ErrorMessage}", errorMessage); ApiProblemDetails unknownError; if (env.IsDevelopment()) { unknownError = new ApiProblemDetails("https://tools.ietf.org/html/rfc7231#section-6.6.1", StatusCodes.Status500InternalServerError, context.Request.HttpContext.Request.Path, errorMessage, ex.Demystify().StackTrace, correlationContextAccessor.CorrelationContext.CorrelationId, context.Request.HttpContext.TraceIdentifier); } else { unknownError = new ApiProblemDetails("https://tools.ietf.org/html/rfc7231#section-6.6.1", StatusCodes.Status500InternalServerError, context.Request.HttpContext.Request.Path, errorMessage, null, correlationContextAccessor.CorrelationContext.CorrelationId, context.Request.HttpContext.TraceIdentifier); } string errorResponseBody = JsonConvert.SerializeObject(unknownError); context.Response.ContentType = "application/problem+json"; context.Response.StatusCode = StatusCodes.Status500InternalServerError; await context.Response.WriteAsync(errorResponseBody); } context.Response.Body.Seek(0, SeekOrigin.Begin); string responseBody = await new StreamReader(context.Response.Body).ReadToEndAsync(); context.Response.Body.Seek(0, SeekOrigin.Begin); if (Enumerable.Range(400, 599).Contains(context.Response.StatusCode) && responseBody.Contains("traceId", StringComparison.InvariantCultureIgnoreCase)) { ApiProblemDetails problem = JsonConvert.DeserializeObject <ApiProblemDetails>(responseBody); LogContext.PushProperty(Logs.TraceId, problem.TraceId); } string endResponseBody = (responseBody.Length > General.MaxLogFieldLength) ? responseBody.Substring(0, General.MaxLogFieldLength) : responseBody; Log.ForContext(Logs.ResponseHeaders, context.Response.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), true) .ForContext(Logs.ResponseBody, endResponseBody) .Information("Response information {RequestMethod} {RequestPath} {StatusCode}", context.Request.Method, context.Request.Path, context.Response.StatusCode); await responseBodyMemoryStream.CopyToAsync(originalResponseBodyReference); } }