Header based routing in ASP.NET Core

I ended up writing a custom middleware to rewrite the uri before hitting the mvc layer. Works nicely!

public Task Invoke(HttpContext context)
{
    const string apiPath = "/api";

    var magic context.Request.Headers["Magic"];

    if (string.IsNullOrWhiteSpace(magic))
    {
        return _next.Invoke(context);
    }

    var path = context.Request.Path.ToUriComponent();

    if (!path.StartsWith(apiPath))
    {
        return _next.Invoke(context);
    }

    path = string.Concat(path.Skip(apiPath.Length));
    path = $"{apiPath}/{magic}{path}";
    context.Request.Path = path;

    return _next.Invoke(context);
}

While writing a custom middleware is certainly a good option, another option would be to create an action constraint (by implementing the IActionContraint interface). This would allow you to decorate your methods with an attribute which dictates the header / value that must be provided in the request in order for a given method to be invoked.

I found this to be particularly helpful when working with WebHooks where the external system uses a single URL for all hooks, and uses a header to differentiate the different action types.

Here is what a simple implementation of this attribute could look like:

public class HttpHeaderAttribute : Attribute, IActionConstraint
{
    public string Header { get; set; }
    public string Value { get; set; }

    public HttpHeaderAttribute (string header, string value)
    {
        Header = header;
        Value = value;
    }

    public bool Accept(ActionConstraintContext context)
    {
        if (context.RouteContext.HttpContext.Request.Headers.TryGetValue(Header, out var value))
        {
            return value[0] == Value;
        }

        return false;
    }

    public int Order => 0;
}

And here's a sample of how you could use it:

    [HttpPost, Route("webhook"), HttpHeader("X-Drchrono-Event", "PATIENT_CREATE")]
    public IActionResult DrChronoPatientCreate(WebHookData data)
    {

    }

    [HttpPost, Route("webhook"), HttpHeader("X-Drchrono-Event", "PATIENT_MODIFY")]
    public IActionResult DrChronoPatientModify(WebHookData data)
    {

    }