Skip to content

Commit 8fce38e

Browse files
committed
add aspnet common stuff
1 parent 8ed38c5 commit 8fce38e

8 files changed

Lines changed: 166 additions & 0 deletions

File tree

Common.AspNet/Common.AspNet.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<OutputType>Library</OutputType>
4+
</PropertyGroup>
5+
6+
<ItemGroup>
7+
<ProjectReference Include="..\Common\Common.csproj" />
8+
</ItemGroup>
9+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using OpenShock.Common.Problems;
2+
3+
namespace OpenShock.Common.Errors;
4+
5+
public static class ExceptionError
6+
{
7+
public static ExceptionProblem Exception => new ExceptionProblem();
8+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Net;
2+
using System.Text.Json;
3+
using Microsoft.AspNetCore.Diagnostics;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.Extensions.Hosting;
6+
using Microsoft.Extensions.Logging;
7+
using OpenShock.Common.Errors;
8+
9+
namespace OpenShock.Common.ExceptionHandling;
10+
11+
public sealed class OpenShockExceptionHandler : IExceptionHandler
12+
{
13+
private readonly IHostEnvironment _env;
14+
private readonly ILogger _logger;
15+
private readonly JsonSerializerOptions _jsonSerializerOptions;
16+
17+
public OpenShockExceptionHandler(IHostEnvironment env, ILoggerFactory loggerFactory, JsonSerializerOptions jsonSerializerOptions)
18+
{
19+
_env = env;
20+
_logger = loggerFactory.CreateLogger("RequestInfo");
21+
_jsonSerializerOptions = jsonSerializerOptions;
22+
}
23+
24+
public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
25+
{
26+
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
27+
_logger.LogError(exception, "Unhandled exception for {Method} {Path}", context.Request.Method, context.Request.Path);
28+
29+
if (_env.IsDevelopment())
30+
{
31+
await PrintRequestInfo(context);
32+
}
33+
34+
await ExceptionError.Exception.WriteAsJsonAsync(context, _jsonSerializerOptions, cancellationToken);
35+
return context.Response.HasStarted;
36+
}
37+
38+
private async Task PrintRequestInfo(HttpContext context)
39+
{
40+
// Rewind our body reader, so we can read it again.
41+
context.Request.Body.Seek(0, SeekOrigin.Begin);
42+
// Used to read from the body stream.
43+
using var stream = new StreamReader(context.Request.Body);
44+
45+
// Create Dictionaries to be logging in our RequestInfo object for both Header values and Query parameters.
46+
var headers = context.Request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString());
47+
var queryParams = context.Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString());
48+
49+
// Create our RequestInfo object.
50+
var requestInfo = new RequestInfo
51+
{
52+
Body = await stream.ReadToEndAsync(),
53+
Headers = headers,
54+
TraceId = context.TraceIdentifier,
55+
Method = context.Request.Method,
56+
Path = context.Request.Path.Value,
57+
Query = queryParams
58+
};
59+
60+
// Finally log this object on Information level.
61+
_logger.LogInformation("{@RequestInfo}", requestInfo);
62+
}
63+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using OpenShock.Common.Problems;
3+
4+
namespace OpenShock.Common;
5+
6+
public class OpenShockControllerBase : ControllerBase
7+
{
8+
[NonAction]
9+
public ObjectResult Problem(OpenShockProblem problem) => problem.ToObjectResult(HttpContext);
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Net;
2+
3+
namespace OpenShock.Common.Problems;
4+
5+
public sealed class ExceptionProblem : OpenShockProblem
6+
{
7+
public ExceptionProblem() : base("Exception", "An unknown exception occurred", HttpStatusCode.InternalServerError,
8+
"An unknown error occurred. Please try again later. If the issue persists reach out to support.")
9+
{
10+
}
11+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Net;
2+
using System.Net.Mime;
3+
using System.Text.Json;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc;
6+
7+
namespace OpenShock.Common.Problems;
8+
9+
/// <summary>
10+
/// Represents a problem
11+
/// </summary>
12+
public class OpenShockProblem : ProblemDetails
13+
{
14+
public OpenShockProblem(string type, string title, HttpStatusCode status = HttpStatusCode.BadRequest,
15+
string? detail = null) : this(type, title, (int)status, detail)
16+
{
17+
}
18+
19+
private OpenShockProblem(string type, string title, int status = 400, string? detail = null)
20+
{
21+
Type = type;
22+
Title = title;
23+
Detail = detail;
24+
Status = status;
25+
}
26+
27+
[Obsolete("This is the exact same as title or detail if present, refer to using title in the future")]
28+
public string Message => Detail ?? Title!;
29+
30+
[Obsolete("This is the exact same as requestId, refer to using requestId in the future")]
31+
public string? TraceId => RequestId;
32+
33+
public string? RequestId { get; set; }
34+
35+
public ObjectResult ToObjectResult(HttpContext context)
36+
{
37+
RequestId = context.TraceIdentifier;
38+
39+
return new ObjectResult(this)
40+
{
41+
StatusCode = Status
42+
};
43+
}
44+
45+
public Task WriteAsJsonAsync(HttpContext context, JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken)
46+
{
47+
context.Response.StatusCode = Status ?? StatusCodes.Status400BadRequest;
48+
RequestId = context.TraceIdentifier;
49+
50+
return context.Response.WriteAsJsonAsync(this, jsonSerializerOptions, MediaTypeNames.Application.ProblemJson, cancellationToken);
51+
}
52+
}

Common.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<Solution>
22
<Project Path="Common\Common.csproj" Type="Classic C#" />
3+
<Project Path="Common.AspNet\Common.AspNet.csproj" Type="Classic C#" />
34
<Project Path="Common.Tests\Common.Tests.csproj" Type="Classic C#" />
45
<Project Path="DynamicLinq\DynamicLinq.csproj" Type="Classic C#" />
56
<Project Path="DynamicLinq.Tests\DynamicLinq.Tests.csproj" Type="Classic C#" />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// ReSharper disable UnusedAutoPropertyAccessor.Global
2+
namespace OpenShock.Common.ExceptionHandling;
3+
4+
public sealed class RequestInfo
5+
{
6+
public required string? Path { get; set; }
7+
public required IDictionary<string, string> Query { get; set; }
8+
public required string Body { get; set; }
9+
public required string Method { get; set; }
10+
public required string TraceId { get; set; }
11+
public required IDictionary<string, string> Headers { get; set; }
12+
}

0 commit comments

Comments
 (0)