Skip to content

Commit

Permalink
Adjusted to the latest the greatest .NET 8 improvements around Proble…
Browse files Browse the repository at this point in the history
…m Details
  • Loading branch information
oskardudycz committed Feb 6, 2024
1 parent 6760a56 commit a05d540
Show file tree
Hide file tree
Showing 19 changed files with 207 additions and 127 deletions.
Original file line number Diff line number Diff line change
@@ -1,62 +1,68 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Builder;
using System.Net;
using Core.Exceptions;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace Core.WebApi.Middlewares.ExceptionHandling;

public static class ExceptionHandlingMiddleware
public class ExceptionToProblemDetailsHandler(Func<Exception, HttpContext, ProblemDetails?>? customExceptionMap)
: IExceptionHandler
{
public static IApplicationBuilder UseExceptionHandlingMiddleware(
this IApplicationBuilder app,
Func<Exception, HttpContext, ProblemDetails?>? customExceptionMap = null
) =>
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.ContentType = "application/problem+json";
if (context.RequestServices.GetService<IProblemDetailsService>() is not { } problemDetailsService)
return;
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
var exception = exceptionHandlerFeature?.Error;
if (exception is null)
return;
var details = customExceptionMap?.Invoke(exception, context) ??
MapExceptionUsingDefaults(exception);
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken
)
{
var details = customExceptionMap?.Invoke(exception, httpContext) ?? exception.MapToProblemDetails();

var problem = new ProblemDetailsContext { HttpContext = context, ProblemDetails = details };
httpContext.Response.StatusCode = details.Status ?? StatusCodes.Status500InternalServerError;
await httpContext.Response
.WriteAsJsonAsync(
new ProblemDetails
{
Title = "An error occurred",
Detail = exception.Message,
Type = exception.GetType().Name,
Status = (int)HttpStatusCode.BadRequest
}, cancellationToken: cancellationToken).ConfigureAwait(false);

problem.ProblemDetails.Extensions.Add("exception", exceptionHandlerFeature?.Error.ToString());
return true;
}
}

await problemDetailsService.WriteAsync(problem).ConfigureAwait(false);
});
});
public static class ExceptionHandlingMiddleware
{
public static IServiceCollection AddDefaultExceptionHandler(
this IServiceCollection serviceCollection,
Func<Exception, HttpContext, ProblemDetails?>? customExceptionMap = null
) =>
serviceCollection
.AddSingleton<IExceptionHandler>(new ExceptionToProblemDetailsHandler(customExceptionMap))
.AddProblemDetails();
}

private static ProblemDetails MapExceptionUsingDefaults(Exception exception)
public static class ProblemDetailsExtensions
{
public static ProblemDetails MapToProblemDetails(this Exception exception)
{
var statusCode = exception switch
{
ArgumentException _ => StatusCodes.Status400BadRequest,
ValidationException _ => StatusCodes.Status400BadRequest,
UnauthorizedAccessException _ => StatusCodes.Status401Unauthorized,
InvalidOperationException _ => StatusCodes.Status403Forbidden,
AggregateNotFoundException => StatusCodes.Status404NotFound,
NotImplementedException _ => StatusCodes.Status501NotImplemented,
_ => StatusCodes.Status500InternalServerError
};

return exception.MapToProblemDetails(statusCode);
}
}

public static class ProblemDetailsExtensions
{
public static ProblemDetails MapToProblemDetails(
this Exception exception,
int statusCode,
Expand Down
13 changes: 7 additions & 6 deletions Sample/AsyncProjections/SmartHome.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@
c.OperationFilter<MetadataOperationFilter>();
})
.AddCoreServices()
.AddTemperaturesModule(builder.Configuration)
.AddOptimisticConcurrencyMiddleware();

var app = builder.Build();

app.UseExceptionHandlingMiddleware(
.AddDefaultExceptionHandler(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null,
})
.AddTemperaturesModule(builder.Configuration)
.AddOptimisticConcurrencyMiddleware();

var app = builder.Build();

