How to configure ASP.NET Core 5.0 to conform to Zalando’s RESTful API Guidelines

post-thumb

Zalando’s RESTful API Guidelines describes in detail how they design RESTful APIs and even offer a configurable OpenAPI Linter called zally. In this blog post, I want to show you how to configure ASP.NET Core 5.0 Web APIs to conform to three specific rules regarding path and JSON body layout. For that propose I modified the “weather forecast” starter template to demonstrate the rules which can be found on my GitHub profile.

  • Rule #118: Property names must be ASCII snake_case (and never camelCase)
  • Rule #129: use lowercase separate words with hyphens for path segments
  • Rule #130: use snake_case (never camelCase) for query parameters

Rule #118: Property names must be ASCII snake_case (and never camelCase)

First let us look how the default JSON body output looks like. Have a look at WeatherForecast class has the properties TemperatureCelsius and TemperatureFahrenheit.

public class WeatherForecast
{
    public DateTime Date { get; set; }

    public int TemperatureCelsius { get; set; }

    public int TemperatureFahrenheit => 32 + (int) (TemperatureCelsius / 0.5556);

    public string Summary { get; set; }
}

Without changing the setting of the Framework, we get the snakeCase formatting „temperatureCelsius“ and „temperatureFahrenheit“ as JSON property names.

[
  {
    "date": "2021-07-07T08:31:49.9734021+02:00",
    "temperatureCelsius": 39,
    "temperatureFahrenheit": 102,
    "summary": "Warm"
  },
  {
    "date": "2021-07-09T08:31:49.9734114+02:00",
    "temperatureCelsius": 29,
    "temperatureFahrenheit": 84,
    "summary": "Scorching"
  }
]

By default, APS.NET Core 5.0 uses System.Text.Json which currently only supports camelCase to serialize and deserialize objects into JSON. There is an open issue on the .NET runtime repository to add snake_case support.

Solution #1 use [JsonPropertyName]

The simple solution is to use [JsonPropertyName] Annotation from System.Text.Json.Serialization and define all property names by hand.

    public class WeatherForecast
    {
        public DateTime Date { get; set; }

        [JsonPropertyName("temperature_celsius")]
        public int TemperatureCelsius { get; set; }

        [JsonPropertyName("temperature_fahrenheit")]
        public int TemperatureFahrenheit => 32 + (int)(TemperatureCelsius / 0.5556);

        public string Summary { get; set; }
    }

Which produces the desired output.

[
  {
    "date": "2021-07-07T09:19:30.9345519+02:00",
    "temperature_celsius": 43,
    "temperature_fahrenheit": 109,
    "summary": "Sweltering"
  },
  {
    "date": "2021-07-08T09:19:30.9345608+02:00",
    "temperature_celsius": 5,
    "temperature_fahrenheit": 40,
    "summary": "Hot"
  }
]

Solution #2 use Newtonsoft.Json

Another solution is to use Newtonsoft.Json for serialization and configure it to use the SnakeCaseNamingStrategy. Here for you need to add a dependency on Microsoft.AspNetCore.Mvc.NewtonsoftJson and Swashbuckle.AspNetCore.Newtonsoft. Then configure ASP.NET Core and Swashbuckle to use Newtonsoft.Json instead of System.Text.Json.

  <itemgroup>
    <packagereference include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" version="5.0.7"></packagereference>
    <packagereference include="Swashbuckle.AspNetCore" version="6.1.4"></packagereference>
    <packagereference include="Swashbuckle.AspNetCore.Newtonsoft" version="6.1.4"></packagereference>
  </itemgroup>
public void ConfigureServices(IServiceCollection services)
{

    services.AddControllers()
        .AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.ContractResolver = new DefaultContractResolver
            {                        
                NamingStrategy = new SnakeCaseNamingStrategy()
            };

            options.SerializerSettings.Converters.Add(new StringEnumConverter());
        });

    services.AddSwaggerGenNewtonsoftSupport();

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo {Title = "ZalandoRestfulApiGuidelinesWebApi", Version = "v1"});
    });
}

Rule #129: use lowercase separate words with hyphens for path segments

By default, the Framework uses the Route Attribute [Route(„[controller]“)] to generate the path. For example, the WeatherForecastController will be reachable unter the /WeatherForecast path. But that does not comply to the rule. /weather-forecast would be compliant.

[ApiController]
[Route("[controller]")]
[Consumes("application/json")]
[Produces("application/json")]
public class WeatherForecastController : ControllerBase
{
    ...
}

Solution #1 explicit define the [Route]

Simply use a constant string in the [Route] attribute. see line 2: [Route(„weather-forecast“)]

[ApiController]
[Route("weather-forecast")]
[Consumes("application/json")]
[Produces("application/json")]
public class WeatherForecastController : ControllerBase
{
    ...
}

Solution #2 use a RouteTokenTransformerConvention

Use of an RouteTokenTransformerConvention to conform to this rule.

// Source https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0#use-a-parameter-transformer-to-customize-token-replacement
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString(),
            "([a-z])([A-Z])",
            "$1-$2",
            RegexOptions.CultureInvariant,
            TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}
//Startup.cs

public void ConfigureServices(IServiceCollection services)
{ 
    services.AddControllers(options =>
    {
      	options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
    });
}

Rule #130: use snake_case (never camelCase) for query parameters

Use of [FromQuery] Attribute to define the correct formatting.

  [HttpGet]
  public IEnumerable<WeatherForecast> Search(
      [FromQuery(Name = "q")] string query,
      [FromQuery(Name = "created_before")] string createdBefore)
  {
    ...
  }

Der Objektkultur-Newsletter

Mit unserem Newsletter informieren wir Sie stets über die neuesten Blogbeiträge,
Webcasts und weiteren spannende Themen rund um die Digitalisierung.

Newsletter abonnieren