Skip to content

Commit 4a6e5cb

Browse files
committed
Refactor API response filter and helper.
1 parent dd5207d commit 4a6e5cb

File tree

9 files changed

+66
-53
lines changed

9 files changed

+66
-53
lines changed

src/Initium/Attributes/ApiResponseAttribute.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,12 @@ namespace Initium.Attributes;
55

66
[SuppressMessage("ReSharper", "UnusedMember.Global")]
77
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
8-
public class ApiResponseAttribute : Attribute
8+
public class ApiResponseAttribute(HttpStatusCode statusCode, string message) : Attribute
99
{
10-
public int StatusCode { get; }
11-
public string Message { get; }
10+
public HttpStatusCode StatusCode { get; } = statusCode;
11+
public string Message { get; } = message;
1212

13-
public ApiResponseAttribute(HttpStatusCode statusCode, string message)
13+
public ApiResponseAttribute(int statusCode, string message) : this((HttpStatusCode)statusCode, message)
1414
{
15-
StatusCode = (int)statusCode;
16-
Message = message;
17-
}
18-
19-
public ApiResponseAttribute(int statusCode, string message)
20-
{
21-
StatusCode = statusCode;
22-
Message = message;
2315
}
2416
}

src/Initium/Attributes/ValidateModelAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using System.Net;
23
using FluentValidation;
34
using Initium.Response;
4-
using Microsoft.AspNetCore.Http;
55
using Microsoft.AspNetCore.Mvc.Filters;
66

77
namespace Initium.Attributes;
@@ -32,7 +32,7 @@ public void OnActionExecuting(ActionExecutingContext context)
3232
context.Result = ApiResponseBuilder
3333
.CreateFromContext(context.HttpContext)
3434
.WithMessage("One or more validation errors occurred.")
35-
.WithStatusCode(StatusCodes.Status400BadRequest)
35+
.WithStatusCode(HttpStatusCode.BadRequest)
3636
.WithErrors(validationResult.Errors)
3737
.BuildAsJsonResult();
3838
}

src/Initium/Filters/ApiExceptionFilter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ public void OnException(ExceptionContext context)
2626
{
2727
ApiException ex => apiResponseBuilder
2828
.WithMessage(ex.Message)
29-
.WithStatusCode((int)ex.StatusCode)
29+
.WithStatusCode(ex.StatusCode)
3030
.BuildAsJsonResult(),
3131

3232
NotImplementedException ex => apiResponseBuilder
3333
.WithMessage(ex.Message)
34-
.WithStatusCode((int)HttpStatusCode.NotImplemented)
34+
.WithStatusCode(HttpStatusCode.NotImplemented)
3535
.BuildAsJsonResult(),
3636

3737
_ => apiResponseBuilder
3838
.WithMessage("An unexpected error occurred.")
39-
.WithStatusCode((int)HttpStatusCode.InternalServerError)
39+
.WithStatusCode(HttpStatusCode.InternalServerError)
4040
.BuildAsJsonResult()
4141
};
4242

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
using System.Net;
12
using Microsoft.AspNetCore.Mvc;
23
using Microsoft.AspNetCore.Mvc.Filters;
34
using Initium.Helpers;
45
using Initium.Response;
56
using Initium.Results;
7+
using Microsoft.Extensions.Logging;
68

79
namespace Initium.Filters;
810

911
/// <summary>
1012
/// Filter responsible for transforming <see cref="ServiceResult"/> objects into a standardized <see cref="ApiResponse"/>.
1113
/// </summary>
12-
internal class ApiResponseFilter : ActionFilterAttribute
14+
internal class ApiResponseFilter(ILogger<ApiResponseFilter> logger) : ActionFilterAttribute
1315
{
1416
/// <summary>
1517
/// Called after the action executed, and transforms <see cref="ServiceResult"/> into a standardized <see cref="ApiResponse"/>.
@@ -18,26 +20,34 @@ internal class ApiResponseFilter : ActionFilterAttribute
1820
public override void OnActionExecuted(ActionExecutedContext context)
1921
{
2022
// Check if the result of the action is an ObjectResult containing a ServiceResult.
21-
if (context.Result is not ObjectResult { Value: ServiceResult serviceResult })
22-
return;
23+
if (context.Result is not ObjectResult { Value: ServiceResult serviceResult }) return;
24+
25+
// Determine the HTTP status code:
26+
// 1. Use ServiceResult status code if set.
27+
// 2. Otherwise, use the default based on the HTTP method.
28+
var statusCode =
29+
serviceResult.StatusCode
30+
?? ApiResponseHelper.GetDefaultStatusCode(context.HttpContext);
2331

24-
// Extract API response attributes from the current action descriptor (method or controller level).
25-
var actionDescriptor = context.ActionDescriptor;
26-
var apiResponseAttributes = ApiResponseHelper.GetApiResponseAttributes(actionDescriptor);
27-
28-
var statusCode = (int?)serviceResult.StatusCode ?? context.HttpContext.Response.StatusCode;
32+
// Determine the response message:
33+
// 1. Use ServiceResult message if set.
34+
// 2. Otherwise, get the message from [ApiResponse] attributes.
35+
// 3. If none found, use the default message for the StatusCode.
36+
var message =
37+
serviceResult.Message
38+
?? GetApiResponseMessage(context, statusCode)
39+
?? ApiResponseHelper.GetDefaultMessageForStatusCode(statusCode);
2940

30-
// Find the matching ApiResponseAttribute based on the HTTP status code.
31-
var matchingAttribute = apiResponseAttributes.FirstOrDefault(attribute =>
32-
attribute.StatusCode == statusCode);
33-
3441
// Create an ApiResponse object, including HTTP context details.
3542
context.Result = ApiResponseBuilder
3643
.CreateFromContext(context.HttpContext)
3744
.WithStatusCode(statusCode)
38-
.WithMessage(serviceResult.Message
39-
?? matchingAttribute?.Message
40-
?? ApiResponseHelper.GetDefaultMessageForStatusCode(statusCode))
45+
.WithMessage(message)
46+
// .WithData(serviceResult.Data)
47+
.WithCustomHeaders(serviceResult.Metadata)
4148
.BuildAsJsonResult();
4249
}
50+
51+
private static string? GetApiResponseMessage(ActionExecutedContext context, HttpStatusCode statusCode) =>
52+
ApiResponseHelper.GetApiResponseAttributes(context.ActionDescriptor).FirstOrDefault(attribute => attribute.StatusCode == statusCode)?.Message;
4353
}

