Fast Free Geolocation with freegeoip.net

If you need a simple, server-side way to locate a user based on IP address then you might be interested in the freegeoip.net API. As the name suggests, the service is totally free and has a generous request limit. It is also very easy to use. This post looks at how to call it from .NET.

Usage

Using the freegeoip.net API is incredibly simple. All you need to do is issue an HTTP GET request passing the IP address in the URL:

GET https://freegeoip.net/json/172.217.23.132

This results in the following JSON response:

{  
    "ip":"172.217.23.132",
    "country_code":"US",
    "country_name":"United States",
    "region_code":"CA",
    "region_name":"California",
    "city":"Mountain View",
    "zip_code":"94043",
    "time_zone":"America/Los_Angeles",
    "latitude":37.4192,
    "longitude":-122.0574,
    "metro_code":807
}

The service is limited to a generous 15000 queries per hour. No sign up or API key is necessary and limits are enforced by IP address. If you find freegeoip.net useful, please consider donating to this community funded project.

Behind the scenes, freegeoip.net makes use of the open source GeoLite2 Data from MaxMind. You can of course download and use this data yourself but freegeoip.net is so fast and easy, I see little benefit if you can live with the request limit.

Note that MaxMind also offers MaxMind GeoIP2 which is a commercial offering that provides more accurate results than the free version.

Calling the API from .NET Core

Given the simple nature of the API, creating a C# helper function is trivial.

First we create a class to store the result. We use JSON.NET attributes to map the property names:

public class GeoInfo
{
    [JsonProperty("country_code")]
    public string CountryCode { get; set; }

    [JsonProperty("country_name")]
    public string CountryName { get; set; }

    [JsonProperty("region_code")]
    public string RegionCode { get; set; }

    [JsonProperty("region_name")]
    public string RegionName { get; set; }

    [JsonProperty("city")]
    public string City { get; set; }

    [JsonProperty("zip_code")]
    public string ZipCode { get; set; }

    [JsonProperty("latitude")]
    public decimal Latitude { get; set; }

    [JsonProperty("longitude")]
    public string Longitude { get; set; }
}

Then we construct the request, make the call and deserialise the result to our GeoInfo class:

public class GeoHelper
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<GeoHelper> _logger;
    private readonly HttpClient _httpClient;

    public GeoHelper(IHttpContextAccessor httpContextAccessor, ILogger<GeoHelper> logger)
    {
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
        _httpClient = new HttpClient()
        {
            Timeout = TimeSpan.FromSeconds(5)
        };
    }

    public async Task<GeoInfo> GetGeoInfo()
    {
        var ip = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress;

        try
        {
            var response = await _httpClient.GetAsync($"http://freegeoip.net/json/{ip}");

            if (response.IsSuccessStatusCode)
            {
                var json = await response.Content.ReadAsStringAsync();

                return JsonConvert.DeserializeObject<GeoInfo>(json);
            }
        }
        catch(Exception ex)
        {
            _logger.LogError(ex, "Failed to retrieve geo info for ip '{0}'", ip);                
        }            

        return null;
    }
}

That's all there is to it. The only other thing to be aware of is the use of HttpContext.Connection.RemoteIpAddress to retrieve the IP address of the client. Unfortunately, out of the box in .NET Core, this is unlikely to work.

The problem lies in the fact that when using Kestrel, you always have another web server sitting in front acting as a reverse proxy. This is typically IIS or Nginx. When using HttpContext.Connection.RemoteIpAddress, you are actually getting the IP address of the remote proxy.

To fix this issue, a little more work is necessary. The reverse proxy generally adds a number of headers including one or more for the 'real' client IP address. You can access these values quite easily via HttpContext.Request.Headers["header-name"] but a better approach is to add the UseForwardedHeaders middleware to the pipeline which among other things sets the HttpContext.Connection.RemoteIpAddress to the correct value and attempts to prevent spoofing. It can be a bit of a headache to set up however depending on your hosting arrangements.

Summary

This post looked at accessing the freegeoip.net geolocation API from .NET Core. The service allows you to retrieve location information such as city, country (and even longitude/latitude) about a user based on IP address. The nice thing about it is that no API key and/or registration is necessary so you can get started incredibly fast.

I am not affiliated with the site in any way but have used the service successfully on a couple of projects and can attest to its speed and reliability.

Useful or Interesting?

If you liked the article, I would really appreciate it if you could share it with your Twitter followers.

Share on Twitter

Comments

Be the first person to comment on this article.