User GeoLocation in ASP.NET Core

If you want to get the physical location of a user on your site there’s a couple of different methods that you can use, you can either use their IP address to get an approximate location or you can request a more accurate location from them using the HTML geolocation API. An example of both these mothods in action can be seen here.

Location From IP Address

When someone requests a page from your site the request object provides the IP address of the user and this IP address can be used to look up their approximate location using a free service such as FreeGeoIP (now ipstack). I’ve created a NuGet package called FreeGeoIPCore to make calling the API easier, though it looks like I’ll need to create a new one as it’s being depreciated soon.

The request object can be retrieved in your controller from IHttpContextAccessor which is available through Dependency Injection (DI).

WeatherController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using FreeGeoIPCore;
using MetOfficeDataPoint;
using MetOfficeDataPoint.Models;
using MetOfficeDataPoint.Models.GeoCoordinate;

namespace bitScry.Controllers.Projects
{
    [Route("Projects/[controller]")]
    public class WeatherController : Controller
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IConfiguration _config;

        public WeatherController(IConfiguration config, IHttpContextAccessor httpContextAccessor)
        {
            _config = config;
            _httpContextAccessor = httpContextAccessor;
        }

        public IActionResult Index(double longitude, double latitude)
        {
            FreeGeoIPClient ipClient = new FreeGeoIPClient();

            string ipAddress = AppCode.Projects.Weather.GetRequestIP(_httpContextAccessor);

            FreeGeoIPCore.Models.Location location = ipClient.GetLocation(ipAddress).Result;

            GeoCoordinate coordinate = new GeoCoordinate();

            // If location is provided then use over IP address
            if (longitude == 0 && latitude == 0)
            {
                coordinate.Longitude = location.Longitude;
                coordinate.Latitude = location.Latitude;
            }
            else
            {
                coordinate.Longitude = longitude;
                coordinate.Latitude = latitude;
            }
			
            return View();
        }
    }
}
AppCode.Projects.Weather

using bitScry.Models.Projects.Weather;
using FreeGeoIPCore.AppCode;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using System;
using System.Linq;

namespace bitScry.AppCode.Projects
{
    public static class Weather
    {
        public static string GetRequestIP(IHttpContextAccessor httpContextAccessor, bool tryUseXForwardHeader = true)
        {
            string ip = null;

            if (tryUseXForwardHeader)
                ip = GetHeaderValueAs<string>(httpContextAccessor, "X-Forwarded-For").SplitCsv().FirstOrDefault();

            // RemoteIpAddress is always null in DNX RC1 Update1 (bug).
            if (ip.IsNullOrWhitespace() && httpContextAccessor.HttpContext?.Connection?.RemoteIpAddress != null)
                ip = httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();

            if (ip.IsNullOrWhitespace())
                ip = GetHeaderValueAs<string>(httpContextAccessor, "REMOTE_ADDR");

            // _httpContextAccessor.HttpContext?.Request?.Host this is the local host.

            if (ip.IsNullOrWhitespace())
                throw new Exception("Unable to determine caller's IP.");

            // Remove port if on IP address
            ip = ip.Substring(0, ip.IndexOf(":"));

            return ip;
        }

        public static T GetHeaderValueAs<T>(IHttpContextAccessor httpContextAccessor, string headerName)
        {
            StringValues values;

            if (httpContextAccessor.HttpContext?.Request?.Headers?.TryGetValue(headerName, out values) ?? false)
            {
                string rawValues = values.ToString();   // writes out as Csv when there are multiple.

                if (!rawValues.IsNullOrWhitespace())
                    return (T)Convert.ChangeType(values.ToString(), typeof(T));
            }
            return default(T);
        }
    }
}

Location From HTML Geolocation API

You can request a users location on page load if you’d like but in the below example I have a button that when clicked by a user calls a javascript function to get the location and then redirects to the the above controller with the returned latitude and longitude as URL parameters.

Index

<div class="row">
	<button id="locationButton" type="button" class="btn btn-default" onclick="getLocation()">Use Actual Location</button>
</div>

<script type="text/javascript">
	function getLocation() {
		if (navigator.geolocation) {
			navigator.geolocation.getCurrentPosition(useLocation);
		}
	};

	function useLocation(position) {
		window.location.replace('?longitude=' + position.coords.longitude + '&latitude=' + position.coords.latitude);
	};
</script>

Leave a Reply

Your email address will not be published. Required fields are marked *