0

I'm creating a ASP.NET Core (.NET Core 3.1) API that accepts requests with JSON content.

If I POST to my endpoint non-JSON request content (while running in debug in VS with IIS Express) I get the response similar to:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
    "title": "Unsupported Media Type",
    "status": 415,
    "traceId": "|a53c33b7-43fa646efa5c6ab9."
}

Instead of this being sent out in the response I would like to create my own response for this scenario. I would like to do this by changing the pipeline if possible.

Within my Startup.cs I added:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await next.Invoke();

        if (context.Response.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            await context.Response.WriteAsync("Unsupported media type handled");
        }
    });
    
    // other calls on app
}

However, upon running the app and hitting my endpoint I now get:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
    "title": "Unsupported Media Type",
    "status": 415,
    "traceId": "|3ec437d7-4b20f64c28d04f16."
}Unsupported media type handled

I've tried calling context.Response.Clear(); before I write my new text, but this just outputs the JSON above without my custom text. Which seems even stranger.

What is going on here? It feels like something is handling and creating the 415 response before my pipeline even starts. Or what might I be doing wrong?

11
  • I am not entirely sure but could you try with context.Response.ReasonPhrase? Commented Jun 8, 2022 at 8:42
  • The ReasonPhrase property does not appear to be on the context object. If it was I'm guessing its just text about 415, no? Commented Jun 8, 2022 at 8:48
  • 2
    When you call next.Invoke(), the JSON has already been written to the response, so you're just appending your text to that. Commented Jun 8, 2022 at 8:48
  • The ProblemDetail response is being automatically generated by ASP.NET core. That's a standard format used to communicate the client calling the API that he made a mistake in the way he performed the call. So, I would suggest you to stick with it because that's a documented web standard. You can customize this ASP.NET core behavior as documented here learn.microsoft.com/en-us/aspnet/core/web-api/… Commented Jun 8, 2022 at 9:01
  • @bytedev, the ReasonPhrase is on context.Response Commented Jun 8, 2022 at 9:11

2 Answers 2

1

I highly encourage you to reconsider your initial idea to replace the response content you are getting from ASP.NET core with your own custom response content.

ASP.NET core is opinionated about API controllers error handling. They have decided to handle some client errors (for instance the 415 status code) by using the Problem Details specification.

Quotes from the Problem Details RFC:

This document defines a "problem detail" as a way to carry machine- readable details of errors in a HTTP response to avoid the need to define new error response formats for HTTP APIs.

HTTP [RFC7230] status codes are sometimes not sufficient to convey enough information about an error to be helpful. While humans behind Web browsers can be informed about the nature of the problem with an HTML [W3C.REC-html5-20141028] response body, non-human consumers of so-called "HTTP APIs" are usually not.

This specification defines simple JSON [RFC7159] and XML [W3C.REC-xml-20081126] document formats to suit this purpose. They are designed to be reused by HTTP APIs, which can identify distinct "problem types" specific to their needs.

This is a web standard to communicate errors between machines, so it is a choice which makes sense in the context of API controllers error handling.

Notice that this standard has been designed to avoid reinventing the wheel each time, by defining custom error contracts to communicate errors from web apis and having to document them for clients.

This documentation explains that for some HTTP status codes indicating a client error (e.g.: your 415 Unsopported Media Type status code) a JSON response compliant with the Problem Details specification is automatically created by the ASP.NET core framework.

You have a few choices here:

  1. you can decide to suppress this behavior by setting ApiBehaviorOptions.SuppressMapClientErrors = true;. This way you'll get a plain status code (the 415 status code in your case) as the response, with no response content at all.
  2. you can decide to customize some properties of the returned Problem Details JSON payload, as documented here
  3. you can decide to take control of the generation of the Problem Details JSON response, by providing your own implementation of ProblemDetailsFactory as documented here

I would avoid option 3.

In my opinion the best thing you can do is sticking with option 2, if you really want to customize the error message contained inside the problem details JSON object.

Here is an example:

public void ConfigureServices(IServiceCollection services)
{
  services
    .AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
      options.ClientErrorMapping[415].Title = "My custom message";
    });
}

The response content you will get (for an invalid request, a request with no content for example) is the following:

custom Problem Details title

Another option you can evaluate is option 1: suppressing the default client error handling done by ASP.NET core and take full control of the response in case of a 415 response status code.

This is an example:

public void ConfigureServices(IServiceCollection services)
{
  services
    .AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
      options.SuppressMapClientErrors = true;
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
  }

  app.Use(async (context, next) =>
  {
    await next();

    // Handle the 415 response
    if (context.Response.StatusCode == 415)
    {
      context.Response.ContentType = "application/json";

      var json = JsonSerializer.Serialize(new { message = "You need to add a request body!" });
      await context.Response.WriteAsync(json);
    }
  });

  app.UseRouting();

  app.UseAuthorization();

  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllers();
  });
}

The response payaload you will get (for an invalid request, a request with no content for example) is the following:

custom response content

Notice that here I'm able to take full control of the response content because, by disabling the default client error mapping done by ASP.NET core (options.SuppressMapClientErrors = true;), the response you get when sending an invalid POST request is a plain 415 status code, with no response content at all. So, when writing to the response stream (await context.Response.WriteAsync(json);), you are writing inside an empty response stream, so only our custom JSON is sent to the client as the response content.

In your first attempt you forgot to disable the automatic response created by ASP.NET core in case of 415 response status code, so the code await context.Response.WriteAsync("Unsupported media type handled"); is appending content to the response stream, after the response content already written by the ASP.NET core framework automatically.

Sign up to request clarification or add additional context in comments.

Comments

0

from here

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-6.0#exception-handler-lambda

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

4 Comments

Thanks, but as mentioned in above comments my UseExceptionHandler is not firing on this scenario.
you are just writing to the response not using UseExceptionHandler at all
I am where it would say // other calls on app in my example, I have just not defined it for the question for the sake of brevity. Believe me, it isn't getting fired :-). It seems something is handling and swallowing the problem before my user defined pipeline executes. As Enrico mentions above this is probably part of ASP.NET. My question is about is there any way to stop ASP.NET from doing this and instead execute code I provide.
@bytedev ok i'm not sure but i use them and they work fine for custom response :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.