Skip to content

Commit 106bf7f

Browse files
authored
Merge pull request #12 from Mathavana/develop
Develop - Global Error Handle
2 parents 404f77a + 535da6c commit 106bf7f

File tree

10 files changed

+105
-5
lines changed

10 files changed

+105
-5
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ We will try to create an API application using asp.net core.
1717
- Models, Enums, Context, Repositories moved to Supermarket.Core project
1818
- xUnit Test Project added
1919
- CircleCi
20-
- Standard response for error (400,404) results (Similar to ApiController model validation result)
20+
- Standard response for error (400,404, 500) results (Similar to ApiController model validation result)
21+
- Global Error Handling
2122

2223
### Next ToDo List:
2324
- Role Based Authentication/Authorization
24-
- Global Error Handling
2525
- Dockerization
2626
- More test cases needed
2727
- Proper model implementation

Supermarket/ApiResponse/BadRequestResponse.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ namespace Supermarket.ApiResponse
88
public class BadRequestResponse : ValidationProblemDetails
99
{
1010
public Guid TraceId { get; set; }
11+
1112
public BadRequestResponse(string error) : base(new Dictionary<string, string[]> { { "error", new[] { error } } })
1213
{
1314
TraceId = Guid.NewGuid();
1415
Title = HttpStatusCode.BadRequest.ToString();
16+
Status = (int)HttpStatusCode.BadRequest;
1517
}
1618
}
1719
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Net;
5+
6+
namespace Supermarket.ApiResponse
7+
{
8+
public class InternalServerErrorResponse : ValidationProblemDetails
9+
{
10+
public Guid TraceId { get; set; }
11+
12+
public InternalServerErrorResponse(string error) : base(new Dictionary<string, string[]> { { "error", new[] { error } } })
13+
{
14+
TraceId = Guid.NewGuid();
15+
Title = HttpStatusCode.InternalServerError.ToString();
16+
Status = (int)HttpStatusCode.InternalServerError;
17+
}
18+
}
19+
}

Supermarket/ApiResponse/NotFoundResponse.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,24 @@ namespace Supermarket.ApiResponse
88
public class NotFoundResponse : ValidationProblemDetails
99
{
1010
public Guid TraceId { get; set; }
11+
1112
public NotFoundResponse(string error) : base(new Dictionary<string, string[]> { { "error", new[] { error } } })
1213
{
1314
TraceId = Guid.NewGuid();
1415
Title = HttpStatusCode.NotFound.ToString();
16+
Status = (int)HttpStatusCode.NotFound;
1517
}
18+
19+
////In feature, to log the not found response
20+
//public NotFoundResponse(string error, ILogger logger) : base(new Dictionary<string, string[]> { { "error", new[] { error } } })
21+
//{
22+
// TraceId = Guid.NewGuid();
23+
// Title = HttpStatusCode.NotFound.ToString();
24+
// Status = (int)HttpStatusCode.NotFound;
25+
// logger.LogInformation(JsonConvert.SerializeObject(this,
26+
// Formatting.Indented,
27+
// new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }
28+
// ));
29+
//}
1630
}
1731
}

Supermarket/Extensions/ServiceExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Supermarket.Core.Models;
1414
using Supermarket.Core.Repositories;
1515
using Supermarket.Core.Repositories.Contracts;
16+
using Supermarket.Middleware;
1617
using Supermarket.Swagger;
1718
using Swashbuckle.AspNetCore.Swagger;
1819
using System;
@@ -157,5 +158,10 @@ public static void ConfigureVersionedApiExplorer(this IServiceCollection service
157158
options.SubstituteApiVersionInUrl = true;
158159
});
159160
}
161+
162+
public static void ConfigureCustomExceptionMiddleware(this IApplicationBuilder app)
163+
{
164+
app.UseMiddleware<ExceptionMiddleware>();
165+
}
160166
}
161167
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.Extensions.Logging;
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Serialization;
5+
using Supermarket.ApiResponse;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Net;
10+
using System.Threading.Tasks;
11+
12+
namespace Supermarket.Middleware
13+
{
14+
public class ExceptionMiddleware
15+
{
16+
private readonly RequestDelegate _next;
17+
private readonly ILogger<ExceptionMiddleware> _logger;
18+
19+
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
20+
{
21+
_logger = logger;
22+
_next = next;
23+
}
24+
25+
public async Task InvokeAsync(HttpContext httpContext)
26+
{
27+
try
28+
{
29+
await _next(httpContext);
30+
}
31+
catch (Exception ex)
32+
{
33+
_logger.LogError($"API Error: {ex}");
34+
await HandleExceptionAsync(httpContext, ex);
35+
}
36+
}
37+
38+
private Task HandleExceptionAsync(HttpContext context, Exception exception)
39+
{
40+
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
41+
42+
////Refer https://stackoverflow.com/questions/38630076/asp-net-core-web-api-exception-handling
43+
44+
var result = JsonConvert.SerializeObject(
45+
new InternalServerErrorResponse(exception.Message),
46+
Formatting.Indented,
47+
new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }
48+
);
49+
_logger.LogError(result);
50+
context.Response.ContentType = "application/json";
51+
context.Response.StatusCode = (int)code;
52+
return context.Response.WriteAsync(result);
53+
}
54+
}
55+
}

Supermarket/Startup.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ public void ConfigureServices(IServiceCollection services)
4545

4646
services.ConfigureRepositoryWrapper();
4747

48-
services.AddMvc().AddJsonOptions(options =>
48+
services.AddMvcCore().AddJsonOptions(options =>
4949
{
50+
options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
5051
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
5152
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
5253

@@ -64,9 +65,13 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env,
6465
IApiVersionDescriptionProvider provider, ILoggerFactory loggerFactory)
6566
{
6667
loggerFactory.AddSerilog();
68+
69+
app.ConfigureCustomExceptionMiddleware();
70+
6771
if (env.IsDevelopment())
6872
{
6973
app.UseDeveloperExceptionPage();
74+
app.UseDatabaseErrorPage();
7075
}
7176
else
7277
{

Supermarket/Supermarket.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="3.2.0" />
2727
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
2828
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.3" />
29+
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
2930
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
3031
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" />
3132
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />

Supermarket/V1/Controllers/AboutController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Microsoft.AspNetCore.Http;
22
using Microsoft.AspNetCore.Mvc;
33
using Microsoft.Extensions.Logging;
4-
using System.Threading.Tasks;
54

65
namespace Supermarket.V1.Controller
76
{

Supermarket/V1/Controllers/ProductsController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.AspNetCore.Mvc;
55
using Supermarket.Core.Models;
66
using Supermarket.Core.Repositories.Contracts;
7-
using Supermarket.Resources;
87
using Supermarket.V1.Dtos.ProductDtos;
98
using System.Collections.Generic;
109
using System.Threading.Tasks;

0 commit comments

Comments
 (0)