Skip to content

Commit

Permalink
調整 api response wrapping filter 的內容以配合 blog 說明
Browse files Browse the repository at this point in the history
  • Loading branch information
YuChia-Wei committed Oct 11, 2024
1 parent 7488633 commit a184382
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;

namespace dotnetLab.WebApi.Controllers.Requests;

public class DataAnnotationsValidateRequest
{
[MaxLength(10)]
[MinLength(1)]
public string SerialId { get; set; }
}
32 changes: 32 additions & 0 deletions src/dotnetLab.WebApi/Controllers/SampleController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public SampleController(IMediator mediator)
/// <param name="request"></param>
/// <returns></returns>
[HttpGet]
[HttpGet("use-fluent-validation")]
[ParameterValidator<SimpleDocQueryValidator>]
// [ApiPermissionAuthorize(Policy = "SamplePolicy",
// Roles = "Admin",
Expand All @@ -58,6 +59,27 @@ public async Task<IActionResult> Get([FromQuery] SimpleDocQueryRequest request)
return this.Ok(simpleDocumentViewModel);
}

/// <summary>
/// test api response warpper
/// </summary>
/// <returns></returns>
[HttpGet("use-data-annotations-validation")]
[ProducesResponseType<ApiResponse<SimpleDocumentViewModel>>(200)]
public async Task<IActionResult> Get([FromQuery] DataAnnotationsValidateRequest request)
{
return this.Ok("");
}

/// <summary>
/// test api response warpper
/// </summary>
/// <returns></returns>
[HttpGet("action-result")]
public async Task<IActionResult> GetActionResult()
{
return this.Ok("");
}

/// <summary>
/// get sample exception response
/// </summary>
Expand All @@ -69,6 +91,16 @@ public async Task<IActionResult> GetExceptionResponse()
throw new AggregateException();
}

/// <summary>
/// test api response warpper
/// </summary>
/// <returns></returns>
[HttpGet("object")]
public async Task<object> GetObject()
{
return "Hello World";
}

/// <summary>
/// create simple doc
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
namespace dotnetLab.WebApi.Infrastructure.ResponseWrapper;
using System.Text.Json.Serialization;

namespace dotnetLab.WebApi.Infrastructure.ResponseWrapper;

/// <summary>
/// api error information
/// </summary>
public class ApiErrorInformation
{
/// <summary>
/// 錯誤代號
/// </summary>
public string ErrorCode { get; set; }

/// <summary>
/// 錯誤訊息
/// </summary>
public string Message { get; set; }

/// <summary>
/// 錯誤詳細說明
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string Description { get; set; }
}
14 changes: 0 additions & 14 deletions src/dotnetLab.WebApi/Infrastructure/ResponseWrapper/ApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ namespace dotnetLab.WebApi.Infrastructure.ResponseWrapper;
/// <typeparam name="T"></typeparam>
public class ApiResponse<T>
{
public ApiResponse() { }

public ApiResponse(T data, string responseCode = "")
{
this.ResponseCode = responseCode;
this.Data = data;
}

/// <summary>
/// api 的追蹤編號
/// </summary>
Expand All @@ -30,12 +22,6 @@ public ApiResponse(T data, string responseCode = "")
/// </summary>
public string RequestPath { get; set; }

/// <summary>
/// 執行編號,用以回應執行狀態
/// </summary>
/// <example>SUCCESS</example>
public string ResponseCode { get; set; }

/// <summary>
/// 回應資料
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
using System.Diagnostics;
using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace dotnetLab.WebApi.Infrastructure.ResponseWrapper;

/// <summary>
/// 成功執行的 api 回應包裝器
/// </summary>
public class ApiResponseWrappingFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// 確保只在成功的情況下進行包裝
if (context.Result is ObjectResult { StatusCode: >= 200 and < 300 } objectResult)
if (IsBinaryResult(context))
{
return;
}

if (TryGetSuccessResultData(context, out var successObjectResult))
{
// var traceId = context.HttpContext.Request.Headers["X-Trace-Id"].ToString() ?? "no-trace-id";

Expand All @@ -20,16 +28,98 @@ public void OnResultExecuting(ResultExecutingContext context)
Id = traceId,
ApiVersion = context.HttpContext.ApiVersioningFeature().RawRequestedApiVersion,
RequestPath = $"{context.HttpContext.Request.Path}.{context.HttpContext.Request.Method}",
ResponseCode = "SUCCESS",
Data = objectResult.Value
Data = successObjectResult?.Value
};

context.Result = new ObjectResult(wrappedResponse)
{
Formatters = successObjectResult?.Formatters,
ContentTypes = successObjectResult?.ContentTypes,
StatusCode = successObjectResult?.StatusCode,
DeclaredType = successObjectResult?.DeclaredType
};
}

