Skip to content

Instantly share code, notes, and snippets.

@ssemi
Created August 15, 2024 01:34
Show Gist options
  • Select an option

  • Save ssemi/726bae0455f29c76a2e602136bfba79a to your computer and use it in GitHub Desktop.

Select an option

Save ssemi/726bae0455f29c76a2e602136bfba79a to your computer and use it in GitHub Desktop.
GeoIP Service + Memory Cache
public class GeoIpService : IGeoIpService
{
private readonly ILogger<GeoIpService> _logger;
private readonly IDistributedCache _distributedCache;
private readonly IHostEnvironment _hostEnvironment;
public GeoIpService(ILogger<GeoIpService> logger,
IDistributedCache distributedCache,
IHostEnvironment hostEnvironment)
{
_logger = logger;
_distributedCache = distributedCache;
_hostEnvironment = hostEnvironment;
}
/// <summary>
/// Get Country Code (alpha-2)
/// </summary>
public async Task<string> GetCountryCode(string ipAddress, string databasePath = "")
{
if (string.IsNullOrWhiteSpace(databasePath)
|| File.Exists(databasePath) is false)
{
return string.Empty;
}
if (string.IsNullOrWhiteSpace(ipAddress)
|| ipAddress.IsIPv4() is false)
{
return string.Empty;
}
const string isoCode = "iso_code";
// find cache
var cachedData = await _distributedCache.GetAsync(ipAddress);
if (cachedData is not null)
{
return await JsonSerializer.DeserializeAsync<string?>(new MemoryStream(cachedData)) ?? string.Empty;
}
// find db
var result = GetData(ipAddress, databasePath);
var isoCountryCode = result?[isoCode]?.ToString() ?? string.Empty;
if (string.IsNullOrWhiteSpace(isoCountryCode))
{
return string.Empty;
}
try
{
// save cache
await _distributedCache.SetAsync(ipAddress,
JsonSerializer.SerializeToUtf8Bytes(isoCountryCode),
new DistributedCacheEntryOptions
{
SlidingExpiration =
_hostEnvironment.IsProduction()
? TimeSpan.FromHours(12)
: TimeSpan.FromSeconds(10)
}
);
}
catch (Exception ex)
{
_logger.LogError(ex, "({nameof}) : {message}",
nameof(_distributedCache.SetAsync), ex.Message);
}
return isoCountryCode;
}
/// <summary>
/// Get CountryInfo
/// </summary>
public async Task<IDictionary<string, object>?> GetCountryInfo(string ipAddress, string databasePath = "")
{
if (string.IsNullOrWhiteSpace(databasePath)
|| File.Exists(databasePath) is false)
{
return null;
}
if (string.IsNullOrWhiteSpace(ipAddress)
|| ipAddress.IsIPv4() is false)
{
return null;
}
return await Task.FromResult(GetData(ipAddress, databasePath));
}
/// <summary>
/// Get data from IPAddress
/// </summary>
private IDictionary<string, object>? GetData(string ipAddress, string databasePath = "")
{
var ip = IPAddress.Parse(ipAddress);
var geoIpData = GetGeoIpData(ip, databasePath);
return geoIpData;
}
/// <summary>
/// Read Database file
/// </summary>
private IDictionary<string, object>? GetGeoIpData(IPAddress ip, string databasePath)
{
try
{
using var reader = new Reader(databasePath);
var data = reader.Find<Dictionary<string, object>>(ip);
if (data is null || data.Count is 0)
{
return null;
}
return data.GetKeyInfo("country") ?? data.GetKeyInfo("registered_country");
}
catch (Exception ex)
{
_logger.LogError(ex, "{exception}", ex.Message);
}
return null;
}
}
public static class ExtensionHelper
{
public static Dictionary<string, object>? GetKeyInfo(this Dictionary<string, object> data, string keyName)
=> data.Keys.Contains(keyName)
? data[keyName] as Dictionary<string, object>
: null;
public static bool IsIPv4(this string value)
{
var quads = value.Split('.');
if ((quads.Length == 4) is false)
{
return false;
}
foreach (var quad in quads)
{
// if parse fails
// or length of parsed int != length of quad string (i.e.; '1' vs '001')
// or parsed int < 0
// or parsed int > 255
// return false
if (int.TryParse(quad, out var q) == false
|| q.ToString().Length.Equals(quad.Length) == false
|| q is < 0 or > 255)
{
return false;
}
}
return true;
}
}
public interface IGeoIpService
{
/// <summary>
/// ISO 3166-1 alpha-2
/// </summary>
Task<string> GetCountryCode(string ipAddress, string databasePath = "");
/// <summary>
/// Get GeoIP Db Data
/// </summary>
Task<IDictionary<string, object>?> GetCountryInfo(string ipAddress, string databasePath = "");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment