From e3934e6735d343c9c006f034370f57db835602ef Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 23 Apr 2018 18:44:28 +0300 Subject: [PATCH 01/21] exception handling pipeline --- ...tCore.ExceptionHandling.Integration.csproj | 23 +++ .../Controllers/ValuesController.cs | 54 +++++++ .../Program.cs | 25 ++++ .../Startup.cs | 41 +++++ .../appsettings.Development.json | 10 ++ .../appsettings.json | 15 ++ .../AppBuilderExtensions.cs | 40 +++++ ...munity.AspNetCore.ExceptionHandling.csproj | 12 ++ .../Events.cs | 12 ++ .../ExceptionHandlingPolicyMiddleware.cs | 128 ++++++++++++++++ .../ExceptionHandlingPolicyOptions.cs | 140 ++++++++++++++++++ ...xceptionHandlingPolicyOptionsExtensions.cs | 51 +++++++ .../IExceptionHandler.cs | 11 ++ .../LogExceptionHandler.cs | 40 +++++ .../PolicyBuilder.cs | 15 ++ .../ReThrowExceptionHandler.cs | 14 ++ Commmunity.AspNetCore.sln | 31 ++++ 17 files changed, 662 insertions(+) create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Integration/Program.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.json create mode 100644 Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Events.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/ReThrowExceptionHandler.cs create mode 100644 Commmunity.AspNetCore.sln diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj new file mode 100644 index 0000000..162558b --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs new file mode 100644 index 0000000..ab39c38 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Commmunity.AspNetCore.ExceptionHandling.Integration.Controllers +{ + [Route("api/[controller]")] + public class ValuesController : Controller + { + // GET api/values + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public string Get(int id) + { + if (id > 10) + { + throw new ArgumentOutOfRangeException(); + } + + if (id > 5) + { + throw new InvalidCastException(); + } + + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody]string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Program.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Program.cs new file mode 100644 index 0000000..af95272 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Commmunity.AspNetCore.ExceptionHandling.Integration +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs new file mode 100644 index 0000000..a02f313 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Integration +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + + services.AddExceptionHandlingPolicies(options => + options.EnsureException() + .EnsureHandler().EnsureHandler()); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + app.UseDeveloperExceptionPage().UseExceptionHandlingPolicies(); + + app.UseMvc(); + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json b/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json new file mode 100644 index 0000000..fa8ce71 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.json b/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.json new file mode 100644 index 0000000..26bb0ac --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs new file mode 100644 index 0000000..56e2694 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + //TODO: add warning for policy override + //TODO: add response handler + //TODO: add retry handler + //TODO: policy builder + //TODO: add api exception and handler + //TODO: add terminate policies pipeline handler ??? + public static class AppBuilderExtensions + { + public static IApplicationBuilder UseExceptionHandlingPolicies(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + public static IServiceCollection AddExceptionHandlingPolicies(this IServiceCollection services, Action options = null) + { + services.TryAddSingleton(); + if (options != null) + { + services.Configure(options); + } + + services.TryAddSingleton(); + services.TryAddSingleton(); + + return services; + } + + public static IApplicationBuilder UseExceptionHandlingPolicies(this IApplicationBuilder app, ExceptionHandlingPolicyOptions options) + { + return app.UseMiddleware(Options.Create(options)); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj new file mode 100644 index 0000000..b2c7ec2 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + + diff --git a/Commmunity.AspNetCore.ExceptionHandling/Events.cs b/Commmunity.AspNetCore.ExceptionHandling/Events.cs new file mode 100644 index 0000000..e849336 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Events.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public static class Events + { + public static readonly EventId HandlerError = new EventId(100, "HandlerExecutionError"); + public static readonly EventId PolicyNotFound = new EventId(101, "PolicyForExceptionNotRegistered"); + public static readonly EventId HandlersNotFound = new EventId(102, "HandlersCollectionEmpty"); + public static readonly EventId HandlerNotCreated = new EventId(103, "HandlersCanNotBeCreated"); + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs new file mode 100644 index 0000000..8b1e851 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public class ExceptionHandlingPolicyMiddleware : IMiddleware + { + private readonly IOptions options; + + public ExceptionHandlingPolicyMiddleware(IOptions options) + { + this.options = options ?? throw new ArgumentNullException(nameof(options)); + } + + private static async Task EnumerateExceptionMapping( + HttpContext context, + ExceptionHandlingPolicyOptions policyOptions, + Exception exception, + RequestDelegate next) + { + Type exceptionType = exception.GetType(); + + ILogger logger = context.RequestServices.GetService>() ?? + NullLoggerFactory.Instance.CreateLogger(); + + bool? throwRequired = null; + + foreach (Type type in policyOptions.GetExceptionsInternal()) + { + if (type.IsAssignableFrom(exceptionType)) + { + throwRequired = await EnumerateHandlers(context, exception, next, policyOptions, logger); + + break; + } + } + + if (!throwRequired.HasValue) + { + logger.LogWarning(Events.PolicyNotFound, + "Handlers mapping for exception type {exceptionType} not exists. Exception will be re-thrown. RequestId: {RequestId}", + exceptionType, context.TraceIdentifier); + } + + return throwRequired ?? true; + } + + private static async Task EnumerateHandlers( + HttpContext context, + Exception exception, + RequestDelegate next, + ExceptionHandlingPolicyOptions policyOptions, + ILogger logger) + { + bool? throwRequired = null; + Type exceptionType = exception.GetType(); + + IEnumerable handlers = policyOptions.GetHandlersInternal(exceptionType); + + foreach (Type handlerType in handlers) + { + try + { + IExceptionHandler handler = + context.RequestServices.GetService(handlerType) as IExceptionHandler; + + if (handler == null) + { + throwRequired = true; + logger.LogError(Events.HandlerNotCreated, + "Handler type {handlerType} can't be created because it not registered in IServiceProvider. RequestId: {RequestId}", + handlerType, context.TraceIdentifier); + } + else + { + throwRequired = await handler.Handle(context, exception, next); + } + } + catch (Exception e) + { + logger.LogError(Events.HandlerError, e, + "Unhandled exception executing handler of type {handlerType} on exception of type {exceptionType}. RequestId: {RequestId}", + handlerType, exceptionType, context.TraceIdentifier); + throwRequired = true; + } + + if (throwRequired.Value) + { + break; + } + } + + if (!throwRequired.HasValue) + { + logger.LogWarning(Events.HandlersNotFound, + "Handlers collection for exception type {exceptionType} is empty. Exception will be re-thrown. RequestId: {RequestId}", + exceptionType, context.TraceIdentifier); + } + + return throwRequired ?? true; + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + try + { + await next(context); + } + catch (Exception exception) + { + ExceptionHandlingPolicyOptions policyOptions = this.options.Value; + + bool throwRequired = await EnumerateExceptionMapping(context, policyOptions, exception, next); + + if (throwRequired) + { + throw; + } + } + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs new file mode 100644 index 0000000..2f8361b --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public class ExceptionHandlingPolicyOptions : IOptions + { + public ExceptionHandlingPolicyOptions Value => this; + + private readonly OrderedDictionary handlers = new OrderedDictionary(); + + public ExceptionHandlingPolicyOptions EnsureException(Type exceptionType, int index = -1) + { + if (!typeof(Exception).IsAssignableFrom(exceptionType)) + { + throw new ArgumentOutOfRangeException(nameof(exceptionType), exceptionType, + $"Exception type should implement {typeof(Exception).Name}"); + } + + if (handlers.Contains(exceptionType)) + { + if (index >= 0) + { + object values = handlers[exceptionType]; + handlers.Remove(exceptionType); + handlers.Insert(index, exceptionType, values); + } + } + else + { + if (index < 0) + { + handlers.Add(exceptionType, new List()); + } + else + { + handlers.Insert(index, exceptionType, new List()); + } + } + + return this; + } + + public ExceptionHandlingPolicyOptions RemoveException(Type exceptionType) + { + if (this.handlers.Contains(exceptionType)) + { + this.handlers.Remove(exceptionType); + } + + return this; + } + + public ExceptionHandlingPolicyOptions EnsureHandler(Type exceptionType, Type handlerType, int index = -1) + { + if (!typeof(IExceptionHandler).IsAssignableFrom(handlerType)) + { + throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, + $"Handler type should implement {typeof(IExceptionHandler).Name}"); + } + + this.EnsureException(exceptionType); + + List list = this.handlers[exceptionType] as List; + + if (list.Any(type => type == handlerType)) + { + if (index >= 0) + { + list.Remove(handlerType); + list.Insert(index, handlerType); + } + } + else + { + if (index < 0) + { + list.Add(handlerType); + } + else + { + list.Insert(index, handlerType); + } + } + + return this; + } + + public ExceptionHandlingPolicyOptions RemoveHandler(Type exceptionType, Type handlerType) + { + if (this.handlers.Contains(exceptionType)) + { + List list = this.handlers[exceptionType] as List; + + if (list.Contains(handlerType)) + { + list.Remove(handlerType); + } + } + + return this; + } + + public ExceptionHandlingPolicyOptions ClearExceptions() + { + this.handlers.Clear(); + return this; + } + + public ExceptionHandlingPolicyOptions ClearHandlers(Type exceptionType) + { + if (this.handlers.Contains(exceptionType)) + { + List list = this.handlers[exceptionType] as List; + + list.Clear(); + } + + return this; + } + + internal IEnumerable GetHandlersInternal(Type exceptionType) + { + if (this.handlers.Contains(exceptionType)) + { + return this.handlers[exceptionType] as List; + } + + return Enumerable.Empty(); + } + + internal IEnumerable GetExceptionsInternal() + { + return this.handlers.Keys.OfType(); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs new file mode 100644 index 0000000..af81283 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public static class ExceptionHandlingPolicyOptionsExtensions + { + public static ExceptionMapping EnsureException( + this ExceptionHandlingPolicyOptions options, int index = -1) where TException : Exception + { + options.EnsureException(typeof(TException), index); + return new ExceptionMapping(options, typeof(TException)); + } + + public static ExceptionMapping EnsureHandler( + this ExceptionMapping options, int index = -1) + where THandler : IExceptionHandler + { + options.Value.EnsureHandler(options.Type, typeof(THandler), index); + return options; + } + + public static ExceptionMapping RemoveHandler( + this ExceptionMapping options) + where THandler : IExceptionHandler + { + options.Value.RemoveHandler(options.Type, typeof(THandler)); + return options; + } + + public static ExceptionMapping Clear( + this ExceptionMapping options) + { + options.Value.ClearHandlers(options.Type); + return options; + } + } + + public class ExceptionMapping : IOptions + { + public Type Type { get; } + + public ExceptionHandlingPolicyOptions Value { get; } + + internal ExceptionMapping(ExceptionHandlingPolicyOptions options, Type type) + { + this.Type = type; + this.Value = options; + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs new file mode 100644 index 0000000..9409b26 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public interface IExceptionHandler + { + Task Handle(HttpContext httpContext, Exception exception, RequestDelegate next); + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs new file mode 100644 index 0000000..1e8e46c --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public class LogExceptionHandler : IExceptionHandler + { + private readonly Func _eventIdFactory; + private readonly Func _messageFactory; + private readonly Func _parametersFactory; + private readonly string _category; + + public LogExceptionHandler(string category = null, Func eventIdFactory = null, Func messageFactory = null, Func parametersFactory = null) + { + _eventIdFactory = eventIdFactory; + _messageFactory = messageFactory; + _parametersFactory = parametersFactory; + _category = category ?? nameof(LogExceptionHandler); + } + public Task Handle(HttpContext httpContext, Exception exception, RequestDelegate next) + { + ILoggerFactory loggerFactory = + httpContext.RequestServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory; + + if (loggerFactory != null) + { + ILogger logger = loggerFactory.CreateLogger(this._category); + + logger.LogError(_eventIdFactory?.Invoke(httpContext, exception) ?? new EventId(500, "UnhandledException"), + exception, + this._messageFactory?.Invoke(httpContext, exception) ?? "Unhandled error occured. RequestId: {requestId}.", + this._parametersFactory?.Invoke(httpContext, exception) ?? new object[] { httpContext.TraceIdentifier }); + } + + return Task.FromResult(false); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs new file mode 100644 index 0000000..e25973e --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public class PolicyBuilder : ExceptionHandlingPolicyOptions + { + private readonly IServiceCollection _services; + + public PolicyBuilder(IServiceCollection services) + { + _services = services ?? throw new ArgumentNullException(nameof(services)); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ReThrowExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/ReThrowExceptionHandler.cs new file mode 100644 index 0000000..d6c0475 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/ReThrowExceptionHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public class ReThrowExceptionHandler : IExceptionHandler + { + public Task Handle(HttpContext httpContext, Exception exception, RequestDelegate next) + { + return Task.FromResult(true); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.sln b/Commmunity.AspNetCore.sln new file mode 100644 index 0000000..f99197a --- /dev/null +++ b/Commmunity.AspNetCore.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2015 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling", "Commmunity.AspNetCore.ExceptionHandling\Commmunity.AspNetCore.ExceptionHandling.csproj", "{97ECCF71-494E-48FA-995A-AB1F13975A61}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commmunity.AspNetCore.ExceptionHandling.Integration", "Commmunity.AspNetCore.ExceptionHandling.Integration\Commmunity.AspNetCore.ExceptionHandling.Integration.csproj", "{393C6033-4255-43C3-896D-BFE30E264E4A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {97ECCF71-494E-48FA-995A-AB1F13975A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97ECCF71-494E-48FA-995A-AB1F13975A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97ECCF71-494E-48FA-995A-AB1F13975A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97ECCF71-494E-48FA-995A-AB1F13975A61}.Release|Any CPU.Build.0 = Release|Any CPU + {393C6033-4255-43C3-896D-BFE30E264E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {393C6033-4255-43C3-896D-BFE30E264E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {393C6033-4255-43C3-896D-BFE30E264E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {393C6033-4255-43C3-896D-BFE30E264E4A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {18E2D6C5-7E06-4096-843F-534B6D926BE4} + EndGlobalSection +EndGlobal From 544c89276817fc8650fe4bff47bb2a648b108eb0 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Tue, 24 Apr 2018 18:15:34 +0300 Subject: [PATCH 02/21] exception handling --- .../Startup.cs | 10 +- .../AppBuilderExtensions.cs | 20 +--- .../Builder/ExceptionMapping.cs | 20 ++++ .../Builder/PolicyBuilder.cs | 79 +++++++++++++ ...munity.AspNetCore.ExceptionHandling.csproj | 4 + .../Const.cs | 7 ++ .../{ => Exc}/ReThrowExceptionHandler.cs | 4 +- .../ExceptionHandlingPolicyMiddleware.cs | 14 +-- .../ExceptionHandlingPolicyOptions.cs | 42 ++++++- ...xceptionHandlingPolicyOptionsExtensions.cs | 51 --------- .../IExceptionHandler.cs | 2 +- .../IExceptionPolicyBuilder.cs | 11 ++ .../LogExceptionHandler.cs | 40 ------- .../Logs/CommonConfigurationException.cs | 12 ++ .../Logs/LogExceptionHandler.cs | 48 ++++++++ .../Logs/LogHandlerOptions.cs | 22 ++++ .../PolicyBuilder.cs | 15 --- .../PolicyBuilderExtensions.cs | 107 ++++++++++++++++++ 18 files changed, 372 insertions(+), 136 deletions(-) create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Const.cs rename Commmunity.AspNetCore.ExceptionHandling/{ => Exc}/ReThrowExceptionHandler.cs (76%) delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index a02f313..819a9ab 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -26,15 +26,19 @@ public void ConfigureServices(IServiceCollection services) services.AddMvc(); services.AddExceptionHandlingPolicies(options => - options.EnsureException() - .EnsureHandler().EnsureHandler()); + { + options.AddLogHandler(l => l.Level = LogLevel.Debug); + options.ForException().AddLogHandler().AddRethrowHandler(); + }); + + services.AddLogging(b => b.AddConsole()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseDeveloperExceptionPage().UseExceptionHandlingPolicies(); - + app.UseExceptionHandler() app.UseMvc(); } } diff --git a/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs index 56e2694..87ed962 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs @@ -1,4 +1,5 @@ using System; +using Commmunity.AspNetCore.ExceptionHandling.Builder; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -18,23 +19,14 @@ public static IApplicationBuilder UseExceptionHandlingPolicies(this IApplication { return app.UseMiddleware(); } - public static IServiceCollection AddExceptionHandlingPolicies(this IServiceCollection services, Action options = null) + public static IServiceCollection AddExceptionHandlingPolicies(this IServiceCollection services, Action builder) { + PolicyBuilder policyBuilder = new PolicyBuilder(services); + builder?.Invoke(policyBuilder); + services.TryAddSingleton>(policyBuilder.Options); services.TryAddSingleton(); - if (options != null) - { - services.Configure(options); - } - services.TryAddSingleton(); - services.TryAddSingleton(); - - return services; - } - - public static IApplicationBuilder UseExceptionHandlingPolicies(this IApplicationBuilder app, ExceptionHandlingPolicyOptions options) - { - return app.UseMiddleware(Options.Create(options)); + return policyBuilder; } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs b/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs new file mode 100644 index 0000000..3083285 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs @@ -0,0 +1,20 @@ +namespace Commmunity.AspNetCore.ExceptionHandling.Builder +{ + using System; + using Microsoft.Extensions.DependencyInjection; + + public class ExceptionMapping : IExceptionPolicyBuilder + where TException : Exception + { + public IExceptionPolicyBuilder Builder { get; } + + internal ExceptionMapping(IExceptionPolicyBuilder builder) + { + Builder = builder ?? throw new ArgumentNullException(nameof(builder)); + } + + public IServiceCollection Services => Builder.Services; + + public ExceptionHandlingPolicyOptions Options => Builder.Options; + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs b/Commmunity.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs new file mode 100644 index 0000000..83e6c8f --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; + +namespace Commmunity.AspNetCore.ExceptionHandling.Builder +{ + public class PolicyBuilder : IExceptionPolicyBuilder, IServiceCollection + { + public IServiceCollection Services { get; } + + public ExceptionHandlingPolicyOptions Options { get; } = new ExceptionHandlingPolicyOptions(); + + public PolicyBuilder(IServiceCollection services) + { + this.Services = services ?? throw new ArgumentNullException(nameof(services)); + } + + public IEnumerator GetEnumerator() + { + return Services.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable) Services).GetEnumerator(); + } + + public void Add(ServiceDescriptor item) + { + Services.Add(item); + } + + public void Clear() + { + Services.Clear(); + } + + public bool Contains(ServiceDescriptor item) + { + return Services.Contains(item); + } + + public void CopyTo(ServiceDescriptor[] array, int arrayIndex) + { + Services.CopyTo(array, arrayIndex); + } + + public bool Remove(ServiceDescriptor item) + { + return Services.Remove(item); + } + + public int Count => Services.Count; + + public bool IsReadOnly => Services.IsReadOnly; + + public int IndexOf(ServiceDescriptor item) + { + return Services.IndexOf(item); + } + + public void Insert(int index, ServiceDescriptor item) + { + Services.Insert(index, item); + } + + public void RemoveAt(int index) + { + Services.RemoveAt(index); + } + + public ServiceDescriptor this[int index] + { + get => Services[index]; + set => Services[index] = value; + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj index b2c7ec2..5001036 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj +++ b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/Commmunity.AspNetCore.ExceptionHandling/Const.cs b/Commmunity.AspNetCore.ExceptionHandling/Const.cs new file mode 100644 index 0000000..bc4437b --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Const.cs @@ -0,0 +1,7 @@ +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public static class Const + { + public const string Category = "Commmunity.AspNetCore.ExceptionHandling"; + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ReThrowExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs similarity index 76% rename from Commmunity.AspNetCore.ExceptionHandling/ReThrowExceptionHandler.cs rename to Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs index d6c0475..ae63bcd 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/ReThrowExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs @@ -2,11 +2,11 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -namespace Commmunity.AspNetCore.ExceptionHandling +namespace Commmunity.AspNetCore.ExceptionHandling.Exc { public class ReThrowExceptionHandler : IExceptionHandler { - public Task Handle(HttpContext httpContext, Exception exception, RequestDelegate next) + public Task Handle(HttpContext httpContext, Exception exception) { return Task.FromResult(true); } diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs index 8b1e851..9fec57a 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs @@ -21,13 +21,12 @@ public ExceptionHandlingPolicyMiddleware(IOptions EnumerateExceptionMapping( HttpContext context, ExceptionHandlingPolicyOptions policyOptions, - Exception exception, - RequestDelegate next) + Exception exception) { Type exceptionType = exception.GetType(); ILogger logger = context.RequestServices.GetService>() ?? - NullLoggerFactory.Instance.CreateLogger(); + NullLoggerFactory.Instance.CreateLogger(Const.Category); bool? throwRequired = null; @@ -35,7 +34,7 @@ private static async Task EnumerateExceptionMapping( { if (type.IsAssignableFrom(exceptionType)) { - throwRequired = await EnumerateHandlers(context, exception, next, policyOptions, logger); + throwRequired = await EnumerateHandlers(context, exception, policyOptions, logger); break; } @@ -53,8 +52,7 @@ private static async Task EnumerateExceptionMapping( private static async Task EnumerateHandlers( HttpContext context, - Exception exception, - RequestDelegate next, + Exception exception, ExceptionHandlingPolicyOptions policyOptions, ILogger logger) { @@ -79,7 +77,7 @@ private static async Task EnumerateHandlers( } else { - throwRequired = await handler.Handle(context, exception, next); + throwRequired = await handler.Handle(context, exception); } } catch (Exception e) @@ -116,7 +114,7 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) { ExceptionHandlingPolicyOptions policyOptions = this.options.Value; - bool throwRequired = await EnumerateExceptionMapping(context, policyOptions, exception, next); + bool throwRequired = await EnumerateExceptionMapping(context, policyOptions, exception); if (throwRequired) { diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs index 2f8361b..88065d2 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -12,6 +13,8 @@ public class ExceptionHandlingPolicyOptions : IOptions commonHandlers = new List(); + public ExceptionHandlingPolicyOptions EnsureException(Type exceptionType, int index = -1) { if (!typeof(Exception).IsAssignableFrom(exceptionType)) @@ -66,6 +69,26 @@ public ExceptionHandlingPolicyOptions EnsureHandler(Type exceptionType, Type han List list = this.handlers[exceptionType] as List; + ProcessHandlersList(list, handlerType, index); + + return this; + } + + public ExceptionHandlingPolicyOptions EnsureCommonHandler(Type handlerType, int index = -1) + { + if (!typeof(IExceptionHandler).IsAssignableFrom(handlerType)) + { + throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, + $"Handler type should implement {typeof(IExceptionHandler).Name}"); + } + + ProcessHandlersList(this.commonHandlers, handlerType, index); + + return this; + } + + private static void ProcessHandlersList(List list, Type handlerType, int index) + { if (list.Any(type => type == handlerType)) { if (index >= 0) @@ -85,8 +108,6 @@ public ExceptionHandlingPolicyOptions EnsureHandler(Type exceptionType, Type han list.Insert(index, handlerType); } } - - return this; } public ExceptionHandlingPolicyOptions RemoveHandler(Type exceptionType, Type handlerType) @@ -104,6 +125,16 @@ public ExceptionHandlingPolicyOptions RemoveHandler(Type exceptionType, Type han return this; } + public ExceptionHandlingPolicyOptions RemoveCommonHandler(Type handlerType) + { + if (this.commonHandlers.Contains(handlerType)) + { + this.commonHandlers.Remove(handlerType); + } + + return this; + } + public ExceptionHandlingPolicyOptions ClearExceptions() { this.handlers.Clear(); @@ -122,6 +153,13 @@ public ExceptionHandlingPolicyOptions ClearHandlers(Type exceptionType) return this; } + public ExceptionHandlingPolicyOptions ClearCommonHandlers() + { + this.commonHandlers.Clear(); + + return this; + } + internal IEnumerable GetHandlersInternal(Type exceptionType) { if (this.handlers.Contains(exceptionType)) diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs deleted file mode 100644 index af81283..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptionsExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Microsoft.Extensions.Options; - -namespace Commmunity.AspNetCore.ExceptionHandling -{ - public static class ExceptionHandlingPolicyOptionsExtensions - { - public static ExceptionMapping EnsureException( - this ExceptionHandlingPolicyOptions options, int index = -1) where TException : Exception - { - options.EnsureException(typeof(TException), index); - return new ExceptionMapping(options, typeof(TException)); - } - - public static ExceptionMapping EnsureHandler( - this ExceptionMapping options, int index = -1) - where THandler : IExceptionHandler - { - options.Value.EnsureHandler(options.Type, typeof(THandler), index); - return options; - } - - public static ExceptionMapping RemoveHandler( - this ExceptionMapping options) - where THandler : IExceptionHandler - { - options.Value.RemoveHandler(options.Type, typeof(THandler)); - return options; - } - - public static ExceptionMapping Clear( - this ExceptionMapping options) - { - options.Value.ClearHandlers(options.Type); - return options; - } - } - - public class ExceptionMapping : IOptions - { - public Type Type { get; } - - public ExceptionHandlingPolicyOptions Value { get; } - - internal ExceptionMapping(ExceptionHandlingPolicyOptions options, Type type) - { - this.Type = type; - this.Value = options; - } - } -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs index 9409b26..2d08941 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs @@ -6,6 +6,6 @@ namespace Commmunity.AspNetCore.ExceptionHandling { public interface IExceptionHandler { - Task Handle(HttpContext httpContext, Exception exception, RequestDelegate next); + Task Handle(HttpContext httpContext, Exception exception); } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs b/Commmunity.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs new file mode 100644 index 0000000..e9f6f2d --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public interface IExceptionPolicyBuilder + { + IServiceCollection Services { get; } + + ExceptionHandlingPolicyOptions Options { get; } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs deleted file mode 100644 index 1e8e46c..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/LogExceptionHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace Commmunity.AspNetCore.ExceptionHandling -{ - public class LogExceptionHandler : IExceptionHandler - { - private readonly Func _eventIdFactory; - private readonly Func _messageFactory; - private readonly Func _parametersFactory; - private readonly string _category; - - public LogExceptionHandler(string category = null, Func eventIdFactory = null, Func messageFactory = null, Func parametersFactory = null) - { - _eventIdFactory = eventIdFactory; - _messageFactory = messageFactory; - _parametersFactory = parametersFactory; - _category = category ?? nameof(LogExceptionHandler); - } - public Task Handle(HttpContext httpContext, Exception exception, RequestDelegate next) - { - ILoggerFactory loggerFactory = - httpContext.RequestServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory; - - if (loggerFactory != null) - { - ILogger logger = loggerFactory.CreateLogger(this._category); - - logger.LogError(_eventIdFactory?.Invoke(httpContext, exception) ?? new EventId(500, "UnhandledException"), - exception, - this._messageFactory?.Invoke(httpContext, exception) ?? "Unhandled error occured. RequestId: {requestId}.", - this._parametersFactory?.Invoke(httpContext, exception) ?? new object[] { httpContext.TraceIdentifier }); - } - - return Task.FromResult(false); - } - } -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs new file mode 100644 index 0000000..c980674 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Commmunity.AspNetCore.ExceptionHandling.Logs +{ + public class CommonConfigurationException : Exception + { + public CommonConfigurationException() + { + throw new NotSupportedException("This exception not intended to use"); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs new file mode 100644 index 0000000..bbf6a9d --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Internal; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Logs +{ + public class LogExceptionHandler : IExceptionHandler + where TException : Exception + { + private readonly IOptions> _settings; + + private static readonly EventId DefaultEvent = new EventId(500, "UnhandledException"); + + public LogHandlerOptions Settings => this._settings.Value; + + public LogExceptionHandler(IOptions> settings) + { + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + public Task Handle(HttpContext httpContext, Exception exception) + { + ILoggerFactory loggerFactory = + httpContext.RequestServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory; + + if (loggerFactory != null) + { + ILogger logger = loggerFactory.CreateLogger(this.Settings.Category ?? Const.Category); + + EventId eventId = this.Settings.EventIdFactory != null + ? this.Settings.EventIdFactory(httpContext, exception) + : DefaultEvent; + + object state = this.Settings.StateFactory?.Invoke(httpContext, exception, this.Settings) ?? + new FormattedLogValues("Unhandled error occured. RequestId: {requestId}.", + httpContext.TraceIdentifier); + + Func formatter = this.Settings.Formatter ?? ((o, e) => o.ToString()); + + logger.Log(this.Settings.Level, eventId, state, exception, formatter); + } + + return Task.FromResult(false); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs new file mode 100644 index 0000000..8e8368f --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Logs +{ + public class LogHandlerOptions : IOptions> + where TException : Exception + { + public LogHandlerOptions Value => this; + + public Func EventIdFactory { get; set; } + + public Func Formatter { get; set; } + + public Func, object> StateFactory { get; set; } + + public string Category { get; set; } + public LogLevel Level { get; set; } = LogLevel.Error; + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs deleted file mode 100644 index e25973e..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilder.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; - -namespace Commmunity.AspNetCore.ExceptionHandling -{ - public class PolicyBuilder : ExceptionHandlingPolicyOptions - { - private readonly IServiceCollection _services; - - public PolicyBuilder(IServiceCollection services) - { - _services = services ?? throw new ArgumentNullException(nameof(services)); - } - } -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs new file mode 100644 index 0000000..09a78da --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -0,0 +1,107 @@ +using System; +using Commmunity.AspNetCore.ExceptionHandling.Builder; +using Commmunity.AspNetCore.ExceptionHandling.Exc; +using Commmunity.AspNetCore.ExceptionHandling.Logs; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public static class PolicyBuilderExtensions + { + public static ExceptionMapping ForException( + this IExceptionPolicyBuilder builder, int index = -1) where TException : Exception + { + builder.Options.EnsureException(typeof(TException), index); + return new ExceptionMapping(builder); + } + + public static ExceptionMapping EnsureHandler( + this ExceptionMapping builder, int index = -1) + where THandler : class , IExceptionHandler + where TException : Exception + + { + builder.Options.Value.EnsureHandler(typeof(TException), typeof(THandler), index); + builder.Services.TryAddSingleton(); + return builder; + } + + public static IExceptionPolicyBuilder EnsureCommonHandler( + this IExceptionPolicyBuilder builder, int index = -1) + where THandler : class, IExceptionHandler + { + builder.Options.Value.EnsureCommonHandler(typeof(THandler), index); + builder.Services.TryAddSingleton(); + return builder; + } + + public static ExceptionMapping RemoveHandler( + this ExceptionMapping builder) + where THandler : IExceptionHandler + where TException : Exception + { + builder.Options.Value.RemoveHandler(typeof(TException), typeof(THandler)); + return builder; + } + + public static IExceptionPolicyBuilder RemoveCommonHandler( + this IExceptionPolicyBuilder builder, int index = -1) + where THandler : class, IExceptionHandler + { + builder.Options.RemoveCommonHandler(typeof(THandler)); + return builder; + } + + public static ExceptionMapping Clear( + this ExceptionMapping builder) + where TException : Exception + { + builder.Options.Value.ClearHandlers(typeof(TException)); + return builder; + } + + public static IExceptionPolicyBuilder ClearCommonHandlers( + this IExceptionPolicyBuilder builder) + { + builder.Options.ClearCommonHandlers(); + return builder; + } + + // rethrow + public static ExceptionMapping AddRethrowHandler( + this ExceptionMapping builder, int index = -1) + where TException : Exception + { + return builder.EnsureHandler(index); + } + + public static IExceptionPolicyBuilder AddRethrowHandler( + this IExceptionPolicyBuilder builder, int index = -1) + where TException : Exception + { + return builder.EnsureCommonHandler(index); + } + + // Log + public static ExceptionMapping AddLogHandler( + this ExceptionMapping builder, Action> settings = null, int index = -1) + where TException : Exception + { + LogHandlerOptions options = new LogHandlerOptions(); + settings?.Invoke(options); + builder.Services.TryAddSingleton(options); + return builder.EnsureHandler>(index); + } + + public static IExceptionPolicyBuilder AddLogHandler( + this IExceptionPolicyBuilder builder, Action> settings = null, int index = -1) + { + LogHandlerOptions options = new LogHandlerOptions(); + settings?.Invoke(options); + builder.Services.TryAddSingleton(options); + + return builder.EnsureCommonHandler>(index); + } + } +} \ No newline at end of file From 45d3c4f9713f7e06b8f395fe098faac051912875 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Sat, 28 Apr 2018 17:34:38 +0300 Subject: [PATCH 03/21] add raw response head modifications --- .../Startup.cs | 5 +- ...munity.AspNetCore.ExceptionHandling.csproj | 4 -- .../ExceptionHandlingPolicyOptions.cs | 34 +-------- .../Handlers/HandlerStrongType.cs | 37 ++++++++++ .../Handlers/HandlerWithLogger.cs | 19 +++++ .../Handlers/HandlerWithLoggerOptions.cs | 19 +++++ .../Logs/CommonConfigurationException.cs | 12 ---- .../PolicyBuilderExtensions.cs | 70 ++++++++----------- .../Response/RawResponseExceptionHandler.cs | 45 ++++++++++++ .../Response/RawResponseHandlerOptions.cs | 15 ++++ .../Response/SetHeadersHandler.cs | 15 ++++ .../Response/SetHeadersOptions.cs | 25 +++++++ .../Response/SetStatusCodeHandler.cs | 15 ++++ .../Response/SetStatusCodeOptions.cs | 25 +++++++ 14 files changed, 247 insertions(+), 93 deletions(-) create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeOptions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index 819a9ab..4c70244 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -27,8 +27,8 @@ public void ConfigureServices(IServiceCollection services) services.AddExceptionHandlingPolicies(options => { - options.AddLogHandler(l => l.Level = LogLevel.Debug); - options.ForException().AddLogHandler().AddRethrowHandler(); + options.For().AddLog().AddRethrow(); + options.For().AddLog().AddStatusCode(e => 400).AddHeaders((h, e) => h["X-qwe"] = e.Message); }); services.AddLogging(b => b.AddConsole()); @@ -38,7 +38,6 @@ public void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseDeveloperExceptionPage().UseExceptionHandlingPolicies(); - app.UseExceptionHandler() app.UseMvc(); } } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj index 5001036..b2c7ec2 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj +++ b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj @@ -9,8 +9,4 @@ - - - - diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs index 88065d2..66f165e 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs @@ -13,8 +13,6 @@ public class ExceptionHandlingPolicyOptions : IOptions commonHandlers = new List(); - public ExceptionHandlingPolicyOptions EnsureException(Type exceptionType, int index = -1) { if (!typeof(Exception).IsAssignableFrom(exceptionType)) @@ -74,19 +72,6 @@ public ExceptionHandlingPolicyOptions EnsureHandler(Type exceptionType, Type han return this; } - public ExceptionHandlingPolicyOptions EnsureCommonHandler(Type handlerType, int index = -1) - { - if (!typeof(IExceptionHandler).IsAssignableFrom(handlerType)) - { - throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, - $"Handler type should implement {typeof(IExceptionHandler).Name}"); - } - - ProcessHandlersList(this.commonHandlers, handlerType, index); - - return this; - } - private static void ProcessHandlersList(List list, Type handlerType, int index) { if (list.Any(type => type == handlerType)) @@ -124,17 +109,7 @@ public ExceptionHandlingPolicyOptions RemoveHandler(Type exceptionType, Type han return this; } - - public ExceptionHandlingPolicyOptions RemoveCommonHandler(Type handlerType) - { - if (this.commonHandlers.Contains(handlerType)) - { - this.commonHandlers.Remove(handlerType); - } - - return this; - } - + public ExceptionHandlingPolicyOptions ClearExceptions() { this.handlers.Clear(); @@ -153,13 +128,6 @@ public ExceptionHandlingPolicyOptions ClearHandlers(Type exceptionType) return this; } - public ExceptionHandlingPolicyOptions ClearCommonHandlers() - { - this.commonHandlers.Clear(); - - return this; - } - internal IEnumerable GetHandlersInternal(Type exceptionType) { if (this.handlers.Contains(exceptionType)) diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs new file mode 100644 index 0000000..2199c2f --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Logs; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public abstract class HandlerStrongType : HandlerWithLogger, IExceptionHandler + where TException : Exception + { + private static readonly EventId ExceptionTypeNotMatchGenericType = new EventId(136, "ExceptionTypeNotMatchGenericType"); + + protected HandlerStrongType(HandlerWithLoggerOptions options, ILoggerFactory loggerFactory) : base(options, + loggerFactory) + { + } + + public async Task Handle(HttpContext httpContext, Exception exception) + { + if (exception is TException e) + { + return await this.HandleStrongType(httpContext, e); + } + else + { + this.Logger.LogError(ExceptionTypeNotMatchGenericType, + "Excpetion type {exceptionType} not match current generic type {genericType}. Exception will be re-thrown.", + exception.GetType(), typeof(TException)); + + return true; + } + } + + protected abstract Task HandleStrongType(HttpContext httpContext, TException exception); + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs new file mode 100644 index 0000000..2fc04c0 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public class HandlerWithLogger + { + private readonly HandlerWithLoggerOptions _options; + private readonly ILoggerFactory _loggerFactory; + + public HandlerWithLogger(HandlerWithLoggerOptions options, ILoggerFactory loggerFactory) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + } + + protected ILogger Logger => this._loggerFactory.CreateLogger(_options.LoggerCategory); + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs new file mode 100644 index 0000000..588ce19 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs @@ -0,0 +1,19 @@ +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public class HandlerWithLoggerOptions + { + private string _loggerCategory = null; + + public string LoggerCategory + { + get => _loggerCategory ?? Const.Category; + set + { + if (!string.IsNullOrWhiteSpace(value)) + { + this._loggerCategory = value; + } + } + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs deleted file mode 100644 index c980674..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/Logs/CommonConfigurationException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Commmunity.AspNetCore.ExceptionHandling.Logs -{ - public class CommonConfigurationException : Exception - { - public CommonConfigurationException() - { - throw new NotSupportedException("This exception not intended to use"); - } - } -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index 09a78da..d1585f3 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -2,6 +2,8 @@ using Commmunity.AspNetCore.ExceptionHandling.Builder; using Commmunity.AspNetCore.ExceptionHandling.Exc; using Commmunity.AspNetCore.ExceptionHandling.Logs; +using Commmunity.AspNetCore.ExceptionHandling.Response; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -9,7 +11,7 @@ namespace Commmunity.AspNetCore.ExceptionHandling { public static class PolicyBuilderExtensions { - public static ExceptionMapping ForException( + public static ExceptionMapping For( this IExceptionPolicyBuilder builder, int index = -1) where TException : Exception { builder.Options.EnsureException(typeof(TException), index); @@ -27,15 +29,6 @@ public static ExceptionMapping EnsureHandler( return builder; } - public static IExceptionPolicyBuilder EnsureCommonHandler( - this IExceptionPolicyBuilder builder, int index = -1) - where THandler : class, IExceptionHandler - { - builder.Options.Value.EnsureCommonHandler(typeof(THandler), index); - builder.Services.TryAddSingleton(); - return builder; - } - public static ExceptionMapping RemoveHandler( this ExceptionMapping builder) where THandler : IExceptionHandler @@ -45,14 +38,6 @@ public static ExceptionMapping RemoveHandler( return builder; } - public static IExceptionPolicyBuilder RemoveCommonHandler( - this IExceptionPolicyBuilder builder, int index = -1) - where THandler : class, IExceptionHandler - { - builder.Options.RemoveCommonHandler(typeof(THandler)); - return builder; - } - public static ExceptionMapping Clear( this ExceptionMapping builder) where TException : Exception @@ -61,30 +46,16 @@ public static ExceptionMapping Clear( return builder; } - public static IExceptionPolicyBuilder ClearCommonHandlers( - this IExceptionPolicyBuilder builder) - { - builder.Options.ClearCommonHandlers(); - return builder; - } - // rethrow - public static ExceptionMapping AddRethrowHandler( + public static IExceptionPolicyBuilder AddRethrow( this ExceptionMapping builder, int index = -1) where TException : Exception - { + { return builder.EnsureHandler(index); } - public static IExceptionPolicyBuilder AddRethrowHandler( - this IExceptionPolicyBuilder builder, int index = -1) - where TException : Exception - { - return builder.EnsureCommonHandler(index); - } - // Log - public static ExceptionMapping AddLogHandler( + public static ExceptionMapping AddLog( this ExceptionMapping builder, Action> settings = null, int index = -1) where TException : Exception { @@ -94,14 +65,31 @@ public static ExceptionMapping AddLogHandler( return builder.EnsureHandler>(index); } - public static IExceptionPolicyBuilder AddLogHandler( - this IExceptionPolicyBuilder builder, Action> settings = null, int index = -1) + // Set status code + public static ExceptionMapping AddStatusCode( + this ExceptionMapping builder, Func settings = null, int index = -1) + where TException : Exception + { + if (settings != null) + { + builder.Services.Configure>(codeOptions => + codeOptions.StatusCodeFactory = settings); + } + + return builder.EnsureHandler>(index); + } + + public static ExceptionMapping AddHeaders( + this ExceptionMapping builder, Action settings = null, int index = -1) + where TException : Exception { - LogHandlerOptions options = new LogHandlerOptions(); - settings?.Invoke(options); - builder.Services.TryAddSingleton(options); + if (settings != null) + { + builder.Services.Configure>(codeOptions => + codeOptions.SetHeadersAction = settings); + } - return builder.EnsureCommonHandler>(index); + return builder.EnsureHandler>(index); } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs new file mode 100644 index 0000000..92bb69d --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Logs; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Response +{ + public class RawResponseExceptionHandler : HandlerStrongType + where TException : Exception + { + private readonly RawResponseHandlerOptions _options; + + private static readonly EventId ResponseHasStarted = new EventId(127, "ResponseAlreadyStarted"); + + public RawResponseExceptionHandler(IOptions> options, ILoggerFactory loggerFactory) + : base(options.Value, loggerFactory) + { + _options = options.Value ?? throw new ArgumentNullException(nameof(options)); + } + + protected override async Task HandleStrongType(HttpContext httpContext, TException exception) + { + if (httpContext.Response.HasStarted) + { + this.Logger.LogError(ResponseHasStarted, + "Unable to execute {handletType} handler, because respnse already started. Exception will be re-thrown.", + this.GetType()); + + return true; + } + + await HandleResponseAsync(httpContext, exception); + + return false; + } + + protected virtual async Task HandleResponseAsync(HttpContext httpContext, TException exception) + { + if (_options.SetResponse != null) await _options.SetResponse(httpContext, exception); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs new file mode 100644 index 0000000..a4f427f --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Response +{ + public class RawResponseHandlerOptions : HandlerWithLoggerOptions, + IOptions> + where TException : Exception + { + public Func SetResponse { get; set; } = null; + public RawResponseHandlerOptions Value => this; + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs new file mode 100644 index 0000000..b2bcaa5 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Response +{ + public class SetHeadersHandler : RawResponseExceptionHandler + where TException : Exception + { + public SetHeadersHandler(IOptions> options, + ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) + { + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs new file mode 100644 index 0000000..020be82 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Response +{ + public class SetHeadersOptions : IOptions>, IOptions> + where TException : Exception + { + public SetHeadersOptions Value => this; + + public Action SetHeadersAction = + (headers, exception) => { }; + + RawResponseHandlerOptions IOptions>.Value => + new RawResponseHandlerOptions + { + SetResponse = + async (context, exception) => + { + this.SetHeadersAction(context.Response.Headers, exception); + } + }; + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeHandler.cs new file mode 100644 index 0000000..77eda43 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeHandler.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Response +{ + public class SetStatusCodeHandler : RawResponseExceptionHandler + where TException : Exception + { + public SetStatusCodeHandler(IOptions> options, + ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) + { + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeOptions.cs new file mode 100644 index 0000000..9e603fe --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeOptions.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Response +{ + public class SetStatusCodeOptions : IOptions>, IOptions> + where TException : Exception + { + public SetStatusCodeOptions Value => this; + + public Func StatusCodeFactory = + exception => StatusCodes.Status500InternalServerError; + + RawResponseHandlerOptions IOptions>.Value => + new RawResponseHandlerOptions + { + SetResponse = + async (context, exception) => + { + context.Response.StatusCode = this.StatusCodeFactory(exception); + } + }; + } +} \ No newline at end of file From c188820e70759950dc7329df6758fbad846c3333 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Sat, 28 Apr 2018 18:45:42 +0300 Subject: [PATCH 04/21] response headers handler --- .../Startup.cs | 4 +- .../Builder/ExceptionMapping.cs | 2 +- .../Builder/IExceptionMapping.cs | 14 +++++ .../PolicyBuilderExtensions.cs | 54 ++++++++++--------- ...usCodeHandler.cs => NewResponseHandler.cs} | 4 +- ...usCodeOptions.cs => NewResponseOptions.cs} | 7 ++- 6 files changed, 54 insertions(+), 31 deletions(-) create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs rename Commmunity.AspNetCore.ExceptionHandling/Response/{SetStatusCodeHandler.cs => NewResponseHandler.cs} (62%) rename Commmunity.AspNetCore.ExceptionHandling/Response/{SetStatusCodeOptions.cs => NewResponseOptions.cs} (65%) diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index 4c70244..dc08ca2 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -28,7 +28,7 @@ public void ConfigureServices(IServiceCollection services) services.AddExceptionHandlingPolicies(options => { options.For().AddLog().AddRethrow(); - options.For().AddLog().AddStatusCode(e => 400).AddHeaders((h, e) => h["X-qwe"] = e.Message); + options.For().AddLog().AddNewResponse(e => 400).WithHeaders((h, e) => h["X-qwe"] = e.Message); }); services.AddLogging(b => b.AddConsole()); @@ -37,7 +37,7 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { - app.UseDeveloperExceptionPage().UseExceptionHandlingPolicies(); + app.UseDeveloperExceptionPage().UseExceptionHandler().UseExceptionHandlingPolicies(); app.UseMvc(); } } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs b/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs index 3083285..e7d66f8 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs @@ -3,7 +3,7 @@ using System; using Microsoft.Extensions.DependencyInjection; - public class ExceptionMapping : IExceptionPolicyBuilder + public class ExceptionMapping : IExceptionPolicyBuilder, IExceptionMapping, IResponseHandlers where TException : Exception { public IExceptionPolicyBuilder Builder { get; } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs b/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs new file mode 100644 index 0000000..d37b111 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs @@ -0,0 +1,14 @@ +using System; + +namespace Commmunity.AspNetCore.ExceptionHandling.Builder +{ + public interface IExceptionMapping : IExceptionPolicyBuilder + where TException : Exception + { + } + + public interface IResponseHandlers : IExceptionPolicyBuilder + where TException : Exception + { + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index d1585f3..9281791 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -11,26 +11,25 @@ namespace Commmunity.AspNetCore.ExceptionHandling { public static class PolicyBuilderExtensions { - public static ExceptionMapping For( + public static IExceptionMapping For( this IExceptionPolicyBuilder builder, int index = -1) where TException : Exception { builder.Options.EnsureException(typeof(TException), index); return new ExceptionMapping(builder); } - public static ExceptionMapping EnsureHandler( - this ExceptionMapping builder, int index = -1) + public static void EnsureHandler( + this IExceptionPolicyBuilder builder, int index = -1) where THandler : class , IExceptionHandler where TException : Exception { builder.Options.Value.EnsureHandler(typeof(TException), typeof(THandler), index); builder.Services.TryAddSingleton(); - return builder; } - public static ExceptionMapping RemoveHandler( - this ExceptionMapping builder) + public static IExceptionMapping RemoveHandler( + this IExceptionMapping builder) where THandler : IExceptionHandler where TException : Exception { @@ -38,8 +37,8 @@ public static ExceptionMapping RemoveHandler( return builder; } - public static ExceptionMapping Clear( - this ExceptionMapping builder) + public static IExceptionMapping Clear( + this IExceptionMapping builder) where TException : Exception { builder.Options.Value.ClearHandlers(typeof(TException)); @@ -48,39 +47,44 @@ public static ExceptionMapping Clear( // rethrow public static IExceptionPolicyBuilder AddRethrow( - this ExceptionMapping builder, int index = -1) + this IExceptionMapping builder, int index = -1) where TException : Exception - { - return builder.EnsureHandler(index); + { + builder.EnsureHandler(index); + return builder; } // Log - public static ExceptionMapping AddLog( - this ExceptionMapping builder, Action> settings = null, int index = -1) + public static IExceptionMapping AddLog( + this IExceptionMapping builder, Action> settings = null, int index = -1) where TException : Exception { LogHandlerOptions options = new LogHandlerOptions(); settings?.Invoke(options); builder.Services.TryAddSingleton(options); - return builder.EnsureHandler>(index); + builder.EnsureHandler>(index); + + return builder; } // Set status code - public static ExceptionMapping AddStatusCode( - this ExceptionMapping builder, Func settings = null, int index = -1) + public static IResponseHandlers AddNewResponse( + this IExceptionMapping builder, Func statusCodeFactory = null, int index = -1) where TException : Exception { - if (settings != null) + if (statusCodeFactory != null) { - builder.Services.Configure>(codeOptions => - codeOptions.StatusCodeFactory = settings); + builder.Services.Configure>(codeOptions => + codeOptions.StatusCodeFactory = statusCodeFactory); } - - return builder.EnsureHandler>(index); + + builder.EnsureHandler>(index); + + return builder as IResponseHandlers; } - public static ExceptionMapping AddHeaders( - this ExceptionMapping builder, Action settings = null, int index = -1) + public static IResponseHandlers WithHeaders( + this IResponseHandlers builder, Action settings = null, int index = -1) where TException : Exception { if (settings != null) @@ -89,7 +93,9 @@ public static ExceptionMapping AddHeaders( codeOptions.SetHeadersAction = settings); } - return builder.EnsureHandler>(index); + builder.EnsureHandler>(index); + + return builder; } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs similarity index 62% rename from Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeHandler.cs rename to Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs index 77eda43..6766b25 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs @@ -4,10 +4,10 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Response { - public class SetStatusCodeHandler : RawResponseExceptionHandler + public class NewResponseHandler : RawResponseExceptionHandler where TException : Exception { - public SetStatusCodeHandler(IOptions> options, + public NewResponseHandler(IOptions> options, ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) { } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs similarity index 65% rename from Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeOptions.cs rename to Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs index 9e603fe..d4de4f8 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/SetStatusCodeOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs @@ -4,10 +4,10 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Response { - public class SetStatusCodeOptions : IOptions>, IOptions> + public class NewResponseOptions : IOptions>, IOptions> where TException : Exception { - public SetStatusCodeOptions Value => this; + public NewResponseOptions Value => this; public Func StatusCodeFactory = exception => StatusCodes.Status500InternalServerError; @@ -18,6 +18,9 @@ public class SetStatusCodeOptions : IOptions { + context.Response.Headers.Clear(); + if (context.Response.Body.CanSeek) + context.Response.Body.SetLength(0L); context.Response.StatusCode = this.StatusCodeFactory(exception); } }; From 25c39df0707b1a93fe17edd6cf2e5e74bdad0e8d Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Fri, 13 Jul 2018 20:11:52 +0300 Subject: [PATCH 05/21] responce body and advanced chaining --- ...tCore.ExceptionHandling.Integration.csproj | 1 + .../Controllers/ValuesController.cs | 6 ++ .../Startup.cs | 32 +++++++- .../Builder/IExceptionMapping.cs | 2 +- .../Exc/ReThrowExceptionHandler.cs | 5 +- .../ExceptionHandlingPolicyMiddleware.cs | 48 +++++++---- .../Handlers/HandlerOptions.cs | 7 ++ .../Handlers/HandlerResult.cs | 13 +++ .../Handlers/HandlerStrongType.cs | 9 +-- .../Handlers/HandlerWithLogger.cs | 2 +- .../Handlers/HandlerWithLoggerOptions.cs | 4 +- .../Handlers/NextChainHandler.cs | 14 ++++ .../IExceptionHandler.cs | 3 +- .../Logs/LogExceptionHandler.cs | 5 +- .../PolicyBuilderExtensions.cs | 80 ++++++++++++++----- .../RequestStartedBehaviour.cs | 9 +++ .../Response/NewResponseHandler.cs | 16 ++-- .../Response/NewResponseOptions.cs | 38 ++++----- .../Response/RawResponseExceptionHandler.cs | 32 ++++++-- .../Response/RawResponseHandlerOptions.cs | 4 +- .../Response/SetHeadersHandler.cs | 16 ++-- .../Response/SetHeadersOptions.cs | 34 ++++---- 22 files changed, 267 insertions(+), 113 deletions(-) create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj index 162558b..564d7de 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj @@ -10,6 +10,7 @@ + diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs index ab39c38..b8f7dd5 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -20,6 +21,11 @@ public IEnumerable Get() [HttpGet("{id}")] public string Get(int id) { + if (id > 15) + { + throw new InvalidConstraintException(); + } + if (id > 10) { throw new ArgumentOutOfRangeException(); diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index dc08ca2..500a865 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -27,17 +29,39 @@ public void ConfigureServices(IServiceCollection services) services.AddExceptionHandlingPolicies(options => { - options.For().AddLog().AddRethrow(); - options.For().AddLog().AddNewResponse(e => 400).WithHeaders((h, e) => h["X-qwe"] = e.Message); + options.For().Log().Rethrow(); + options.For() + .NewResponse(e => 400) + .WithHeaders((h, e) => h["X-qwe"] = e.Message) + .WithBody((stream, exception) => + { + using (StreamWriter sw = new StreamWriter(stream)) + { + return sw.WriteAsync(exception.ToString()); + } + }) + .NextChain(); + options.For().Log(lo => + { + lo.Formatter = (o, e) => "qwe"; + }) + .NewResponse(e => 500, RequestStartedBehaviour.Ignore).WithBody( + async (stream, exception) => + { + using (StreamWriter sw = new StreamWriter(stream)) + { + await sw.WriteAsync("unhandled exception"); + } + }); }); - services.AddLogging(b => b.AddConsole()); + services.AddLogging(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { - app.UseDeveloperExceptionPage().UseExceptionHandler().UseExceptionHandlingPolicies(); + app.UseResponseBuffering().UseDeveloperExceptionPage().UseExceptionHandlingPolicies(); app.UseMvc(); } } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs b/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs index d37b111..2190572 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs @@ -7,7 +7,7 @@ public interface IExceptionMapping : IExceptionPolicyBuilder { } - public interface IResponseHandlers : IExceptionPolicyBuilder + public interface IResponseHandlers : IExceptionMapping where TException : Exception { } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs index ae63bcd..8286bb5 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs @@ -1,14 +1,15 @@ using System; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Microsoft.AspNetCore.Http; namespace Commmunity.AspNetCore.ExceptionHandling.Exc { public class ReThrowExceptionHandler : IExceptionHandler { - public Task Handle(HttpContext httpContext, Exception exception) + public Task Handle(HttpContext httpContext, Exception exception) { - return Task.FromResult(true); + return Task.FromResult(HandlerResult.ReThrow); } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs index 9fec57a..2de10ab 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -28,36 +29,49 @@ private static async Task EnumerateExceptionMapping( ILogger logger = context.RequestServices.GetService>() ?? NullLoggerFactory.Instance.CreateLogger(Const.Category); - bool? throwRequired = null; + bool mappingExists = false; + HandlerResult handleResult = HandlerResult.ReThrow; foreach (Type type in policyOptions.GetExceptionsInternal()) { if (type.IsAssignableFrom(exceptionType)) - { - throwRequired = await EnumerateHandlers(context, exception, policyOptions, logger); + { + mappingExists = true; + handleResult = await EnumerateHandlers(context, type, exception, policyOptions, logger); - break; + if (handleResult == HandlerResult.ReThrow) + { + return true; + } + + if (handleResult != HandlerResult.NextChain) + { + break; + } } } - if (!throwRequired.HasValue) + if (!mappingExists) { logger.LogWarning(Events.PolicyNotFound, "Handlers mapping for exception type {exceptionType} not exists. Exception will be re-thrown. RequestId: {RequestId}", exceptionType, context.TraceIdentifier); + + return false; } - return throwRequired ?? true; + return handleResult == HandlerResult.ReThrow; } - private static async Task EnumerateHandlers( + private static async Task EnumerateHandlers( HttpContext context, + Type exceptionType, Exception exception, ExceptionHandlingPolicyOptions policyOptions, ILogger logger) { - bool? throwRequired = null; - Type exceptionType = exception.GetType(); + bool handlerExecuted = false; + HandlerResult handleResult = HandlerResult.ReThrow; IEnumerable handlers = policyOptions.GetHandlersInternal(exceptionType); @@ -70,14 +84,15 @@ private static async Task EnumerateHandlers( if (handler == null) { - throwRequired = true; + handlerExecuted = false; logger.LogError(Events.HandlerNotCreated, "Handler type {handlerType} can't be created because it not registered in IServiceProvider. RequestId: {RequestId}", handlerType, context.TraceIdentifier); } else { - throwRequired = await handler.Handle(context, exception); + handleResult = await handler.Handle(context, exception); + handlerExecuted = true; } } catch (Exception e) @@ -85,23 +100,26 @@ private static async Task EnumerateHandlers( logger.LogError(Events.HandlerError, e, "Unhandled exception executing handler of type {handlerType} on exception of type {exceptionType}. RequestId: {RequestId}", handlerType, exceptionType, context.TraceIdentifier); - throwRequired = true; + + return HandlerResult.ReThrow; } - if (throwRequired.Value) + if (handleResult != HandlerResult.NextHandler) { break; } } - if (!throwRequired.HasValue) + if (!handlerExecuted) { logger.LogWarning(Events.HandlersNotFound, "Handlers collection for exception type {exceptionType} is empty. Exception will be re-thrown. RequestId: {RequestId}", exceptionType, context.TraceIdentifier); + + return HandlerResult.ReThrow; } - return throwRequired ?? true; + return handleResult; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs new file mode 100644 index 0000000..8db28b8 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs @@ -0,0 +1,7 @@ +namespace Commmunity.AspNetCore.ExceptionHandling.Handlers +{ + public class HandlerOptions + { + public RequestStartedBehaviour RequestStartedBehaviour { get; set; } = RequestStartedBehaviour.ReThrow; + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs new file mode 100644 index 0000000..bfdab4a --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs @@ -0,0 +1,13 @@ +namespace Commmunity.AspNetCore.ExceptionHandling.Handlers +{ + public enum HandlerResult + { + ReThrow = 0, + + NextHandler = 1, + + NextChain = 2, + + Terminate = 3 + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs index 2199c2f..883bbc0 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs @@ -1,10 +1,9 @@ using System; using System.Threading.Tasks; -using Commmunity.AspNetCore.ExceptionHandling.Logs; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Commmunity.AspNetCore.ExceptionHandling +namespace Commmunity.AspNetCore.ExceptionHandling.Handlers { public abstract class HandlerStrongType : HandlerWithLogger, IExceptionHandler where TException : Exception @@ -16,7 +15,7 @@ protected HandlerStrongType(HandlerWithLoggerOptions options, ILoggerFactory log { } - public async Task Handle(HttpContext httpContext, Exception exception) + public async Task Handle(HttpContext httpContext, Exception exception) { if (exception is TException e) { @@ -28,10 +27,10 @@ public async Task Handle(HttpContext httpContext, Exception exception) "Excpetion type {exceptionType} not match current generic type {genericType}. Exception will be re-thrown.", exception.GetType(), typeof(TException)); - return true; + return HandlerResult.ReThrow; } } - protected abstract Task HandleStrongType(HttpContext httpContext, TException exception); + protected abstract Task HandleStrongType(HttpContext httpContext, TException exception); } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs index 2fc04c0..d362d74 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs @@ -1,7 +1,7 @@ using System; using Microsoft.Extensions.Logging; -namespace Commmunity.AspNetCore.ExceptionHandling +namespace Commmunity.AspNetCore.ExceptionHandling.Handlers { public class HandlerWithLogger { diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs index 588ce19..fcd3b86 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs @@ -1,6 +1,6 @@ -namespace Commmunity.AspNetCore.ExceptionHandling +namespace Commmunity.AspNetCore.ExceptionHandling.Handlers { - public class HandlerWithLoggerOptions + public class HandlerWithLoggerOptions : HandlerOptions { private string _loggerCategory = null; diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs new file mode 100644 index 0000000..fb1b791 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Commmunity.AspNetCore.ExceptionHandling.Handlers +{ + public class NextChainHandler : IExceptionHandler + { + public Task Handle(HttpContext httpContext, Exception exception) + { + return Task.FromResult(HandlerResult.NextChain); + } + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs index 2d08941..c6c8b01 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs @@ -1,11 +1,12 @@ using System; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Microsoft.AspNetCore.Http; namespace Commmunity.AspNetCore.ExceptionHandling { public interface IExceptionHandler { - Task Handle(HttpContext httpContext, Exception exception); + Task Handle(HttpContext httpContext, Exception exception); } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs index bbf6a9d..97b73b1 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Internal; @@ -20,7 +21,7 @@ public LogExceptionHandler(IOptions> settings) { _settings = settings ?? throw new ArgumentNullException(nameof(settings)); } - public Task Handle(HttpContext httpContext, Exception exception) + public Task Handle(HttpContext httpContext, Exception exception) { ILoggerFactory loggerFactory = httpContext.RequestServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory; @@ -42,7 +43,7 @@ public Task Handle(HttpContext httpContext, Exception exception) logger.Log(this.Settings.Level, eventId, state, exception, formatter); } - return Task.FromResult(false); + return Task.FromResult(HandlerResult.NextHandler); } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index 9281791..672a27f 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -1,11 +1,16 @@ using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; using Commmunity.AspNetCore.ExceptionHandling.Builder; using Commmunity.AspNetCore.ExceptionHandling.Exc; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Commmunity.AspNetCore.ExceptionHandling.Logs; using Commmunity.AspNetCore.ExceptionHandling.Response; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; namespace Commmunity.AspNetCore.ExceptionHandling { @@ -46,7 +51,7 @@ public static IExceptionMapping Clear( } // rethrow - public static IExceptionPolicyBuilder AddRethrow( + public static IExceptionPolicyBuilder Rethrow( this IExceptionMapping builder, int index = -1) where TException : Exception { @@ -54,46 +59,81 @@ public static IExceptionPolicyBuilder AddRethrow( return builder; } + // next chain + public static IExceptionPolicyBuilder NextChain( + this IExceptionMapping builder, int index = -1) + where TException : Exception + { + builder.EnsureHandler(index); + return builder; + } + // Log - public static IExceptionMapping AddLog( + public static IExceptionMapping Log( this IExceptionMapping builder, Action> settings = null, int index = -1) where TException : Exception { - LogHandlerOptions options = new LogHandlerOptions(); - settings?.Invoke(options); - builder.Services.TryAddSingleton(options); + builder.Services.Configure>(o => + { + var ho = o as LogHandlerOptions; + settings?.Invoke(ho); + }); + builder.EnsureHandler>(index); return builder; } // Set status code - public static IResponseHandlers AddNewResponse( - this IExceptionMapping builder, Func statusCodeFactory = null, int index = -1) + public static IResponseHandlers NewResponse( + this IExceptionMapping builder, + Func statusCodeFactory = null, + RequestStartedBehaviour requestStartedBehaviour = RequestStartedBehaviour.ReThrow, + int index = -1) where TException : Exception - { - if (statusCodeFactory != null) + { + builder.Services.Configure>(responceOptions => { - builder.Services.Configure>(codeOptions => - codeOptions.StatusCodeFactory = statusCodeFactory); - } + responceOptions.RequestStartedBehaviour = requestStartedBehaviour; + responceOptions.SetResponse.Add((context, exception) => + { + if (context.Response.Body.CanSeek) + context.Response.Body.SetLength(0L); + context.Response.StatusCode = + statusCodeFactory?.Invoke(exception) ?? (int) HttpStatusCode.InternalServerError; + return Task.CompletedTask; + }); + }); - builder.EnsureHandler>(index); + builder.EnsureHandler>(index); return builder as IResponseHandlers; } public static IResponseHandlers WithHeaders( - this IResponseHandlers builder, Action settings = null, int index = -1) + this IResponseHandlers builder, Action settings, int index = -1) where TException : Exception { - if (settings != null) - { - builder.Services.Configure>(codeOptions => - codeOptions.SetHeadersAction = settings); - } + builder.Services.Configure>(responceOptions => + { + responceOptions.SetResponse.Add((context, exception) => + { + settings?.Invoke(context.Response.Headers, exception); + return Task.CompletedTask; + }); + }); + + return builder; + } - builder.EnsureHandler>(index); + public static IResponseHandlers WithBody( + this IResponseHandlers builder, Func settings, int index = -1) + where TException : Exception + { + builder.Services.Configure>(responceOptions => + { + responceOptions.SetResponse.Add((context, exception) => settings(context.Response.Body, exception)); + }); return builder; } diff --git a/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs b/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs new file mode 100644 index 0000000..c977f76 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs @@ -0,0 +1,9 @@ +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public enum RequestStartedBehaviour + { + ReThrow = 0, + + Ignore = 1 + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs index 6766b25..6efb3e9 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs @@ -4,12 +4,12 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Response { - public class NewResponseHandler : RawResponseExceptionHandler - where TException : Exception - { - public NewResponseHandler(IOptions> options, - ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) - { - } - } + //public class NewResponseHandler : RawResponseExceptionHandler + //where TException : Exception + //{ + // public NewResponseHandler(IOptions> options, + // ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) + // { + // } + //} } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs index d4de4f8..c97a640 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs @@ -4,25 +4,25 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Response { - public class NewResponseOptions : IOptions>, IOptions> - where TException : Exception - { - public NewResponseOptions Value => this; + //public class NewResponseOptions : HandlerOptions, IOptions>, IOptions> + //where TException : Exception + //{ + // public NewResponseOptions Value => this; - public Func StatusCodeFactory = - exception => StatusCodes.Status500InternalServerError; + // public Func StatusCodeFactory = + // exception => StatusCodes.Status500InternalServerError; - RawResponseHandlerOptions IOptions>.Value => - new RawResponseHandlerOptions - { - SetResponse = - async (context, exception) => - { - context.Response.Headers.Clear(); - if (context.Response.Body.CanSeek) - context.Response.Body.SetLength(0L); - context.Response.StatusCode = this.StatusCodeFactory(exception); - } - }; - } + // RawResponseHandlerOptions IOptions>.Value => + // new RawResponseHandlerOptions + // { + // SetResponse = + // async (context, exception) => + // { + // context.Response.Headers.Clear(); + // if (context.Response.Body.CanSeek) + // context.Response.Body.SetLength(0L); + // context.Response.StatusCode = this.StatusCodeFactory(exception); + // } + // }; + //} } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs index 92bb69d..e6d7bf3 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Commmunity.AspNetCore.ExceptionHandling.Logs; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -21,25 +22,40 @@ public RawResponseExceptionHandler(IOptions HandleStrongType(HttpContext httpContext, TException exception) + protected override async Task HandleStrongType(HttpContext httpContext, TException exception) { if (httpContext.Response.HasStarted) { - this.Logger.LogError(ResponseHasStarted, - "Unable to execute {handletType} handler, because respnse already started. Exception will be re-thrown.", - this.GetType()); + if (this._options.RequestStartedBehaviour == RequestStartedBehaviour.ReThrow) + { + this.Logger.LogError(ResponseHasStarted, + "Unable to execute {handletType} handler, because respnse already started. Exception will be re-thrown.", + this.GetType()); - return true; + return HandlerResult.ReThrow; + } + else + { + return HandlerResult.NextHandler; + } + } await HandleResponseAsync(httpContext, exception); - return false; + return HandlerResult.NextHandler; } - protected virtual async Task HandleResponseAsync(HttpContext httpContext, TException exception) + protected virtual Task HandleResponseAsync(HttpContext httpContext, TException exception) { - if (_options.SetResponse != null) await _options.SetResponse(httpContext, exception); + Task result = Task.CompletedTask; + + foreach (Func func in this._options.SetResponse) + { + result = result.ContinueWith(task => func(httpContext, exception)); + } + + return result; } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs index a4f427f..d19d5ee 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; @@ -9,7 +11,7 @@ public class RawResponseHandlerOptions : HandlerWithLoggerOptions, IOptions> where TException : Exception { - public Func SetResponse { get; set; } = null; + public List> SetResponse { get; set; } = new List>(); public RawResponseHandlerOptions Value => this; } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs index b2bcaa5..ec8edb3 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs @@ -4,12 +4,12 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Response { - public class SetHeadersHandler : RawResponseExceptionHandler - where TException : Exception - { - public SetHeadersHandler(IOptions> options, - ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) - { - } - } + //public class SetHeadersHandler : RawResponseExceptionHandler + // where TException : Exception + //{ + // public SetHeadersHandler(IOptions> options, + // ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) + // { + // } + //} } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs index 020be82..fe55d60 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs @@ -4,22 +4,24 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Response { - public class SetHeadersOptions : IOptions>, IOptions> - where TException : Exception - { - public SetHeadersOptions Value => this; + //public class SetHeadersOptions : IOptions>, IOptions> + // where TException : Exception + //{ + // public SetHeadersOptions Value => this; - public Action SetHeadersAction = - (headers, exception) => { }; + // public Action SetHeadersAction = + // (headers, exception) => { }; - RawResponseHandlerOptions IOptions>.Value => - new RawResponseHandlerOptions - { - SetResponse = - async (context, exception) => - { - this.SetHeadersAction(context.Response.Headers, exception); - } - }; - } + // RawResponseHandlerOptions IOptions>.Value + // { + // get + // { + // return new RawResponseHandlerOptions + // { + // SetResponse = + // async (context, exception) => { this.SetHeadersAction(context.Response.Headers, exception); } + // }; + // } + // } + //} } \ No newline at end of file From a562de5279eaa0146b9fba7b400ff49a07ed9e89 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Sat, 14 Jul 2018 11:06:45 +0300 Subject: [PATCH 06/21] mark handled concept --- .../Controllers/ValuesController.cs | 11 +++ .../Startup.cs | 18 +++-- .../Events.cs | 4 + .../ExceptionHandlingPolicyMiddleware.cs | 78 +++++++++++++++---- .../Handlers/HandlerOptions.cs | 7 -- .../Handlers/HandlerResult.cs | 4 +- .../Handlers/HandlerWithLoggerOptions.cs | 2 +- .../Handlers/MarkHandledHandler.cs | 16 ++++ .../Logs/DisableLoggingHandler.cs | 21 +++++ .../Logs/LogExceptionHandler.cs | 5 ++ .../PolicyBuilderExtensions.cs | 51 +++++++++--- .../RequestStartedBehaviour.cs | 2 +- .../Response/NewResponseHandler.cs | 15 ---- .../Response/NewResponseOptions.cs | 28 ------- .../Response/RawResponseHandlerOptions.cs | 1 + .../Response/SetHeadersHandler.cs | 15 ---- .../Response/SetHeadersOptions.cs | 27 ------- .../Retry/RetryHandler.cs | 53 +++++++++++++ .../Retry/RetryHandlerOptions.cs | 17 ++++ 19 files changed, 247 insertions(+), 128 deletions(-) delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs index b8f7dd5..4afbd96 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs @@ -21,6 +21,17 @@ public IEnumerable Get() [HttpGet("{id}")] public string Get(int id) { + + if (id > 25) + { + throw new DuplicateWaitObjectException(); + } + + if (id > 20) + { + throw new DuplicateNameException(); + } + if (id > 15) { throw new InvalidConstraintException(); diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index 500a865..24fe993 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.IO; using System.Linq; using System.Text; @@ -29,9 +30,13 @@ public void ConfigureServices(IServiceCollection services) services.AddExceptionHandlingPolicies(options => { + options.For().Retry().NextChain(); + options.For().Retry(); + options.For().Log().Rethrow(); + options.For() - .NewResponse(e => 400) + .Response(e => 400) .WithHeaders((h, e) => h["X-qwe"] = e.Message) .WithBody((stream, exception) => { @@ -41,18 +46,17 @@ public void ConfigureServices(IServiceCollection services) } }) .NextChain(); - options.For().Log(lo => - { - lo.Formatter = (o, e) => "qwe"; - }) - .NewResponse(e => 500, RequestStartedBehaviour.Ignore).WithBody( + options.For() + .Log(lo => { lo.Formatter = (o, e) => "qwe"; }) + .Response(e => 500, RequestStartedBehaviour.SkipHandler).WithBody( async (stream, exception) => { using (StreamWriter sw = new StreamWriter(stream)) { await sw.WriteAsync("unhandled exception"); } - }); + }) + .Handled(); }); services.AddLogging(); diff --git a/Commmunity.AspNetCore.ExceptionHandling/Events.cs b/Commmunity.AspNetCore.ExceptionHandling/Events.cs index e849336..73acc80 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Events.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Events.cs @@ -8,5 +8,9 @@ public static class Events public static readonly EventId PolicyNotFound = new EventId(101, "PolicyForExceptionNotRegistered"); public static readonly EventId HandlersNotFound = new EventId(102, "HandlersCollectionEmpty"); public static readonly EventId HandlerNotCreated = new EventId(103, "HandlersCanNotBeCreated"); + public static readonly EventId RetryForStartedResponce = new EventId(104, "RetryForStartedResponce"); + public static readonly EventId Retry = new EventId(105, "Retry"); + public static readonly EventId UnhandledResult = new EventId(106, "UnhandledResult"); + public static readonly EventId RetryIterationExceedLimit = new EventId(107, "RetryIterationExceedLimit"); } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs index 2de10ab..cd4ffb3 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs @@ -12,6 +12,8 @@ namespace Commmunity.AspNetCore.ExceptionHandling { public class ExceptionHandlingPolicyMiddleware : IMiddleware { + public const int MaxRetryIterations = 10; + private readonly IOptions options; public ExceptionHandlingPolicyMiddleware(IOptions options) @@ -19,15 +21,13 @@ public ExceptionHandlingPolicyMiddleware(IOptions EnumerateExceptionMapping( + private static async Task EnumerateExceptionMapping( HttpContext context, ExceptionHandlingPolicyOptions policyOptions, - Exception exception) + Exception exception, + ILogger logger) { - Type exceptionType = exception.GetType(); - - ILogger logger = context.RequestServices.GetService>() ?? - NullLoggerFactory.Instance.CreateLogger(Const.Category); + Type exceptionType = exception.GetType(); bool mappingExists = false; HandlerResult handleResult = HandlerResult.ReThrow; @@ -38,12 +38,7 @@ private static async Task EnumerateExceptionMapping( { mappingExists = true; handleResult = await EnumerateHandlers(context, type, exception, policyOptions, logger); - - if (handleResult == HandlerResult.ReThrow) - { - return true; - } - + if (handleResult != HandlerResult.NextChain) { break; @@ -57,10 +52,10 @@ private static async Task EnumerateExceptionMapping( "Handlers mapping for exception type {exceptionType} not exists. Exception will be re-thrown. RequestId: {RequestId}", exceptionType, context.TraceIdentifier); - return false; + return HandlerResult.ReThrow; } - return handleResult == HandlerResult.ReThrow; + return handleResult; } private static async Task EnumerateHandlers( @@ -123,6 +118,14 @@ private static async Task EnumerateHandlers( } public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + ILogger logger = context.RequestServices.GetService>() ?? + NullLoggerFactory.Instance.CreateLogger(Const.Category); + + await InvokeWithRetryAsync(context, next, logger, 0); + } + + private async Task InvokeWithRetryAsync(HttpContext context, RequestDelegate next, ILogger logger, int iteration) { try { @@ -132,10 +135,53 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) { ExceptionHandlingPolicyOptions policyOptions = this.options.Value; - bool throwRequired = await EnumerateExceptionMapping(context, policyOptions, exception); + var result = await EnumerateExceptionMapping(context, policyOptions, exception, logger); + + if (result == HandlerResult.ReThrow) + { + throw; + } + + if (result == HandlerResult.Retry) + { + // We can't do anything if the response has already started, just abort. + if (context.Response.HasStarted) + { + logger.LogWarning(Events.RetryForStartedResponce, + exception, + "Retry requested when responce already started. Exception will be re-thrown. RequestId: {RequestId}", + context.TraceIdentifier); + + throw; + } - if (throwRequired) + if (iteration > MaxRetryIterations) + { + logger.LogCritical(Events.RetryIterationExceedLimit, + exception, + "Retry iterations count exceed limit of {limit}. Possible issues with retry policy configuration. Exception will be re-thrown. RequestId: {RequestId}", + MaxRetryIterations, + context.TraceIdentifier); + + throw; + } + + logger.LogWarning(Events.Retry, + exception, + "Retry requested. Iteration {iteration} RequestId: {RequestId}", + iteration, + context.TraceIdentifier); + + await InvokeWithRetryAsync(context, next, logger, iteration + 1); + } + + if (result != HandlerResult.Handled) { + logger.LogWarning(Events.UnhandledResult, + exception, + "After execution of all handlers exception was not marked as handled and will be re thrown. RequestId: {RequestId}", + context.TraceIdentifier); + throw; } } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs deleted file mode 100644 index 8db28b8..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Commmunity.AspNetCore.ExceptionHandling.Handlers -{ - public class HandlerOptions - { - public RequestStartedBehaviour RequestStartedBehaviour { get; set; } = RequestStartedBehaviour.ReThrow; - } -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs index bfdab4a..aeb4e9a 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs @@ -8,6 +8,8 @@ public enum HandlerResult NextChain = 2, - Terminate = 3 + Retry = 3, + + Handled = 4 } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs index fcd3b86..355cb03 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs @@ -1,6 +1,6 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Handlers { - public class HandlerWithLoggerOptions : HandlerOptions + public class HandlerWithLoggerOptions { private string _loggerCategory = null; diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs new file mode 100644 index 0000000..cbe778d --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Commmunity.AspNetCore.ExceptionHandling.Handlers +{ + public class MarkHandledHandler : IExceptionHandler + { + public Task Handle(HttpContext httpContext, Exception exception) + { + return Task.FromResult(HandlerResult.Handled); + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs new file mode 100644 index 0000000..9ce1de3 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; +using Microsoft.AspNetCore.Http; + +namespace Commmunity.AspNetCore.ExceptionHandling.Logs +{ + class DisableLoggingHandler : IExceptionHandler + { + public const string DisableLoggingFlagKey = "427EDE68BE9A"; + + public Task Handle(HttpContext httpContext, Exception exception) + { + exception.Data[DisableLoggingFlagKey] = string.Empty; + + return Task.FromResult(HandlerResult.NextHandler); + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs index 97b73b1..60a5c3d 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs @@ -23,6 +23,11 @@ public LogExceptionHandler(IOptions> settings) } public Task Handle(HttpContext httpContext, Exception exception) { + if (exception.Data.Contains(DisableLoggingHandler.DisableLoggingFlagKey)) + { + return Task.FromResult(HandlerResult.NextHandler); + } + ILoggerFactory loggerFactory = httpContext.RequestServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory; diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index 672a27f..dcc874f 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -7,6 +7,7 @@ using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Commmunity.AspNetCore.ExceptionHandling.Logs; using Commmunity.AspNetCore.ExceptionHandling.Response; +using Commmunity.AspNetCore.ExceptionHandling.Retry; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -68,24 +69,50 @@ public static IExceptionPolicyBuilder NextChain( return builder; } + // mark handled + public static IExceptionPolicyBuilder Handled( + this IExceptionMapping builder, int index = -1) + where TException : Exception + { + builder.EnsureHandler(index); + return builder; + } + + // Retry + public static IExceptionMapping Retry( + this IExceptionMapping builder, Action> settings = null, int index = -1) + where TException : Exception + { + builder.Services.Configure>(opt => settings?.Invoke(opt)); + + builder.EnsureHandler>(index); + + return builder; + } + // Log public static IExceptionMapping Log( this IExceptionMapping builder, Action> settings = null, int index = -1) where TException : Exception { - builder.Services.Configure>(o => - { - var ho = o as LogHandlerOptions; - settings?.Invoke(ho); - }); + builder.Services.Configure>(opt => settings?.Invoke(opt)); builder.EnsureHandler>(index); return builder; } + public static IExceptionMapping DisableLog( + this IExceptionMapping builder, int index = -1) + where TException : Exception + { + builder.EnsureHandler(index); + + return builder; + } + // Set status code - public static IResponseHandlers NewResponse( + public static IResponseHandlers Response( this IExceptionMapping builder, Func statusCodeFactory = null, RequestStartedBehaviour requestStartedBehaviour = RequestStartedBehaviour.ReThrow, @@ -96,11 +123,10 @@ public static IResponseHandlers NewResponse( { responceOptions.RequestStartedBehaviour = requestStartedBehaviour; responceOptions.SetResponse.Add((context, exception) => - { - if (context.Response.Body.CanSeek) - context.Response.Body.SetLength(0L); + { context.Response.StatusCode = statusCodeFactory?.Invoke(exception) ?? (int) HttpStatusCode.InternalServerError; + return Task.CompletedTask; }); }); @@ -132,7 +158,12 @@ public static IResponseHandlers WithBody( { builder.Services.Configure>(responceOptions => { - responceOptions.SetResponse.Add((context, exception) => settings(context.Response.Body, exception)); + responceOptions.SetResponse.Add((context, exception) => + { + if (context.Response.Body.CanSeek) + context.Response.Body.SetLength(0L); + return settings(context.Response.Body, exception); + }); }); return builder; diff --git a/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs b/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs index c977f76..64cad98 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs @@ -4,6 +4,6 @@ public enum RequestStartedBehaviour { ReThrow = 0, - Ignore = 1 + SkipHandler = 1 } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs deleted file mode 100644 index 6efb3e9..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseHandler.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Commmunity.AspNetCore.ExceptionHandling.Response -{ - //public class NewResponseHandler : RawResponseExceptionHandler - //where TException : Exception - //{ - // public NewResponseHandler(IOptions> options, - // ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) - // { - // } - //} -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs deleted file mode 100644 index c97a640..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/NewResponseOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; - -namespace Commmunity.AspNetCore.ExceptionHandling.Response -{ - //public class NewResponseOptions : HandlerOptions, IOptions>, IOptions> - //where TException : Exception - //{ - // public NewResponseOptions Value => this; - - // public Func StatusCodeFactory = - // exception => StatusCodes.Status500InternalServerError; - - // RawResponseHandlerOptions IOptions>.Value => - // new RawResponseHandlerOptions - // { - // SetResponse = - // async (context, exception) => - // { - // context.Response.Headers.Clear(); - // if (context.Response.Body.CanSeek) - // context.Response.Body.SetLength(0L); - // context.Response.StatusCode = this.StatusCodeFactory(exception); - // } - // }; - //} -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs index d19d5ee..f761c9c 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs @@ -13,5 +13,6 @@ public class RawResponseHandlerOptions : HandlerWithLoggerOptions, { public List> SetResponse { get; set; } = new List>(); public RawResponseHandlerOptions Value => this; + public RequestStartedBehaviour RequestStartedBehaviour { get; set; } = RequestStartedBehaviour.ReThrow; } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs deleted file mode 100644 index ec8edb3..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersHandler.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Commmunity.AspNetCore.ExceptionHandling.Response -{ - //public class SetHeadersHandler : RawResponseExceptionHandler - // where TException : Exception - //{ - // public SetHeadersHandler(IOptions> options, - // ILoggerFactory loggerFactory) : base(options.Value, loggerFactory) - // { - // } - //} -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs deleted file mode 100644 index fe55d60..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/SetHeadersOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; - -namespace Commmunity.AspNetCore.ExceptionHandling.Response -{ - //public class SetHeadersOptions : IOptions>, IOptions> - // where TException : Exception - //{ - // public SetHeadersOptions Value => this; - - // public Action SetHeadersAction = - // (headers, exception) => { }; - - // RawResponseHandlerOptions IOptions>.Value - // { - // get - // { - // return new RawResponseHandlerOptions - // { - // SetResponse = - // async (context, exception) => { this.SetHeadersAction(context.Response.Headers, exception); } - // }; - // } - // } - //} -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs new file mode 100644 index 0000000..e32ee4e --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Handlers; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; + +namespace Commmunity.AspNetCore.ExceptionHandling.Retry +{ + class RetryHandler : IExceptionHandler + where TException : Exception + { + private const string CurrentRetryGuardKey = "71DAAFEC7B56"; + + private readonly RetryHandlerOptions options; + + public RetryHandler(IOptions> options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + this.options = options.Value; + } + + public Task Handle(HttpContext httpContext, Exception exception) + { + if (httpContext.Response.HasStarted) + { + // Retry is not possible, so let's next handler decide + return Task.FromResult(HandlerResult.NextHandler); + } + + if (!httpContext.Items.ContainsKey(CurrentRetryGuardKey)) + { + httpContext.Items[CurrentRetryGuardKey] = 0; + } + + if ((int)httpContext.Items[CurrentRetryGuardKey] < this.options.MaxRetryCount) + { + httpContext.Items[CurrentRetryGuardKey] = (int)httpContext.Items[CurrentRetryGuardKey] + 1; + return Task.FromResult(HandlerResult.Retry); + } + else + { + // Retry is not possible, so let's next handler decide + return Task.FromResult(HandlerResult.NextHandler); + } + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs new file mode 100644 index 0000000..69b680e --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Commmunity.AspNetCore.ExceptionHandling.Retry +{ + public class RetryHandlerOptions : IOptions> + where TException : Exception + { + public RetryHandlerOptions Value => this; + + public RequestStartedBehaviour RequestStartedBehaviour { get; set; } = RequestStartedBehaviour.ReThrow; + + public int MaxRetryCount { get; set; } = 1; + } +} From 41c49eee3c78f2860c2de306be39e2f6ce883d99 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Sat, 14 Jul 2018 13:22:11 +0300 Subject: [PATCH 07/21] json body handler --- ...tCore.ExceptionHandling.Integration.csproj | 2 +- .../Startup.cs | 13 +-- ...munity.AspNetCore.ExceptionHandling.csproj | 17 +++- .../PolicyBuilderExtensions.cs | 82 ++++++++++++++++-- .../Response/RawResponseExceptionHandler.cs | 21 +++++ Commmunity.AspNetCore.sln | 8 +- README.md | 2 +- build.ps1 | 3 + sgn.snk | Bin 0 -> 596 bytes 9 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 build.ps1 create mode 100644 sgn.snk diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj index 564d7de..6ce5eea 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj @@ -9,7 +9,7 @@ - + diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index 24fe993..379fc30 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -38,7 +38,7 @@ public void ConfigureServices(IServiceCollection services) options.For() .Response(e => 400) .WithHeaders((h, e) => h["X-qwe"] = e.Message) - .WithBody((stream, exception) => + .WithBody((req,stream, exception) => { using (StreamWriter sw = new StreamWriter(stream)) { @@ -48,17 +48,10 @@ public void ConfigureServices(IServiceCollection services) .NextChain(); options.For() .Log(lo => { lo.Formatter = (o, e) => "qwe"; }) - .Response(e => 500, RequestStartedBehaviour.SkipHandler).WithBody( - async (stream, exception) => - { - using (StreamWriter sw = new StreamWriter(stream)) - { - await sw.WriteAsync("unhandled exception"); - } - }) + .Response(e => 500, RequestStartedBehaviour.SkipHandler).ClearCacheHeaders().WithBodyJson((r, e) => new { msg = e.Message, path = r.Path }) .Handled(); }); - + services.AddLogging(); } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj index b2c7ec2..62f7368 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj +++ b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj @@ -1,12 +1,27 @@ - + netstandard2.0 + Middleware to configure exception handling policies. Configure chain of handlers per exception type. OOTB handlers: log, retry, set responce headers and body + https://github.com/IharYakimush/AspNetCore + https://github.com/IharYakimush/AspNetCore/blob/develop/LICENSE + IharYakimush + AspNetCore exception handling policy + 2.0.0.0 + true + true + 2.0.0.0 + + IharYakimush + 2.0.0 + true + ..\sgn.snk + diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index dcc874f..2dce607 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -12,6 +12,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; namespace Commmunity.AspNetCore.ExceptionHandling { @@ -122,12 +124,10 @@ public static IResponseHandlers Response( builder.Services.Configure>(responceOptions => { responceOptions.RequestStartedBehaviour = requestStartedBehaviour; + responceOptions.SetResponse.Add((context, exception) => - { - context.Response.StatusCode = - statusCodeFactory?.Invoke(exception) ?? (int) HttpStatusCode.InternalServerError; - - return Task.CompletedTask; + { + return RawResponseExceptionHandler.SetStatusCode(context, exception, statusCodeFactory); }); }); @@ -140,11 +140,38 @@ public static IResponseHandlers WithHeaders( this IResponseHandlers builder, Action settings, int index = -1) where TException : Exception { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + builder.Services.Configure>(responceOptions => { responceOptions.SetResponse.Add((context, exception) => { - settings?.Invoke(context.Response.Headers, exception); + settings.Invoke(context.Response.Headers, exception); + return Task.CompletedTask; + }); + }); + + return builder; + } + + public static IResponseHandlers ClearCacheHeaders( + this IResponseHandlers builder, int index = -1) + where TException : Exception + { + builder.Services.Configure>(responceOptions => + { + responceOptions.SetResponse.Add((context, exception) => + { + HttpResponse response = context.Response; + + response.Headers[HeaderNames.CacheControl] = "no-cache"; + response.Headers[HeaderNames.Pragma] = "no-cache"; + response.Headers[HeaderNames.Expires] = "-1"; + response.Headers.Remove(HeaderNames.ETag); + return Task.CompletedTask; }); }); @@ -153,20 +180,59 @@ public static IResponseHandlers WithHeaders( } public static IResponseHandlers WithBody( - this IResponseHandlers builder, Func settings, int index = -1) + this IResponseHandlers builder, Func settings, int index = -1) where TException : Exception { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + builder.Services.Configure>(responceOptions => { responceOptions.SetResponse.Add((context, exception) => { if (context.Response.Body.CanSeek) context.Response.Body.SetLength(0L); - return settings(context.Response.Body, exception); + + return settings(context.Request,context.Response.Body, exception); }); }); return builder; } + + public static IResponseHandlers WithBodyJson( + this IResponseHandlers builder, Func value, JsonSerializerSettings settings = null, int index = -1) + where TException : Exception + { + return builder.WithBody((request, stream, exception) => + { + if (settings == null) + { + settings = request.HttpContext.RequestServices.GetService(); + } + + if (settings == null) + { + settings = new JsonSerializerSettings(); + } + + JsonSerializer jsonSerializer = JsonSerializer.Create(settings); + + var headers = request.HttpContext.Response.Headers; + if (!headers.ContainsKey(HeaderNames.ContentType)) + { + headers[HeaderNames.ContentType] = "application/json"; + } + + using (TextWriter textWriter = new StreamWriter(stream)) + { + jsonSerializer.Serialize(textWriter, value(request, exception)); + } + + return Task.CompletedTask; + }); + } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs index e6d7bf3..de6375f 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Threading; using System.Threading.Tasks; using Commmunity.AspNetCore.ExceptionHandling.Handlers; @@ -12,6 +13,8 @@ namespace Commmunity.AspNetCore.ExceptionHandling.Response public class RawResponseExceptionHandler : HandlerStrongType where TException : Exception { + public const string StatusCodeSetKey = "5D1CFED34A39"; + private readonly RawResponseHandlerOptions _options; private static readonly EventId ResponseHasStarted = new EventId(127, "ResponseAlreadyStarted"); @@ -57,5 +60,23 @@ protected virtual Task HandleResponseAsync(HttpContext httpContext, TException e return result; } + + public static Task SetStatusCode(HttpContext context, TException exception, Func statusCodeFactory) + { + if(statusCodeFactory == null) + { + if (!context.Items.ContainsKey(StatusCodeSetKey)) + { + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + } + } + else + { + context.Response.StatusCode = statusCodeFactory.Invoke(exception); + context.Items[StatusCodeSetKey] = true; + } + + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.sln b/Commmunity.AspNetCore.sln index f99197a..92ee599 100644 --- a/Commmunity.AspNetCore.sln +++ b/Commmunity.AspNetCore.sln @@ -5,7 +5,13 @@ VisualStudioVersion = 15.0.27428.2015 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling", "Commmunity.AspNetCore.ExceptionHandling\Commmunity.AspNetCore.ExceptionHandling.csproj", "{97ECCF71-494E-48FA-995A-AB1F13975A61}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commmunity.AspNetCore.ExceptionHandling.Integration", "Commmunity.AspNetCore.ExceptionHandling.Integration\Commmunity.AspNetCore.ExceptionHandling.Integration.csproj", "{393C6033-4255-43C3-896D-BFE30E264E4A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling.Integration", "Commmunity.AspNetCore.ExceptionHandling.Integration\Commmunity.AspNetCore.ExceptionHandling.Integration.csproj", "{393C6033-4255-43C3-896D-BFE30E264E4A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C825A429-B51F-4E5D-BC78-5E0A390D0C38}" + ProjectSection(SolutionItems) = preProject + build.ps1 = build.ps1 + README.md = README.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/README.md b/README.md index 6780fe5..72e9d9f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # AspNetCore-ExceptionHandling -Exception handling middleware for AspNetCore +Middleware to configure exception handling policies. Configure chain of handlers per exception type. OOTB handlers: log, retry, set responce headers and body diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..c940f6a --- /dev/null +++ b/build.ps1 @@ -0,0 +1,3 @@ +dotnet restore .\Commmunity.AspNetCore.sln + +dotnet build .\Commmunity.AspNetCore.sln -c Release \ No newline at end of file diff --git a/sgn.snk b/sgn.snk new file mode 100644 index 0000000000000000000000000000000000000000..03a5a535289d7545fd92296cae24bb640b1f3005 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097%O^Vqg-pEGMu3jmHmaRXe8K9&g69%cW z&jn?Rt}aNS@KChi){rj`=#q+f|Hw~sO3sPG-rYp|mAi_Mn=5LyQX4^yV+A!01hQYM zFAg`c+Zj9d{6VGORXG+(@jbQiTNLLgbgKHq6TWjp&1rpwcGA}?I(;pRpe?F`ZuQf< zYAxxAuZU;bL>vHoBr?&+%gosV;bQRq@gX1bIK^-kK?^$MCegi9S@ zi+EU|{J&od9WU7L>)xwK&0|gJd)eX7Y}EvH7qeU{U+m)!+oQzz68%X;yOqK^GxZ6x z`(@cI0l^45cdPu=1h$cOAiDq{8UOm-rzU?qMlhSe-uRpd1BceVGQL10SAJ`#%LB+b z%X*$``@se28qA#nYhmo_lzp?L@gcJn*-fAUms#Wj%Z_tk(b-A;T(Ul68h5mHp`sJ+wAvh zdUDFl2g9sy(NTXne!EnxMVhJ=JLXzpLzgcJW%jzr$HX-P3sj~uUIy0Ap)okxkG+=N zb`9UTh>O)(b-k?D09s;RfA-Kpm@u(ezPbN!$7^4RyD`!1WPTuzjPZ8qEC7)FwLvSM z=HN8M6DlEhGM8%;-9QDNL*p2c5H1ztiy?@?A58G#PaknYYMiszD_zr3fE2gYS^-=t i((i!;T4!y Date: Sat, 14 Jul 2018 17:05:56 +0300 Subject: [PATCH 08/21] json body --- .../Startup.cs | 13 ++++--- .../ExceptionHandlingPolicyMiddleware.cs | 2 ++ .../PolicyBuilderExtensions.cs | 32 ++++++++++------- .../Response/RawResponseExceptionHandler.cs | 34 +++++++++++++++++-- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index 379fc30..0935159 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -31,21 +31,20 @@ public void ConfigureServices(IServiceCollection services) services.AddExceptionHandlingPolicies(options => { options.For().Retry().NextChain(); + options.For().Retry(); options.For().Log().Rethrow(); options.For() .Response(e => 400) - .WithHeaders((h, e) => h["X-qwe"] = e.Message) - .WithBody((req,stream, exception) => - { - using (StreamWriter sw = new StreamWriter(stream)) - { - return sw.WriteAsync(exception.ToString()); - } + .Headers((h, e) => h["X-qwe"] = e.Message) + .WithBody((req,sw, exception) => + { + return sw.WriteAsync(exception.ToString()); }) .NextChain(); + options.For() .Log(lo => { lo.Formatter = (o, e) => "qwe"; }) .Response(e => 500, RequestStartedBehaviour.SkipHandler).ClearCacheHeaders().WithBodyJson((r, e) => new { msg = e.Message, path = r.Path }) diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs index cd4ffb3..5979080 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs @@ -172,6 +172,8 @@ private async Task InvokeWithRetryAsync(HttpContext context, RequestDelegate nex iteration, context.TraceIdentifier); + context.Response.Headers.Clear(); + await InvokeWithRetryAsync(context, next, logger, iteration + 1); } diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index 2dce607..af71b66 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -136,7 +136,7 @@ public static IResponseHandlers Response( return builder as IResponseHandlers; } - public static IResponseHandlers WithHeaders( + public static IResponseHandlers Headers( this IResponseHandlers builder, Action settings, int index = -1) where TException : Exception { @@ -180,22 +180,19 @@ public static IResponseHandlers ClearCacheHeaders( } public static IResponseHandlers WithBody( - this IResponseHandlers builder, Func settings, int index = -1) + this IResponseHandlers builder, Func settings, int index = -1) where TException : Exception { if (settings == null) { throw new ArgumentNullException(nameof(settings)); - } + } builder.Services.Configure>(responceOptions => { responceOptions.SetResponse.Add((context, exception) => { - if (context.Response.Body.CanSeek) - context.Response.Body.SetLength(0L); - - return settings(context.Request,context.Response.Body, exception); + return RawResponseExceptionHandler.SetBody(context, exception, settings); }); }); @@ -206,7 +203,7 @@ public static IResponseHandlers WithBodyJson( this IResponseHandlers builder, Func value, JsonSerializerSettings settings = null, int index = -1) where TException : Exception { - return builder.WithBody((request, stream, exception) => + return builder.WithBody((request, streamWriter, exception) => { if (settings == null) { @@ -226,12 +223,23 @@ public static IResponseHandlers WithBodyJson( headers[HeaderNames.ContentType] = "application/json"; } - using (TextWriter textWriter = new StreamWriter(stream)) + TObject val = value(request, exception); + + //return Task.CompletedTask; + using (MemoryStream ms = new MemoryStream()) { - jsonSerializer.Serialize(textWriter, value(request, exception)); - } + using (StreamWriter sw = new StreamWriter(ms, streamWriter.Encoding, 1024, true)) + { + jsonSerializer.Serialize(sw, val); + } - return Task.CompletedTask; + ms.Seek(0, SeekOrigin.Begin); + byte[] array = ms.ToArray(); + BinaryWriter binaryWriter = new BinaryWriter(streamWriter.BaseStream, streamWriter.Encoding, true); + binaryWriter.Write(array); + + return Task.CompletedTask; + } }); } } diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs index de6375f..ca96207 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs @@ -1,7 +1,10 @@ using System; +using System.IO; using System.Net; +using System.Text; using System.Threading; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Builder; using Commmunity.AspNetCore.ExceptionHandling.Handlers; using Commmunity.AspNetCore.ExceptionHandling.Logs; using Microsoft.AspNetCore.Http; @@ -15,6 +18,8 @@ public class RawResponseExceptionHandler : HandlerStrongType _options; private static readonly EventId ResponseHasStarted = new EventId(127, "ResponseAlreadyStarted"); @@ -40,8 +45,7 @@ protected override async Task HandleStrongType(HttpContext httpCo else { return HandlerResult.NextHandler; - } - + } } await HandleResponseAsync(httpContext, exception); @@ -78,5 +82,31 @@ public static Task SetStatusCode(HttpContext context, TException exception, Func return Task.CompletedTask; } + + public static Task SetBody(HttpContext context, TException exception, Func settings) + where TException : Exception + { + if (!context.Items.ContainsKey(BodySetKey)) + { + context.Items[BodySetKey] = true; + + Stream stream = context.Response.Body; + + if (stream.CanSeek) + { + stream.Seek(0, SeekOrigin.Begin); + //stream.SetLength(0); + } + + if (stream.CanWrite) + { + StreamWriter writer = new StreamWriter(stream,Encoding.UTF8,1024, true); + + return settings(context.Request, writer, exception); + } + } + + return Task.CompletedTask; + } } } \ No newline at end of file From 469caf81620e5e2a443018267e8ce1cfff31c565 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 12:01:23 +0300 Subject: [PATCH 09/21] responce body for MVC --- ...tCore.ExceptionHandling.Integration.csproj | 5 +- .../Startup.cs | 9 ++- ...ty.AspNetCore.ExceptionHandling.Mvc.csproj | 34 ++++++++++ .../PolicyBuilderExtensions.cs | 64 +++++++++++++++++++ ...re.ExceptionHandling.NewtonsoftJson.csproj | 24 +++++++ .../PolicyBuilderExtensions.cs | 59 +++++++++++++++++ ...munity.AspNetCore.ExceptionHandling.csproj | 24 ++++++- .../PolicyBuilderExtensions.cs | 49 +------------- .../Response/RawResponseExceptionHandler.cs | 3 +- Commmunity.AspNetCore.sln | 12 ++++ 10 files changed, 223 insertions(+), 60 deletions(-) create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj create mode 100644 Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj index 6ce5eea..22c17db 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 @@ -9,7 +9,7 @@ - + @@ -18,6 +18,7 @@ + diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index 0935159..1bd3ce6 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Mvc; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -39,15 +40,13 @@ public void ConfigureServices(IServiceCollection services) options.For() .Response(e => 400) .Headers((h, e) => h["X-qwe"] = e.Message) - .WithBody((req,sw, exception) => - { - return sw.WriteAsync(exception.ToString()); - }) + .WithBody((req,sw, exception) => sw.WriteAsync(exception.ToString())) .NextChain(); options.For() .Log(lo => { lo.Formatter = (o, e) => "qwe"; }) - .Response(e => 500, RequestStartedBehaviour.SkipHandler).ClearCacheHeaders().WithBodyJson((r, e) => new { msg = e.Message, path = r.Path }) + //.Response(e => 500, RequestStartedBehaviour.SkipHandler).ClearCacheHeaders().WithBodyJson((r, e) => new { msg = e.Message, path = r.Path }) + .Response(e => 500, RequestStartedBehaviour.SkipHandler).ClearCacheHeaders().WithBodyObjectResult((r, e) => new { msg = e.Message, path = r.Path }) .Handled(); }); diff --git a/Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj b/Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj new file mode 100644 index 0000000..a5b1bf0 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp2.1 + Extension methods to configure exception handler which write MVC action result to responce body. Userfull for writing objects + https://github.com/IharYakimush/AspNetCore + https://github.com/IharYakimush/AspNetCore/blob/develop/LICENSE + IharYakimush + AspNetCore exception handling policy mvc action result + 2.0.0.0 + true + true + 2.0.0.0 + + IharYakimush + 2.0.0 + true + ..\sgn.snk + + Library + + + + + + + + + + + + + + diff --git a/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs new file mode 100644 index 0000000..8b7c191 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs @@ -0,0 +1,64 @@ +using System; +using Commmunity.AspNetCore.ExceptionHandling.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Commmunity.AspNetCore.ExceptionHandling.Mvc +{ + public static class PolicyBuilderExtensions + { + private static readonly RouteData EmptyRouteData = new RouteData(); + + private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor(); + + public static IResponseHandlers WithBodyActionResult( + this IResponseHandlers builder, Func resultFactory, int index = -1) + where TException : Exception + where TResult : IActionResult + { + return builder.WithBody((request, streamWriter, exception) => + { + var context = request.HttpContext; + var executor = context.RequestServices.GetService>(); + + if (executor == null) + { + throw new InvalidOperationException($"No result executor for '{typeof(TResult).FullName}' has been registered."); + } + + var routeData = context.GetRouteData() ?? EmptyRouteData; + + var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor); + + return executor.ExecuteAsync(actionContext, resultFactory(request, exception)); + }); + } + + public static IResponseHandlers WithBodyActionResult( + this IResponseHandlers builder, TResult result, int index = -1) + where TException : Exception + where TResult : IActionResult + { + return builder.WithBodyActionResult((request, exception) => result); + } + + public static IResponseHandlers WithBodyObjectResult( + this IResponseHandlers builder, TObject value, int index = -1) + where TException : Exception + { + return builder.WithBodyActionResult(new ObjectResult(value), index); + } + + public static IResponseHandlers WithBodyObjectResult( + this IResponseHandlers builder, Func valueFactory, int index = -1) + where TException : Exception + { + return builder.WithBodyActionResult( + (request, exception) => new ObjectResult(valueFactory(request, exception)), index); + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj b/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj new file mode 100644 index 0000000..ec9b243 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.0 + Extension methods to configure exception handler which write object serialized using Newtonsoft.Json serializer to responce body. In case of using netcore2.1+ use Commmunity.AspNetCore.ExceptionHandling.Mvc package instead + IharYakimush + IharYakimush + https://github.com/IharYakimush/AspNetCore/blob/develop/LICENSE + https://github.com/IharYakimush/AspNetCore + AspNetCore exception handling policy json response + 2.0.0.0 + 2.0.0 + + + + + + + + + + + + diff --git a/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs new file mode 100644 index 0000000..4c141cc --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Commmunity.AspNetCore.ExceptionHandling.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; + +namespace Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson +{ + public static class PolicyBuilderExtensions + { + [Obsolete("In case of using netcore2.1+ use Commmunity.AspNetCore.ExceptionHandling.Mvc instead")] + public static IResponseHandlers WithBodyJson( + this IResponseHandlers builder, Func value, JsonSerializerSettings settings = null, int index = -1) + where TException : Exception + { + return builder.WithBody((request, streamWriter, exception) => + { + if (settings == null) + { + settings = request.HttpContext.RequestServices.GetService(); + } + + if (settings == null) + { + settings = new JsonSerializerSettings(); + } + + JsonSerializer jsonSerializer = JsonSerializer.Create(settings); + + var headers = request.HttpContext.Response.Headers; + if (!headers.ContainsKey(HeaderNames.ContentType)) + { + headers[HeaderNames.ContentType] = "application/json"; + } + + TObject val = value(request, exception); + + //return Task.CompletedTask; + using (MemoryStream ms = new MemoryStream()) + { + using (StreamWriter sw = new StreamWriter(ms, streamWriter.Encoding, 1024, true)) + { + jsonSerializer.Serialize(sw, val); + } + + ms.Seek(0, SeekOrigin.Begin); + byte[] array = ms.ToArray(); + BinaryWriter binaryWriter = new BinaryWriter(streamWriter.BaseStream, streamWriter.Encoding, true); + binaryWriter.Write(array); + + return Task.CompletedTask; + } + }); + } + } +} diff --git a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj index 62f7368..424151e 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj +++ b/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.0;netcoreapp2.1 Middleware to configure exception handling policies. Configure chain of handlers per exception type. OOTB handlers: log, retry, set responce headers and body https://github.com/IharYakimush/AspNetCore https://github.com/IharYakimush/AspNetCore/blob/develop/LICENSE @@ -16,12 +16,30 @@ 2.0.0 true ..\sgn.snk + + Library + - + + TRACE;DEBUG + + + + NETCOREAPP;NETCOREAPP2_1 + + + + + + + + NETSTANDARD;NETSTANDARD2_0 + + + - diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index af71b66..adc3db7 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Net; using System.Threading.Tasks; using Commmunity.AspNetCore.ExceptionHandling.Builder; using Commmunity.AspNetCore.ExceptionHandling.Exc; @@ -11,9 +10,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; -using Newtonsoft.Json; namespace Commmunity.AspNetCore.ExceptionHandling { @@ -197,50 +194,6 @@ public static IResponseHandlers WithBody( }); return builder; - } - - public static IResponseHandlers WithBodyJson( - this IResponseHandlers builder, Func value, JsonSerializerSettings settings = null, int index = -1) - where TException : Exception - { - return builder.WithBody((request, streamWriter, exception) => - { - if (settings == null) - { - settings = request.HttpContext.RequestServices.GetService(); - } - - if (settings == null) - { - settings = new JsonSerializerSettings(); - } - - JsonSerializer jsonSerializer = JsonSerializer.Create(settings); - - var headers = request.HttpContext.Response.Headers; - if (!headers.ContainsKey(HeaderNames.ContentType)) - { - headers[HeaderNames.ContentType] = "application/json"; - } - - TObject val = value(request, exception); - - //return Task.CompletedTask; - using (MemoryStream ms = new MemoryStream()) - { - using (StreamWriter sw = new StreamWriter(ms, streamWriter.Encoding, 1024, true)) - { - jsonSerializer.Serialize(sw, val); - } - - ms.Seek(0, SeekOrigin.Begin); - byte[] array = ms.ToArray(); - BinaryWriter binaryWriter = new BinaryWriter(streamWriter.BaseStream, streamWriter.Encoding, true); - binaryWriter.Write(array); - - return Task.CompletedTask; - } - }); - } + } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs index ca96207..7075350 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs @@ -83,8 +83,7 @@ public static Task SetStatusCode(HttpContext context, TException exception, Func return Task.CompletedTask; } - public static Task SetBody(HttpContext context, TException exception, Func settings) - where TException : Exception + public static Task SetBody(HttpContext context, TException exception, Func settings) { if (!context.Items.ContainsKey(BodySetKey)) { diff --git a/Commmunity.AspNetCore.sln b/Commmunity.AspNetCore.sln index 92ee599..e158909 100644 --- a/Commmunity.AspNetCore.sln +++ b/Commmunity.AspNetCore.sln @@ -13,6 +13,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commmunity.AspNetCore.ExceptionHandling.Mvc", "Commmunity.AspNetCore.ExceptionHandling.Mvc\Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj", "{0D080E5A-9500-43AC-88CD-069947CFA5EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson", "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson\Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj", "{B3BAD0B5-15BC-46EE-A224-9DAF10376B81}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +31,14 @@ Global {393C6033-4255-43C3-896D-BFE30E264E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU {393C6033-4255-43C3-896D-BFE30E264E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {393C6033-4255-43C3-896D-BFE30E264E4A}.Release|Any CPU.Build.0 = Release|Any CPU + {0D080E5A-9500-43AC-88CD-069947CFA5EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D080E5A-9500-43AC-88CD-069947CFA5EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D080E5A-9500-43AC-88CD-069947CFA5EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D080E5A-9500-43AC-88CD-069947CFA5EF}.Release|Any CPU.Build.0 = Release|Any CPU + {B3BAD0B5-15BC-46EE-A224-9DAF10376B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3BAD0B5-15BC-46EE-A224-9DAF10376B81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3BAD0B5-15BC-46EE-A224-9DAF10376B81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3BAD0B5-15BC-46EE-A224-9DAF10376B81}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From c316aea1721688946e9dd0af3980f48ede465710 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 14:39:57 +0300 Subject: [PATCH 10/21] add API comments --- .../Startup.cs | 4 +- .../PolicyBuilderExtensions.cs | 98 +++++++++- .../PolicyBuilderExtensions.cs | 28 ++- .../Logs/LogExceptionHandler.cs | 18 +- .../Logs/LogHandlerOptions.cs | 27 ++- .../PolicyBuilderExtensions.cs | 175 +++++++++++++++++- .../RequestStartedBehaviour.cs | 9 - .../Response/RawResponseExceptionHandler.cs | 2 +- .../Response/RawResponseHandlerOptions.cs | 2 +- .../ResponseAlreadyStartedBehaviour.cs | 16 ++ .../Retry/RetryHandlerOptions.cs | 8 +- 11 files changed, 352 insertions(+), 35 deletions(-) delete mode 100644 Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs create mode 100644 Commmunity.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index 1bd3ce6..c4c5206 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -45,8 +45,8 @@ public void ConfigureServices(IServiceCollection services) options.For() .Log(lo => { lo.Formatter = (o, e) => "qwe"; }) - //.Response(e => 500, RequestStartedBehaviour.SkipHandler).ClearCacheHeaders().WithBodyJson((r, e) => new { msg = e.Message, path = r.Path }) - .Response(e => 500, RequestStartedBehaviour.SkipHandler).ClearCacheHeaders().WithBodyObjectResult((r, e) => new { msg = e.Message, path = r.Path }) + //.Response(e => 500, ResponseAlreadyStartedBehaviour.GoToNextHandler).ClearCacheHeaders().WithBodyJson((r, e) => new { msg = e.Message, path = r.Path }) + .Response(e => 500, ResponseAlreadyStartedBehaviour.GoToNextHandler).ClearCacheHeaders().WithObjectResult((r, e) => new { msg = e.Message, path = r.Path }) .Handled(); }); diff --git a/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs index 8b7c191..fc6af49 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs @@ -15,7 +15,28 @@ public static class PolicyBuilderExtensions private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor(); - public static IResponseHandlers WithBodyActionResult( + /// + /// Set to response and pass control to next handler. + /// + /// + /// The exception type + /// + /// + /// The action result type. Should implement . + /// + /// + /// The policy builder + /// + /// + /// The factory. + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// + public static IResponseHandlers WithActionResult( this IResponseHandlers builder, Func resultFactory, int index = -1) where TException : Exception where TResult : IActionResult @@ -38,26 +59,89 @@ public static IResponseHandlers WithBodyActionResult WithBodyActionResult( + /// + /// Set to response and pass control to next handler. + /// + /// + /// The exception type + /// + /// + /// The action result type. Should implement . + /// + /// + /// The policy builder + /// + /// + /// The action result. + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// + public static IResponseHandlers WithActionResult( this IResponseHandlers builder, TResult result, int index = -1) where TException : Exception where TResult : IActionResult { - return builder.WithBodyActionResult((request, exception) => result); + return builder.WithActionResult((request, exception) => result); } - public static IResponseHandlers WithBodyObjectResult( + /// + /// Set to response and pass control to next handler. + /// + /// + /// The exception type + /// + /// + /// The result object type. + /// + /// + /// The policy builder + /// + /// + /// The result object. + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// + public static IResponseHandlers WithObjectResult( this IResponseHandlers builder, TObject value, int index = -1) where TException : Exception { - return builder.WithBodyActionResult(new ObjectResult(value), index); + return builder.WithActionResult(new ObjectResult(value), index); } - public static IResponseHandlers WithBodyObjectResult( + /// + /// Set to response and pass control to next handler. + /// + /// + /// The exception type + /// + /// + /// The result object type. + /// + /// + /// The policy builder + /// + /// + /// The result object factory. + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// + public static IResponseHandlers WithObjectResult( this IResponseHandlers builder, Func valueFactory, int index = -1) where TException : Exception { - return builder.WithBodyActionResult( + return builder.WithActionResult( (request, exception) => new ObjectResult(valueFactory(request, exception)), index); } } diff --git a/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs index 4c141cc..14a5b76 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs @@ -11,9 +11,33 @@ namespace Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson { public static class PolicyBuilderExtensions { + /// + /// Write serialized object to response using and pass control to next handler. + /// + /// + /// The exception type + /// + /// + /// The result object type. + /// + /// + /// The policy builder + /// + /// + /// The result object factory. + /// + /// + /// The JSON serializer settings . + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// [Obsolete("In case of using netcore2.1+ use Commmunity.AspNetCore.ExceptionHandling.Mvc instead")] public static IResponseHandlers WithBodyJson( - this IResponseHandlers builder, Func value, JsonSerializerSettings settings = null, int index = -1) + this IResponseHandlers builder, Func valueFactory, JsonSerializerSettings settings = null, int index = -1) where TException : Exception { return builder.WithBody((request, streamWriter, exception) => @@ -36,7 +60,7 @@ public static IResponseHandlers WithBodyJson( headers[HeaderNames.ContentType] = "application/json"; } - TObject val = value(request, exception); + TObject val = valueFactory(request, exception); //return Task.CompletedTask; using (MemoryStream ms = new MemoryStream()) diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs index 60a5c3d..4f03556 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs @@ -23,17 +23,23 @@ public LogExceptionHandler(IOptions> settings) } public Task Handle(HttpContext httpContext, Exception exception) { - if (exception.Data.Contains(DisableLoggingHandler.DisableLoggingFlagKey)) + var logLevel = this.Settings.Level?.Invoke(httpContext, exception) ?? LogLevel.Error; + + if (logLevel == LogLevel.None) { return Task.FromResult(HandlerResult.NextHandler); } - ILoggerFactory loggerFactory = - httpContext.RequestServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory; + if (exception.Data.Contains(DisableLoggingHandler.DisableLoggingFlagKey)) + { + return Task.FromResult(HandlerResult.NextHandler); + } - if (loggerFactory != null) + if (httpContext.RequestServices.GetService(typeof(ILoggerFactory)) is ILoggerFactory loggerFactory) { - ILogger logger = loggerFactory.CreateLogger(this.Settings.Category ?? Const.Category); + ILogger logger = + loggerFactory.CreateLogger(this.Settings.Category?.Invoke(httpContext, exception) ?? + Const.Category); EventId eventId = this.Settings.EventIdFactory != null ? this.Settings.EventIdFactory(httpContext, exception) @@ -45,7 +51,7 @@ public Task Handle(HttpContext httpContext, Exception exception) Func formatter = this.Settings.Formatter ?? ((o, e) => o.ToString()); - logger.Log(this.Settings.Level, eventId, state, exception, formatter); + logger.Log(logLevel, eventId, state, exception, formatter); } return Task.FromResult(HandlerResult.NextHandler); diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs index 8e8368f..c8b5836 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs @@ -1,22 +1,45 @@ using System; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Internal; using Microsoft.Extensions.Options; namespace Commmunity.AspNetCore.ExceptionHandling.Logs { + /// + /// The log handler options + /// + /// + /// The exception type + /// public class LogHandlerOptions : IOptions> where TException : Exception { public LogHandlerOptions Value => this; + /// + /// Factory for + /// public Func EventIdFactory { get; set; } + /// + /// The Formatter. By default state.ToString(). + /// public Func Formatter { get; set; } + /// + /// Foctory for log entry state. By default with TraceIdentifier. + /// public Func, object> StateFactory { get; set; } - public string Category { get; set; } - public LogLevel Level { get; set; } = LogLevel.Error; + /// + /// Factory for log category. By default "Commmunity.AspNetCore.ExceptionHandling" will be used. + /// + public Func Category { get; set; } + + /// + /// Factory for log level. By default error will be used. In case of none log entry will be skipped. + /// + public Func Level { get; set; } } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index adc3db7..d491b6c 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -51,6 +51,21 @@ public static IExceptionMapping Clear( } // rethrow + /// + /// Re throw exception and stop handlers chain processing. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// public static IExceptionPolicyBuilder Rethrow( this IExceptionMapping builder, int index = -1) where TException : Exception @@ -60,6 +75,21 @@ public static IExceptionPolicyBuilder Rethrow( } // next chain + /// + /// Terminates current handlers chain and try to execute next handlers chain which match exception type. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// public static IExceptionPolicyBuilder NextChain( this IExceptionMapping builder, int index = -1) where TException : Exception @@ -69,6 +99,21 @@ public static IExceptionPolicyBuilder NextChain( } // mark handled + /// + /// Terminate handlers chain execution and mark exception as "handled" which means that it will not be re thrown. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// public static IExceptionPolicyBuilder Handled( this IExceptionMapping builder, int index = -1) where TException : Exception @@ -78,6 +123,24 @@ public static IExceptionPolicyBuilder Handled( } // Retry + /// + /// Terminate handlers chain and execute middleware pipeline (registered after exception handling policy middleware) again. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// The retry settings. See for details. + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// public static IExceptionMapping Retry( this IExceptionMapping builder, Action> settings = null, int index = -1) where TException : Exception @@ -90,6 +153,24 @@ public static IExceptionMapping Retry( } // Log + /// + /// Log exception using registered in services collection and pass control to next handler in chain + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// The logs settings. See for details. + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// public static IExceptionMapping Log( this IExceptionMapping builder, Action> settings = null, int index = -1) where TException : Exception @@ -100,8 +181,22 @@ public static IExceptionMapping Log( return builder; } - - public static IExceptionMapping DisableLog( + /// + /// Disable logging of this particular exception in further handlers for current request. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// + public static IExceptionMapping DisableFurtherLog( this IExceptionMapping builder, int index = -1) where TException : Exception { @@ -111,16 +206,37 @@ public static IExceptionMapping DisableLog( } // Set status code + /// + /// Configure response and pass control to next handler. It is recommended to finish chain using when response builder will be configured. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// The begaviour in case response already started. + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// The factory for response status code. By default 500 will be used, unless code was already set by another handler for current request. + /// + /// + /// Response builder. + /// public static IResponseHandlers Response( this IExceptionMapping builder, Func statusCodeFactory = null, - RequestStartedBehaviour requestStartedBehaviour = RequestStartedBehaviour.ReThrow, + ResponseAlreadyStartedBehaviour responseAlreadyStartedBehaviour = ResponseAlreadyStartedBehaviour.ReThrow, int index = -1) where TException : Exception { builder.Services.Configure>(responceOptions => { - responceOptions.RequestStartedBehaviour = requestStartedBehaviour; + responceOptions.ResponseAlreadyStartedBehaviour = responseAlreadyStartedBehaviour; responceOptions.SetResponse.Add((context, exception) => { @@ -133,6 +249,24 @@ public static IResponseHandlers Response( return builder as IResponseHandlers; } + /// + /// Modify response headers + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// Action for response headers modification + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Response builder + /// public static IResponseHandlers Headers( this IResponseHandlers builder, Action settings, int index = -1) where TException : Exception @@ -154,6 +288,21 @@ public static IResponseHandlers Headers( return builder; } + /// + /// Set response headers which revents response from being cached. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// public static IResponseHandlers ClearCacheHeaders( this IResponseHandlers builder, int index = -1) where TException : Exception @@ -176,6 +325,24 @@ public static IResponseHandlers ClearCacheHeaders( return builder; } + /// + /// Set response body, close response builder and pass control to next handler. + /// + /// + /// The exception type + /// + /// + /// The policy builder + /// + /// + /// Delegate to write response using . + /// + /// + /// Handler index in the chain. Optional. By default handler added to the end of chain. + /// + /// + /// Policy builder + /// public static IResponseHandlers WithBody( this IResponseHandlers builder, Func settings, int index = -1) where TException : Exception diff --git a/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs b/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs deleted file mode 100644 index 64cad98..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling/RequestStartedBehaviour.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Commmunity.AspNetCore.ExceptionHandling -{ - public enum RequestStartedBehaviour - { - ReThrow = 0, - - SkipHandler = 1 - } -} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs index 7075350..253be58 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs @@ -34,7 +34,7 @@ protected override async Task HandleStrongType(HttpContext httpCo { if (httpContext.Response.HasStarted) { - if (this._options.RequestStartedBehaviour == RequestStartedBehaviour.ReThrow) + if (this._options.ResponseAlreadyStartedBehaviour == ResponseAlreadyStartedBehaviour.ReThrow) { this.Logger.LogError(ResponseHasStarted, "Unable to execute {handletType} handler, because respnse already started. Exception will be re-thrown.", diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs index f761c9c..96a23f5 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs @@ -13,6 +13,6 @@ public class RawResponseHandlerOptions : HandlerWithLoggerOptions, { public List> SetResponse { get; set; } = new List>(); public RawResponseHandlerOptions Value => this; - public RequestStartedBehaviour RequestStartedBehaviour { get; set; } = RequestStartedBehaviour.ReThrow; + public ResponseAlreadyStartedBehaviour ResponseAlreadyStartedBehaviour { get; set; } = ResponseAlreadyStartedBehaviour.ReThrow; } } \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs b/Commmunity.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs new file mode 100644 index 0000000..b838b35 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs @@ -0,0 +1,16 @@ +namespace Commmunity.AspNetCore.ExceptionHandling +{ + public enum ResponseAlreadyStartedBehaviour + { + /// + /// Re Throw exception if response already strated + /// + ReThrow = 0, + + + /// + /// Skip current handler and go to next handler in the chain + /// + GoToNextHandler = 1 + } +} \ No newline at end of file diff --git a/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs index 69b680e..14a5a27 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs @@ -10,8 +10,14 @@ public class RetryHandlerOptions : IOptions Value => this; - public RequestStartedBehaviour RequestStartedBehaviour { get; set; } = RequestStartedBehaviour.ReThrow; + /// + /// The behaviour in case of retry can't be executed due to responce already started. Default: re throw. + /// + public ResponseAlreadyStartedBehaviour ResponseAlreadyStartedBehaviour { get; set; } = ResponseAlreadyStartedBehaviour.ReThrow; + /// + /// Max retry count + /// public int MaxRetryCount { get; set; } = 1; } } From d71343651cae2374c599cab9c67f54c5f34771b1 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 15:27:13 +0300 Subject: [PATCH 11/21] response stream unable to write exception --- .../Logs/LogHandlerOptions.cs | 2 +- .../Response/RawResponseExceptionHandler.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs index c8b5836..eae202f 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs @@ -35,7 +35,7 @@ public class LogHandlerOptions : IOptions /// Factory for log category. By default "Commmunity.AspNetCore.ExceptionHandling" will be used. /// - public Func Category { get; set; } + public Func Category { get; set; } /// /// Factory for log level. By default error will be used. In case of none log entry will be skipped. diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs index 253be58..7114085 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs @@ -94,15 +94,19 @@ public static Task SetBody(HttpContext context, TException exception, Func Date: Mon, 16 Jul 2018 15:51:10 +0300 Subject: [PATCH 12/21] Update README.md --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index 72e9d9f..d87191e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,41 @@ # AspNetCore-ExceptionHandling Middleware to configure exception handling policies. Configure chain of handlers per exception type. OOTB handlers: log, retry, set responce headers and body + +### Sample +``` +public void ConfigureServices(IServiceCollection services) +{ + services.AddMvc(); + + services.AddExceptionHandlingPolicies(options => + { + options.For().Rethrow(); + + options.For().Retry().NextChain(); + + options.For() + .Response(e => 400) + .Headers((h, e) => h["X-MyCustomHeader"] = e.Message) + .WithBody((req,sw, exception) => sw.WriteAsync(exception.ToString())) + .NextChain(); + + // Ensure that all exception types are handled by adding handler for generic exception at the end. + options.For() + .Log(lo => + { + lo.EventIdFactory = (c, e) => new EventId(123, "UnhandlerException"); + lo.Category = (context, exception) => "MyCategory"; + }) + .Response(e => 500, ResponseAlreadyStartedBehaviour.GoToNextHandler) + .ClearCacheHeaders() + .WithObjectResult((r, e) => new { msg = e.Message, path = r.Path }) + .Handled(); + }); +} + +public void Configure(IApplicationBuilder app, IHostingEnvironment env) +{ + app.UseExceptionHandlingPolicies(); + app.UseMvc(); +} +``` From 01c028525cd050f17abf23678498a281ee858dfe Mon Sep 17 00:00:00 2001 From: IharYakimush Date: Mon, 16 Jul 2018 15:53:50 +0300 Subject: [PATCH 13/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d87191e..0bebd1c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ public void ConfigureServices(IServiceCollection services) { options.For().Rethrow(); - options.For().Retry().NextChain(); + options.For().Retry(ro => ro.MaxRetryCount = 2).NextChain(); options.For() .Response(e => 400) From fc016198a814fba0cce135fe6a2848c29198e888 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 17:20:21 +0300 Subject: [PATCH 14/21] rename next policy method and update readme --- .../Startup.cs | 4 ++-- .../PolicyBuilderExtensions.cs | 2 +- Commmunity.AspNetCore.sln | 6 ++++-- README.md | 17 ++++++++++++++++- Transitions.png | Bin 0 -> 28827 bytes Transitions.vsdx | Bin 0 -> 34328 bytes 6 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 Transitions.png create mode 100644 Transitions.vsdx diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs index c4c5206..bf77a61 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs +++ b/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs @@ -31,7 +31,7 @@ public void ConfigureServices(IServiceCollection services) services.AddExceptionHandlingPolicies(options => { - options.For().Retry().NextChain(); + options.For().Retry().NextPolicy(); options.For().Retry(); @@ -41,7 +41,7 @@ public void ConfigureServices(IServiceCollection services) .Response(e => 400) .Headers((h, e) => h["X-qwe"] = e.Message) .WithBody((req,sw, exception) => sw.WriteAsync(exception.ToString())) - .NextChain(); + .NextPolicy(); options.For() .Log(lo => { lo.Formatter = (o, e) => "qwe"; }) diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs index d491b6c..fdf4408 100644 --- a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs +++ b/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs @@ -90,7 +90,7 @@ public static IExceptionPolicyBuilder Rethrow( /// /// Policy builder /// - public static IExceptionPolicyBuilder NextChain( + public static IExceptionPolicyBuilder NextPolicy( this IExceptionMapping builder, int index = -1) where TException : Exception { diff --git a/Commmunity.AspNetCore.sln b/Commmunity.AspNetCore.sln index e158909..b6e19af 100644 --- a/Commmunity.AspNetCore.sln +++ b/Commmunity.AspNetCore.sln @@ -11,11 +11,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject build.ps1 = build.ps1 README.md = README.md + Transitions.png = Transitions.png + Transitions.vsdx = Transitions.vsdx EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commmunity.AspNetCore.ExceptionHandling.Mvc", "Commmunity.AspNetCore.ExceptionHandling.Mvc\Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj", "{0D080E5A-9500-43AC-88CD-069947CFA5EF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling.Mvc", "Commmunity.AspNetCore.ExceptionHandling.Mvc\Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj", "{0D080E5A-9500-43AC-88CD-069947CFA5EF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson", "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson\Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj", "{B3BAD0B5-15BC-46EE-A224-9DAF10376B81}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson", "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson\Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj", "{B3BAD0B5-15BC-46EE-A224-9DAF10376B81}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/README.md b/README.md index 0bebd1c..e9cb887 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # AspNetCore-ExceptionHandling Middleware to configure exception handling policies. Configure chain of handlers per exception type. OOTB handlers: log, retry, set responce headers and body -### Sample +### Code Sample ``` public void ConfigureServices(IServiceCollection services) { @@ -39,3 +39,18 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.UseMvc(); } ``` +### Policy handlers transitions +When exception catched in middleware it try to apply handlers from first registered policy siutable for given exception. Policy contains a chain of handlers. Each handler perform some action and apply transition. To prevent re throw of exception handlers chain MUST ends with "Handled" transition. +Following handlers currently supported: + +| Handler | Action | Transition | +| ---------| ------------- | ------------- | +| Rethrow | Apply ReThrow transition | ReThrow | +| NextPolicy | Try to execute next policy siutable for given exception | NextPolicy | +| Handled | Mark exception as handled to prevent it from bein re thrown | Handled | +| Retry | Execute aspnetcore pipeline again if retry count not exceed limit | Retry (if retry limit not exceeded) or NextHandler | +| Log | Log exception | NextHandler | +| DisableFurtherLog | Prevent exception from being logged again in current middleware (for current request only) | NextHandler | +| Response | Modify response (set status code, headers and body) depending on further response builder configuration | NextHandler | + +![alt text](/Transitions.png) diff --git a/Transitions.png b/Transitions.png new file mode 100644 index 0000000000000000000000000000000000000000..5dcf23903831ada3ec50a352c61f15d85a292145 GIT binary patch literal 28827 zcmeEuXH=9)w{FvbAc&XjYwc;a55+W)h2n0eRE%i_t z0>KT1K%hm0Q1F+Vx1%0_|KZpxOFn?)c3$}ge&Cthm%k5zdv(8mYAPd|~;u!le{ zJjMQpGhs~U41ql1mVS6&)me9W{EjJyPSWJ=<1vy9>d(zq)P1G#u;+Ep{O(tG6Y5V0 z(gZD};)lO7eiw3+ux<$cM9qGZOE^aPUtNGk2dR4UP!nVN-m9xPZ@PJzNKW ze8+iS&*Bg{kY??+s;O4NIjQjdZc+Atrh`_+VurF=l0pquatU@LgomeRb$R*T_VOr& z+}P}FT0&Ds28(bJ_&OSX6Da|oLz5P4y8#Aj=2YHDWYAu&8PARqup z!2$lhX}MhM`sB&?g98*kd++Gz=)}ZCQ`5^R90+8uP!}<~#>!5c4~2Nwp(x-ro`Z2p_%bF(QXo&W_?CaZS!{q?rUvCUVuo7LHFh9xpfvXZ zj7@8y|LkC-*+~9+=i5X+OSRQvZd#yj2KgVSk@B|XeWdYq(B+TfW$2jx4vRgHOayLr zak`6+NI|m}RgvX4Ryd`lX+Mm_fOr}xg1r|HYSA2GEbeb1{bcxx4pm>EDm(FB_A9?2 z43V0;gZ)T(_QY_uBkpiLSiFz`-Zdw-Jy^tq~+ON&bEfLw{A-6)opcJCG4d`Pi4)ocgG-9)a7c^|>@x3Rc2_hGlB%x#;#2 z?HBv5fey&czWVg5-WF#ZIIHh$yYIa*obLr>%ge;!*zku@PIM@MbNQl!Q@WUuhAmg{|rq@G+BZ4`gq z+1VVh_TU@S^^ZEPksX28Rrqt+Lbqu6gZC_!vK>vPb^Qwdsx#)(&s^||#N__7wihm4 z?3^!7^DGs4#Kg}kw zH!AWHZR}iVbKNc76>d`PQ7F=ie}Njzb0DP;8Fe-_>1=`~TpQ6(0bQTjrJCy>q}FPm zE8W3MHp!P12-((|m( z14&o_fwZ3ogJunRIWSX>s?E0T6+U9m&(N_`rQvP3l&#hvnW@>xkPC&(h1B|!@CCjs z#}+f+$fCGD-k_{AV_Q_o%PQDkv{trVNdl(l89pBDA_P{R7f)G>izLh8p-rAC&#a{q zxzTaS-jH2n;!2setD*6%zfA?}y1Cir-213fA_)8x6#25YJh?!R`AQ$1-3s?LgWB^;qQB2~7PWmolF06{co-`0O0Fas^7KfVUmCM#sI}%Gp}g^3U?*P3>mZ8W zaEG{w-|ItAau%KMG5T~J%@5-z-cWW)(>)giQ`QXXsnk#54O-*obMgcB*ZXEkt>)S1 z{6r())pg@KZamX3E&Y)HtzV0B^Wsg1D&N`Q_(4A9a7xnf1|@vRJ4<69zRow7qbb6P%Zu%gM!6#I4Cp;+J(R z#jHE33LVpN-CrrpGTT!9Vl%EupPYrOZJ+9^uV$aiCHzhM2~H%nz#FKjsL0c+G1qUm z{85nK9xX1xMdzpSnbcPa(ZLJjEF!suWZK9#IHx#n=jC`+9FAdwZJ1#1z%G<*1j)n!Wyp zhK81w*W{sReaIl*`CCxT#>Pfnoew!05e8!_Fvt0J4vy6v7n&%H=to(aC?2pbtpjIK z7hq>eNnKoQTY^JEYP^nf@fpPt*oj@PWwo}k>FVk#f^=Q5JK#M!eAU8$hC&pJr4eKl z6cl5yYhbGu9Er&PVT} z{lR(K&>*I3QfPbyp7i!DI5QuFQbA5+e&nNeS5%aa+R4djb#+z9>CGGk_8eVVT~&DW zXr}4HkrGn>WDpoCc+M)=ONV<7CMJ?$K^~V%?j5h|9mxtpHDhk?o_^RlJ>Qk2prEiz zUO+!Qod=FgMCxgjFAxPx&rtkIZ~F~&tHRa0%Hu?C2Uz$L_|eCq#rRlKo$^E!9;gKm z9_*Nu^`#2R>OgT?;mCA%*&o z!5M$8CYosOLsVjk>seHs8VM8$cGE3!guGv^{|X^$`a%X>d?_qgtjdq&vkUo{1Z_{4 zjqU(FsDI_}`pSDZUpvf7zG!%n@kX+6CO+Ch3V~1l^4Ah=N{jH6@##MOw%2s|29V{E;`3#}zFCjts>VWFL z)x{K6JC(aN=44E4YMPRTWK$xSCs1=~E@p|(74wg_FG*fwH_8`8qAt}opZigPlci6-Ac13LeH{74Jyg36FX#6o{3i3>FRj7u@2_QnM|=mRPO!O zvhsOnqW(7XjWE&i(EfG?Et~#_gN1L(3l#?wb#iPHz2P;ytUo>nRSa6W)jOy+N8`qv z{agE24_+Elm-{q;}Y>Jdeux>q~0U5d&I$RbA6@jjZ$Rx$&bmC=l-xrn*E8etdAp5hsV z`QKF+c_NLzhL+ssBUi8_f6t}TURG!ZOIw@S?R_PIoZ2^Q8WO_SuqFHw}I z9wx?aY!|p>4b5CFv<~UB9D2KOJx0U#$z|`XtG|q&A*=G>wjuRJV@16UWe7Nv{xXpC z5|QesQIJpGzy5HuxKajz&wc8b4E`Tx)u3$NH-EPTPN0)vzGRdfHLi%=O(Nld#9S)l z{6y(IW+gni+6BiFzHO@Tb?;l`-^uWdhCTiv?Z_(xagljq;>ieW9ULK1Jc+ zv{Tpu2UZcSa)A>eM|-pO4sndINaejeLR8|xk2SFdv&fPXft;6pbFMZq7*Cvnrk4~8 zWIjd6@o}AwjQ3o6)utqyh}<2&vK0z$H+N?raAs9xyRmui;JES)x;6aKo=$%iS?xZg zDh^YnpSzFWPD`izP91LmuhjImBgxA#BVXz;>{cZ&O8#cLTV zfjZM6DDA5R+8t`Vho>8eH~nkl0zI&W{ciqW74H4^E$h*HlYcbP4zx^nG(~rGu&dIx z@R@tZ4;;fuNl9M2>qcH)qWf;;F5vzjt`knG>m*0@0Xhzr1 z574Yka#JuNJ`keJq%upg+AgmJsfrG`{YSzzCu$>d))-af-*JA#_deQEBp|{3u5fNtD z)4Sd~0>%5K*Kw|&gA1rf!#H>>^1>#m8Pndth}G5AqoboWYd_QNpmrWW0A5d>qttdV!|pulHBctBKk>I5{<`Ys>e|&d&DtN0KXs9TX&0ia|u6 z7@6`6FtRwAY`5pBH(Lxf@10iomL8BX-4S-o9rd%VBAsmPK*FUQY(njOc0`A6oAD@y z*DSu&|2|IfXF2f^Y`F?q2b(Wkxtbn(AJq)Pegq(mTf8V`9h9JoueFrIMX+Km1F?K+ zNBnDrloCpf@n8te(&YX{F048@mB*=p@1R-uV&iKI2?Tz+FPmmjHFnjE!?WFReTvA+ zOpQEQUPS^aK4#rF;KgiTF$sSS6V9 zfA1--AO|~w!LY>%?(*6_;9L6kxu*k3PS?fgm;gWv8xV`H{Gveq2YIYyT5m?IvE3uV zh)qwUB6s>0s^bUF2{;r4M^;3}3HeH!3EJ9Ij|yemFVkFv`k_5OOZbtVZK}Xfm8oS3vwBvFO5~hSF~?_@ zjo@=|E&L>c=>?S0lZg1t0!jNvuGBhkZ*hVF+jKRHrC7CGVb`e^PDPJ#hsFMJ>g0IW z_nc+vr%~H73B-#g0r{78BftAE>wPhj?sI88g~!H59_y&w8trFgKsG)Q7AF+xZE0L> zKg`c-`AoflBJ$8g^5$fE)UsCWrbkc)JqF!A}DPz&T2 z_e)d+)(+vQo)j7qWP}t%VQ zFqm$;)xKhs)7hD?p&Vu`PN7kS5Xcv?X?rV{+VZmUE)9#X-1zA>S^@w zPmH*(rNija#XT375--{_Xa*zGSUT<)6_@mP(Vx>=g)ut0mYH_e0@p0vK^dcAX(s{s*bfo%L*=IryXT}g?^+vLx z8P=C2d}vs9m1#V2k(RkDA!qMI!ukWZ*gOl}@z^XH0ZZ6Q=G(r$=cB_L8^>gU7w%{X z+8vZR&L5<_lRXfMDtSDd8r!Nl-#_-WET@?q-=1nA8!>8_>kj*?l z9z5RDi%yAaH4!5g{xRycZ*%;WqNcm2o25+AgrJ%B)7RRz5P#3ztxx0yg0sLoSIi(9 z5aLpi@)a*uyb%E2VoojDOXKr8rb*?&mDC(ZoyVI^<}op@OG9={8C z3^crmvm8AN@1 zT(wM(FE#xU)75x00iID^0ksb|#5+ud9=ENH1unsR)J2xN?9;LbB1e_S$HJ7KUL8+x z$G;ztthn@`yIg5wh|?~6z*^YWSav$ng!8(2@7Un?k1ag(f3e{oDF1#WvG}aszTh;%OrgV?WXm) zuy?m!?~*lsc^Hz&$KjzwyvBa@HWU5e87H4+&Z`q!HwN;ZbsU;9*3)%6vwT;8TYW7z zd`h|CcBOwgky@2HFITpzb&&r^k?GmlGhx=cVk6_Q@u2K==bPi$?BNds{JQo!n$|jbc9zlh=J%r(RnuB; zhFXP;im9YsZ^&!wo29!z#6HQlI%z#$AzR-gj(Ek<6gGynz+JsHs=vR^5^Li zzIMT<_Rp%98H6p4{Lj>6$MdUfH*TXSw|!IzQ7O0T+edbMxyZ_7W+>hT>hkgkjmC%n zFi>`SbrA0=_K4%@SyUQTopQyPsMLL5yPfSMn;kQYGUS#s?hw{y3XNe z;}#7Z{jtM=?;@2-Dte7RXiXFj>mlJwTYAlNYFDC^?X4_gH6rchCWnnto095k`_mgu zj-Uf6zQk-pN7f0A0=`|x2y;By<@G1=RYbEZ%WwUDb@jINm>+LIV*807OY1{d-La?ZRTGje#;^v7q^X^IGZ3Gm;dx}1^lq@u{hqqO)53x<^5~{QU^(C z5d?Ck@4+UHxIl;lOZQ{V6i)FFFO{)$9|QGWwsj+kAD+ z7y6tH-A40)ZC+b@uYv@agiH*1H}Ih6MnQqW?b{9X6nr~cCKg20s+RX|-`g-+lTiCU zIAcM@h&RCO$~uNqcx(MLrT^K2oG(c?2X8;_isB@tP)n5NqOMRIb+Nr{b{^hjfr%|z z#f&GU3c;RF;HH~RB<)?E{>XO?&EGw?&{%5_{IR`>dQ~=KT{MI>V$|<6Kb)4cbw)2K z^7WmrX-i!_T#L`sx=-&RXo@(D2dcRoZ-p;DIS<$BI8qT+KS7A^F8eqv6^DTx*4v?w zeeIL*yWcqQ-YqBw?@R*t)ZU?-{rsPwc+W(EPoFx}vZw#~$?!}l_*B~g&lYR?M}rZY zao|&VhgNpVKR2SuR|vICT99$h~rfk;e!0TdT7@*q_x5MX)is|Hj?S66p?XQ#+p`toRp zvE|HjE;=uc)X>Fb)t9yYKIh>XtkPhy`WIix3ZB{B-CbVJ!Q%P!;JSpjZ$W3^R*B5~!>!*y({zfI}a+^?_K@w9UrMF7Fh67yevq z3)1NeDseKxjUMcLd2(osJl;P#@^p4?Zi~Y&<7&lmadZ33!z}x0yRHuB ze#TWg`^%$dle$H{x)Il#a8%{zXw?VyPxkuM(F5$<@f zGl8{2VoE@h`S?Ab?-$n))l^n$f0@n<2?;?W*$g_ghv~#;R0?XjY{Nqe+%t`K+(_KP zg_r1OO$BgC+K~!=878?>?I9G%v-ae;cO|Ij|vl3W&^E;jleyd zb0@K{Ko+R<)&oQ$M+=IDg{4Xtt+=p1@qK%n`LNC7k@bc3_eIEfx?#RyNqVJ7(GQx0 zf<1QS%Pw?wmdBsH_7uP~U}3lDFyHR?R*}|C?#H9K>cqlVPv%u{8uR$n=bfteSDgrV zKaTWwge;9bcw6{h?$KfB|@ja-= zBsC;e9I^bWwmi1!mNaKlu(ht9O>llzfuxQ%|DQE@-4Px|8eOTviIMX)j9Cg74a;^S z-uLD*YDvD)b~rt94(@}uS1kaM3aB0F`R>Elg-n(T^-lFtcX^3E_y9sxNI{RcTxp|gaek^f#wi~ttT64Kdu@Xo}9{PM=u96M=| zq={z`110&*o=2Db1wEeyUR%OEo~YS8TF4Qa3^X{Dv0 zHZ(H+P-9E{@J|U&+GZl$tLv4s2n=AzM4R>Y@6;n7k$2Lzb*R0)fMo5<71DFjVbiIp z@fESUVq+}g_8#b3;BYs8v8nZYO69nM-7g(e-A0dKs>&#a+@s}3ZrHDNnZ&WgyV?_e zoq6L{ZssCPp2k{yzb8$nE@e_4%5m|LMpG7+otU@Oim8p&?md@ zw@NrfVrVHe_ezx})bhUVVzKAgZ|wqyQ`z&g_YM4mtheLz9^Q4D>IzTHE4^0gJ0bsI z`kp66%l=b=H}4*}hpY)mvds}!y*1Zz@u�Unj3)9vdHz^;O9)D0uA1803o&yT!9q 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 literal 0 HcmV?d00001 diff --git a/Transitions.vsdx b/Transitions.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..69744dac79655843e3e1f9251a5ecbb59b9f7b5f GIT binary patch literal 34328 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»e>w@OuSjk`wp4W4aH(YR!tUM9&wUfnjFEnz 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

*>M3CrDP94sUvS)~vIcnNw^(iGVr@n$4YSt^S z1w!EvpgSsio*~L!d;zhvM6aJ{!vh&9> zirqM5_*sAoZW>gZcqUlQho$m^11ZG&`Z))Gx)*jEfwt31JPE)ew^z^G{cE6?xF`$rfpx(TKCI+98{Z=OMXLXMV@;6nDw9`Jh~Utvb*w zvhEG~*2Fx*VxkTmIuQ-LH!pTyE#H@LVC&PisQjdMGl5pm%5m9t+wdCCf*q4sK87Yw zdYBxp2$v{@WNT8qq{wt@NV}>ls}dhpqVuzzd7f61^$LT>m9J!y2+q@lw#X|*W=*)n zr>5%DTWK0om~VmiV0vpaS{@2lkImkb+gfYps_Oft+6WJ|-Oqn{Ix#%oxagdADAr$q ze!#PEamh^5)ojW#y=lBV@;=05NWXuk?f*>dVX5|n#m^;~+^3K)0ssWye=ofB?d<-M z*PjNVfaJLVpGW-v{m4}Av00^u>40D2!)POu3@5?{S{6ykJ@N&GmS=O7Ek@2`G#1yG zIy)6xjml~g^&2OPk8vl(rzp!f93d6E>tUB1@Z>~>jCTpj^Pl|3d zu8+XBd?;sesvX6P1p}!hFFrsx=uTlg1zt%YjY%phc}Nv{_T1rQY|dPr^{!1Q;>MCe zL(D5y2oxc)jvO9s?-uzXrNO>Fhwk!JcHqyHWT>JUno7Zu+U#r{6+BdBie1nFT^%_* zQHv(2<9{L$Fo4QrLbBv52?yvLGgaCMx+_%t2@%H4lJkG+bJvd8LkF=s5g$No2MpJe zOIYBjvOY!dY6{{3Bf&gs&=2Gwe|Uercu9pDaw2bjC%emT}rf8NHk{t*TDLaQd5 z)JvUHO~A=E03LLwjHxF?smBJJZQ33-9X@i`wFg@z9KQ$k1j;4g0J{ES2mG=pL)P`8 zPPC)+@HWZN->Y!IG$c)0ex|z3XXojs&-L%gVCd}VWNZC@^Wpb3pglp()}J0@@KWj* zuK1-mAR?i!LU+FrlZhdo3j>5m##C~{z&Q2GMFo9SNEU0o<|dz;_hdplEa_$80$eEc zm>I+vAP81IC>6tc&=yI?sbMZ;+AL!YrU)TB5g_UGn|AF^rGSUBGNvX%9Q z1!tl?&5sHdq5Z}XyhvoSNZ_1pN$%fm1RaY#nhsCwVDeb%cUswG`1}?!lWB6NGfR&$ z?k(c1is99t8!}Cf9iCHpsjdU=P7h?n@jwUfUtr$8Z1C=y(o~NcOErc5#Mwtm;=zIc zg4`Bu*_O<6MqMhaw+6xY3Ye4pm~%9d)`<`!?x1xS*Bm(?w?}ib<1#-E6f-ddgY1;m z(905Ej;=n1b2>FYu3)m#Y50QB=^{#ty=W3SOYsYzCSKvnFxq2#dGZ>N zB#pw&ms%L)xDLX`^jLlTA23tlLf~jPq&kxp@6{rqtX@m8RAiEtR|sY$&GvH38n6Qr zZ|P&iXD{9hbT=5xKI1;#AHQN%X`;B?i+owAsz9*La@DUN$U0`;AMoYb3rl>VDE)=; zGk#P>4$!qSx#Tppx>e%vZ1&_mD3G1-ML^^dmYD&~^ll_xCz|dMWEFyV)5#YX2j-<)!`;@SoP^ z{~q7YEx;Ot0Mg8o+`k!9Tf5!jmkMTSH_EXT{pYi{TTgIQLKkX!b zqu@W+BL77FOG^Fk%_aUM{^`H)n@CUaFXEpv6MwB@{`OS;>1*(tMoRdfwEuQF_;Wyi z>cjs=5PlkH{O4EvH?{abF@LI8{>F@aI^X{t<}WSFKQVu5;{_6Jo6Z5B-$Zw1)-@h<_{^q}>MgFAyslV}? z=D`0i+P`x0pRa$Zar}w-v&sKAMp^jpF@LrE|B3mt{qZ-(PwHQoe}1=rJpb+K`SS+T zZ-jxx|8~3S&td%eC%|t8kn_KW@o#?#{2BhAORe9*00675|2BmGT6p~#{hzhzzegXr u|6BC$O7%~`e-;k^4v6&ncffy@5%N-?pBLqSw40&=RDc2isQUin-Two+v+P&^ literal 0 HcmV?d00001 From 3115bb2c8005a55a249e5b7d367d7c0cdcc1e2c1 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 17:32:27 +0300 Subject: [PATCH 15/21] update readme --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e9cb887..a1c8283 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.UseMvc(); } ``` -### Policy handlers transitions + +### Policy exception handler transitions When exception catched in middleware it try to apply handlers from first registered policy siutable for given exception. Policy contains a chain of handlers. Each handler perform some action and apply transition. To prevent re throw of exception handlers chain MUST ends with "Handled" transition. Following handlers currently supported: @@ -53,4 +54,12 @@ Following handlers currently supported: | DisableFurtherLog | Prevent exception from being logged again in current middleware (for current request only) | NextHandler | | Response | Modify response (set status code, headers and body) depending on further response builder configuration | NextHandler | +Sample of transitions: ![alt text](/Transitions.png) + +### Nuget +| Package | Target | Comments | +| ---------| ------------- | ------------- | +| https://www.nuget.org/packages/Commmunity.AspNetCore.ExceptionHandling | netstandard2.0;netcoreapp2.1 | Main functionality | +| https://www.nuget.org/packages/Commmunity.AspNetCore.ExceptionHandling.Mvc | netcoreapp2.1 | Alllow to use MVC IActionResult (including ObjectResult) in 'Response' handler | +| https://www.nuget.org/packages/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson | netstandard2.0; | Allow to set Json serialized object as a response body in 'Response' handler. Use it only if 'Commmunity.AspNetCore.ExceptionHandling.Mvc' usage not possible | From 5f0fd1613f7825a31034230e3c52b433bf2fe004 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 17:35:52 +0300 Subject: [PATCH 16/21] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a1c8283..f5e5300 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# AspNetCore-ExceptionHandling -Middleware to configure exception handling policies. Configure chain of handlers per exception type. OOTB handlers: log, retry, set responce headers and body +# AspNetCore Exception Handling +Middleware to configure AspNetCore exception handling policies. Allows to set a chain of exception handlers per exception type. OOTB handlers: log, retry, set responce headers and body ### Code Sample ``` From 340c3825371fabf7e0fb9cb561e3285c4ab391f0 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 17:41:23 +0300 Subject: [PATCH 17/21] rename projects --- Commmunity.AspNetCore.sln | 8 ++++---- ...munity.AspNetCore.ExceptionHandling.Integration.csproj | 4 ++-- .../Controllers/ValuesController.cs | 0 .../Program.cs | 0 .../Startup.cs | 0 .../appsettings.Development.json | 0 .../appsettings.json | 0 .../Community.AspNetCore.ExceptionHandling.Mvc.csproj | 8 ++++---- .../PolicyBuilderExtensions.cs | 0 ...ity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj | 2 +- .../PolicyBuilderExtensions.cs | 0 .../AppBuilderExtensions.cs | 0 .../Builder/ExceptionMapping.cs | 0 .../Builder/IExceptionMapping.cs | 0 .../Builder/PolicyBuilder.cs | 0 .../Community.AspNetCore.ExceptionHandling.csproj | 0 .../Const.cs | 0 .../Events.cs | 0 .../Exc/ReThrowExceptionHandler.cs | 0 .../ExceptionHandlingPolicyMiddleware.cs | 0 .../ExceptionHandlingPolicyOptions.cs | 0 .../Handlers/HandlerResult.cs | 0 .../Handlers/HandlerStrongType.cs | 0 .../Handlers/HandlerWithLogger.cs | 0 .../Handlers/HandlerWithLoggerOptions.cs | 0 .../Handlers/MarkHandledHandler.cs | 0 .../Handlers/NextChainHandler.cs | 0 .../IExceptionHandler.cs | 0 .../IExceptionPolicyBuilder.cs | 0 .../Logs/DisableLoggingHandler.cs | 0 .../Logs/LogExceptionHandler.cs | 0 .../Logs/LogHandlerOptions.cs | 0 .../PolicyBuilderExtensions.cs | 0 .../Response/RawResponseExceptionHandler.cs | 0 .../Response/RawResponseHandlerOptions.cs | 0 .../ResponseAlreadyStartedBehaviour.cs | 0 .../Retry/RetryHandler.cs | 0 .../Retry/RetryHandlerOptions.cs | 0 38 files changed, 11 insertions(+), 11 deletions(-) rename Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj => Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj (84%) rename {Commmunity.AspNetCore.ExceptionHandling.Integration => Community.AspNetCore.ExceptionHandling.Integration}/Controllers/ValuesController.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling.Integration => Community.AspNetCore.ExceptionHandling.Integration}/Program.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling.Integration => Community.AspNetCore.ExceptionHandling.Integration}/Startup.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling.Integration => Community.AspNetCore.ExceptionHandling.Integration}/appsettings.Development.json (100%) rename {Commmunity.AspNetCore.ExceptionHandling.Integration => Community.AspNetCore.ExceptionHandling.Integration}/appsettings.json (100%) rename Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj => Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj (96%) rename {Commmunity.AspNetCore.ExceptionHandling.Mvc => Community.AspNetCore.ExceptionHandling.Mvc}/PolicyBuilderExtensions.cs (100%) rename Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj => Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj (94%) rename {Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson => Community.AspNetCore.ExceptionHandling.NewtonsoftJson}/PolicyBuilderExtensions.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/AppBuilderExtensions.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Builder/ExceptionMapping.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Builder/IExceptionMapping.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Builder/PolicyBuilder.cs (100%) rename Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj => Community.AspNetCore.ExceptionHandling/Community.AspNetCore.ExceptionHandling.csproj (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Const.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Events.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Exc/ReThrowExceptionHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/ExceptionHandlingPolicyMiddleware.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/ExceptionHandlingPolicyOptions.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Handlers/HandlerResult.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Handlers/HandlerStrongType.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Handlers/HandlerWithLogger.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Handlers/HandlerWithLoggerOptions.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Handlers/MarkHandledHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Handlers/NextChainHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/IExceptionHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/IExceptionPolicyBuilder.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Logs/DisableLoggingHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Logs/LogExceptionHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Logs/LogHandlerOptions.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/PolicyBuilderExtensions.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Response/RawResponseExceptionHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Response/RawResponseHandlerOptions.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/ResponseAlreadyStartedBehaviour.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Retry/RetryHandler.cs (100%) rename {Commmunity.AspNetCore.ExceptionHandling => Community.AspNetCore.ExceptionHandling}/Retry/RetryHandlerOptions.cs (100%) diff --git a/Commmunity.AspNetCore.sln b/Commmunity.AspNetCore.sln index b6e19af..2a89372 100644 --- a/Commmunity.AspNetCore.sln +++ b/Commmunity.AspNetCore.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27428.2015 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling", "Commmunity.AspNetCore.ExceptionHandling\Commmunity.AspNetCore.ExceptionHandling.csproj", "{97ECCF71-494E-48FA-995A-AB1F13975A61}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.AspNetCore.ExceptionHandling", "Community.AspNetCore.ExceptionHandling\Community.AspNetCore.ExceptionHandling.csproj", "{97ECCF71-494E-48FA-995A-AB1F13975A61}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling.Integration", "Commmunity.AspNetCore.ExceptionHandling.Integration\Commmunity.AspNetCore.ExceptionHandling.Integration.csproj", "{393C6033-4255-43C3-896D-BFE30E264E4A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.AspNetCore.ExceptionHandling.Integration", "Community.AspNetCore.ExceptionHandling.Integration\Community.AspNetCore.ExceptionHandling.Integration.csproj", "{393C6033-4255-43C3-896D-BFE30E264E4A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C825A429-B51F-4E5D-BC78-5E0A390D0C38}" ProjectSection(SolutionItems) = preProject @@ -15,9 +15,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Transitions.vsdx = Transitions.vsdx EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling.Mvc", "Commmunity.AspNetCore.ExceptionHandling.Mvc\Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj", "{0D080E5A-9500-43AC-88CD-069947CFA5EF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.AspNetCore.ExceptionHandling.Mvc", "Community.AspNetCore.ExceptionHandling.Mvc\Community.AspNetCore.ExceptionHandling.Mvc.csproj", "{0D080E5A-9500-43AC-88CD-069947CFA5EF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson", "Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson\Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj", "{B3BAD0B5-15BC-46EE-A224-9DAF10376B81}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.AspNetCore.ExceptionHandling.NewtonsoftJson", "Community.AspNetCore.ExceptionHandling.NewtonsoftJson\Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj", "{B3BAD0B5-15BC-46EE-A224-9DAF10376B81}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj b/Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj similarity index 84% rename from Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj rename to Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj index 22c17db..bf6f3cb 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Integration/Commmunity.AspNetCore.ExceptionHandling.Integration.csproj +++ b/Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs b/Community.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs rename to Community.AspNetCore.ExceptionHandling.Integration/Controllers/ValuesController.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Program.cs b/Community.AspNetCore.ExceptionHandling.Integration/Program.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling.Integration/Program.cs rename to Community.AspNetCore.ExceptionHandling.Integration/Program.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs b/Community.AspNetCore.ExceptionHandling.Integration/Startup.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling.Integration/Startup.cs rename to Community.AspNetCore.ExceptionHandling.Integration/Startup.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json b/Community.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json rename to Community.AspNetCore.ExceptionHandling.Integration/appsettings.Development.json diff --git a/Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.json b/Community.AspNetCore.ExceptionHandling.Integration/appsettings.json similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling.Integration/appsettings.json rename to Community.AspNetCore.ExceptionHandling.Integration/appsettings.json diff --git a/Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj b/Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj similarity index 96% rename from Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj rename to Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj index a5b1bf0..46ef686 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.Mvc/Commmunity.AspNetCore.ExceptionHandling.Mvc.csproj +++ b/Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj @@ -21,14 +21,14 @@ - - - - + + + + diff --git a/Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs b/Community.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs rename to Community.AspNetCore.ExceptionHandling.Mvc/PolicyBuilderExtensions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj b/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj similarity index 94% rename from Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj rename to Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj index ec9b243..8ddd03b 100644 --- a/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj +++ b/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj @@ -18,7 +18,7 @@ - + diff --git a/Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs b/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs rename to Community.AspNetCore.ExceptionHandling.NewtonsoftJson/PolicyBuilderExtensions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs b/Community.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs rename to Community.AspNetCore.ExceptionHandling/AppBuilderExtensions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs b/Community.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs rename to Community.AspNetCore.ExceptionHandling/Builder/ExceptionMapping.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs b/Community.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs rename to Community.AspNetCore.ExceptionHandling/Builder/IExceptionMapping.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs b/Community.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs rename to Community.AspNetCore.ExceptionHandling/Builder/PolicyBuilder.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj b/Community.AspNetCore.ExceptionHandling/Community.AspNetCore.ExceptionHandling.csproj similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Commmunity.AspNetCore.ExceptionHandling.csproj rename to Community.AspNetCore.ExceptionHandling/Community.AspNetCore.ExceptionHandling.csproj diff --git a/Commmunity.AspNetCore.ExceptionHandling/Const.cs b/Community.AspNetCore.ExceptionHandling/Const.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Const.cs rename to Community.AspNetCore.ExceptionHandling/Const.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Events.cs b/Community.AspNetCore.ExceptionHandling/Events.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Events.cs rename to Community.AspNetCore.ExceptionHandling/Events.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs b/Community.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs rename to Community.AspNetCore.ExceptionHandling/Exc/ReThrowExceptionHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs b/Community.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs rename to Community.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyMiddleware.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs b/Community.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs rename to Community.AspNetCore.ExceptionHandling/ExceptionHandlingPolicyOptions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs b/Community.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs rename to Community.AspNetCore.ExceptionHandling/Handlers/HandlerResult.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs b/Community.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs rename to Community.AspNetCore.ExceptionHandling/Handlers/HandlerStrongType.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs b/Community.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs rename to Community.AspNetCore.ExceptionHandling/Handlers/HandlerWithLogger.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs b/Community.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs rename to Community.AspNetCore.ExceptionHandling/Handlers/HandlerWithLoggerOptions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs b/Community.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs rename to Community.AspNetCore.ExceptionHandling/Handlers/MarkHandledHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs b/Community.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs rename to Community.AspNetCore.ExceptionHandling/Handlers/NextChainHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs b/Community.AspNetCore.ExceptionHandling/IExceptionHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/IExceptionHandler.cs rename to Community.AspNetCore.ExceptionHandling/IExceptionHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs b/Community.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs rename to Community.AspNetCore.ExceptionHandling/IExceptionPolicyBuilder.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs b/Community.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs rename to Community.AspNetCore.ExceptionHandling/Logs/DisableLoggingHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs b/Community.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs rename to Community.AspNetCore.ExceptionHandling/Logs/LogExceptionHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs b/Community.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs rename to Community.AspNetCore.ExceptionHandling/Logs/LogHandlerOptions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs b/Community.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs rename to Community.AspNetCore.ExceptionHandling/PolicyBuilderExtensions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs b/Community.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs rename to Community.AspNetCore.ExceptionHandling/Response/RawResponseExceptionHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs b/Community.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs rename to Community.AspNetCore.ExceptionHandling/Response/RawResponseHandlerOptions.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs b/Community.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs rename to Community.AspNetCore.ExceptionHandling/ResponseAlreadyStartedBehaviour.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs b/Community.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs rename to Community.AspNetCore.ExceptionHandling/Retry/RetryHandler.cs diff --git a/Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs b/Community.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs similarity index 100% rename from Commmunity.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs rename to Community.AspNetCore.ExceptionHandling/Retry/RetryHandlerOptions.cs From fd8d1aa9120fd23e88ea03492244259e3e560421 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 17:43:06 +0300 Subject: [PATCH 18/21] fix package description --- ...ty.AspNetCore.ExceptionHandling.Mvc.csproj | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj diff --git a/Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj b/Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj new file mode 100644 index 0000000..8b31509 --- /dev/null +++ b/Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp2.1 + Extension methods to configure exception handler which write MVC action result to responce body. Usefull for writing objects + https://github.com/IharYakimush/AspNetCore + https://github.com/IharYakimush/AspNetCore/blob/develop/LICENSE + IharYakimush + AspNetCore exception handling policy mvc action result + 2.0.0.0 + true + true + 2.0.0.0 + + IharYakimush + 2.0.0 + true + ..\sgn.snk + + Library + + + + + + + + + + + + + + From 68254a2bcb32e0079c39b3b123650bcdf44e21c7 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 17:45:05 +0300 Subject: [PATCH 19/21] rename proj --- ...ty.AspNetCore.ExceptionHandling.Mvc.csproj | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj diff --git a/Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj b/Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj deleted file mode 100644 index 8b31509..0000000 --- a/Commmunity.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - netcoreapp2.1 - Extension methods to configure exception handler which write MVC action result to responce body. Usefull for writing objects - https://github.com/IharYakimush/AspNetCore - https://github.com/IharYakimush/AspNetCore/blob/develop/LICENSE - IharYakimush - AspNetCore exception handling policy mvc action result - 2.0.0.0 - true - true - 2.0.0.0 - - IharYakimush - 2.0.0 - true - ..\sgn.snk - - Library - - - - - - - - - - - - - - From 08f88ae4c878a859d9182a7fc3c5212a6c4bb6a8 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 17:48:58 +0300 Subject: [PATCH 20/21] rename projects --- ...ommunity.AspNetCore.ExceptionHandling.Integration.csproj | 6 +++--- .../Community.AspNetCore.ExceptionHandling.Mvc.csproj | 2 +- ...unity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj | 4 ++-- Commmunity.AspNetCore.sln => Community.AspNetCore.sln | 0 4 files changed, 6 insertions(+), 6 deletions(-) rename Commmunity.AspNetCore.sln => Community.AspNetCore.sln (100%) diff --git a/Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj b/Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj index bf6f3cb..a51e348 100644 --- a/Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj +++ b/Community.AspNetCore.ExceptionHandling.Integration/Community.AspNetCore.ExceptionHandling.Integration.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -18,8 +18,8 @@ - - + + diff --git a/Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj b/Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj index 46ef686..420bc3a 100644 --- a/Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj +++ b/Community.AspNetCore.ExceptionHandling.Mvc/Community.AspNetCore.ExceptionHandling.Mvc.csproj @@ -28,7 +28,7 @@ - + diff --git a/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj b/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj index 8ddd03b..48176ed 100644 --- a/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj +++ b/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -18,7 +18,7 @@ - + diff --git a/Commmunity.AspNetCore.sln b/Community.AspNetCore.sln similarity index 100% rename from Commmunity.AspNetCore.sln rename to Community.AspNetCore.sln From f6338a8df440f196bdf540332b5b26b7252b7707 Mon Sep 17 00:00:00 2001 From: Ihar Yakimush Date: Mon, 16 Jul 2018 18:02:04 +0300 Subject: [PATCH 21/21] package for json proj --- ...mmunity.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj | 2 ++ build.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj b/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj index 48176ed..6710f72 100644 --- a/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj +++ b/Community.AspNetCore.ExceptionHandling.NewtonsoftJson/Community.AspNetCore.ExceptionHandling.NewtonsoftJson.csproj @@ -11,6 +11,8 @@ 2.0.0.0 2.0.0 + true + true diff --git a/build.ps1 b/build.ps1 index c940f6a..b9a7ff1 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,3 +1,3 @@ -dotnet restore .\Commmunity.AspNetCore.sln +dotnet restore .\Community.AspNetCore.sln -dotnet build .\Commmunity.AspNetCore.sln -c Release \ No newline at end of file +dotnet build .\Community.AspNetCore.sln -c Release \ No newline at end of file