diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
index afed27b0e2e0e06a2bca47231e588a98482ac016..657b7d66d1144930af5a8018cbcc555f15661224 100644
--- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
+++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
@@ -558,6 +558,12 @@ namespace Microsoft.AspNetCore.Http
                     var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();
                     if (feature?.CanHaveBody == true)
                     {
+                        if (!httpContext.Request.HasJsonContentType())
+                        {
+                            Log.UnexpectedContentType(httpContext, httpContext.Request.ContentType, factoryContext.ThrowOnBadRequest);
+                            httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
+                            return;
+                        }
                         try
                         {
                             bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
@@ -590,6 +596,12 @@ namespace Microsoft.AspNetCore.Http
                     var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();
                     if (feature?.CanHaveBody == true)
                     {
+                        if (!httpContext.Request.HasJsonContentType())
+                        {
+                            Log.UnexpectedContentType(httpContext, httpContext.Request.ContentType, factoryContext.ThrowOnBadRequest);
+                            httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
+                            return;
+                        }
                         try
                         {
                             bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
@@ -603,7 +615,7 @@ namespace Microsoft.AspNetCore.Http
                         {
 
                             Log.RequestBodyInvalidDataException(httpContext, ex, factoryContext.ThrowOnBadRequest);
-                            httpContext.Response.StatusCode = 400;
+                            httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                             return;
                         }
                     }
@@ -1155,6 +1167,9 @@ namespace Microsoft.AspNetCore.Http
             private const string RequiredParameterNotProvidedLogMessage = @"Required parameter ""{ParameterType} {ParameterName}"" was not provided from {Source}.";
             private const string RequiredParameterNotProvidedExceptionMessage = @"Required parameter ""{0} {1}"" was not provided from {2}.";
 
+            private const string UnexpectedContentTypeLogMessage = @"Expected a supported JSON media type but got ""{ContentType}"".";
+            private const string UnexpectedContentTypeExceptionMessage = @"Expected a supported JSON media type but got ""{0}"".";
+
             // This doesn't take a shouldThrow parameter because an IOException indicates an aborted request rather than a "bad" request so
             // a BadHttpRequestException feels wrong. The client shouldn't be able to read the Developer Exception Page at any rate.
             public static void RequestBodyIOException(HttpContext httpContext, IOException exception)
@@ -1204,6 +1219,20 @@ namespace Microsoft.AspNetCore.Http
             [LoggerMessage(4, LogLevel.Debug, RequiredParameterNotProvidedLogMessage, EventName = "RequiredParameterNotProvided")]
             private static partial void RequiredParameterNotProvided(ILogger logger, string parameterType, string parameterName, string source);
 
+            public static void UnexpectedContentType(HttpContext httpContext, string? contentType, bool shouldThrow)
+            {
+                if (shouldThrow)
+                {
+                    var message = string.Format(CultureInfo.InvariantCulture, UnexpectedContentTypeExceptionMessage, contentType);
+                    throw new BadHttpRequestException(message, StatusCodes.Status415UnsupportedMediaType);
+                }
+
+                UnexpectedContentType(GetLogger(httpContext), contentType ?? "(none)");
+            }
+
+            [LoggerMessage(6, LogLevel.Debug, UnexpectedContentTypeLogMessage, EventName = "UnexpectedContentType")]
+            private static partial void UnexpectedContentType(ILogger logger, string contentType);
+
             private static ILogger GetLogger(HttpContext httpContext)
             {
                 var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
index 16c844ec762ed5a8361c94952ff1354ca4d58ff0..21e4d409dc72f83ddd32e9028ebd4b8b459f9877 100644
--- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
+++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
@@ -2360,7 +2360,93 @@ namespace Microsoft.AspNetCore.Routing.Internal
             Assert.False(httpContext.RequestAborted.IsCancellationRequested);
             var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
             Assert.Equal(@"""Hello Tester. This is from an extension method.""", decodedResponseBody);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task RequestDelegateRejectsNonJsonContent(bool shouldThrow)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.Request.Headers["Content-Type"] = "application/xml";
+            httpContext.Request.Headers["Content-Length"] = "1";
+            httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton(LoggerFactory);
+            httpContext.RequestServices = serviceCollection.BuildServiceProvider();
+
+            var factoryResult = RequestDelegateFactory.Create((HttpContext context, Todo todo) =>
+            {
+            }, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = shouldThrow });
+            var requestDelegate = factoryResult.RequestDelegate;
 
+            var request = requestDelegate(httpContext);
+
+            if (shouldThrow)
+            {
+                var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => request);
+                Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", ex.Message);
+                Assert.Equal(StatusCodes.Status415UnsupportedMediaType, ex.StatusCode);
+            }
+            else
+            {
+                await request;
+
+                Assert.Equal(415, httpContext.Response.StatusCode);
+                var logMessage = Assert.Single(TestSink.Writes);
+                Assert.Equal(new EventId(6, "UnexpectedContentType"), logMessage.EventId);
+                Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
+                Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", logMessage.Message);
+            }
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task RequestDelegateWithBindAndImplicitBodyRejectsNonJsonContent(bool shouldThrow)
+        {
+            Todo originalTodo = new()
+            {
+                Name = "Write more tests!"
+            };
+
+            var httpContext = new DefaultHttpContext();
+
+            var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
+            var stream = new MemoryStream(requestBodyBytes);
+            httpContext.Request.Body = stream;
+            httpContext.Request.Headers["Content-Type"] = "application/xml";
+            httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
+            httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton(LoggerFactory);
+            httpContext.RequestServices = serviceCollection.BuildServiceProvider();
+
+            var factoryResult = RequestDelegateFactory.Create((HttpContext context, JsonTodo customTodo, Todo todo) =>
+            {
+            }, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = shouldThrow });
+            var requestDelegate = factoryResult.RequestDelegate;
+
+            var request = requestDelegate(httpContext);
+
+            if (shouldThrow)
+            {
+                var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => request);
+                Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", ex.Message);
+                Assert.Equal(StatusCodes.Status415UnsupportedMediaType, ex.StatusCode);
+            }
+            else
+            {
+                await request;
+
+                Assert.Equal(415, httpContext.Response.StatusCode);
+                var logMessage = Assert.Single(TestSink.Writes);
+                Assert.Equal(new EventId(6, "UnexpectedContentType"), logMessage.EventId);
+                Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
+                Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", logMessage.Message);
+            }
         }
 
         private DefaultHttpContext CreateHttpContext()
@@ -2399,6 +2485,17 @@ namespace Microsoft.AspNetCore.Routing.Internal
             }
         }
 
+        private class JsonTodo : Todo
+        {
+            public static async ValueTask<JsonTodo?> BindAsync(HttpContext context, ParameterInfo parameter)
+            {
+                // manually call deserialize so we don't check content type
+                var body = await JsonSerializer.DeserializeAsync<JsonTodo>(context.Request.Body);
+                context.Request.Body.Position = 0;
+                return body;
+            }
+        }
+
         private record struct TodoStruct(int Id, string? Name, bool IsComplete) : ITodo;
 
         private interface ITodo