Allowing an empty JSON body, or no Content-Type, in a C# ASP.NET Core API

NOTE: In applications targeting net7.0+, Microsoft has fixed this issue, and you can set your Object to be default null (MyClass thingamagic= null), having it work as expected.
Leaving the below hack in here for anyone stuck on net6 or below.

You may be here because you’re getting this:

{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.13","title":"Unsupported Media Type","status":415,"traceId":"00-0d785f3d1cb4fd71bc36ee25561e4b48-6bc5df1f0d070024-00"}

Or this:

{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-8ab7d4ff2d97d5ec040c25058b8d6fff-64b7c8e21e11563b-00","errors":{"":["A non-empty request body is required."]}}

Or even this:

{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-2fec89d15f0fbc0a7deaf1dd96656e15-e7fabe160575f645-00","errors":{"$":["The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."]}}

You may have tried adding this:

[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]

And you’ve been unsuccessful in accepting queries with no Content-Type, Content-Length 0.

What you need is an attribute that will override the missing Content-Type, provide an empty JSON body for the parser, and set an appropriate Content-Length to match, and only do so when the Content-Type is missing or JSON already, so you keep the other validation.

Luckily, I’ve written one. So here you go. Leave a comment if this helps you?

[AttributeUsage(AttributeTargets.Method)]
public class AllowEmptyJsonBodyAttribute : Attribute, IResourceFilter
{
private const string StreamOverride = "StreamOverride";

public void OnResourceExecuting(ResourceExecutingContext context)
{
var request = context.HttpContext.Request;
if (!string.IsNullOrEmpty(request.ContentType) && !request.HasJsonContentType() || (request.ContentLength ?? 0) != 0) return;
request.ContentType = "application/json";
context.HttpContext.Items[StreamOverride] = request.Body; // store the original stream
var emptyPayload = Encoding.UTF8.GetBytes("{}");
request.Body = new MemoryStream(emptyPayload); // replace the stream
request.ContentLength = emptyPayload.Length;
}

public void OnResourceExecuted(ResourceExecutedContext context)
{
if (!context.HttpContext.Items.TryGetValue(StreamOverride, out var o) || o is not Stream s) return;
var request = context.HttpContext.Request;
request.Body.Dispose(); // this disposes our injected stream
request.Body = s; // put the original back, so it can be cleaned up as usual
}
}

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *