Skip to content

Commit 1129ee2

Browse files
authored
Merge pull request #347 from exceptionless/feature/upgrade-workflows-request-info
Fixes request-info collection across the ASP.NET Core / ASP.NET (System.Web) / WebApi platform integrations to avoid reading POST bodies for handled errors,
2 parents 8510806 + 49a2c0f commit 1129ee2

File tree

17 files changed

+164
-56
lines changed

17 files changed

+164
-56
lines changed

.github/workflows/build-linux.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ name: Build Linux
22
on: [push, pull_request]
33
env:
44
DOTNET_CLI_TELEMETRY_OPTOUT: 1
5+
DOTNET_NOLOGO: 1
56
jobs:
67
build:
78
runs-on: ubuntu-latest
89
steps:
910
- name: Checkout
10-
uses: actions/checkout@v4
11+
uses: actions/checkout@v6
1112
with:
1213
fetch-depth: 0
13-
- name: Setup .NET Core 8
14-
uses: actions/setup-dotnet@v4
14+
- name: Setup .NET SDK
15+
uses: actions/setup-dotnet@v5
1516
with:
16-
dotnet-version: 8.x
17+
global-json-file: global.json
1718
- name: Build Reason
1819
run: "echo ref: ${{github.ref}} event: ${{github.event_name}}"
1920
- name: Build Version

.github/workflows/build-osx.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ name: Build OSX
22
on: [push, pull_request]
33
env:
44
DOTNET_CLI_TELEMETRY_OPTOUT: 1
5+
DOTNET_NOLOGO: 1
56
jobs:
67
build:
78
runs-on: macOS-latest
89
steps:
910
- name: Checkout
10-
uses: actions/checkout@v4
11+
uses: actions/checkout@v6
1112
with:
1213
fetch-depth: 0
13-
- name: Setup .NET Core 8
14-
uses: actions/setup-dotnet@v4
14+
- name: Setup .NET SDK
15+
uses: actions/setup-dotnet@v5
1516
with:
16-
dotnet-version: 8.x
17+
global-json-file: global.json
1718
- name: Build Reason
1819
run: "echo ref: ${{github.ref}} event: ${{github.event_name}}"
1920
- name: Build Version

.github/workflows/build-windows.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ name: Build Windows
22
on: [push, pull_request]
33
env:
44
DOTNET_CLI_TELEMETRY_OPTOUT: 1
5+
DOTNET_NOLOGO: 1
56
jobs:
67
build:
78
runs-on: windows-latest
89
steps:
910
- name: Checkout
10-
uses: actions/checkout@v4
11+
uses: actions/checkout@v6
1112
with:
1213
fetch-depth: 0
13-
- name: Setup .NET Core 8
14-
uses: actions/setup-dotnet@v4
14+
- name: Setup .NET SDK
15+
uses: actions/setup-dotnet@v5
1516
with:
16-
dotnet-version: 8.x
17+
global-json-file: global.json
1718
- name: Build Reason
1819
run: "echo ref: ${{github.ref}} event: ${{github.event_name}}"
1920
- name: Build Version

build/common.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
<PackageReleaseNotes>https://github.com/exceptionless/Exceptionless.Net/releases</PackageReleaseNotes>
77
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
88
<MinVerTagPrefix>v</MinVerTagPrefix>
9+
<EnableWindowsTargeting>true</EnableWindowsTargeting>
910

10-
<Copyright>Copyright (c) 2025 Exceptionless. All rights reserved.</Copyright>
11+
<Copyright>Copyright © $([System.DateTime]::Now.ToString(yyyy)) Exceptionless. All rights reserved.</Copyright>
1112
<Authors>Exceptionless</Authors>
1213
<NoWarn>$(NoWarn);CS1591;NU1701</NoWarn>
1314
<WarningsAsErrors>true</WarningsAsErrors>

global.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "8.0.100",
3+
"version": "10.0.100",
44
"rollForward": "latestMinor"
55
}
6-
}
6+
}

samples/Exceptionless.SampleAspNetCore/Controllers/ValuesController.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ public Dictionary<string, string> Get() {
2626

2727
try {
2828
throw new Exception($"Handled Exception: {Guid.NewGuid()}");
29-
} catch (Exception handledException) {
29+
}
30+
catch (Exception handledException) {
3031
// Use the ToExceptionless extension method to submit this handled exception to Exceptionless using the client instance from DI.
3132
handledException.ToExceptionless(_exceptionlessClient).Submit();
3233
}
3334

3435
try {
3536
throw new Exception($"Handled Exception (Default Client): {Guid.NewGuid()}");
36-
} catch (Exception handledException) {
37+
}
38+
catch (Exception handledException) {
3739
// Use the ToExceptionless extension method to submit this handled exception to Exceptionless using the default client instance (ExceptionlessClient.Default).
3840
// This works and is convenient, but its generally not recommended to use static singleton instances because it makes testing and
3941
// other things harder.
@@ -44,4 +46,4 @@ public Dictionary<string, string> Get() {
4446
throw new Exception($"Unhandled Exception: {Guid.NewGuid()}");
4547
}
4648
}
47-
}
49+
}

