.NET Shorts – Use HttpContextAccessor

Problem

Sometimes, you have to get some data from headers/tokens. Sometimes, you have to do it in your application layer. And things can get wierd. You could potentally extract it at controller level and send it as parameter. But that’s just ugly.

Luckly, now you have IHttpContextAccessor that makes things easy for you, I, on the other hand, am entirely different story. Don’t get me wrong, I’m using it, but..

It’s just this one thing…

using Microsoft.AspNetCore.Http;

This drives me crazy. If AspNetCore wasn’t in the name of the package, I doubt that I would even bother. But since IHttpContextAccessor was introduced I am having this silly discussion with myself every time I have to use it. I can argue that IHttpContextAccessor is part of a Presentation Layer – you know, whole HttpContext thing. Also, it fits into Infrastructure Layer – if you argue that that package Microsoft.AspNetCore.Http is an “External Dependency”. But, would it be an end of a world if we put it in Application Layer – I guess not.

Solution

Let’s go with Infrastructure Layer approach.

  • Add folder HttpContextAccessor in your Company.Shorts.Application.Contracts
    • Inside that folder add IHttpContextAccessorAdapter.cs

I like to be explicit with my contracts, but that’s just personal preference.

namespace Company.Shorts.Application.Contracts.HttpContextAccessor
{
    public interface IHttpContextAccessorAdapter
    {
        public string GetName();
    }
}

Then, let’s create Company.Shorts.Infrastructure.HttpContextAccessorAdapter project in Infrastructure Layer.

  • Add Internal folder
    • Inside add DefaultHttpContextAccessorAdapter.cs
  • Add DependencyInjection.cs
namespace Company.Shorts.Infrastructure.HttpContextAccessorAdapter.Internal
{
    using Company.Shorts.Application.Contracts.HttpContextAccessor;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Primitives;
    using System;
    using System.ComponentModel;

    internal sealed class DefaultHttpContextAccessorAdapter : IHttpContextAccessorAdapter
    {
        private const string NameHeaderValue = "x-name";
        private readonly IHttpContextAccessor httpContextAccessor;

        public DefaultHttpContextAccessorAdapter(IHttpContextAccessor httpContextAccessor)
        {
            this.httpContextAccessor = httpContextAccessor;
        }

        public string GetName()
        {
            return this.GetRequiredHeaderValue<string>(NameHeaderValue);
        }

        private T GetRequiredHeaderValue<T>(string key)
        {
            StringValues values = GetValues(key);

            return values.Count switch
            {
                0 => throw new InvalidOperationException($"Value not found for header '{key}'."),
                > 1 => throw new InvalidOperationException($"Multiple unexpected values found for header '{key}'."),
                _ => ConvertSafeTo<T>(values)
            };
        }

        private static T? ConvertTo<T>(string value, TypeConverter? converter = null)
        {
            converter ??= TypeDescriptor.GetConverter(typeof(T));

            return (T?)converter.ConvertFromString(value);
        }

        private static T ConvertSafeTo<T>(string value, TypeConverter? converter = null)
        {
            T? convertedValue = ConvertTo<T>(value, converter);

            if (convertedValue is null)
            {
                throw new InvalidOperationException($"Unable to convert header value '{value}' to type of '{typeof(T)}'.");
            }

            return convertedValue;
        }

        private StringValues GetValues(string key)
        {
            this.httpContextAccessor!.HttpContext!.Request.Headers.TryGetValue(key, out var values);

            return values;
        }
    }
}
namespace Company.Shorts.Infrastructure.HttpContextAccessorAdapter
{
    using Company.Shorts.Application.Contracts.HttpContextAccessor;
    using Company.Shorts.Infrastructure.HttpContextAccessorAdapter.Internal;
    using Microsoft.Extensions.DependencyInjection;

    public static class DependencyInjection
    {
        public static IServiceCollection AddHttpContextAdapter(this IServiceCollection services)
        {
            services.AddHttpContextAccessor();

            services.AddScoped<IHttpContextAccessorAdapter, DefaultHttpContextAccessorAdapter>();

            return services;
        }
    }
}

In your Company.Shorts add dependency to Company.Shorts.Infrastructure.HttpContextAccessorAdapter and call AddHttpContextAdapter extension method in Startup.cs

...

services.AddHttpContextAdapter();

...

Now, you can use your IHttpContextAccessorAdapter.cs in your Application Layer to get x-name header value.

Clone and checkout branch shorts/use-http-context-accessor to see it in action.

Benefits?

  • Less dependencies in Application Layer
  • Everything you need to get from HttpContext is at same place
  • Having explicit contracts makes code more understandable.