C# ASP.NET : Hide model properties from Swagger doc 
Tags: csharp, dotnet, swagger
Published: 2021-12-04 05:54:43

When I use OData with C# Web API, I can hide any field easily by either using ``Ignore`` method when creating EDM or using ``IgnoreDataMember`` attribute. However, it doesn't hide these properties from Swagger UI. In this article, I share what I am doing to sync up OData model and Swagger UI.

You can skip to "Hide property from the swagger" if you don't care about OData.

# Hide class property in OData with ASP.NET

There are several ways to hide the property in OData. Let's say I enabled OData to dotnet new Web API template (the WeatherForecast).

I modified WeatherForecast class to 
- Add key
- Add secret property
- Update TemperatureF to have setter

```csharp
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;

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

        public int TemperatureC { get; set; }

        public int TemperatureF { 
            get 
            {
                return 32 + (int)(TemperatureC / 0.5556);
            }
            set { }
        }

        [Key]
        public string Summary { get; set; }

        public string Secret { set; get; }
    }
}
```

If I run the solution and query the data, I see the following.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cr458xlewylxggeynm13.png)
 
I can hide the secret property by updating model builder.

```csharp
private static IEdmModel GetEdmModel()
{
    ODataConventionModelBuilder builder = new();
    builder.EntitySet<WeatherForecast>("WeatherForecasts");
    builder.EntityType<WeatherForecast>().Ignore(x => x.Secret);
    builder.EnableLowerCamelCase();
    return builder.GetEdmModel();
}
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01aa3xh6kb98xgsg4am0.png)

I can also use IgnoreDataMember instead of using ignore when building model.

```csharp
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;

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

        public int TemperatureC { get; set; }

        public int TemperatureF { 
            get 
            {
                return 32 + (int)(TemperatureC / 0.5556);
            }
            set { }
        }

        [Key]
        public string Summary { get; set; }

        [IgnoreDataMember]
        public string Secret { set; get; }
    }
}
```

# Swagger doc

Even though I could control OData result and schema, it won't reflect swagger doc. So I still see the Secret property defined in WeatherForecast model in swagger UI.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qxfsmmw07zvnu85pc2c.png)
 
# Hide property from the swagger

I can use ISchemaFilter to control it. Add new class to the solution.

```csharp
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

namespace swaggertest
{
    public class MySwaggerSchemaFilter : ISchemaFilter
    {
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            if (schema?.Properties == null)
            {
                return;
            }

            var ignoreDataMemberProperties = context.Type.GetProperties()
                .Where(t => t.GetCustomAttribute<IgnoreDataMemberAttribute>() != null);

            foreach (var ignoreDataMemberProperty in ignoreDataMemberProperties)
            {
                var propertyToHide = schema.Properties.Keys
                    .SingleOrDefault(x => x.ToLower() == ignoreDataMemberProperty.Name.ToLower());

                if (propertyToHide != null)
                {
                    schema.Properties.Remove(propertyToHide);
                }
            }
        }
    }
}
```

Then specify the filter in startup.

```csharp
public void ConfigureServices(IServiceCollection services)
{

    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "swaggertest", Version = "v1" });
        c.SchemaFilter<MySwaggerSchemaFilter>();
    });
    services.AddOData();
}
```

# Generated Swagger UI

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/centoqg2qzpe7yozgmo5.png)

# How this works?

Each model is passed as OpenApiSchema argument to ``ISchemaFilter.Apply`` method. I can obtain attributes which assigned to properties of the model by using ``GetCustomAttribute`` method from Reflection namespace. So I look for properties which has "IgnoreDataMember" attribute (In this case, Secret property).

Then call ``schema.Properties.Remove`` method to remove them.

# SwaggerExclude attribute?

Some article or stackoverflow answers indicates to use ``SwaggerExclude`` attribute to specify which properties to hide. I think it's good idea to define your own custom attribute for granular control. But I use ``IgnoreDataMember`` attribute to decide which properties to hide. Of course you can write a bit more code in ``Apply`` method to do more complex logic.

# Summary

OData and Swagger are totally different technology which end up inconsistent results between document and behavior. I try to sync then as easy as possible, but if you know better way, I am happy to hear the solution!