src/Platforms/Exceptionless.AspNetCore/ExceptionlessAspNetCorePlugin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void Run(EventPluginContext context) {
3232
return;
3333

3434
try {
35-
ri = httpContext.GetRequestInfo(context.Client.Configuration);
35+
ri = httpContext.GetRequestInfo(context.Client.Configuration, context.ContextData.IsUnhandledError);
3636
} catch (Exception ex) {
3737
context.Log.Error(typeof(ExceptionlessAspNetCorePlugin), ex, "Error adding request info.");
3838
}

src/Platforms/Exceptionless.AspNetCore/ExceptionlessExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ public static IApplicationBuilder UseExceptionless(this IApplicationBuilder app,
6868
/// </summary>
6969
/// <param name="context">The http context to gather information from.</param>
7070
/// <param name="config">The config.</param>
71-
public static RequestInfo GetRequestInfo(this HttpContext context, ExceptionlessConfiguration config) {
72-
return RequestInfoCollector.Collect(context, config);
71+
/// <param name="isUnhandledError">Whether this is an unhandled error. POST data is only collected for unhandled errors to avoid consuming the request stream.</param>
72+
public static RequestInfo GetRequestInfo(this HttpContext context, ExceptionlessConfiguration config, bool isUnhandledError = false) {
73+
return RequestInfoCollector.Collect(context, config, isUnhandledError);
7374
}
7475

7576
/// <summary>

src/Platforms/Exceptionless.AspNetCore/RequestInfoCollector.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static class RequestInfoCollector {
1616
private const int MAX_BODY_SIZE = 50 * 1024;
1717
private const int MAX_DATA_ITEM_LENGTH = 1000;
1818

19-
public static RequestInfo Collect(HttpContext context, ExceptionlessConfiguration config) {
19+
public static RequestInfo Collect(HttpContext context, ExceptionlessConfiguration config, bool isUnhandledError = false) {
2020
if (context == null)
2121
return null;
2222

@@ -50,7 +50,9 @@ public static RequestInfo Collect(HttpContext context, ExceptionlessConfiguratio
5050
if (config.IncludeQueryString)
5151
info.QueryString = context.Request.Query.ToDictionary(exclusionList);
5252

53-
if (config.IncludePostData && !String.Equals(context.Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
53+
// Only collect POST data for unhandled errors to avoid consuming the request stream
54+
// and breaking model binding for handled errors where the app continues processing.
55+
if (config.IncludePostData && isUnhandledError && !String.Equals(context.Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
5456
info.PostData = GetPostData(context, config, exclusionList);
5557

5658
return info;
@@ -59,13 +61,8 @@ public static RequestInfo Collect(HttpContext context, ExceptionlessConfiguratio
5961
private static object GetPostData(HttpContext context, ExceptionlessConfiguration config, string[] exclusionList) {
6062
var log = config.Resolver.GetLog();
6163

62-
if (context.Request.HasFormContentType && context.Request.Form.Count > 0) {
63-
log.Debug("Reading POST data from Request.Form");
64-
return context.Request.Form.ToDictionary(exclusionList);
65-
}
66-
67-
var contentLength = context.Request.ContentLength.GetValueOrDefault();
68-
if(contentLength == 0) {
64+
long contentLength = context.Request.ContentLength.GetValueOrDefault();
65+
if (contentLength == 0) {
6966
string message = "Content-length was zero, empty post.";
7067
log.Debug(message);
7168
return message;
@@ -98,6 +95,14 @@ private static object GetPostData(HttpContext context, ExceptionlessConfiguratio
9895
return message;
9996
}
10097

98+
// Form check must come after seekability and position checks above: accessing
99+
// Request.Form triggers reading the request body stream.
100+
if (context.Request.HasFormContentType && context.Request.Form.Count > 0) {
101+
log.Debug("Reading POST data from Request.Form");
102+
context.Request.Body.Position = originalPosition;
103+
return context.Request.Form.ToDictionary(exclusionList);
104+
}
105+
101106
// pass default values, except for leaveOpen: true. This prevents us from disposing the underlying stream
102107
using (var inputStream = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true)) {
103108
var sb = new StringBuilder();

src/Platforms/Exceptionless.Web/ExceptionlessWebExtensions.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,25 @@ public static class ExceptionlessWebExtensions {
1111
/// </summary>
1212
/// <param name="context">The http context to gather information from.</param>
1313
/// <param name="config">The config.</param>
14-
public static RequestInfo GetRequestInfo(this HttpContext context, ExceptionlessConfiguration config) {
14+
/// <param name="isUnhandledError">Whether this is an unhandled error. POST data is only collected for unhandled errors to avoid consuming the request stream.</param>
15+
public static RequestInfo GetRequestInfo(this HttpContext context, ExceptionlessConfiguration config, bool isUnhandledError = false) {
1516
if (context == null)
1617
return null;
1718

18-
return GetRequestInfo(context.ToWrapped(), config);
19+
return GetRequestInfo(context.ToWrapped(), config, isUnhandledError);
1920
}
2021

2122
/// <summary>
2223
/// Adds the current request info.
2324
/// </summary>
2425
/// <param name="context">The http context to gather information from.</param>
2526
/// <param name="config">The config.</param>
26-
public static RequestInfo GetRequestInfo(this HttpContextBase context, ExceptionlessConfiguration config) {
27+
/// <param name="isUnhandledError">Whether this is an unhandled error. POST data is only collected for unhandled errors to avoid consuming the request stream.</param>
28+
public static RequestInfo GetRequestInfo(this HttpContextBase context, ExceptionlessConfiguration config, bool isUnhandledError = false) {
2729
if (context == null && HttpContext.Current != null)
2830
context = HttpContext.Current.ToWrapped();
2931

30-
return RequestInfoCollector.Collect(context, config);
32+
return RequestInfoCollector.Collect(context, config, isUnhandledError);
3133
}
3234

3335
/// <summary>

0 commit comments

Comments
 (0)