Skip to content

Commit

Permalink
⚗️ Experiment with grouping endpoints into a single file.
Browse files Browse the repository at this point in the history
  • Loading branch information
jasontaylordev committed Jun 28, 2023
1 parent eab4ce1 commit d88bff0
Show file tree
Hide file tree
Showing 10 changed files with 472 additions and 486 deletions.
424 changes: 212 additions & 212 deletions src/WebUI/ClientApp/src/app/web-api-client.ts

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions src/WebUI/Endpoints/TodoListEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using CleanArchitecture.Application.TodoLists.Commands.CreateTodoList;
using CleanArchitecture.Application.TodoLists.Commands.DeleteTodoList;
using CleanArchitecture.Application.TodoLists.Commands.UpdateTodoList;
using CleanArchitecture.Application.TodoLists.Queries.ExportTodos;
using CleanArchitecture.Application.TodoLists.Queries.GetTodos;
using CleanArchitecture.WebUI.Filters;

namespace CleanArchitecture.WebUI.Endpoints;

public class TodoListEndpoints
{
private const string GroupName = "TodoLists";

public static void Map(WebApplication app)
{
var group = app
.MapGroup($"/api/{GroupName}")
.WithTags(GroupName)
.RequireAuthorization()
.WithOpenApi()
.AddEndpointFilter<ApiExceptionFilter>();

group
.MapGet("/", async (ISender sender) => await sender.Send(new GetTodosQuery()))
.WithName($"{GroupName}_GetTodoLists");

group
.MapGet("/Export/{id}",
async (ISender sender, int id) =>
{
var vm = await sender.Send(new ExportTodosQuery { ListId = id });
return Results.File(vm.Content, vm.ContentType, vm.FileName);
})
.WithName($"{GroupName}_ExportTodos"); ;

group
.MapPost("/", async (ISender sender, CreateTodoListCommand command) => await sender.Send(command))
.WithName($"{GroupName}_CreateTodoList"); ;

group
.MapPut("/{id}",
async (ISender sender, int id, UpdateTodoListCommand command) =>
{
if (id != command.Id) return Results.BadRequest();
await sender.Send(command);
return Results.NoContent();
})
.WithName($"{GroupName}_UpdateTodoList"); ;

group
.MapDelete("/{id}",
async (ISender sender, int id) =>
{
await sender.Send(new DeleteTodoListCommand(id));
return Results.NoContent();
})
.WithName($"{GroupName}_DeleteTodoList"); ;
}
}
150 changes: 75 additions & 75 deletions src/WebUI/Infrastructure/ApiExceptionFilter.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
using Ardalis.GuardClauses;
using CleanArchitecture.Application.Common.Exceptions;
using Microsoft.AspNetCore.Mvc;

namespace CleanArchitecture.WebUI.Filters;

public class ApiExceptionFilter : IEndpointFilter
{
private readonly IDictionary<Type, Func<Exception, IResult>> _exceptionHandlers;

public ApiExceptionFilter()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Func<Exception, IResult>>
{
{ typeof(ValidationException), HandleValidationException },
{ typeof(NotFoundException), HandleNotFoundException },
{ typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException },
{ typeof(ForbiddenAccessException), HandleForbiddenAccessException },
};
using CleanArchitecture.Application.Common.Exceptions;
using Microsoft.AspNetCore.Mvc;

namespace CleanArchitecture.WebUI.Filters;

public class ApiExceptionFilter : IEndpointFilter
{
private readonly IDictionary<Type, Func<Exception, IResult>> _exceptionHandlers;

public ApiExceptionFilter()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Func<Exception, IResult>>
{
{ typeof(ValidationException), HandleValidationException },
{ typeof(NotFoundException), HandleNotFoundException },
{ typeof(UnauthorizedAccessException), HandleUnauthorizedAccessException },
{ typeof(ForbiddenAccessException), HandleForbiddenAccessException },
};
}

public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
Expand All @@ -37,71 +37,71 @@ public ApiExceptionFilter()

throw;
}
}

private IResult? HandleException(Exception ex)
}

private IResult? HandleException(Exception ex)
{
var type = ex.GetType();

if (_exceptionHandlers.ContainsKey(type))
{
return _exceptionHandlers[type].Invoke(ex);

if (_exceptionHandlers.ContainsKey(type))
{
return _exceptionHandlers[type].Invoke(ex);
}

return null;
}

private IResult HandleValidationException(Exception ex)
{
var exception = (ValidationException)ex;

var details = new ValidationProblemDetails(exception.Errors)
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
};

return Results.BadRequest(details);
}

private IResult HandleNotFoundException(Exception ex)
{
var exception = (NotFoundException)ex;

var details = new ProblemDetails()
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Title = "The specified resource was not found.",
Detail = exception.Message
};

return Results.NotFound(details);
}

private IResult HandleUnauthorizedAccessException(Exception ex)
{
var details = new ProblemDetails
{
Status = StatusCodes.Status401Unauthorized,
Title = "Unauthorized",
Type = "https://tools.ietf.org/html/rfc7235#section-3.1"
};
return null;
}

private IResult HandleValidationException(Exception ex)
{
var exception = (ValidationException)ex;

var details = new ValidationProblemDetails(exception.Errors)
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
};

return Results.BadRequest(details);
}

private IResult HandleNotFoundException(Exception ex)
{
var exception = (NotFoundException)ex;

var details = new ProblemDetails()
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
Title = "The specified resource was not found.",
Detail = exception.Message
};

return Results.NotFound(details);
}

private IResult HandleUnauthorizedAccessException(Exception ex)
{
var details = new ProblemDetails
{
Status = StatusCodes.Status401Unauthorized,
Title = "Unauthorized",
Type = "https://tools.ietf.org/html/rfc7235#section-3.1"
};

return Results.Json(
details,
statusCode: StatusCodes.Status401Unauthorized);
}

private IResult HandleForbiddenAccessException(Exception ex)
{
var details = new ProblemDetails
{
Status = StatusCodes.Status403Forbidden,
Title = "Forbidden",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3"
statusCode: StatusCodes.Status401Unauthorized);
}

private IResult HandleForbiddenAccessException(Exception ex)
{
var details = new ProblemDetails
{
Status = StatusCodes.Status403Forbidden,
Title = "Forbidden",
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3"
};

return Results.Json(
details,
statusCode: StatusCodes.Status403Forbidden);
statusCode: StatusCodes.Status403Forbidden);
}
}
}
2 changes: 2 additions & 0 deletions src/WebUI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CleanArchitecture.Infrastructure.Persistence;
using CleanArchitecture.WebUI.Endpoints;

var builder = WebApplication.CreateBuilder(args);

Expand Down Expand Up @@ -47,5 +48,6 @@
app.MapFallbackToFile("index.html");

app.MapEndpoints();
TodoListEndpoints.Map(app);

app.Run();
12 changes: 0 additions & 12 deletions src/WebUI/TodoLists/CreateTodoList.cs

This file was deleted.

16 changes: 0 additions & 16 deletions src/WebUI/TodoLists/DeleteTodoList.cs

This file was deleted.

16 changes: 0 additions & 16 deletions src/WebUI/TodoLists/ExportTodos.cs

This file was deleted.

12 changes: 0 additions & 12 deletions src/WebUI/TodoLists/GetTodoLists.cs

This file was deleted.

19 changes: 0 additions & 19 deletions src/WebUI/TodoLists/UpdateTodoList.cs

This file was deleted.

Loading

0 comments on commit d88bff0

Please sign in to comment.