ASP.NET Core 3.1 API : Adding SCIM Media Type to JSON Formatter

On a recent project I worked on creating a SCIM (rfc 7642, 7643, 7644) provider using ASP.NET Core. The SCIM standard uses its own media type of application/scim+json by default. So I had to figure out how to add the scim media type to the existing json formatter. In the process I ran into several issues that I will highlight below.

Validate Accept Header

By default ASP.NET Core does not require content negotiation. If an Accept header is not present then ASP.NET Core will return JSON. To turn on validation of the content negotiation you have to set ReturnHttpNotAcceptable to true on the MvcOptions object when configuring your service. In addition you will want to set RespectBrowserAcceptHeader to true on the MvcOptions object to better support browsers calling your API. More details can be found here and here.

public void ConfigureServices(IServiceCollection services)
{
// ...

	services.AddControllers(config =>
	{​
		config.RespectBrowserAcceptHeader = true;​
		config.ReturnHttpNotAcceptable = true;​
	}).AddNewtonsoftJson();

// ...
}

Adding Media Type to JSON Formatter

By default the JSON formatter is registered with several media types that it supports (application/json, text/json, text/plain). For my project I needed to add application/scim+json as a supported media type. Unfortunately it was very hard to figure out how to accomplish adding a media type to a formatter that is already registered. I finally stumbled across this post on HATEOAS in ASP.NET Core Web API that helped me figure out the approach.

At first I tried something similar to this (will not work):

public void ConfigureServices(IServiceCollection services)
{
// ...

	services.AddControllers(config =>
	{​
		config.RespectBrowserAcceptHeader = true;​
		config.ReturnHttpNotAcceptable = true;​

		var jsonFormatter = c.OutputFormatters​
			.OfType<NewtonsoftJsonOutputFormatter>()?.FirstOrDefault();​
	​
		if (jsonFormatter != null)​
		{​
			jsonFormatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/scim+json"));​
		}​
	}).AddNewtonsoftJson();

// ...
}

This code attempts to find the NewtonsoftJsonOutputFormatter in the OutputFormatters collection and add the scim media type to the SupportedMediaTypes collection.

Working Around Newtonsoft Json Issues

Since Microsoft has moved to use their own Json library instead of Newtonsoft Json you have to explicitly add Newtonsoft Json if you want the original behavior. Thus the call to AddNewtonsoftJson. The problem with the above approach is that the AddController configuration lambda code executes before the Newtonsoft Json libraries are swapped in for the in-the-box JSON formatter. As a result you have to register a separate Configure lambda after the AddNewtonsoftJson executes.

public void ConfigureServices(IServiceCollection services)
{
// ...

	services.AddControllers(config =>
	{​
		config.RespectBrowserAcceptHeader = true;​
		config.ReturnHttpNotAcceptable = true;​
	}).AddNewtonsoftJson();

	services.Configure<MvcOptions>(c =>​
	{​
		var jsonFormatter = c.OutputFormatters​
			.OfType<NewtonsoftJsonOutputFormatter>()?.FirstOrDefault();​
​
		if (jsonFormatter != null)​
		{​
			jsonFormatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/scim+json"));​
		}​
	});

// ...
}

Using the default Json Formatter

If you are not using Newtonsoft then you can use the same approach, just change the formatter from NewtonsoftJsonOutputFormatter to SystemTextJsonOutputFormatter.

ProducesAttribute in the Way

Once I added the scim media type to the JSON formatter I tested and the scim media type still didn’t work for me. I searched the code for application/json and found the ProducesAttribute set on the controller I was using to testing. The ProducesAttribute effectively overrides any settings defined on the configured formatters. So a ProducesAttribute of [Produces(“application/json”)] was blocking the scim media type from working. Details on the ProducesAttribute are here. There are two options that worked for dealing with this issue.

  1. Remove the ProducesAtrribute. This will enable content negotiation to work properly.
  2. Change the ProducesAttribute to [Produces(“application/scim+json”, “application/json”)]. This will allow both application/json and application/scim+json to work as Accept headers while returning application/scim+json as the Content-Type.

Leave a comment