ASP.NET Core 3.0 / Swashbuckle : restrict responses content types globally
Asked Answered
P

5

8

This is basically the same question as How do I set or remove the Default Response Content Type Using SwashBuckle, but for .NET Core 3.0

By default in .NET Core 3.0, you configure a web api with services.AddControllers() and then you configure swagger with swashbuckle with services.AddSwaggerGen() + app.UseSwagger()

That is working fine, however the swagger.json contains multiple response content types for every operation (text/plain + application/json + text/json)

I know I can restrict these response content types by adding [Produces] and [Consumes] to my operations, but I'd like to avoid that for each and every operation (i.e I want to do that globally)

Please note that I preferably want to use System.Text.Json but if you have a solution that works only with Newtonsoft.JSON then it's better than nothing ;)

Pavlov answered 21/11, 2019 at 10:59 Comment(0)
F
6

You can create custom filter for swagger

internal class AssignContentTypeFilter : IOperationFilter
{

    public void Apply(Operation operation, OperationFilterContext context)
    {
        operation.Consumes.Clear();
        operation.Consumes.Add("application/json");

        operation.Produces.Clear();
        operation.Produces.Add("application/json");
    }
}

then

services.AddSwaggerGen(cfg => cfg.OperationFilter<AssignContentTypeFilter>());
Fibrinogen answered 21/11, 2019 at 11:13 Comment(6)
The idea works but with the new 5.0.0-rc4, you need to use operation.RequestBody.Content and operation.Responses[].ContentPavlov
Great, you can add this note to the answerFibrinogen
@ChristopheBlin where do I need to use them?Jahncke
@FrancoScarpa you register the filters with services.AddSwagerGen as said in the answerPavlov
Answer outdated and doesn't workScuffle
@ThomasWilliams of course because it answers the question from 2019 and I'm sure it is still valid for .NET Core 3.0 (because the question is about .NET Core 3.0)Fibrinogen
G
9

Swashbuckle.AspNetCore.SwaggerGen 5.0 uses the OpenApiOperation to describe API operations.

using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

public class AssignContentTypeFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (operation.Responses.ContainsKey("200"))
        {
            operation.Responses.Clear();
        }

        var data = new OpenApiResponse
        {
            Description = "Ok",
            Content = new Dictionary<string, OpenApiMediaType>
            {
                ["application/json"] = new OpenApiMediaType(),
                ["application/xml"] = new OpenApiMediaType(),
            }
        };

        operation.Responses.Add("200", data);
    }
}

In Startup.cs

        services.AddSwaggerGen(q =>
        {
            q.SwaggerDoc("v1", new OpenApiInfo
            {
                Title = "mytitle",
                Version = "v1",
            });
            q.OperationFilter<AssignContentTypeFilter>();  
        });
Galipot answered 6/3, 2020 at 10:57 Comment(1)
Sorry to downvote but this answer does not answer my question (contrary to the accepted answer) but just explain the filters (that I already mention in the question)Pavlov
F
6

You can create custom filter for swagger

internal class AssignContentTypeFilter : IOperationFilter
{

    public void Apply(Operation operation, OperationFilterContext context)
    {
        operation.Consumes.Clear();
        operation.Consumes.Add("application/json");

        operation.Produces.Clear();
        operation.Produces.Add("application/json");
    }
}

then

services.AddSwaggerGen(cfg => cfg.OperationFilter<AssignContentTypeFilter>());
Fibrinogen answered 21/11, 2019 at 11:13 Comment(6)
The idea works but with the new 5.0.0-rc4, you need to use operation.RequestBody.Content and operation.Responses[].ContentPavlov
Great, you can add this note to the answerFibrinogen
@ChristopheBlin where do I need to use them?Jahncke
@FrancoScarpa you register the filters with services.AddSwagerGen as said in the answerPavlov
Answer outdated and doesn't workScuffle
@ThomasWilliams of course because it answers the question from 2019 and I'm sure it is still valid for .NET Core 3.0 (because the question is about .NET Core 3.0)Fibrinogen
H
2

This is what worked for me in Swashbuckle.AspNetCore.SwaggerGen 5.0:

    using System.Collections.Generic;
    using Microsoft.OpenApi.Models;
    using Swashbuckle.AspNetCore.SwaggerGen;

    internal class ContentTypeOperationFilter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            if (operation.RequestBody == null)
            {
                return;
            }

            operation.RequestBody.Content = new Dictionary<string, OpenApiMediaType>
            {
                { "application/json",  new OpenApiMediaType() }
            };

            foreach (var response in operation.Responses)
            {
                response.Value.Content = new Dictionary<string, OpenApiMediaType>
                {
                    { "application/json",  new OpenApiMediaType() }
                };
            }
        }
    }

Startup.cs (modified from sjokkogutten's answer):

        services.AddSwaggerGen(q =>
        {
            q.SwaggerDoc("v1", new OpenApiInfo
            {
                Title = "mytitle",
                Version = "v1",
            });
            q.OperationFilter<ContentTypeOperationFilter>();  
        });
Heresiarch answered 16/9, 2021 at 1:22 Comment(0)
C
1

The text/plain ends up in the generated swagger/openapi spec because by default API controllers have a StringOutputFormatter (from the Microsoft.AspNetCore.Mvc.Formatters namespace) available.

This MSDN goes into detail, but the crux of it is that by doing:

services.AddControllers(options =>
{
    options.OutputFormatters.RemoveType<StringOutputFormatter>();
});

..(probably in your Startup/ConfigureServices) you remove the relevant formatter and text/plain no longer appears in the generated swagger doc

Note: remember to install/import Microsoft.AspNetCore.Mvc.Formatters

Counterscarp answered 21/9, 2021 at 8:47 Comment(1)
How can I remove text/json from swagger globally? Is application/json and text/json added to swagger because of SystemTextJsonOutputFormatter ?Liquidambar
L
0

Version for Swashbuckle.AspNetCore.SwaggerGen 6+. Removes only text/plain content type. Works for string result in my case.

internal class RemoveTextContentOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        foreach (var (_, response) in operation.Responses)
        {
            if (response.Content.ContainsKey("text/plain"))
                response.Content.Remove("text/plain");
        }
    }
}

Generated swagger for string operation:

"/api/news/{newsArticleId}/file-link": {
      "get": {
        "tags": [
          "NewsApi"
        ],
        "operationId": "NewsApi_GetUploadFileLink",
        "parameters": [
          {
            "name": "newsArticleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "500": {
            "description": "Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              },
              "text/json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
Lei answered 12/4, 2021 at 11:49 Comment(2)
I thought response.Content is a Dictionary i.e. Key is unique. A straight up remove will do/doesn't thus need a linq Where/foreach/remove because there will only be 0 or 1 and remove doesn't throw if key is absent..Counterscarp
@CaiusJard it is a prototype :) Final version may be simplier: if (response.Content.ContainsKey("text/plain")) response.Content.Remove("text/plain"); Lei

© 2022 - 2024 — McMap. All rights reserved.