How to hide a property just in post request description of swagger using swashbuckle?
Asked Answered
N

4

13

I am new to ASP.NET Core and this question looks simple but I couldn't find a proper solution online. So here's the problem.
This is the structure of the class that I am using.

public class Alert
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public string AlertId { get; set; }
    public string Type { get; set; }

}

This is the description for the Post request API in swagger.

{
  "alertId": "string",
  "type": "string"
}

Since I am using [DatabaseGenerated(DatabaseGeneratedOption.Identity)] annotation alertId in the post request is optional. My aim is to hide alertId from the post request description only.
I am using ASP.NET Core 3.1, EF Core(3.1.1) and Swashbuckle.AspDotNetCore(5.1.0).
Please Help.
Thank you.

Notarial answered 14/4, 2020 at 19:45 Comment(5)
It is better not to return your 'Alert' object to the client. You can create a separate model (for example 'AlertDto') which would contain only the properties you want to return in a response.Epigraphy
As Ivan has said, create a class that models the data you need from the user. After getting the data, you can map it to to the Alert entity.Weinberg
As Ivan has said, create a class that models the data you need from the user. After getting the data, you can map it to to the Alert entity.Weinberg
I have been trying to do this for a while now. I wanted to hide/exclude the ID property from HttpPost using a custom HttpPostIgnore-attribute. But when I think about it, it would be confusing if I have a Schema (model) in Swagger act different across methods from how it is documented in the Schema section. I dont want my API to be too out of standard, so I am discontinuing my attempt, and create separate DTOs for different methods instead. Just wanted to put my perspective out there :)Galactometer
hi @Galactometer , is my answer what you want?Misconduct
M
23

You can use the Swashbuckle.AspNetCore.Annotations package, it allows you to mark that some properties are only displayed in the input parameters, and some are only displayed in the output.

In your case, you want to hide the AlertId in the input parameter of the post, you just need to do this by the [SwaggerSchema]:

public class Alert
{
    [SwaggerSchema(ReadOnly = true)]
    public string AlertId { get; set; }
    public string Type { get; set; }
}

See more about it in the Documentation

In the ConfigureServices method of Startup.cs, enable annotations within in the Swagger config block:

services.AddSwaggerGen(c =>
{
   ...

   c.EnableAnnotations();
});
Misconduct answered 15/7, 2021 at 10:34 Comment(5)
Hi, thanks for the tag! It sounds like what I want, but its not working as expected in any of my projects - at least not by just installing the NuGet and adding the tag. I will try follow the documentation step by step and check it out again, in case I have done something funny in my code, or have missed some SwaggerSchema initialization.Galactometer
@Galactometer yes, initialization with c.EnableAnnotations()Misconduct
Thank you! Everyone suggests using JsonIgnore, but that prevents serialization, so even your GET requests hide the field. Glad to see there was an actual solution out there, and a totally straight-forward one at that.Discommodity
Thanks. It worked perfectly. I was trying to use JsonIgnore but it did'nt work at all. Now using Annotations from SwaggerSchema, everything is working. Thanks!Polacre
[SwaggerSchema(ReadOnly = true)] dos not work for me. Parameter is still shown in OpenApi.json (as read only thow)Diderot
D
5

You can add the [JsonIgnore] attribute to the AlertId field to ensure that the post request will not get the content of the AlertId.

  public class Alert
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [JsonIgnore]
        public string AlertId { get; set; }
        public string Type { get; set; }

    }

Here it the test result:

enter image description here

Darnelldarner answered 15/4, 2020 at 7:22 Comment(0)
T
1

If you are using AzureFunctions.Extensions.Swashbuckle then simply changing the Setter to internal will prevent it from showing in the Post request schema:

public class Alert
{
    public string AlertId { get; internal set; }
    public string Type { get; set; }
}
Tombolo answered 15/9, 2022 at 22:48 Comment(0)
M
0

I tried plenty of different alternatives, and none of them suited my needs. According to GitHub issues, [SwaggerSchema(ReadOnly = true)] doesn't seem to work; using a Schema processor doesn't work either, especially when dealing with OData controllers.

I eventually solved it by using an IDocumentFilter.

Document Filter:

