diff --git a/IPinfo.Tests/IPApiCoreTest.cs b/IPinfo.Tests/IPApiCoreTest.cs
new file mode 100644
index 0000000..e67e422
--- /dev/null
+++ b/IPinfo.Tests/IPApiCoreTest.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+using IPinfo.Models;
+
+namespace IPinfo.Tests
+{
+ public class IPApiCoreTest
+ {
+ [Fact]
+ public void TestGetDetailsIPV4()
+ {
+ string ip = "8.8.8.8";
+ IPinfoClientCore client = new IPinfoClientCore.Builder()
+ .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
+ .Build();
+
+ IPResponseCore actual = client.IPApi.GetDetails(ip);
+
+ Assert.Equal("8.8.8.8", actual.IP);
+ Assert.Equal("dns.google", actual.Hostname);
+ Assert.False(actual.Bogon);
+
+ // Geo assertions
+ Assert.NotNull(actual.Geo);
+ Assert.NotNull(actual.Geo.City);
+ Assert.NotNull(actual.Geo.Region);
+ Assert.NotNull(actual.Geo.RegionCode);
+ Assert.Equal("US", actual.Geo.CountryCode);
+ Assert.Equal("United States", actual.Geo.Country);
+ Assert.Equal("United States", actual.Geo.CountryName);
+ Assert.False(actual.Geo.IsEU);
+ Assert.NotNull(actual.Geo.Continent);
+ Assert.NotNull(actual.Geo.ContinentCode);
+ Assert.NotEqual(0, actual.Geo.Latitude);
+ Assert.NotEqual(0, actual.Geo.Longitude);
+ Assert.NotNull(actual.Geo.Timezone);
+ Assert.NotNull(actual.Geo.PostalCode);
+ Assert.Equal("🇺🇸", actual.Geo.CountryFlag.Emoji);
+ Assert.Equal("U+1F1FA U+1F1F8", actual.Geo.CountryFlag.Unicode);
+ Assert.Equal("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", actual.Geo.CountryFlagURL);
+ Assert.Equal("USD", actual.Geo.CountryCurrency.Code);
+ Assert.Equal("$", actual.Geo.CountryCurrency.Symbol);
+ Assert.Equal("NA", actual.Geo.ContinentInfo.Code);
+ Assert.Equal("North America", actual.Geo.ContinentInfo.Name);
+
+ // AS assertions
+ Assert.NotNull(actual.As);
+ Assert.Equal("AS15169", actual.As.Asn);
+ Assert.NotNull(actual.As.Name);
+ Assert.NotNull(actual.As.Domain);
+ Assert.NotNull(actual.As.Type);
+
+ // Network flags
+ Assert.False(actual.IsAnonymous);
+ Assert.True(actual.IsAnycast);
+ Assert.True(actual.IsHosting);
+ Assert.False(actual.IsMobile);
+ Assert.False(actual.IsSatellite);
+ }
+
+ [Fact]
+ public void TestGetDetailsIPV6()
+ {
+ string ip = "2001:4860:4860::8888";
+ IPinfoClientCore client = new IPinfoClientCore.Builder()
+ .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
+ .Build();
+
+ IPResponseCore actual = client.IPApi.GetDetails(ip);
+
+ Assert.Equal("2001:4860:4860::8888", actual.IP);
+
+ // Geo assertions
+ Assert.NotNull(actual.Geo);
+ Assert.Equal("US", actual.Geo.CountryCode);
+ Assert.Equal("United States", actual.Geo.Country);
+ Assert.NotNull(actual.Geo.City);
+ Assert.NotNull(actual.Geo.Region);
+
+ // AS assertions
+ Assert.NotNull(actual.As);
+ Assert.NotNull(actual.As.Asn);
+ Assert.NotNull(actual.As.Name);
+ Assert.NotNull(actual.As.Domain);
+
+ // Network flags
+ Assert.False(actual.IsAnonymous);
+ Assert.False(actual.IsMobile);
+ Assert.False(actual.IsSatellite);
+ }
+
+ [Fact]
+ public void TestBogonIPV4()
+ {
+ string ip = "127.0.0.1";
+ IPinfoClientCore client = new IPinfoClientCore.Builder()
+ .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
+ .Build();
+
+ IPResponseCore actual = client.IPApi.GetDetails(ip);
+
+ Assert.Equal("127.0.0.1", actual.IP);
+ Assert.True(actual.Bogon);
+ }
+
+ [Fact]
+ public void TestBogonIPV6()
+ {
+ string ip = "2001:0:c000:200::0:255:1";
+ IPinfoClientCore client = new IPinfoClientCore.Builder()
+ .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
+ .Build();
+
+ IPResponseCore actual = client.IPApi.GetDetails(ip);
+
+ Assert.Equal("2001:0:c000:200::0:255:1", actual.IP);
+ Assert.True(actual.Bogon);
+ }
+ }
+}
diff --git a/src/IPinfo/Apis/IPApiCore.cs b/src/IPinfo/Apis/IPApiCore.cs
new file mode 100644
index 0000000..e2c0713
--- /dev/null
+++ b/src/IPinfo/Apis/IPApiCore.cs
@@ -0,0 +1,117 @@
+using System.Net;
+using System.Threading.Tasks;
+using System.Threading;
+
+using IPinfo.Utilities;
+using IPinfo.Http.Client;
+using IPinfo.Http.Request;
+using IPinfo.Models;
+using IPinfo.Http.Response;
+using IPinfo.Cache;
+
+namespace IPinfo.Apis
+{
+ ///
+ /// IPApiCore.
+ ///
+ public sealed class IPApiCore : BaseApi
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// httpClient.
+ /// token.
+ /// cacheHandler.
+ internal IPApiCore(IHttpClient httpClient, string token, CacheHandler cacheHandler)
+ : base(httpClient, token, cacheHandler)
+ {
+ this.BaseUrl = "https://api.ipinfo.io/lookup/";
+ }
+
+ ///
+ /// Retrieves details of an IP address.
+ ///
+ /// The IP address of the user to retrieve details for.
+ /// Returns the Models.IPResponseCore response from the API call.
+ public Models.IPResponseCore GetDetails(
+ IPAddress ipAddress)
+ {
+ string ipString = ipAddress?.ToString();
+ return this.GetDetails(ipString);
+ }
+
+ ///
+ /// Retrieves details of an IP address.
+ ///
+ /// The IP address of the user to retrieve details for.
+ /// Returns the Models.IPResponseCore response from the API call.
+ public Models.IPResponseCore GetDetails(
+ string ipAddress = "")
+ {
+ Task t = this.GetDetailsAsync(ipAddress);
+ ApiHelper.RunTaskSynchronously(t);
+ return t.Result;
+ }
+
+ ///
+ /// Retrieves details of an IP address.
+ ///
+ /// The IP address of the user to retrieve details for.
+ /// Cancellation token if the request is cancelled.
+ /// Returns the Models.IPResponseCore response from the API call.
+ public Task GetDetailsAsync(
+ IPAddress ipAddress,
+ CancellationToken cancellationToken = default)
+ {
+ string ipString = ipAddress?.ToString();
+ return this.GetDetailsAsync(ipString, cancellationToken);
+ }
+
+ ///
+ /// Retrieves details of an IP address.
+ ///
+ /// The IP address of the user to retrieve details for.
+ /// Cancellation token if the request is cancelled.
+ /// Returns the Models.IPResponseCore response from the API call.
+ public async Task GetDetailsAsync(
+ string ipAddress = "",
+ CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(ipAddress))
+ {
+ ipAddress = "";
+ }
+
+ // first check the data in the cache if cache is available
+ IPResponseCore ipResponse = (IPResponseCore)GetFromCache(ipAddress);
+ if (ipResponse != null)
+ {
+ return ipResponse;
+ }
+
+ if (BogonHelper.IsBogon(ipAddress))
+ {
+ ipResponse = new IPResponseCore()
+ {
+ IP = ipAddress,
+ Bogon = true
+ };
+ return ipResponse;
+ }
+
+ // prepare the API call request to fetch the response.
+ HttpRequest httpRequest = this.CreateGetRequest(this.BaseUrl + ipAddress);
+ // invoke request and get response.
+ HttpStringResponse response = await this.GetClientInstance().ExecuteAsStringAsync(httpRequest, cancellationToken).ConfigureAwait(false);
+ HttpContext context = new HttpContext(httpRequest, response);
+
+ // handle errors defined at the API level.
+ this.ValidateResponse(context);
+
+ var responseModel = JsonHelper.ParseIPResponseCore(response.Body);
+
+ SetInCache(ipAddress, responseModel);
+ return responseModel;
+ }
+ }
+}
diff --git a/src/IPinfo/IPinfoClientCore.cs b/src/IPinfo/IPinfoClientCore.cs
new file mode 100644
index 0000000..2225b7d
--- /dev/null
+++ b/src/IPinfo/IPinfoClientCore.cs
@@ -0,0 +1,120 @@
+using System;
+
+using IPinfo.Http.Client;
+using IPinfo.Apis;
+using IPinfo.Cache;
+using IPinfo.Utilities;
+
+namespace IPinfo
+{
+ ///
+ /// The gateway for IPinfo Core SDK. This class holds the configuration of the SDK.
+ ///
+ public sealed class IPinfoClientCore
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly CacheHandler _cacheHandler;
+ private readonly Lazy _ipApi;
+
+ private IPinfoClientCore(
+ string accessToken,
+ IHttpClient httpClient,
+ CacheHandler cacheHandler,
+ IHttpClientConfiguration httpClientConfiguration)
+ {
+ this._httpClient = httpClient;
+ this._cacheHandler = cacheHandler;
+ this.HttpClientConfiguration = httpClientConfiguration;
+
+ this._ipApi = new Lazy(
+ () => new IPApiCore(this._httpClient, accessToken, cacheHandler));
+ }
+
+ ///
+ /// Gets IPApiCore.
+ ///
+ public IPApiCore IPApi => this._ipApi.Value;
+
+ ///
+ /// Gets the configuration of the Http Client associated with this client.
+ ///
+ public IHttpClientConfiguration HttpClientConfiguration { get; }
+
+ ///
+ /// Gets the configuration of the Http Client associated with this client.
+ ///
+ public ICache Cache { get => _cacheHandler?.Cache; }
+
+ ///
+ /// Builder class.
+ ///
+ public class Builder
+ {
+ private string _accessToken = "";
+ private HttpClientConfiguration.Builder _httpClientConfig = new HttpClientConfiguration.Builder();
+ private IHttpClient _httpClient;
+ private CacheHandler _cacheHandler = new CacheHandler();
+
+ ///
+ /// Sets credentials for BearerAuth.
+ ///
+ /// AccessToken.
+ /// Builder.
+ public Builder AccessToken(string accessToken)
+ {
+ this._accessToken = accessToken;
+ return this;
+ }
+
+ ///
+ /// Sets HttpClientConfig.
+ ///
+ /// Action.
+ /// Builder.
+ public Builder HttpClientConfig(Action action)
+ {
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ action(this._httpClientConfig);
+ return this;
+ }
+
+ ///
+ /// Sets the ICache implementation for the Builder.
+ ///
+ /// ICache implementation. Pass null to disable the cache.
+ /// Builder.
+ public Builder Cache(ICache cache)
+ {
+ // Null is allowed here, which is being used to indicate that user do not want the cache.
+ if(cache == null)
+ {
+ this._cacheHandler = null;
+ }
+ else
+ {
+ this._cacheHandler = new CacheHandler(cache);
+ }
+ return this;
+ }
+
+ ///
+ /// Creates an object of the IPinfoClientCore using the values provided for the builder.
+ ///
+ /// IPinfoClientCore.
+ public IPinfoClientCore Build()
+ {
+ this._httpClient = new HttpClientWrapper(this._httpClientConfig.Build());
+
+ return new IPinfoClientCore(
+ _accessToken,
+ _httpClient,
+ _cacheHandler,
+ _httpClientConfig.Build());
+ }
+ }
+ }
+}
diff --git a/src/IPinfo/Models/IPResponseCore.cs b/src/IPinfo/Models/IPResponseCore.cs
new file mode 100644
index 0000000..6d5c9b3
--- /dev/null
+++ b/src/IPinfo/Models/IPResponseCore.cs
@@ -0,0 +1,150 @@
+using System.Text.Json.Serialization;
+
+namespace IPinfo.Models
+{
+ public class IPResponseCore
+ {
+ [JsonInclude]
+ public string IP { get; internal set; }
+
+ [JsonInclude]
+ public string Hostname { get; private set; }
+
+ [JsonInclude]
+ public bool Bogon { get; internal set; }
+
+ [JsonInclude]
+ public GeoCore Geo { get; private set; }
+
+ [JsonPropertyName("as")]
+ [JsonInclude]
+ public ASCore As { get; private set; }
+
+ [JsonInclude]
+ public object Mobile { get; private set; }
+
+ [JsonInclude]
+ public AnonymousCore Anonymous { get; private set; }
+
+ [JsonPropertyName("is_anonymous")]
+ [JsonInclude]
+ public bool IsAnonymous { get; private set; }
+
+ [JsonPropertyName("is_anycast")]
+ [JsonInclude]
+ public bool IsAnycast { get; private set; }
+
+ [JsonPropertyName("is_hosting")]
+ [JsonInclude]
+ public bool IsHosting { get; private set; }
+
+ [JsonPropertyName("is_mobile")]
+ [JsonInclude]
+ public bool IsMobile { get; private set; }
+
+ [JsonPropertyName("is_satellite")]
+ [JsonInclude]
+ public bool IsSatellite { get; private set; }
+ }
+
+ public class GeoCore
+ {
+ [JsonInclude]
+ public string City { get; private set; }
+
+ [JsonInclude]
+ public string Region { get; private set; }
+
+ [JsonPropertyName("region_code")]
+ [JsonInclude]
+ public string RegionCode { get; private set; }
+
+ [JsonInclude]
+ public string Country { get; private set; }
+
+ [JsonPropertyName("country_code")]
+ [JsonInclude]
+ public string CountryCode { get; private set; }
+
+ public string CountryName { get; internal set; }
+
+ public bool IsEU { get; internal set; }
+
+ public CountryFlag CountryFlag { get; internal set; }
+
+ public string CountryFlagURL { get; internal set; }
+
+ public CountryCurrency CountryCurrency { get; internal set; }
+
+ [JsonInclude]
+ public string Continent { get; private set; }
+
+ [JsonPropertyName("continent_code")]
+ [JsonInclude]
+ public string ContinentCode { get; private set; }
+
+ public Continent ContinentInfo { get; internal set; }
+
+ [JsonInclude]
+ public double Latitude { get; private set; }
+
+ [JsonInclude]
+ public double Longitude { get; private set; }
+
+ [JsonInclude]
+ public string Timezone { get; private set; }
+
+ [JsonPropertyName("postal_code")]
+ [JsonInclude]
+ public string PostalCode { get; private set; }
+
+ [JsonPropertyName("dma_code")]
+ [JsonInclude]
+ public string DmaCode { get; private set; }
+
+ [JsonPropertyName("geoname_id")]
+ [JsonInclude]
+ public string GeonameId { get; private set; }
+
+ [JsonInclude]
+ public int Radius { get; private set; }
+ }
+
+ public class ASCore
+ {
+ [JsonInclude]
+ public string Asn { get; private set; }
+
+ [JsonInclude]
+ public string Name { get; private set; }
+
+ [JsonInclude]
+ public string Domain { get; private set; }
+
+ [JsonInclude]
+ public string Type { get; private set; }
+
+ [JsonPropertyName("last_changed")]
+ [JsonInclude]
+ public string LastChanged { get; private set; }
+ }
+
+ public class AnonymousCore
+ {
+ [JsonPropertyName("is_proxy")]
+ [JsonInclude]
+ public bool IsProxy { get; private set; }
+
+ [JsonPropertyName("is_relay")]
+ [JsonInclude]
+ public bool IsRelay { get; private set; }
+
+ [JsonPropertyName("is_tor")]
+ [JsonInclude]
+ public bool IsTor { get; private set; }
+
+ [JsonPropertyName("is_vpn")]
+ [JsonInclude]
+ public bool IsVpn { get; private set; }
+ }
+}
diff --git a/src/IPinfo/Utilities/JsonHelper.cs b/src/IPinfo/Utilities/JsonHelper.cs
index 201d126..e624fa8 100644
--- a/src/IPinfo/Utilities/JsonHelper.cs
+++ b/src/IPinfo/Utilities/JsonHelper.cs
@@ -112,5 +112,26 @@ internal static IPResponseLite ParseIPResponseLite(string response) {
responseModel.CountryFlagURL = CountryFlagURL + responseModel.CountryCode + ".svg";
return responseModel;
}
+
+ ///
+ /// IPResponseCore object with extra manual parsing.
+ ///
+ /// The json string to be parsed.
+ /// The deserialized IPResponseCore object with extra parsing for geo object country enrichment being done.
+ internal static IPResponseCore ParseIPResponseCore(string response) {
+ IPResponseCore responseModel = JsonHelper.Deserialize(response);
+
+ if (responseModel.Geo != null && !String.IsNullOrEmpty(responseModel.Geo.CountryCode))
+ {
+ responseModel.Geo.CountryName = CountryHelper.GetCountry(responseModel.Geo.CountryCode);
+ responseModel.Geo.IsEU = CountryHelper.IsEU(responseModel.Geo.CountryCode);
+ responseModel.Geo.CountryFlag = CountryHelper.GetCountryFlag(responseModel.Geo.CountryCode);
+ responseModel.Geo.CountryCurrency = CountryHelper.GetCountryCurrency(responseModel.Geo.CountryCode);
+ responseModel.Geo.ContinentInfo = CountryHelper.GetContinent(responseModel.Geo.CountryCode);
+ responseModel.Geo.CountryFlagURL = CountryFlagURL + responseModel.Geo.CountryCode + ".svg";
+ }
+
+ return responseModel;
+ }
}
}