Skip to content

Commit 15f2e6d

Browse files
committed
Catch and log InvalidDataExceptions
1 parent dae72ad commit 15f2e6d

File tree

2 files changed

+102
-7
lines changed

2 files changed

+102
-7
lines changed

src/Http/Routing/src/Internal/MapActionExpressionTreeBuilder.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,16 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
270270
}
271271
catch (IOException ex)
272272
{
273-
LogRequestBodyIOException(httpContext, ex);
273+
Log.RequestBodyIOException(GetLogger(httpContext), ex);
274274
httpContext.Abort();
275275
return;
276276
}
277+
catch (InvalidDataException ex)
278+
{
279+
Log.RequestBodyInvalidDataException(GetLogger(httpContext), ex);
280+
httpContext.Response.StatusCode = 400;
281+
return;
282+
}
277283
}
278284

279285
await invoker(target, httpContext, bodyValue);
@@ -294,10 +300,16 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
294300
}
295301
catch (IOException ex)
296302
{
297-
LogRequestBodyIOException(httpContext, ex);
303+
Log.RequestBodyIOException(GetLogger(httpContext), ex);
298304
httpContext.Abort();
299305
return;
300306
}
307+
catch (InvalidDataException ex)
308+
{
309+
Log.RequestBodyInvalidDataException(GetLogger(httpContext), ex);
310+
httpContext.Response.StatusCode = 400;
311+
return;
312+
}
301313

302314
await invoker(target, httpContext);
303315
};
@@ -316,11 +328,10 @@ public static RequestDelegate BuildRequestDelegate(Delegate action)
316328
};
317329
}
318330

319-
private static void LogRequestBodyIOException(HttpContext httpContext, IOException exception)
331+
private static ILogger GetLogger(HttpContext httpContext)
320332
{
321333
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
322-
var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Routing.MapAction");
323-
Log.RequestBodyIOException(logger, exception);
334+
return loggerFactory.CreateLogger("Microsoft.AspNetCore.Routing.MapAction");
324335
}
325336

326337
private static Expression BindParamenter(Expression sourceExpression, ParameterInfo parameter, string? name)
@@ -433,10 +444,20 @@ private static class Log
433444
new EventId(1, "RequestBodyIOException"),
434445
"Reading the request body failed with an IOException.");
435446

447+
private static readonly Action<ILogger, Exception> _requestBodyInvalidDataException = LoggerMessage.Define(
448+
LogLevel.Debug,
449+
new EventId(2, "RequestBodyInvalidDataException"),
450+
"Reading the request body failed with an InvalidDataException.");
451+
436452
public static void RequestBodyIOException(ILogger logger, IOException exception)
437453
{
438454
_requestBodyIOException(logger, exception);
439455
}
456+
457+
public static void RequestBodyInvalidDataException(ILogger logger, InvalidDataException exception)
458+
{
459+
_requestBodyInvalidDataException(logger, exception);
460+
}
440461
}
441462
}
442463
}

src/Http/Routing/test/UnitTests/Internal/MapActionExpressionTreeBuilderTest.cs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ void TestAction([FromBody(AllowEmpty = true)] BodyStruct bodyStruct)
252252
}
253253

254254
[Fact]
255-
public async Task RequestDelegateLogsFromBodyIOExceptionsAsDebug()
255+
public async Task RequestDelegateLogsFromBodyIOExceptionsAsDebugAndAborts()
256256
{
257257
var invoked = false;
258258

@@ -287,6 +287,43 @@ void TestAction([FromBody] Todo todo)
287287
Assert.Same(ioException, logMessage.Exception);
288288
}
289289