using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

/// <summary>
/// Makes sure readonly parameters are not considered while creating/updating/deleting an entity
/// </summary>
public class SwaggerRemoveReadonlyParametersFromOData_DocumentFilter : IDocumentFilter
{
    private const string BASE_URL = "/"; // You might need to change this

    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        foreach (var apiDesc in context.ApiDescriptions)
        {
            // Ignore GET method (we DO want readonly parameters to be displayed, in this case)
            if (apiDesc.HttpMethod == "GET")
                continue;

            var readonlyParameters = apiDesc.ParameterDescriptions.Where(p => ShouldBeFilteredOut(p, apiDesc.HttpMethod));
            if (!readonlyParameters.Any())
                continue;

            // We need to modify the OpenApiDocument: changes to the DocumentFilterContext will only become
            // visible after we manually reload the swagger.json file
            var apiOperation = FindCorrespondingOperationInDocument(swaggerDoc, apiDesc);

            foreach (var readonlyParameter in readonlyParameters)
            {
                var parameterToRemove = apiOperation.Parameters.Single(p => p.Name == readonlyParameter.Name);
                apiOperation.Parameters.Remove(parameterToRemove);
            }
        }
    }

    private static bool ShouldBeFilteredOut(ApiParameterDescription parameter, string httpMethod)
    {
        // Only consider model binding
        if (parameter.Source.DisplayName != "ModelBinding")
            return false;

        // Identify read only properties... (sometimes Swashbuckle/Swagger shows them anyway, especially in OData controllers)
        if (parameter.ModelMetadata.IsReadOnly == true)
            return true;

        if (parameter.ModelMetadata is not DefaultModelMetadata modelMetadata)
            return false;

        // ...and properties/fields explicitly marked as readonly by decorating them with the [SwaggerReadOnly] attribute...
        var readOnlyAttribute = modelMetadata.Attributes.Attributes.SingleOrDefault(a => a is SwaggerReadOnlyAttribute) as SwaggerReadOnlyAttribute;
        // ...if relevant to the current HTTP method
        if (readOnlyAttribute?.AppliesToHttpMethod(httpMethod) == true)
            return true;

        return false;
    }

    private OpenApiOperation FindCorrespondingOperationInDocument(OpenApiDocument swaggerDoc, ApiDescription apiDesc)
    {
        string pathUrl = BASE_URL + apiDesc.RelativePath;
        OperationType httpMethod = GetOperationTypeFromHttpMethod(apiDesc.HttpMethod!);

        OpenApiPathItem apiPath = swaggerDoc.Paths[pathUrl];
        return apiPath.Operations[httpMethod];
    }

    private static OperationType GetOperationTypeFromHttpMethod(string httpMethod)
    {
        httpMethod = httpMethod.ToLower();
        httpMethod = string.Concat(httpMethod[0].ToString().ToUpper(), httpMethod.AsSpan(1));
        return Enum.Parse<OperationType>(httpMethod);
    }
}

Attribute:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SwaggerReadOnlyAttribute : Attribute 
{
    public string[] HttpMethods { get; set; }


    public SwaggerReadOnlyAttribute() : this(["POST", "PUT", "PATCH"]) { }

    public SwaggerReadOnlyAttribute(params string[] httpMethods) 
    {
        HttpMethods = httpMethods;
    }

    public bool AppliesToHttpMethod(string? httpMethod)
    {
        if (httpMethod == null)
            return true;

        return HttpMethods.Contains(httpMethod.ToUpperInvariant());
    }
}

Register your Document Filter with:

Services.AddSwaggerGen(options => options.DocumentFilter<SwaggerRemoveReadonlyParameters_DocumentFilter>());

Decorate your readonly properties like this:

public class MyEntity
{
    [SwaggerReadOnly] // Hidden by the Document Filter
    public Guid Id { get; set; }

    public string Name { get; set; }

    public string? Description { get; set; }

    [SwaggerReadOnly] // Hidden by the Document Filter
    public DateTime LastModified { get; set; }

    // Automatically hidden by the Document Filter
    public bool HasDescription => !string.IsNullOrEmpty(Description);
}
Mounts answered 18/7, 2024 at 7:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.