-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8fbd108
commit bf55b75
Showing
19 changed files
with
1,234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Routing; | ||
using Paralax.WebApi.Utils; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace Paralax.WebApi | ||
{ | ||
public class EndpointsBuilder : IEndpointsBuilder | ||
{ | ||
private readonly WebApiEndpointDefinitions _definitions; | ||
private readonly IEndpointRouteBuilder _routeBuilder; | ||
|
||
public EndpointsBuilder(IEndpointRouteBuilder routeBuilder, WebApiEndpointDefinitions definitions) | ||
{ | ||
_routeBuilder = routeBuilder; | ||
_definitions = definitions; | ||
} | ||
|
||
public IEndpointsBuilder Get(string path, Func<HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) | ||
{ | ||
var builder = _routeBuilder.MapGet(path, ctx => context?.Invoke(ctx)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition(HttpMethods.Get, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Get<T>(string path, Func<T, HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) where T : class | ||
{ | ||
var builder = _routeBuilder.MapGet(path, ctx => BuildQueryContext(ctx, context)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition<T>(HttpMethods.Get, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Get<TRequest, TResult>(string path, Func<TRequest, HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) where TRequest : class | ||
{ | ||
var builder = _routeBuilder.MapGet(path, ctx => BuildQueryContext(ctx, context)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition<TRequest, TResult>(HttpMethods.Get, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Post(string path, Func<HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) | ||
{ | ||
var builder = _routeBuilder.MapPost(path, ctx => context?.Invoke(ctx)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition(HttpMethods.Post, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Post<T>(string path, Func<T, HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) where T : class | ||
{ | ||
var builder = _routeBuilder.MapPost(path, ctx => BuildRequestContext(ctx, context)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition<T>(HttpMethods.Post, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Put(string path, Func<HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) | ||
{ | ||
var builder = _routeBuilder.MapPut(path, ctx => context?.Invoke(ctx)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition(HttpMethods.Put, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Put<T>(string path, Func<T, HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) where T : class | ||
{ | ||
var builder = _routeBuilder.MapPut(path, ctx => BuildRequestContext(ctx, context)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition<T>(HttpMethods.Put, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Delete(string path, Func<HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) | ||
{ | ||
var builder = _routeBuilder.MapDelete(path, ctx => context?.Invoke(ctx)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition(HttpMethods.Delete, path); | ||
|
||
return this; | ||
} | ||
|
||
public IEndpointsBuilder Delete<T>(string path, Func<T, HttpContext, Task> context = null, | ||
Action<IEndpointConventionBuilder> endpoint = null, bool auth = false, string roles = null, | ||
params string[] policies) where T : class | ||
{ | ||
var builder = _routeBuilder.MapDelete(path, ctx => BuildQueryContext(ctx, context)); | ||
endpoint?.Invoke(builder); | ||
ApplyAuthRolesAndPolicies(builder, auth, roles, policies); | ||
AddEndpointDefinition<T>(HttpMethods.Delete, path); | ||
|
||
return this; | ||
} | ||
|
||
private static void ApplyAuthRolesAndPolicies(IEndpointConventionBuilder builder, bool auth, string roles, | ||
params string[] policies) | ||
{ | ||
if (policies is not null && policies.Any()) | ||
{ | ||
builder.RequireAuthorization(policies); | ||
return; | ||
} | ||
|
||
var hasRoles = !string.IsNullOrWhiteSpace(roles); | ||
var authorize = new AuthorizeAttribute(); | ||
if (hasRoles) | ||
{ | ||
authorize.Roles = roles; | ||
} | ||
|
||
if (auth || hasRoles) | ||
{ | ||
builder.RequireAuthorization(authorize); | ||
} | ||
} | ||
|
||
private static async Task BuildRequestContext<T>(HttpContext httpContext, | ||
Func<T, HttpContext, Task> context = null) where T : class | ||
{ | ||
var request = await httpContext.ReadJsonAsync<T>(); | ||
if (request is null || context is null) | ||
{ | ||
return; | ||
} | ||
|
||
await context.Invoke(request, httpContext); | ||
} | ||
|
||
private static async Task BuildQueryContext<T>(HttpContext httpContext, | ||
Func<T, HttpContext, Task> context = null) where T : class | ||
{ | ||
var request = httpContext.ReadQuery<T>(); | ||
if (request is null || context is null) | ||
{ | ||
return; | ||
} | ||
|
||
await context.Invoke(request, httpContext); | ||
} | ||
|
||
private void AddEndpointDefinition(string method, string path) | ||
{ | ||
_definitions.Add(new WebApiEndpointDefinition | ||
{ | ||
Method = method, | ||
Path = path, | ||
Responses = new List<WebApiEndpointResponse> | ||
{ | ||
new WebApiEndpointResponse { StatusCode = 200 } | ||
} | ||
}); | ||
} | ||
|
||
private void AddEndpointDefinition<T>(string method, string path) | ||
=> AddEndpointDefinition(method, path, typeof(T), null); | ||
|
||
private void AddEndpointDefinition<TRequest, TResult>(string method, string path) | ||
=> AddEndpointDefinition(method, path, typeof(TRequest), typeof(TResult)); | ||
|
||
private void AddEndpointDefinition(string method, string path, Type input, Type output) | ||
{ | ||
if (_definitions.Exists(d => d.Path == path && d.Method == method)) | ||
{ | ||
return; | ||
} | ||
|
||
_definitions.Add(new WebApiEndpointDefinition | ||
{ | ||
Method = method, | ||
Path = path, | ||
Parameters = new List<WebApiEndpointParameter> | ||
{ | ||
new WebApiEndpointParameter | ||
{ | ||
In = method == HttpMethods.Get ? "query" : "body", | ||
Name = input?.Name, | ||
Type = input, | ||
Example = input?.GetDefaultInstance() | ||
} | ||
}, | ||
Responses = new List<WebApiEndpointResponse> | ||
{ | ||
new WebApiEndpointResponse | ||
{ | ||
StatusCode = method == HttpMethods.Get ? 200 : 202, | ||
Type = output, | ||
Example = output?.GetDefaultInstance() | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/Paralax.WebApi/src/Exceptions/ErrorHandlerMiddleware.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using System; | ||
using System.Net; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Logging; | ||
using NetJSON; | ||
|
||
namespace Paralax.WebApi.Exceptions | ||
{ | ||
internal sealed class ErrorHandlerMiddleware : IMiddleware | ||
{ | ||
private readonly IExceptionToResponseMapper _exceptionToResponseMapper; | ||
private readonly ILogger<ErrorHandlerMiddleware> _logger; | ||
|
||
public ErrorHandlerMiddleware(IExceptionToResponseMapper exceptionToResponseMapper, | ||
ILogger<ErrorHandlerMiddleware> logger) | ||
{ | ||
_exceptionToResponseMapper = exceptionToResponseMapper; | ||
_logger = logger; | ||
} | ||
|
||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) | ||
{ | ||
try | ||
{ | ||
await next(context); | ||
} | ||
catch (Exception exception) | ||
{ | ||
_logger.LogError(exception, "An error occurred while processing the request."); | ||
await HandleErrorAsync(context, exception); // Handle the error when caught | ||
} | ||
} | ||
|
||
private async Task HandleErrorAsync(HttpContext context, Exception exception) | ||
{ | ||
var exceptionResponse = _exceptionToResponseMapper.Map(exception); | ||
|
||
context.Response.StatusCode = (int)(exceptionResponse?.StatusCode ?? HttpStatusCode.BadRequest); | ||
|
||
var response = exceptionResponse?.Response; | ||
|
||
if (response is null) | ||
{ | ||
await context.Response.WriteAsync(string.Empty); | ||
return; | ||
} | ||
|
||
context.Response.ContentType = "application/json"; | ||
|
||
// Use NetJSON to serialize the response | ||
var jsonResponse = NetJSON.NetJSON.Serialize(response); | ||
|
||
await context.Response.WriteAsync(jsonResponse); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
using System.Net; | ||
|
||
namespace Paralax.WebApi.Exceptions | ||
{ | ||
public class ExceptionResponse | ||
{ | ||
/// <summary> | ||
/// The response object containing error details or messages. | ||
/// </summary> | ||
public object Response { get; } | ||
|
||
/// <summary> | ||
/// The HTTP status code associated with this exception response. | ||
/// </summary> | ||
public HttpStatusCode StatusCode { get; } | ||
|
||
/// <summary> | ||
/// Constructor for creating an instance of ExceptionResponse. | ||
/// </summary> | ||
/// <param name="response">The response object, typically containing error messages or details.</param> | ||
/// <param name="statusCode">The HTTP status code indicating the type of error.</param> | ||
public ExceptionResponse(object response, HttpStatusCode statusCode) | ||
{ | ||
Response = response; | ||
StatusCode = statusCode; | ||
} | ||
|
||
/// <summary> | ||
/// A utility method to create a standard error response. | ||
/// </summary> | ||
/// <param name="message">Error message to return in the response.</param> | ||
/// <param name="statusCode">HTTP status code of the error.</param> | ||
/// <returns>A structured exception response with the message and status code.</returns> | ||
public static ExceptionResponse Create(string message, HttpStatusCode statusCode) | ||
{ | ||
var response = new | ||
{ | ||
error = true, | ||
message = message | ||
}; | ||
|
||
return new ExceptionResponse(response, statusCode); | ||
} | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/Paralax.WebApi/src/Exceptions/IExceptionToResponseMapper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System; | ||
|
||
namespace Paralax.WebApi.Exceptions | ||
{ | ||
public interface IExceptionToResponseMapper | ||
{ | ||
ExceptionResponse Map(Exception exception); | ||
} | ||
} | ||
|
Oops, something went wrong.