Created
August 15, 2024 01:34
-
-
Save ssemi/726bae0455f29c76a2e602136bfba79a to your computer and use it in GitHub Desktop.
GeoIP Service + Memory Cache
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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