src/Initium/Filters/ImplicitValidationFilter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
using System.Net;
12
using FluentValidation;
23
using Initium.Infrastructure;
34
using Initium.Response;
4-
using Microsoft.AspNetCore.Http;
55
using Microsoft.AspNetCore.Mvc.Filters;
66

77
namespace Initium.Filters;
@@ -36,7 +36,7 @@ public void OnActionExecuting(ActionExecutingContext context)
3636
context.Result = ApiResponseBuilder
3737
.CreateFromContext(context.HttpContext)
3838
.WithMessage("One or more validation errors occurred.")
39-
.WithStatusCode(StatusCodes.Status400BadRequest)
39+
.WithStatusCode(HttpStatusCode.BadRequest)
4040
.WithErrors(validationResult.Errors)
4141
.BuildAsJsonResult();
4242
}

src/Initium/Helpers/ApiResponseHelper.cs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using System.Net;
12
using Microsoft.AspNetCore.Mvc.Abstractions;
23
using Microsoft.AspNetCore.Mvc.Controllers;
34
using Initium.Attributes;
5+
using Microsoft.AspNetCore.Http;
46

57
namespace Initium.Helpers;
68

@@ -41,17 +43,24 @@ public static IEnumerable<ApiResponseAttribute> GetApiResponseAttributes(ActionD
4143
/// A string containing the default message associated with the status code.
4244
/// If the status code is unrecognized, a generic internal server error message is returned.
4345
/// </returns>
44-
public static string GetDefaultMessageForStatusCode(int statusCode) => statusCode switch
46+
public static string GetDefaultMessageForStatusCode(HttpStatusCode statusCode) => statusCode switch
4547
{
46-
200 => "The operation completed successfully.",
47-
201 => "The resource was created successfully.",
48-
204 => "The request was successful, but there is no content to return.",
49-
400 => "One or more validation errors occurred.",
50-
401 => "Access is denied due to invalid credentials",
51-
403 => "Access is forbidden, you do not have the necessary permissions to perform this operation.",
52-
404 => "The requested resource could not be found.",
53-
409 => "A conflict occurred, the request could not be completed due to conflicting changes.",
54-
503 => "The service is currently unavailable.",
48+
HttpStatusCode.OK => "The operation completed successfully.",
49+
HttpStatusCode.Created => "The resource was created successfully.",
50+
HttpStatusCode.NoContent => "The request was successful, but there is no content to return.",
51+
HttpStatusCode.BadRequest => "One or more validation errors occurred.",
52+
HttpStatusCode.Unauthorized => "Access is denied due to invalid credentials.",
53+
HttpStatusCode.Forbidden => "Access is forbidden, you do not have the necessary permissions to perform this operation.",
54+
HttpStatusCode.NotFound => "The requested resource could not be found.",
55+
HttpStatusCode.Conflict => "A conflict occurred, the request could not be completed due to conflicting changes.",
56+
HttpStatusCode.ServiceUnavailable => "The service is currently unavailable.",
5557
_ => "An internal server error occurred."
5658
};
59+
60+
public static HttpStatusCode GetDefaultStatusCode(HttpContext context) => context.Request.Method.ToUpper() switch
61+
{
62+
"POST" => HttpStatusCode.Created,
63+
"DELETE" => HttpStatusCode.NoContent,
64+
_ => (HttpStatusCode)context.Response.StatusCode
65+
};
5766
}

src/Initium/Response/ApiResponseBuilder.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using System.Net;
23
using FluentValidation.Results;
34
using Microsoft.AspNetCore.Http;
45
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.AspNetCore.Mvc.ModelBinding;
57

