Determine a user’s location from their IP Address in ASP.NET Core

5 minute read

To determine a user’s location in a web browser you can use the HTML Geolocation API. There are however a couple of issues with this.

First, the user will be prompted to give permission for you to determine their location. If they deny this request, then you will not be able to determine the location.

Secondly, this is a JavaScript API, and therefore a client-side solution.

But what if you want to determine a user’s location on the server? One way in which you can do this is by determining the user’s location from their IP Address (also referred to as Geo IP).

Even though this method is not as accurate as the HTML Geolocation API (the level of accuracy is typically limited to the city the user is in), there are many instances where this is acceptable, for example:

  • You only want to make content available to users in a specific country.
  • You want to determine a user’s time zone - which you can infer from their city.
  • Maybe you are a nationwide chain store, and have a special promotion you want to display to users in a specific city.
  • The list goes on…

Getting a database of IP Addesses

If you want to implement this functionality, the first thing you need to do is to find a database or some sort of service which will do a Geolocation lookup from an IP Address. There are various of these available, but the one I am going to focus on in this blog post is MaxMind. They have both a downloadable database and a web service you can use.

In this blog post I will be using their Geolite2 database which is free, but not as accurate as their paid offering.

They have various SDKs available and .NET developers can use their GeoIP2 .NET API. ASP.NET Core developers are in luck as the NuGet package supports .NET Standard 1.4.

Using the GeoIP2 .NET API

To demonstrate how to implement this, I have a created a standard ASP.NET Core MVC application using the project templates in Visual Studio 2017.

First thing you need to do is to add the GeoIP2 .NET API NuGet package:

Install-Package MaxMind.GeoIP2

Next, you’ll need to download the MaxMind Geolite database. They have various ones on offer. Go ahead and grab the GeoLite2 City one, and once you have done this, extract the ZIP file and copy the .mmdb file to your ASP.NET project folder.

Next, all you need to do is to write some code which will open a DatabaseReader instance, and then call the City() method, passing the IP Address you want to lookup. In an ASP.NET Core application, the IP Address can be determined by using HttpContext.Connection.RemoteIpAddress.

So with that, here is the full code for my controller action:

public class HomeController : Controller
{
    private readonly IHostingEnvironment _hostingEnvironment;

    public HomeController(IHostingEnvironment hostingEnvironment)
    {
        _hostingEnvironment = hostingEnvironment;
    }

    public IActionResult Index()
    {
        using (var reader = new DatabaseReader(_hostingEnvironment.ContentRootPath + "\\GeoLite2-City.mmdb"))
        {
            // Determine the IP Address of the request
            var ipAddress = HttpContext.Connection.RemoteIpAddress;

            // Get the city from the IP Address
            var city = reader.City(ipAddress);

            return View(city);
        }
    }
}

And the Razor code for my view:

@model MaxMind.GeoIP2.Responses.CityResponse
@{
    ViewData["Title"] = "Home Page";
}

<div class="row" style="margin-top: 50px;">
    <div class="col-md-12">
        <p>Hey there, welcome visitor from <strong>@Model.City, @Model.Country!</strong></p>
        <p>Your time zone is <strong>@Model.Location.TimeZone</strong></p>
    </div>
</div>

Testing on your local machine

When you try and test this on your local machine, your IP Address will resolve to the loopback address (i.e. 127.0.0.1 or ::1). A handy way in which you can fool ASP.NET Core in thinking the request is coming from somewhere else is by using the ForwardedHeadersMiddleware and passing along a X-Forwarded-For header with each request.

First, register the ForwardedHeadersMiddleware when running in Development mode by calling the UseForwardedHeaders extension method. You can pass along an instance of ForwardedHeadersOptions for which your need to set the ForwardedHeaders to look only for the X-Forwarded-For.

When running on IIS, you will also need to set the ForwardLimit to 2. By default this is set to 1, but IIS already acts as a reverse proxy and will add a X-Forwarded-For header to all requests. If the ForwardLimit is set to 1, then the middleware will only pick up the value which was set by IIS, and not the value you are passing in. So be sure to set ForwardLimit to 2 - it had me scratching my head for a while.

public class Startup
{
    // Some code omitted from this code sample for brevity...

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        if (env.IsDevelopment())
        {
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor,

                // IIS is also tagging a X-Forwarded-For header on, so we need to increase this limit, 
                // otherwise the X-Forwarded-For we are passing along from the browser will be ignored
                ForwardLimit = 2 
            });

            app.UseDeveloperExceptionPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

The last thing we need to do is to be able to add the X-Forwarded-For header to all requests. If you are using Chrome you can install the ModHeader extension which will allow you to modify the HTTP headers of requests.

Testing it out

Great, now we can test this out! We will however need a IP Address to test with. The easiest way is to type “what is my ip” in Google, and it will tell you:

Now add an X-Forwarded-For header in ModHeader with that IP Address:

And when I run my application, I get the following output:

If you want some other ones to try out you can head over to the IP Address Ranges by Country page, which will give you all the IP Address ranges assigned to a specific country. It won’t give it to you on city level, but it is good enough to test a few others out.

As you can see when I tested one of the IP addresses from Afghanistan:

More info

Check out the GeoIP2 .NET API documentation for more information on how to use it in your application.

Source code for this sample is available at https://github.com/jerriepelser-blog/aspnetcore-geo-location-from-ip. Please note that I have not checked the actual MaxMind database into GitHub as it is huge. If you want to play with my sample you will have to download it yourself, as I described previously in this blog post.

Did you notice an error? Please help me and the other readers by heading over to the GitHub repo for this blog and submit a Pull Request with the corrections.