Skip to content

Instantly share code, notes, and snippets.

@0xced
Last active November 1, 2023 06:20
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xced/d679d43bb7dd8a9ab805db43e561e48d to your computer and use it in GitHub Desktop.
Save 0xced/d679d43bb7dd8a9ab805db43e561e48d to your computer and use it in GitHub Desktop.
[FromHeader] parameter binding and attribute for ASP.NET Web API + Swashbuckle integration
using System;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
namespace Gist
{
// Adapted from https://stackoverflow.com/questions/20618900/webapi-mapping-parameter-to-header-value/20653775#20653775
public class FromHeaderBinding : HttpParameterBinding
{
private readonly string _name;
public FromHeaderBinding(HttpParameterDescriptor parameter, string headerName) : base(parameter)
{
if (string.IsNullOrEmpty(headerName))
{
throw new ArgumentNullException(nameof(headerName));
}
_name = headerName;
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (actionContext.Request.Headers.TryGetValues(_name, out var values))
{
var converter = TypeDescriptor.GetConverter(Descriptor.ParameterType);
try
{
actionContext.ActionArguments[Descriptor.ParameterName] = converter.ConvertFromString(values.FirstOrDefault());
}
catch (Exception exception)
{
var error = new HttpError("The request is invalid.") { MessageDetail = exception.Message };
throw new HttpResponseException(actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, error));
}
}
else if (Descriptor.IsOptional)
{
actionContext.ActionArguments[Descriptor.ParameterName] = Descriptor.DefaultValue ?? Activator.CreateInstance(Descriptor.ParameterType);
}
else
{
var error = new HttpError("The request is invalid.") { MessageDetail = $"The `{_name}` header is required." };
throw new HttpResponseException(actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, error));
}
return Task.FromResult<object>(null);
}
}
public abstract class FromHeaderAttribute : ParameterBindingAttribute
{
public string HeaderName { get; }
protected FromHeaderAttribute(string headerName)
{
HeaderName = headerName;
}
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new FromHeaderBinding(parameter, HeaderName);
}
}
}
using System.Linq;
using System.Web.Http.Description;
using Swashbuckle.Swagger;
namespace Gist
{
// Adapted from http://analogcoder.com/2015/11/how-to-create-header-using-swashbuckle/
public class FromHeaderAttributeOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
foreach (var httpParameterDescriptor in apiDescription.ActionDescriptor.GetParameters().Where(e => e.GetCustomAttributes<FromHeaderAttribute>().Any()))
{
var parameter = operation.parameters.Single(p => p.name == httpParameterDescriptor.ParameterName);
parameter.name = httpParameterDescriptor.GetCustomAttributes<FromHeaderAttribute>().Single().HeaderName;
parameter.@in = "header";
}
}
}
}
using System.Web.Http;
using Swashbuckle.Application;
namespace Gist
{
public static class SwaggerConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "Sample API");
c.OperationFilter(() => new FromHeaderAttributeOperationFilter());
})
.EnableSwaggerUi();
}
}
}
using System;
using System.Web.Http;
namespace Gist
{
public class FromExampleHeaderAttribute : FromHeaderAttribute
{
public FromExampleHeaderAttribute() : base("X-Example") {}
}
[RoutePrefix("api/Example")]
public class ExampleController : ApiController
{
[HttpGet]
[Route("OptionalString")]
public IHttpActionResult EchoExampleHeaderOptional([FromExampleHeader] string example = "default")
{
return Ok(new { example });
}
[HttpGet]
[Route("RequiredString")]
public IHttpActionResult EchoExampleHeaderRequired([FromExampleHeader] string example)
{
return Ok(new { example });
}
[HttpGet]
[Route("OptionalInt")]
public IHttpActionResult EchoExampleHeaderOptional([FromExampleHeader] int id = 0)
{
return Ok(new { id });
}
[HttpGet]
[Route("RequiredInt")]
public IHttpActionResult EchoExampleHeaderRequired([FromExampleHeader] int id)
{
return Ok(new { id });
}
[HttpGet]
[Route("OptionalNullableDateTimeOffset")]
public IHttpActionResult EchoExampleHeaderOptional([FromExampleHeader] DateTimeOffset? date = null)
{
return Ok(new { date = date ?? DateTimeOffset.Now });
}
[HttpGet]
[Route("OptionalDateTimeOffset")]
public IHttpActionResult EchoExampleHeaderOptional([FromExampleHeader] DateTimeOffset date = default(DateTimeOffset))
{
return Ok(new { date });
}
[HttpGet]
[Route("RequiredDateTimeOffset")]
public IHttpActionResult EchoExampleHeaderRequired([FromExampleHeader] DateTimeOffset date)
{
return Ok(new { date });
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment