Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
<RepositoryUrl>https://github.com/managedcode/Communication</RepositoryUrl>
<PackageProjectUrl>https://github.com/managedcode/Communication</PackageProjectUrl>
<Product>Managed Code - Communication</Product>
<Version>9.0.0</Version>
<PackageVersion>9.0.0</PackageVersion>
<Version>9.0.1</Version>
<PackageVersion>9.0.1</PackageVersion>

</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
Expand Down
39 changes: 0 additions & 39 deletions ManagedCode.Communication.Extensions/CommunicationHubFilter.cs

This file was deleted.

54 changes: 0 additions & 54 deletions ManagedCode.Communication.Extensions/CommunicationMiddleware.cs

This file was deleted.

109 changes: 109 additions & 0 deletions ManagedCode.Communication.Extensions/ExceptionFilterBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace ManagedCode.Communication.Extensions;

public abstract class ExceptionFilterBase(ILogger logger) : IExceptionFilter
{
protected readonly ILogger Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Comment thread
VladKovtun99 marked this conversation as resolved.
Outdated

public virtual void OnException(ExceptionContext context)
{
try
{
var exception = context.Exception;
var actionName = context.ActionDescriptor.DisplayName;
var controllerName = context.ActionDescriptor.RouteValues["controller"] ?? "Unknown";

Logger.LogError(exception, "Unhandled exception in {ControllerName}.{ActionName}",
controllerName, actionName);

var statusCode = GetStatusCodeForException(exception);
var problem = new Problem()
{
Title = exception.GetType().Name,
Detail = exception.Message,
Status = (int)statusCode,
Comment thread
VladKovtun99 marked this conversation as resolved.
Outdated
Instance = context.HttpContext.Request.Path,
Extensions =
{
["traceId"] = context.HttpContext.TraceIdentifier
}
};

var result = Result<Problem>.Fail(exception.Message, problem);

context.Result = new ObjectResult(result)
{
StatusCode = (int)statusCode
};

context.ExceptionHandled = true;

Logger.LogInformation("Exception handled by {FilterType} for {ControllerName}.{ActionName}",
GetType().Name, controllerName, actionName);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error occurred while handling exception in {FilterType}", GetType().Name);

var fallbackProblem = new Problem
{
Title = "An unexpected error occurred",
Status = (int)HttpStatusCode.InternalServerError,
Instance = context.HttpContext.Request.Path
};

context.Result = new ObjectResult(Result<Problem>.Fail("An unexpected error occurred", fallbackProblem))
{
StatusCode = (int)HttpStatusCode.InternalServerError
Comment thread
VladKovtun99 marked this conversation as resolved.
};
context.ExceptionHandled = true;
}
}

protected virtual HttpStatusCode GetStatusCodeForException(Exception exception)
{
return exception switch
{
ArgumentException => HttpStatusCode.BadRequest,
InvalidOperationException => HttpStatusCode.BadRequest,
NotSupportedException => HttpStatusCode.BadRequest,
FormatException => HttpStatusCode.BadRequest,
JsonException => HttpStatusCode.BadRequest,
XmlException => HttpStatusCode.BadRequest,

UnauthorizedAccessException => HttpStatusCode.Unauthorized,

SecurityException => HttpStatusCode.Forbidden,

FileNotFoundException => HttpStatusCode.NotFound,
DirectoryNotFoundException => HttpStatusCode.NotFound,
KeyNotFoundException => HttpStatusCode.NotFound,

TimeoutException => HttpStatusCode.RequestTimeout,
TaskCanceledException => HttpStatusCode.RequestTimeout,
OperationCanceledException => HttpStatusCode.RequestTimeout,

InvalidDataException => HttpStatusCode.Conflict,
Comment thread
VladKovtun99 marked this conversation as resolved.
Outdated

NotImplementedException => HttpStatusCode.NotImplemented,
NotFiniteNumberException => HttpStatusCode.InternalServerError,
OutOfMemoryException => HttpStatusCode.InternalServerError,
StackOverflowException => HttpStatusCode.InternalServerError,
ThreadAbortException => HttpStatusCode.InternalServerError,

_ => HttpStatusCode.InternalServerError
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using ManagedCode.Communication.Extensions;

namespace ManagedCode.Communication.Extensions.Extensions;

Expand All @@ -10,6 +14,14 @@ public static IApplicationBuilder UseCommunication(this IApplicationBuilder app)
if (app == null)
throw new ArgumentNullException(nameof(app));

return app.UseMiddleware<CommunicationMiddleware>();
var serviceProvider = app.ApplicationServices;
var exceptionFilter = serviceProvider.GetRequiredService<ExceptionFilterBase>();
var modelValidationFilter = serviceProvider.GetRequiredService<ModelValidationFilterBase>();

var mvcOptions = serviceProvider.GetRequiredService<IOptions<MvcOptions>>();
mvcOptions.Value.Filters.Add(exceptionFilter);
mvcOptions.Value.Filters.Add(modelValidationFilter);

return app;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using System;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using ManagedCode.Communication.Extensions;

namespace ManagedCode.Communication.Extensions.Extensions;

public static class HubOptionsExtensions
{
public static void AddCommunicationHubFilter(this HubOptions result)
public static void AddCommunicationHubFilter(this HubOptions result, IServiceProvider serviceProvider)
{
result.AddFilter<CommunicationHubFilter>();
var hubFilter = serviceProvider.GetRequiredService<HubExceptionFilterBase>();
result.AddFilter(hubFilter);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,91 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ManagedCode.Communication.Extensions.Extensions;


public static class HostApplicationBuilderExtensions
{
public static IHostApplicationBuilder AddCommunication(this IHostApplicationBuilder builder)
{
builder.Services.AddCommunication(options => options.ShowErrorDetails = builder.Environment.IsDevelopment());
return builder;
}

public static IHostApplicationBuilder AddCommunication(this IHostApplicationBuilder builder, Action<CommunicationOptions> config)
{
builder.Services.AddCommunication(config);
return builder;
}
}

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCommunication(this IServiceCollection services,
Action<CommunicationOptions> options)

public static IServiceCollection AddCommunication(this IServiceCollection services, Action<CommunicationOptions>? configure = null)
{
if (configure != null)
services.Configure(configure);

return services;
}



public static IServiceCollection AddDefaultProblemDetails(this IServiceCollection services)
{
services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = context =>
{
var statusCode = context.ProblemDetails.Status.GetValueOrDefault(StatusCodes.Status500InternalServerError);

context.ProblemDetails.Type ??= $"https://httpstatuses.io/{statusCode}";
context.ProblemDetails.Title ??= ReasonPhrases.GetReasonPhrase(statusCode);
context.ProblemDetails.Instance ??= context.HttpContext.Request.Path;
context.ProblemDetails.Extensions.TryAdd("traceId", Activity.Current?.Id ?? context.HttpContext.TraceIdentifier);
};
});

return services;
}

public static IServiceCollection AddCommunicationExceptionHandler(this IServiceCollection services)
{
services.AddOptions<CommunicationOptions>().Configure(options);
// Ensures that the ProblemDetails service is registered.
services.AddProblemDetails();

services.AddExceptionHandler<CommunicationExceptionHandler>();
return services;
}

public static IServiceCollection AddCommunicationFilters<TExceptionFilter, TModelValidationFilter, THubExceptionFilter>(
Comment thread
VladKovtun99 marked this conversation as resolved.
this IServiceCollection services)
where TExceptionFilter : ExceptionFilterBase
where TModelValidationFilter : ModelValidationFilterBase
where THubExceptionFilter : HubExceptionFilterBase
{
services.AddScoped<TExceptionFilter>();
services.AddScoped<TModelValidationFilter>();
services.AddScoped<THubExceptionFilter>();

services.AddControllers(options =>
{
options.Filters.Add<TExceptionFilter>();
options.Filters.Add<TModelValidationFilter>();
});

services.Configure<HubOptions>(options =>
{
options.AddFilter<THubExceptionFilter>();
});

return services;
}
}
Loading
Loading