Skip to content
This repository was archived by the owner on Nov 25, 2025. It is now read-only.

Commit e3934e6

Browse files
Ihar YakimushIhar Yakimush
authored andcommitted
exception handling pipeline
1 parent e064932 commit e3934e6

17 files changed

+662
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<Folder Include="wwwroot\" />
9+
</ItemGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<ProjectReference Include="..\Commmunity.AspNetCore.ExceptionHandling\Commmunity.AspNetCore.ExceptionHandling.csproj" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Mvc;
6+
7+
namespace Commmunity.AspNetCore.ExceptionHandling.Integration.Controllers
8+
{
9+
[Route("api/[controller]")]
10+
public class ValuesController : Controller
11+
{
12+
// GET api/values
13+
[HttpGet]
14+
public IEnumerable<string> Get()
15+
{
16+
return new string[] { "value1", "value2" };
17+
}
18+
19+
// GET api/values/5
20+
[HttpGet("{id}")]
21+
public string Get(int id)
22+
{
23+
if (id > 10)
24+
{
25+
throw new ArgumentOutOfRangeException();
26+
}
27+
28+
if (id > 5)
29+
{
30+
throw new InvalidCastException();
31+
}
32+
33+
return "value";
34+
}
35+
36+
// POST api/values
37+
[HttpPost]
38+
public void Post([FromBody]string value)
39+
{
40+
}
41+
42+
// PUT api/values/5
43+
[HttpPut("{id}")]
44+
public void Put(int id, [FromBody]string value)
45+
{
46+
}
47+
48+
// DELETE api/values/5
49+
[HttpDelete("{id}")]
50+
public void Delete(int id)
51+
{
52+
}
53+
}
54+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore;
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Commmunity.AspNetCore.ExceptionHandling.Integration
12+
{
13+
public class Program
14+
{
15+
public static void Main(string[] args)
16+
{
17+
BuildWebHost(args).Run();
18+
}
19+
20+
public static IWebHost BuildWebHost(string[] args) =>
21+
WebHost.CreateDefaultBuilder(args)
22+
.UseStartup<Startup>()
23+
.Build();
24+
}
25+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.Extensions.Options;
11+
12+
namespace Commmunity.AspNetCore.ExceptionHandling.Integration
13+
{
14+
public class Startup
15+
{
16+
public Startup(IConfiguration configuration)
17+
{
18+
Configuration = configuration;
19+
}
20+
21+
public IConfiguration Configuration { get; }
22+
23+
// This method gets called by the runtime. Use this method to add services to the container.
24+
public void ConfigureServices(IServiceCollection services)
25+
{
26+
services.AddMvc();
27+
28+
services.AddExceptionHandlingPolicies(options =>
29+
options.EnsureException<ArgumentOutOfRangeException>()
30+
.EnsureHandler<LogExceptionHandler>().EnsureHandler<ReThrowExceptionHandler>());
31+
}
32+
33+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
34+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
35+
{
36+
app.UseDeveloperExceptionPage().UseExceptionHandlingPolicies();
37+
38+
app.UseMvc();
39+
}
40+
}
41+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Logging": {
3+
"IncludeScopes": false,
4+
"LogLevel": {
5+
"Default": "Debug",
6+
"System": "Information",
7+
"Microsoft": "Information"
8+
}
9+
}
10+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"Logging": {
3+
"IncludeScopes": false,
4+
"Debug": {
5+
"LogLevel": {
6+
"Default": "Warning"
7+
}
8+
},
9+
"Console": {
10+
"LogLevel": {
11+
"Default": "Warning"
12+
}
13+
}
14+
}
15+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.DependencyInjection.Extensions;
5+
using Microsoft.Extensions.Options;
6+
7+
namespace Commmunity.AspNetCore.ExceptionHandling
8+
{
9+
//TODO: add warning for policy override
10+
//TODO: add response handler
11+
//TODO: add retry handler
12+
//TODO: policy builder
13+
//TODO: add api exception and handler
14+
//TODO: add terminate policies pipeline handler ???
15+
public static class AppBuilderExtensions
16+
{
17+
public static IApplicationBuilder UseExceptionHandlingPolicies(this IApplicationBuilder app)
18+
{
19+
return app.UseMiddleware<ExceptionHandlingPolicyMiddleware>();
20+
}
21+
public static IServiceCollection AddExceptionHandlingPolicies(this IServiceCollection services, Action<ExceptionHandlingPolicyOptions> options = null)
22+
{
23+
services.TryAddSingleton<ExceptionHandlingPolicyMiddleware>();
24+
if (options != null)
25+
{
26+
services.Configure(options);
27+
}
28+
29+
services.TryAddSingleton<ReThrowExceptionHandler>();
30+
services.TryAddSingleton<LogExceptionHandler>();
31+
32+
return services;
33+
}
34+
35+
public static IApplicationBuilder UseExceptionHandlingPolicies(this IApplicationBuilder app, ExceptionHandlingPolicyOptions options)
36+
{
37+
return app.UseMiddleware<ExceptionHandlingPolicyMiddleware>(Options.Create(options));
38+
}
39+
}
40+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.0" />
9+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.Extensions.Logging;
2+
3+
namespace Commmunity.AspNetCore.ExceptionHandling
4+
{
5+
public static class Events
6+
{
7+
public static readonly EventId HandlerError = new EventId(100, "HandlerExecutionError");
8+
public static readonly EventId PolicyNotFound = new EventId(101, "PolicyForExceptionNotRegistered");
9+
public static readonly EventId HandlersNotFound = new EventId(102, "HandlersCollectionEmpty");
10+
public static readonly EventId HandlerNotCreated = new EventId(103, "HandlersCanNotBeCreated");
11+
}
12+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Logging;
7+
using Microsoft.Extensions.Logging.Abstractions;
8+
using Microsoft.Extensions.Options;
9+
10+
namespace Commmunity.AspNetCore.ExceptionHandling
11+
{
12+
public class ExceptionHandlingPolicyMiddleware : IMiddleware
13+
{
14+
private readonly IOptions<ExceptionHandlingPolicyOptions> options;
15+
16+
public ExceptionHandlingPolicyMiddleware(IOptions<ExceptionHandlingPolicyOptions> options)
17+
{
18+
this.options = options ?? throw new ArgumentNullException(nameof(options));
19+
}
20+
21+
private static async Task<bool> EnumerateExceptionMapping(
22+
HttpContext context,
23+
ExceptionHandlingPolicyOptions policyOptions,
24+
Exception exception,
25+
RequestDelegate next)
26+
{
27+
Type exceptionType = exception.GetType();
28+
29+
ILogger logger = context.RequestServices.GetService<ILogger<ExceptionHandlingPolicyMiddleware>>() ??
30+
NullLoggerFactory.Instance.CreateLogger<ExceptionHandlingPolicyMiddleware>();
31+
32+
bool? throwRequired = null;
33+
34+
foreach (Type type in policyOptions.GetExceptionsInternal())
35+
{
36+
if (type.IsAssignableFrom(exceptionType))
37+
{
38+
throwRequired = await EnumerateHandlers(context, exception, next, policyOptions, logger);
39+
40+
break;
41+
}
42+
}
43+
44+
if (!throwRequired.HasValue)
45+
{
46+
logger.LogWarning(Events.PolicyNotFound,
47+
"Handlers mapping for exception type {exceptionType} not exists. Exception will be re-thrown. RequestId: {RequestId}",
48+
exceptionType, context.TraceIdentifier);
49+
}
50+
51+
return throwRequired ?? true;
52+
}
53+
54+
private static async Task<bool> EnumerateHandlers(
55+
HttpContext context,
56+
Exception exception,
57+
RequestDelegate next,
58+
ExceptionHandlingPolicyOptions policyOptions,
59+
ILogger logger)
60+
{
61+
bool? throwRequired = null;
62+
Type exceptionType = exception.GetType();
63+
64+
IEnumerable<Type> handlers = policyOptions.GetHandlersInternal(exceptionType);
65+
66+
foreach (Type handlerType in handlers)
67+
{
68+
try
69+
{
70+
IExceptionHandler handler =
71+
context.RequestServices.GetService(handlerType) as IExceptionHandler;
72+
73+
if (handler == null)
74+
{
75+
throwRequired = true;
76+
logger.LogError(Events.HandlerNotCreated,
77+
"Handler type {handlerType} can't be created because it not registered in IServiceProvider. RequestId: {RequestId}",
78+
handlerType, context.TraceIdentifier);
79+
}
80+
else
81+
{
82+
throwRequired = await handler.Handle(context, exception, next);
83+
}
84+
}
85+
catch (Exception e)
86+
{
87+
logger.LogError(Events.HandlerError, e,
88+
"Unhandled exception executing handler of type {handlerType} on exception of type {exceptionType}. RequestId: {RequestId}",
89+
handlerType, exceptionType, context.TraceIdentifier);
90+
throwRequired = true;
91+
}
92+
93+
if (throwRequired.Value)
94+
{
95+
break;
96+
}
97+
}
98+
99+
if (!throwRequired.HasValue)
100+
{
101+
logger.LogWarning(Events.HandlersNotFound,
102+
"Handlers collection for exception type {exceptionType} is empty. Exception will be re-thrown. RequestId: {RequestId}",
103+
exceptionType, context.TraceIdentifier);
104+
}
105+
106+
return throwRequired ?? true;
107+
}
108+
109+
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
110+
{
111+
try
112+
{
113+
await next(context);
114+
}
115+
catch (Exception exception)
116+
{
117+
ExceptionHandlingPolicyOptions policyOptions = this.options.Value;
118+
119+
bool throwRequired = await EnumerateExceptionMapping(context, policyOptions, exception, next);
120+
121+
if (throwRequired)
122+
{
123+
throw;
124+
}
125+
}
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)