In this article, we will explore the two main use cases of ignoring properties in Swagger. We will look into when and why we should ignore model properties with Swagger, and how to do it.

To download the source code for this article, you can visit our GitHub repository.

Let’s get started.

What Is Swagger?

Swagger is a documentation framework that can describe REST APIs in a standardized way. It provides a standardized JSON or YAML schema for describing an API, thus making the consumption much easier. It lists every endpoint, their input and output parameters, the connection between them, and authentication requirements. There are several tools available that can generate clients from Swagger documentation.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Project Setup

Let’s start by setting up a new Web Api project. The default .NET template already has Swagger integrated, so we’ll use that. Once the project is generated, let’s check out the Program.cs file. It contains the dependency injection configuration, an endpoint, and a model. Let’s split them up to make the files in the solution follow the single responsibility principle.

First, let’s create a new folder called Models and move the WeatherForecast record into it:

public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int) (TemperatureC / 0.5556);
}

Next, let’s create an Endpoints folder and add a static class inside it called WeatherForecasts. Then move the endpoint registration along with the Summaries array from Program.cs:

public static class WeatherForecasts
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
    
    public static void MapWeatherForecasts(this IEndpointRouteBuilder app)
    {
        app.MapGet("/weatherforecast", () =>
            {
                return Enumerable.Range(1, 5).Select(index =>
                        new WeatherForecast
                        (
                            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                            Random.Shared.Next(-20, 55),
                            Summaries[Random.Shared.Next(Summaries.Length)]
                        ))
                    .ToArray();
            })
            .WithName("GetWeatherForecast")
            .WithOpenApi();
    }
}

Notice the WithOpenApi() call at the end of the method chain. It adds the necessary metadata to the endpoint, so Swagger can use it for document generation.

And lastly, we call this extension method in Program.cs:

app.MapWeatherForecasts();

We successfully slimmed Program.cs down, so now it only contains the application configuration logic. Let’s take a look at four methods called in Program.cs that are related to Swagger:

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

app.UseSwagger();
app.UseSwaggerUI();

The AddEndpointsApiExplorer() method registers endpoint explorers to the DI container. They are used to extract the metadata information added by the WithOpenApi() method from endpoints. Swagger then uses these endpoint explorers to extract the metadata and generate the documentation. The actual classes that perform this generation and their configuration are registered with the AddSwaggerGen() method. To get and explore the generated document using a web UI, we add two middleware objects with the UseSwagger() and UseSwaggerUI() methods. These middlewares intercept requests to the Swagger endpoint, generate the documentation, and return a page where we can explore our endpoints, call them, and inspect the responses.

The Swagger UI

Let’s run the solution, and navigate to the /swagger route. We should see the Swagger UI:

Swagger UI

We can see the GET endpoint and the three object schemas returned from that endpoint. The WeatherForecast schema is already expanded, so we can see all the included properties. Notice the temperatureF property, it is important for us because that is the property we will ignore.

Now let’s explore the different ways of ignoring a property in the WeatherForecast schema.

Ignoring a Property Completely

Ignoring a property completely is useful when we want to hide it not just in Swagger, but completely omit it from the requests and responses. A possible use case for this is when we have a computed property that we do not want to expose to the public. For example TemperatureF on the WeatherForecast object. Let’s explore the two possible ways of ignoring that property.

Using Access Modifiers

The first method is using non-public access modifiers. Of course, this will limit the usability of the property in our codebase. For example, setting the property to private will prevent the serializer from serializing it, since by default it only includes public properties in serialization:

private int TemperatureF => 32 + (int) (TemperatureC / 0.5556);

However, the drawback is, that we won’t be able to access the property outside the class. The other downside of this approach is, that it doesn’t explicitly state our intent of exclusion. For example, we can configure the serializer to include all properties regardless of their access modifier, which may be our intended behavior. But then we don’t have a way to ignore properties. So let’s look at our other option.

Using the JsonIgnoreAttribute

The other way to ignore a property completely is to explicitly tell the JSON serializer to ignore it every time it serializes the object. Let’s achieve that by placing the JsonIgnoreAttribute on the property:

[JsonIgnore]
public int TemperatureF => 32 + (int) (TemperatureC / 0.5556);

Since .NET Core 3.0, the default JSON serializer is the System.Text.Json serializer instead of Newtonsoft.Json. So it is important to use the JsonIgnoreAttribute from the System.Text.Json namespace. If we inspect the WeatherForecast model in the Swagger UI, we can see that the property disappeared:

WeatherForecast model without TemperatureF

Another important aspect we should consider when using the JsonIgnoreAttribute is that it will only work if the model comes from the request body. If it comes from the query string, no deserialization will be performed, but the ASP.NET ModelBinder will bind the values to the properties. So let’s use the BindNeverAttribute instead:

[BindNever]
public int TemperatureF => 32 + (int) (TemperatureC / 0.5556);

Sometimes we just want to hide a property from Swagger, but still keep it in our models. Let’s explore the possible approaches.

Ignore Model Properties Only From Swagger

It’s useful to hide a property only from Swagger for example, when we have a new API version that doesn’t need a property anymore, but we want to keep our schema backward compatible. In this case, we can ignore the property in the Swagger documentation, but still include it in requests and responses, so users of the previous API version won’t experience a breaking change.

Creating Custom Attributes

Let’s create an attribute that we can use to mark properties as ignored:

[AttributeUsage(AttributeTargets.Property)]
public class SwaggerIgnoreAttribute : Attribute
{
}

Let’s place this attribute on the TemperatureF property:

[SwaggerIgnore]
public int TemperatureF => 32 + (int) (TemperatureC / 0.5556);

By having this marker attribute on our property we can modify the Swagger generation process and ignore properties that have this attribute by utilizing schema filters.

Creating The Schema Filter

Swagger examines all schema filters during document generation and applies their filtering logic to the schema. Let’s implement the ISchemaFilter interface:

public class SwaggerIgnoreFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext schemaFilterContext)
    {
        if (schema.Properties.Count == 0)
        {
            return;
        }
        var properties = schemaFilterContext.Type.GetProperties();

        var excludedList = properties
            .Where(m => m.GetCustomAttribute<SwaggerIgnoreAttribute>() is not null)
            .Select(m => m.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? m.Name.ToCamelCase());

        foreach (var excludedName in excludedList)
        {
            schema.Properties.Remove(excludedName);
        }
    }
}

The schema parameter is the schema of one of our classes, and it already includes all of its properties in a Dictionary<string, OpenApiSchema>, where the key is the camelcase property name unless that property has a JsonPropertyNameAttribute. In that case, the name is taken from the attribute exactly. We get all the properties of the actual C# type from the schemaFilterContext. Next, we filter the properties that have our custom SwaggerIgnoreAttribute, and select their name. When selecting the name, we first check if they have a JsonPropertyNameAttribute configured, and if yes we take the name from that, otherwise we get the property’s name, and convert it to camelcase using a custom extension method:

public static class StringExtensions
{
    public static string ToCamelCase(this string input)
    {
        if (string.IsNullOrEmpty(input) || char.IsLower(input[0]))
        {
            return input;
        }
        var chars = input.ToCharArray();
        chars[0] = char.ToLowerInvariant(input[0]);
        
        return new(chars);
    }
}

Now we have a list of camelcase property names that we want to ignore, so we simply loop through them and remove all that is in the schema.Properties dictionary.

Finally, let’s add our schema filter to the Swagger DI configuration:

builder.Services.AddSwaggerGen(o => o.SchemaFilter<SwaggerIgnoreFilter>());

Looking at the Swagger UI once more, we can see that the property is missing from the schema:

WeatherForecast model without TemperatureF

Using this method, we can modify the Swagger documentation only, and still accept and return the values of these properties in our API.

Conclusion

In this article, we explored the different methods to ignore model properties with Swagger documentation. First, we had a brief introduction to Swagger. Then, we explored the use cases of complete property exclusion, where we removed entire properties from the requests and responses. Next, we dived into schema generation and modified it to only ignore properties that are decorated by our custom attribute. We compared the benefits and disadvantages of all methods and examined when to use them.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!