Parse HTTP User Agents with ASP.NET Core

Parse HTTP User Agents with ASP.NET Core

In my last blog post I shared why we wrote our own user agent parser (mycsharp/HttpUserAgentParser) in .NET (reason: performance).

This is now about the correct usage of such a parser.

Static Parsing

The library has a static interface for parsing user agent strings.

string userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36";
HttpUserAgentInformation info = HttpUserAgentParser.Parse(userAgent);

The info object created from this now contains all relevant information of the agent:

UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
Type = HttpUserAgentType.Browser
Platform = {
    Name = "Windows 10",
    PlatformType = HttpUserAgentPlatformType.Windows
}
Name = "Chrome"
Version = "90.0.4430.212"
MobileDeviceType = null

Dependency Injection

For integration via Dependency Injection in ASP.NET Core, package MyCSharp.HttpUserAgentParser.AspNetCore must also be installed. Afterwards, the User Agent Parser can be registered as a service.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpUserAgentParser(); // uses HttpUserAgentParserDefaultProvider and does not cache
}

The registration contains the IHttpUserAgentParserProvider interface, which can now be used for parsing.

public class MyClass
{
    private IHttpUserAgentParserProvider _parserProvider;
    public MyClass(IHttpUserAgentParserProvider parserProvider)
    {
        _parserProvider = parserProvider;
    }
}

    public void MyMethod(string userAgent)
    {
        HttpUserAgentInformation info = _parserProvider.Parse(userAgent);
    }

The default implementation of the registry only registers the parser (HttpUserAgentParserDefaultProvider), without any caching integration or other capabilities.

Caching

Caching is enabled by implementing your own implementation of IHttpUserAgentParserProvider or using the built-in caching functionality.

On the one hand we offer an implementation via a ConcurrentDictionary:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpUserAgentCachedParser(); // uses `HttpUserAgentParserCachedProvider`
    // or
    // services.AddHttpUserAgentParser<YourHttpUserAgentParserCachedProvider>();
}

On the other hand, we also offer a caching implementation based on MemoryCache, for which there is an extra NuGet package: MyCSharp.HttpUserAgentParser.MemoryCache

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpUserAgentMemoryCachedParser();

    // or use options

    services.AddHttpUserAgentMemoryCachedParser(options =>
    {
        options.CacheEntryOptions.SlidingExpiration = TimeSpan.FromMinutes(60); // default is 1 day

        // limit the total entries in the MemoryCache
        //   each unique user agent string counts as one entry
        options.CacheOptions.SizeLimit = 1024; // default is null (= no limit)
    });
}

After that, it is also easy to use the IHttpUserAgentParserProvider interface to use the parser.

All caching mechanisms store the full user agent string as a key, and use a one-time parsing result as the result, which is then always returned. This guarantees that each user agent string only has to go through our parsing mechanism once.