if (TryGetBadRequestObjectResultData(context, out var badRequestObjectResult))
{
var apiErrorInformation = new ApiErrorInformation();
switch (badRequestObjectResult.Value)
{
// FluentValidation 的錯誤物件
case List<ValidationFailure> validationFailures:
apiErrorInformation.ErrorCode = validationFailures.First().ErrorCode;
apiErrorInformation.Message = validationFailures.First().ErrorMessage;
break;

// DataAnnotations 的錯誤物件
case ValidationProblemDetails validationProblemDetails:
apiErrorInformation.ErrorCode = validationProblemDetails.Title;
// 這邊需要額外進入 values 裡面才能取得真的要的錯誤訊息
apiErrorInformation.Message = validationProblemDetails.Errors.Values.FirstOrDefault().FirstOrDefault();
break;

case ApiErrorInformation apiError:
apiErrorInformation = apiError;
break;
}

var traceId = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString();

var wrappedResponse = new ApiResponse<ApiErrorInformation>
{
Id = traceId,
ApiVersion = context.HttpContext.ApiVersioningFeature().RawRequestedApiVersion,
RequestPath = $"{context.HttpContext.Request.Path}.{context.HttpContext.Request.Method}",
Data = apiErrorInformation
};

context.Result = new ObjectResult(wrappedResponse) { StatusCode = objectResult.StatusCode, DeclaredType = objectResult.DeclaredType };
context.Result = new BadRequestObjectResult(wrappedResponse)
{
Formatters = badRequestObjectResult.Formatters,
ContentTypes = badRequestObjectResult.ContentTypes,
StatusCode = badRequestObjectResult.StatusCode,
DeclaredType = badRequestObjectResult.DeclaredType
};
}
}

public void OnResultExecuted(ResultExecutedContext context)
{
// 這裡可以加入在結果執行後的處理邏輯
}

private static bool IsBinaryResult(ResultExecutingContext context)
{
return context.Result is FileResult ||
context.HttpContext.Response.ContentType?.StartsWith("image/") == true ||
context.HttpContext.Response.ContentType == "application/octet-stream";
}

private static bool TryGetBadRequestObjectResultData(ResultExecutingContext context, out BadRequestObjectResult? badRequestResult)
{
if (context.Result is BadRequestObjectResult result)
{
badRequestResult = result;
return true;
}

badRequestResult = null;
return false;
}

private static bool TryGetSuccessResultData(ResultExecutingContext context, out ObjectResult? successObjectResult)
{
switch (context.Result)
{
case OkObjectResult okResult:
successObjectResult = okResult;
return true;
case ObjectResult { StatusCode: >= 200 and < 300 } objectResult:
successObjectResult = objectResult;
return true;
default:
successObjectResult = null;
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public static void UseExceptionWrapper(this WebApplication webApplication)
Id = traceId,
ApiVersion = context.ApiVersioningFeature().RawRequestedApiVersion,
RequestPath = $"{context.Request.Path}.{context.Request.Method}",
ResponseCode = "Error",
Data = new ApiErrorInformation
{
Message = exception?.Message ?? "unknown error",
Expand Down

0 comments on commit a184382

Please sign in to comment.