Skip to content

Commit 0311bfc

Browse files
Improve HttpContextAccessor debugging (#49430)
Co-authored-by: Martin Costello <[email protected]>
1 parent bc6dea4 commit 0311bfc

File tree

3 files changed

+127
-2
lines changed

3 files changed

+127
-2
lines changed

src/Http/Http/src/HttpContextAccessor.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
46
namespace Microsoft.AspNetCore.Http;
57

68
/// <summary>
79
/// Provides an implementation of <see cref="IHttpContextAccessor" /> based on the current execution context.
810
/// </summary>
11+
[DebuggerDisplay("HttpContext = {HttpContext}")]
912
public class HttpContextAccessor : IHttpContextAccessor
1013
{
1114
private static readonly AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();

src/Http/Http/test/Internal/DefaultHttpRequestTests.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Globalization;
55
using Microsoft.AspNetCore.Http.Features;
66
using Microsoft.AspNetCore.Routing;
7+
using Microsoft.AspNetCore.Shared;
78
using Microsoft.Extensions.Primitives;
89

910
namespace Microsoft.AspNetCore.Http;
@@ -246,6 +247,97 @@ public void BodyReader_CanGet()
246247
Assert.NotNull(bodyPipe);
247248
}
248249

250+
[Fact]
251+
public void DebuggerToString_EmptyRequest()
252+
{
253+
var context = new DefaultHttpContext();
254+
255+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
256+
Assert.Equal("(unspecified)", debugText);
257+
}
258+
259+
[Fact]
260+
public void DebuggerToString_HasMethod()
261+
{
262+
var context = new DefaultHttpContext();
263+
context.Request.Method = "GET";
264+
265+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
266+
Assert.Equal("GET (unspecified)", debugText);
267+
}
268+
269+
[Fact]
270+
public void DebuggerToString_HasProtocol()
271+
{
272+
var context = new DefaultHttpContext();
273+
context.Request.Protocol = "HTTP/2";
274+
275+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
276+
Assert.Equal("(unspecified) HTTP/2", debugText);
277+
}
278+
279+
[Fact]
280+
public void DebuggerToString_HasContentType()
281+
{
282+
var context = new DefaultHttpContext();
283+
context.Request.ContentType = "application/json";
284+
285+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
286+
Assert.Equal("(unspecified) application/json", debugText);
287+
}
288+
289+
[Fact]
290+
public void DebuggerToString_HasQueryString()
291+
{
292+
var context = new DefaultHttpContext();
293+
context.Request.QueryString = new QueryString("?query=true");
294+
295+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
296+
Assert.Equal("(unspecified)://(unspecified)?query=true", debugText);
297+
}
298+
299+
[Fact]
300+
public void DebuggerToString_HasCompleteRequestUri()
301+
{
302+
var context = new DefaultHttpContext();
303+
context.Request.Scheme = "http";
304+
context.Request.Host = new HostString("localhost", 8080);
305+
context.Request.Path = "/Path";
306+
context.Request.PathBase = "/PathBase";
307+
context.Request.QueryString = new QueryString("?test=true");
308+
309+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
310+
Assert.Equal("http://localhost:8080/PathBase/Path?test=true", debugText);
311+
}
312+
313+
[Fact]
314+
public void DebuggerToString_HasPartialRequestUri()
315+
{
316+
var context = new DefaultHttpContext();
317+
context.Request.Scheme = "http";
318+
context.Request.Host = new HostString("localhost");
319+
320+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
321+
Assert.Equal("http://localhost", debugText);
322+
}
323+
324+
[Fact]
325+
public void DebuggerToString_HasEverything()
326+
{
327+
var context = new DefaultHttpContext();
328+
context.Request.Method = "GET";
329+
context.Request.Protocol = "HTTP/2";
330+
context.Request.ContentType = "application/json";
331+
context.Request.Scheme = "http";
332+
context.Request.Host = new HostString("localhost", 8080);
333+
context.Request.Path = "/Path";
334+
context.Request.PathBase = "/PathBase";
335+
context.Request.QueryString = new QueryString("?test=true");
336+
337+
var debugText = HttpContextDebugFormatter.RequestToString(context.Request);
338+
Assert.Equal("GET http://localhost:8080/PathBase/Path?test=true HTTP/2 application/json", debugText);
339+
}
340+
249341
private class CustomRouteValuesFeature : IRouteValuesFeature
250342
{
251343
public RouteValueDictionary RouteValues { get; set; }

src/Shared/Debugger/HttpContextDebugFormatter.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,15 @@ public static string ResponseToString(HttpResponse response, string? reasonPhras
3131

3232
public static string RequestToString(HttpRequest request)
3333
{
34-
var text = $"{request.Method} {GetRequestUrl(request, includeQueryString: true)} {request.Protocol}";
34+
var text = GetRequestUrl(request, includeQueryString: true);
35+
if (!string.IsNullOrEmpty(request.Method))
36+
{
37+
text = $"{request.Method} {text}";
38+
}
39+
if (!string.IsNullOrEmpty(request.Protocol))
40+
{
41+
text += $" {request.Protocol}";
42+
}
3543
if (!string.IsNullOrEmpty(request.ContentType))
3644
{
3745
text += $" {request.ContentType}";
@@ -53,6 +61,28 @@ public static string ContextToString(HttpContext context, string? reasonPhrase)
5361

5462
private static string GetRequestUrl(HttpRequest request, bool includeQueryString)
5563
{
56-
return $"{request.Scheme}://{request.Host.Value}{request.PathBase.Value}{request.Path.Value}{(includeQueryString ? request.QueryString.Value : string.Empty)}";
64+
// The URL might be missing because the context was manually created in a test, e.g. new DefaultHttpContext()
65+
if (string.IsNullOrEmpty(request.Scheme) &&
66+
!request.Host.HasValue &&
67+
!request.PathBase.HasValue &&
68+
!request.Path.HasValue &&
69+
!request.QueryString.HasValue)
70+
{
71+
return "(unspecified)";
72+
}
73+
74+
// If some parts of the URL are provided then default the significant parts to avoid a werid output.
75+
var scheme = request.Scheme;
76+
if (string.IsNullOrEmpty(scheme))
77+
{
78+
scheme = "(unspecified)";
79+
}
80+
var host = request.Host.Value;
81+
if (string.IsNullOrEmpty(host))
82+
{
83+
host = "(unspecified)";
84+
}
85+
86+
return $"{scheme}://{host}{request.PathBase.Value}{request.Path.Value}{(includeQueryString ? request.QueryString.Value : string.Empty)}";
5787
}
5888
}

0 commit comments

Comments
 (0)