app.UseExceptionHandler()
.UseOptimisticConcurrencyMiddleware()
.UseRouting()
.UseAuthorization()
Expand Down
16 changes: 9 additions & 7 deletions Sample/ECommerce/Carts/Carts.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
})
.AddKafkaProducer()
.AddCoreServices()
.AddDefaultExceptionHandler(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
.AddCartsModule(builder.Configuration)
.AddOptimisticConcurrencyMiddleware()
.AddOpenTelemetry("Carts", OpenTelemetryOptions.Build(options =>
Expand All @@ -36,20 +43,15 @@

var app = builder.Build();

app.UseExceptionHandlingMiddleware(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
app.UseExceptionHandler()
.UseOptimisticConcurrencyMiddleware()
.UseRouting()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
})
.UseExceptionHandler()
.UseSwagger()
.UseSwaggerUI(c =>
{
Expand Down
15 changes: 8 additions & 7 deletions Sample/ECommerce/Orders/Orders.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
})
.AddKafkaProducerAndConsumer()
.AddCoreServices()
.AddDefaultExceptionHandler(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
.AddOrdersModule(builder.Configuration)
.AddOptimisticConcurrencyMiddleware()
.AddOpenTelemetry("Orders", OpenTelemetryOptions.Build(options =>
Expand All @@ -34,13 +41,7 @@

var app = builder.Build();

app.UseExceptionHandlingMiddleware(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
app.UseExceptionHandler()
.UseOptimisticConcurrencyMiddleware()
.UseRouting()
.UseAuthorization()
Expand Down
15 changes: 8 additions & 7 deletions Sample/ECommerce/Payments/Payments.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
})
.AddKafkaProducer()
.AddCoreServices()
.AddDefaultExceptionHandler(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
.AddPaymentsModule(builder.Configuration)
.AddOptimisticConcurrencyMiddleware()
.AddOpenTelemetry("Payments", OpenTelemetryOptions.Build(options =>
Expand All @@ -34,13 +41,7 @@

var app = builder.Build();

app.UseExceptionHandlingMiddleware(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
app.UseExceptionHandler()
.UseOptimisticConcurrencyMiddleware()
.UseRouting()
.UseAuthorization()
Expand Down
17 changes: 9 additions & 8 deletions Sample/ECommerce/Shipments/Shipments.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
})
.AddKafkaProducer()
.AddCoreServices()
.AddDefaultExceptionHandler(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
// TODO: Add here EF concurrency exception
// ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
.AddShipmentsModule(builder.Configuration)
.AddOpenTelemetry("Shipments", OpenTelemetryOptions.Build(options =>
options.Configure(t =>
Expand All @@ -33,14 +41,7 @@

var app = builder.Build();

app.UseExceptionHandlingMiddleware(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
// TODO: Add here EF concurrency exception
// ConcurrencyException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
app.UseExceptionHandler()
// TODO: Add optimistic concurrency here
// .UseOptimisticConcurrencyMiddleware()
.UseRouting()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\Core.WebApi\Core.WebApi.csproj" />
<ProjectReference Include="..\MarketBasketAnalytics\MarketBasketAnalytics.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Core;
using Core.ElasticSearch;
using Core.EventStoreDB;
using Core.WebApi.Middlewares.ExceptionHandling;
using Microsoft.OpenApi.Models;

namespace MarketBasketAnalytics.Api
Expand All @@ -23,6 +24,7 @@ public void ConfigureServices(IServiceCollection services)
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ECommerce.Api", Version = "v1" });
})
.AddDefaultExceptionHandler()
.AddEventStoreDB(Configuration)
.AddElasticsearch(Configuration)
.AddCoreServices()
Expand All @@ -37,7 +39,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseDeveloperExceptionPage();
}

app.UseSwagger()
app.UseExceptionHandler()
.UseSwagger()
.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ECommerce.Api v1"))
// .UseMiddleware(typeof(ExceptionHandlingMiddleware))
.UseHttpsRedirection()
Expand Down
15 changes: 8 additions & 7 deletions Sample/EventStoreDB/ECommerce/Carts/Carts.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
c.OperationFilter<MetadataOperationFilter>();
})
.AddCoreServices()
.AddDefaultExceptionHandler(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
WrongExpectedVersionException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
.AddEventStoreDBSubscriptionToAll()
.AddCartsModule(builder.Configuration)
.AddOptimisticConcurrencyMiddleware()
Expand All @@ -32,13 +39,7 @@

var app = builder.Build();

app.UseExceptionHandlingMiddleware(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
WrongExpectedVersionException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
app.UseExceptionHandler()
.UseOptimisticConcurrencyMiddleware()
.UseRouting()
.UseAuthorization()
Expand Down
15 changes: 8 additions & 7 deletions Sample/EventStoreDB/Simple/ECommerce.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
c.OperationFilter<MetadataOperationFilter>();
})
.AddCoreServices(builder.Configuration)
.AddDefaultExceptionHandler(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
WrongExpectedVersionException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
.AddEventStoreDBSubscriptionToAll()
.AddECommerceModule(builder.Configuration)
.AddOptimisticConcurrencyMiddleware()
Expand All @@ -32,13 +39,7 @@

var app = builder.Build();

app.UseExceptionHandlingMiddleware(
(exception, _) => exception switch
{
AggregateNotFoundException => exception.MapToProblemDetails(StatusCodes.Status404NotFound),
WrongExpectedVersionException => exception.MapToProblemDetails(StatusCodes.Status412PreconditionFailed),
_ => null
})
app.UseExceptionHandler()
.UseOptimisticConcurrencyMiddleware()
.UseHttpsRedirection()
.UseRouting()
Expand Down
Loading

0 comments on commit a05d540

Please sign in to comment.