68
namespace Initium.Response;
79

@@ -59,9 +61,9 @@ public ApiResponseBuilder WithMessage(string message)
5961
/// </summary>
6062
/// <param name="statusCode">The HTTP status code to include in the response.</param>
6163
/// <returns>The current instance of the <see cref="ApiResponseBuilder"/>.</returns>
62-
public ApiResponseBuilder WithStatusCode(int statusCode)
64+
public ApiResponseBuilder WithStatusCode(HttpStatusCode statusCode)
6365
{
64-
_apiResponse.StatusCode = statusCode;
66+
_apiResponse.StatusCode = (int)statusCode;
6567
return this;
6668
}
6769

src/Initium/Results/InvalidModelStateResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public InvalidModelStateResult(ActionContext actionContext) : base(actionContext
1919
StatusCode = StatusCodes.Status400BadRequest;
2020
Value = ApiResponseBuilder.CreateFromContext(actionContext.HttpContext)
2121
.WithMessage("One or more validation errors occurred.")
22-
.WithStatusCode(StatusCodes.Status400BadRequest)
22+
.WithStatusCode(HttpStatusCode.BadRequest)
2323
.Build();
2424
}
2525
}

src/Initium/Results/ServiceResult.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class ServiceResult : BaseResult
2525
/// <param name="message">An optional message describing the result.</param>
2626
/// <param name="statusCode">The HTTP status code associated with the result. Defaults to <see cref="HttpStatusCode.OK"/>.</param>
2727
/// <returns>A successful <see cref="ServiceResult"/>.</returns>
28-
public static ServiceResult Ok(string? message = null, HttpStatusCode statusCode = HttpStatusCode.OK) => new()
28+
public static ServiceResult Ok(string? message = null, HttpStatusCode? statusCode = null) => new()
2929
{
3030
Success = true,
3131
Message = message,
@@ -49,7 +49,7 @@ public class ServiceResult : BaseResult
4949
/// <param name="message">An optional message describing the error.</param>
5050
/// <param name="statusCode">The HTTP status code associated with the error. Defaults to <see cref="HttpStatusCode.BadRequest"/>.</param>
5151
/// <returns>A failed <see cref="ServiceResult"/>.</returns>
52-
public static ServiceResult Error(string? message = null, HttpStatusCode statusCode = HttpStatusCode.BadRequest) => new()
52+
public static ServiceResult Error(string? message = null, HttpStatusCode? statusCode = null) => new()
5353
{
5454
Success = false,
5555
Message = message,
@@ -101,7 +101,7 @@ public ServiceResult WithStatusCode(HttpStatusCode statusCode)
101101

102102
public ServiceResult WithMetadata(string key, string value)
103103
{
104-
Metadata ??= new Dictionary<string, string>();
104+
// Metadata ??= new Dictionary<string, string>();
105105
Metadata[key] = value;
106106
return this;
107107
}
@@ -149,7 +149,7 @@ public class ServiceResult<TData> : ServiceResult
149149
/// <param name="message">An optional message describing the result.</param>
150150
/// <param name="statusCode">The HTTP status code associated with the result. Defaults to <see cref="HttpStatusCode.OK"/>.</param>
151151
/// <returns>A successful <see cref="ServiceResult{TData}"/>.</returns>
152-
public static ServiceResult<TData> Ok(TData data, string? message = null, HttpStatusCode statusCode = HttpStatusCode.OK) => new()
152+
public static ServiceResult<TData> Ok(TData data, string? message = null, HttpStatusCode? statusCode = null) => new()
153153
{
154154
Success = true,
155155
Data = data,
@@ -163,7 +163,7 @@ public class ServiceResult<TData> : ServiceResult
163163
/// <param name="data">The data returned by the operation.</param>
164164
/// <param name="statusCode">The HTTP status code associated with the result.</param>
165165
/// <returns>A successful <see cref="ServiceResult{TData}"/>.</returns>
166-
public static ServiceResult<TData> Ok(TData data, HttpStatusCode statusCode) => new()
166+
public static ServiceResult<TData> Ok(TData data, HttpStatusCode? statusCode = null) => new()
167167
{
168168
Success = true,
169169
Data = data,
@@ -176,7 +176,7 @@ public class ServiceResult<TData> : ServiceResult
176176
/// <param name="message">An optional message describing the error.</param>
177177
/// <param name="statusCode">The HTTP status code associated with the error. Defaults to <see cref="HttpStatusCode.BadRequest"/>.</param>
178178
/// <returns>A failed <see cref="ServiceResult{TData}"/>.</returns>
179-
public new static ServiceResult<TData> Error(string? message = null, HttpStatusCode statusCode = HttpStatusCode.BadRequest) => new()
179+
public new static ServiceResult<TData> Error(string? message = null, HttpStatusCode? statusCode = null) => new()
180180
{
181181
Success = false,
182182
Message = message,

0 commit comments

Comments
 (0)