How to Include Headers in Swagger

One of the great features of netcore is that it comes with the Swagger library installed by default, and not only that, but it also integrates perfectly with the language, because it is capable of generating our swaggerfile automatically, without us having to do anything. 

However, it has one issue, and that is it does not allow, or at least not in a simple way, the inclusion of headers in the request itself. 

 

1 - Problem Introduction

While it’s true that in this example I’ll break down the problem to its basics, the premise is simple: being able to add headers to our calls via swagger; 

This will allow us to manually test in an easier, simpler way using Swagger itself, since by default, it’s impossible to add such information.

 

For example, in the previous post of this series, we saw how to modify our API response depending on the accept-language attribute using the JsonConverter. What we’re going to do in this post is show how to modify swagger so that it asks for that attribute.

Additionally, we’ll be able to make it optional or required.

 

2 - Create an Attribute for Swagger

The way we’re going to create the attribute is through filters, and this filter can be enabled either on a single endpoint or the entire controller.

 

Also, the attribute itself won’t be generic, it’s not something we can do in a general and magical way, so we’ll need to create an attribute for each required header, although we can still abstract quite a bit of code.

All this code is available on GitHub in the WebPersonal repository (you have the link at the beginning of the post).

 

The first thing we’re going to do is create a folder named Filters, where we’ll create our filters. 

 

The one we’re going to create now is to request a header, so we’ll create it as such: 

namespace WebPersonal.BackEnd.API.Filters
{
    public class AcceptedLanguageHeader : Attribute
    {
        
    }
}

For it to appear in swagger, the first requirement is that it implements the IOperationFilter interface, which is inside the Swashbuckle.AspNetCore.SwaggerGen library. 

 

And it will require us to implement this interface:

public class AcceptedLanguageHeader : Attribute, IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        throw new NotImplementedException();
    }
}

For now, what we’re interested in is the operation parameter, since that’s what will visually modify the interface, and inside operation we access Parameters and add a new one directly:

public class AcceptedLanguageHeader : Attribute, IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        operation.Parameters.Add(new OpenApiParameter()
        {
            Name = "accept-language",
            In = ParameterLocation.Header,
            Required = false,
            Schema = new OpenApiSchema() { Type = "string" }
        });
    }
}

 

Note: as you can see, there is a property called In, this property allows us to indicate where the parameter goes, and we can choose header, query, path, or cookies

 

With this, we can configure swagger to show the attribute.

 

If we go to our startup file (or program.cs, depending on your version) you can go to the services and add the following code, which will generate the attribute in the UI:

 

services.AddSwaggerGen(c =>
{
    c.OperationFilter<AcceptedLanguageHeader>();
});

swagger

 

But this doesn’t help us, since we’re making that header appear in all endpoints, when in fact we only need it in a single one. 

 

3 - Add a Swagger Attribute to a Controller

This is where the reason comes in for creating our AddAcceptedLanguageHeader class inheriting from Attribute. It’s because we’re going to use it as such. 

 

For those not very familiar with attributes, we can filter them to be used on different types of elements. In our case, we’re going to indicate Class, since a controller is a class, and methods, so it can be used on individual endpoints. 

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptedLanguageHeader : Attribute, IOperationFilter
{
    ...
}

Additionally, from here, the context inside apply comes into play, because that’s where we’ll have access to the information of the endpoint being called. 

 

What we’ll do is create a static class that simply receives OperationFilterContext and returns a boolean, which will be true if the endpoint we’re calling has the specified attribute, and whether it’s mandatory, which for now is false.

public class CustomAttribute
{
    public readonly bool ContainsAttribute;
    public readonly bool Mandatory;

    public CustomAttribute(bool containsAttribute, bool mandatory)
    {
        ContainsAttribute = containsAttribute;
        Mandatory = mandatory;
    }
}

public static class OperationFilterContextExtensions
{
    public static CustomAttribute RequireAttribute<T>(this OperationFilterContext context)
    {
        IEnumerable<IFilterMetadata> globalAttributes = context
            .ApiDescription
            .ActionDescriptor
            .FilterDescriptors
            .Select(p => p.Filter);

        object[] controllerAttributes = context
            .MethodInfo?
            .DeclaringType?
            .GetCustomAttributes(true) ?? Array.Empty<object>();

        object[] methodAttributes = context
            .MethodInfo?
            .GetCustomAttributes(true)?? Array.Empty<object>();

        List<T> containsHeaderAttributes = globalAttributes
            .Union(controllerAttributes)
            .Union(methodAttributes)
            .OfType<T>()
            .ToList();
        
        return containsHeaderAttributes.Count == 0 
            ? new CustomAttribute(false, false) 
            : new CustomAttribute(true, false);
    }
}

 

If you’re using NET6 or higher, you can create a record instead of a class to represent CustomAttribute.

NOTE: As you know, when you specify something on the controller, it applies to all endpoints within that controller.



Now we have to modify our filter, so it reads this information and acts accordingly:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptedLanguageHeader : Attribute, IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        CustomAttribute acceptedLanguageHeader = context.RequireAttribute<AcceptedLanguageHeader>();

        if (!acceptedLanguageHeader.ContainsAttribute)
            return;
        
        operation.Parameters.Add(new OpenApiParameter()
        {
            Name = "accept-language",
            In = ParameterLocation.Header,
            Required = acceptedLanguageHeader.Mandatory,
            Schema = new OpenApiSchema() { Type = "string" }
        });
    }
}

 

If the endpoint we’re calling does not contain the attribute, we simply return, which means it will NOT include the Header in the interface. If it needs it, we’ll add the parameter. Also, we set the Required property based on the method’s response.

 

Now if we run the application, no endpoint will have the field to enter the header. 

custom swagger header

If we want it to appear, we must add the Attribute to the controller: 

[ApiController]
[AcceptedLanguageHeader] //HERE
[Route("api/[controller]")]
public class ExampleErrorController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Result.Failure(Guid.Parse("ce6887fb-f8fa-49b7-bcb4-d8538b6c9932"))
            .ToActionResult();
    }
}

swagger filter with headers



3.1 - Specify if an Attribute is Required in Swagger

Now we’re going to indicate if the header has to be required or not.

 

First, we’re going to create an interface, which will only have one property called IsMandatory

public interface ICustomAttribute
{
    public bool IsMandatory { get; }
}

And we’ll implement this interface in each of our custom attributes.

 

Likewise, we’ll pass in the constructor of the filter whether it’s true or false:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptedLanguageHeader : Attribute, ICustomAttribute, IOperationFilter
{
    public static string HeaderName = "accept-language";

    public bool IsMandatory { get; }

    public AcceptedLanguageHeader(bool isMandatory = false)
    {
        IsMandatory = isMandatory;
    }
    
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        CustomAttribute acceptedLanguageHeader = context.RequireAttribute<AcceptedLanguageHeader>();

        if (!acceptedLanguageHeader.ContainsAttribute)
            return;
        
        operation.Parameters.Add(new OpenApiParameter()
        {
            Name = HeaderName,
            In = ParameterLocation.Header,
            Required = acceptedLanguageHeader.Mandatory,
            Schema = new OpenApiSchema() { Type = "string" }
        });
    }
}

 

Now we need to modify our class so the generic type we’ve indicated is required to be an ICustomAttribute, and we also modify the last return to check that property: 

public static CustomAttribute RequireAttribute<T>(this OperationFilterContext context)
where T : ICustomAttribute
{
  
    ....

    return containsHeaderAttributes.Count == 0 
        ? new CustomAttribute(false, false) 
        : new CustomAttribute(true, containsHeaderAttributes.First().IsMandatory);
}

Finally, we modify the endpoint so the header is marked as required:

[ApiController]
[Route("api/[controller]")]
public class ExampleErrorController : ControllerBase
{
    [AcceptedLanguageHeader(true)] 
    [HttpGet]
    public IActionResult Get()
    {
        return Result.Failure(Guid.Parse("ce6887fb-f8fa-49b7-bcb4-d8538b6c9932"))
            .ToActionResult();
    }
}

Now you can see in the interface that it is marked as required:

swagger header as mandatory

NOTE: if you have the attribute specified both at controller and method level, the one that takes precedence is the endpoint’s. 



4 - Middleware to Prevent Hacks

One of the problems of this solution is that it only works in swagger, so if we want to enforce this at API level, we need to implement a middleware that does similar functionality: check if it exists or not.

namespace WebPersonal.BackEnd.API.Middlewares
{
    public class CustomHeaderValidatorMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly string _headerName;


        public CustomHeaderValidatorMiddleware(RequestDelegate next, string headerName)
        {
            _next = next;
            _headerName = headerName;
        }
        

        public async Task Invoke(HttpContext context)
        {
            if (IsHeaderValidated(context))
            {
                await _next.Invoke(context);
            }
            else
            {
                throw new Exception($"the header {_headerName} is mandatory and it is missing");
            }
            
        }

        private bool IsHeaderValidated(HttpContext context)
        {
            Endpoint? endpoint = context.GetEndpoint();
            if (endpoint == null)
                return true;
            
            bool isRequired = IsHeaderRequired(endpoint); 
            if (!isRequired)
                return true;

            bool isIncluded = IsHeaderIncluded(context);

            if (isRequired && isIncluded)
                return true;

            return false;
        }

        private bool IsHeaderIncluded(HttpContext context)
            => context.Request.Headers.Keys.Select(a=>a.ToLower()).Contains(_headerName.ToLower());

        private static bool IsHeaderRequired(Endpoint endpoint)
        {
            var attribute = endpoint.Metadata.GetMetadata<ICustomAttribute>();

            return attribute is { IsMandatory: true };
        }
    }
}

I won't go into much detail, but basically it’s a series of If statements that check if the header passed in the constructor is necessary, if it is present, and if not, it throws an exception. 

 

Now we just need to specify the middleware in the request pipeline, that is, in program.cs

app.UseMiddleware<CustomHeaderValidatorMiddleware>(AcceptedLanguageHeader.HeaderName);

 

If we test without sending the header, it will return the exception:



In another post we might see how to make everything look nicer (unifying the API response), but to prevent hacks, this solution is more than enough.



Conclusion

In this post we have seen how to add a header within the swagger interface

We have learned to specify that header in certain endpoints only

 

Also how to specify a header in swagger as optional or required.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café