290+
[Fact]
291+
public async Task RequestDelegateLogsFromBodyInvalidDataExceptionsAsDebugAndSets400Response()
292+
{
293+
var invoked = false;
294+
295+
var sink = new TestSink(context => context.LoggerName == "Microsoft.AspNetCore.Routing.MapAction");
296+
var testLoggerFactory = new TestLoggerFactory(sink, enabled: true);
297+
298+
void TestAction([FromBody] Todo todo)
299+
{
300+
invoked = true;
301+
}
302+
303+
var invalidDataException = new InvalidDataException();
304+
var serviceCollection = new ServiceCollection();
305+
serviceCollection.AddSingleton<ILoggerFactory>(testLoggerFactory);
306+
307+
var httpContext = new DefaultHttpContext();
308+
httpContext.Request.Headers["Content-Type"] = "application/json";
309+
httpContext.Request.Body = new IOExceptionThrowingRequestBodyStream(invalidDataException);
310+
httpContext.Features.Set<IHttpRequestLifetimeFeature>(new TestHttpRequestLifetimeFeature());
311+
httpContext.RequestServices = serviceCollection.BuildServiceProvider();
312+
313+
var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<Todo>)TestAction);
314+
315+
await requestDelegate(httpContext);
316+
317+
Assert.False(invoked);
318+
Assert.False(httpContext.RequestAborted.IsCancellationRequested);
319+
Assert.Equal(400, httpContext.Response.StatusCode);
320+
321+
var logMessage = Assert.Single(sink.Writes);
322+
Assert.Equal(new EventId(2, "RequestBodyInvalidDataException"), logMessage.EventId);
323+
Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
324+
Assert.Same(invalidDataException, logMessage.Exception);
325+
}
326+
290327
[Fact]
291328
public async Task RequestDelegatePopulatesFromFormParameterBasedOnParameterName()
292329
{
@@ -316,7 +353,7 @@ void TestAction([FromForm] int value)
316353
}
317354

318355
[Fact]
319-
public async Task RequestDelegateLogsFromFormIOExceptionsAsDebug()
356+
public async Task RequestDelegateLogsFromFormIOExceptionsAsDebugAndAborts()
320357
{
321358
var invoked = false;
322359

@@ -351,6 +388,43 @@ void TestAction([FromForm] int value)
351388
Assert.Same(ioException, logMessage.Exception);
352389
}
353390

391+
[Fact]
392+
public async Task RequestDelegateLogsFromFormInvalidDataExceptionsAsDebugAndSets400Response()
393+
{
394+
var invoked = false;
395+
396+
var sink = new TestSink(context => context.LoggerName == "Microsoft.AspNetCore.Routing.MapAction");
397+
var testLoggerFactory = new TestLoggerFactory(sink, enabled: true);
398+
399+
void TestAction([FromForm] int value)
400+
{
401+
invoked = true;
402+
}
403+
404+
var invalidDataException = new InvalidDataException();
405+
var serviceCollection = new ServiceCollection();
406+
serviceCollection.AddSingleton<ILoggerFactory>(testLoggerFactory);
407+
408+
var httpContext = new DefaultHttpContext();
409+
httpContext.Request.Headers["Content-Type"] = "application/x-www-form-urlencoded";
410+
httpContext.Request.Body = new IOExceptionThrowingRequestBodyStream(invalidDataException);
411+
httpContext.Features.Set<IHttpRequestLifetimeFeature>(new TestHttpRequestLifetimeFeature());
412+
httpContext.RequestServices = serviceCollection.BuildServiceProvider();
413+
414+
var requestDelegate = MapActionExpressionTreeBuilder.BuildRequestDelegate((Action<int>)TestAction);
415+
416+
await requestDelegate(httpContext);
417+
418+
Assert.False(invoked);
419+
Assert.False(httpContext.RequestAborted.IsCancellationRequested);
420+
Assert.Equal(400, httpContext.Response.StatusCode);
421+
422+
var logMessage = Assert.Single(sink.Writes);
423+
Assert.Equal(new EventId(2, "RequestBodyInvalidDataException"), logMessage.EventId);
424+
Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
425+
Assert.Same(invalidDataException, logMessage.Exception);
426+
}
427+
354428
[Fact]
355429
public void BuildRequestDelegateThrowsInvalidOperationExceptionGivenBothFromBodyAndFromFormOnDifferentParameters()
356430
{

0 commit comments

Comments
 (0)