From 401189335588e7ee9f95eab37d82658b80b39209 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 17:55:05 +0200 Subject: [PATCH 01/12] Update System.Text.Json to 6.0.10 More info here https://github.com/advisories/GHSA-8g4q-xg66-9fp4 --- src/IPinfo/IPinfo.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IPinfo/IPinfo.csproj b/src/IPinfo/IPinfo.csproj index 2ce4d7a..aa5a277 100644 --- a/src/IPinfo/IPinfo.csproj +++ b/src/IPinfo/IPinfo.csproj @@ -35,9 +35,9 @@ - + - + <_Parameter1>IPinfo.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a3e25284f42d5a26fdeb222c8fb9ac5df0b7421bdf350a8e828c18f72f1ec06fec81eff098d3610ebd64ec3c3b9b66f3801fc3d38fde5968d04c46b319896074cdae04c20a5c4d2dc5438e259265617106567f7357b6daf0fbf301fbe43df5019c53c5668b231423e23411e6e4c4cfae2b0b2e0f6e1333e0fe2cd691c26717d4 From a6cd034167b65840038cf5daddb1d14a31733055 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 18:50:52 +0200 Subject: [PATCH 02/12] Add support for Lite API --- IPinfo.Tests/IPApiLiteTest.cs | 153 ++++++++++++++++++++++++++++ src/IPinfo/Apis/IPApiLite.cs | 131 ++++++++++++++++++++++++ src/IPinfo/IPinfo.csproj | 3 +- src/IPinfo/IPinfoClientLite.cs | 120 ++++++++++++++++++++++ src/IPinfo/Models/IPResponseLite.cs | 43 ++++++++ src/IPinfo/Utilities/JsonHelper.cs | 26 ++++- 6 files changed, 470 insertions(+), 6 deletions(-) create mode 100644 IPinfo.Tests/IPApiLiteTest.cs create mode 100644 src/IPinfo/Apis/IPApiLite.cs create mode 100644 src/IPinfo/IPinfoClientLite.cs create mode 100644 src/IPinfo/Models/IPResponseLite.cs diff --git a/IPinfo.Tests/IPApiLiteTest.cs b/IPinfo.Tests/IPApiLiteTest.cs new file mode 100644 index 0000000..c58c5ee --- /dev/null +++ b/IPinfo.Tests/IPApiLiteTest.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using Xunit; + +using IPinfo.Models; + +namespace IPinfo.Tests +{ + public class IPApiLiteTest + { + [Fact] + public void TestGetDetails() + { + string ip = "8.8.8.8"; + IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) + .Build(); + + IPResponseLite actual = client.IPApi.GetDetails(ip); + + var expectations = new List>() + { + new("8.8.8.8", actual.IP), + new("AS15169", actual.Asn), + new("Google LLC", actual.AsName), + new("google.com", actual.AsDomain), + new("US", actual.CountryCode), + new("United States", actual.Country), + new("United States", actual.CountryName), + new(false, actual.IsEU), + new("🇺🇸", actual.CountryFlag.Emoji), + new("U+1F1FA U+1F1F8", actual.CountryFlag.Unicode), + new("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", actual.CountryFlagURL), + new("USD", actual.CountryCurrency.Code), + new("$", actual.CountryCurrency.Symbol), + new("NA", actual.Continent.Code), + new("North America", actual.Continent.Name), + }; + Assert.All(expectations, pair => Assert.Equal(pair.Item1, pair.Item2)); + } + + [Fact] + public void TestGetDetailsIPV6() + { + string ip = "2001:4860:4860::8888"; + IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) + .Build(); + + IPResponseLite actual = client.IPApi.GetDetails(ip); + + var expectations = new List>() + { + new("2001:4860:4860::8888", actual.IP), + new("AS15169", actual.Asn), + new("Google LLC", actual.AsName), + new("google.com", actual.AsDomain), + new("US", actual.CountryCode), + new("United States", actual.Country), + new("United States", actual.CountryName), + new(false, actual.IsEU), + new("🇺🇸", actual.CountryFlag.Emoji), + new("U+1F1FA U+1F1F8", actual.CountryFlag.Unicode), + new("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", actual.CountryFlagURL), + new("USD", actual.CountryCurrency.Code), + new("$", actual.CountryCurrency.Symbol), + new("NA", actual.Continent.Code), + new("North America", actual.Continent.Name), + }; + Assert.All(expectations, pair => Assert.Equal(pair.Item1, pair.Item2)); + } + + [Fact] + public void TestGetDetailsCurrentIP() + { + IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) + .Build(); + + IPResponseLite actual = client.IPApi.GetDetails(); + + Assert.NotNull(actual.IP); + Assert.NotNull(actual.Asn); + Assert.NotNull(actual.AsName); + Assert.NotNull(actual.AsDomain); + Assert.NotNull(actual.CountryCode); + Assert.NotNull(actual.Country); + Assert.NotNull(actual.CountryName); + Assert.NotNull(actual.CountryFlag); + Assert.NotNull(actual.CountryFlag.Emoji); + Assert.NotNull(actual.CountryFlag.Unicode); + Assert.NotNull(actual.CountryFlagURL); + Assert.NotNull(actual.CountryCurrency); + Assert.NotNull(actual.Continent); + } + + [Fact] + public void TestBogonIPV4() + { + string ip = "127.0.0.1"; + IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) + .Build(); + + IPResponseLite 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"; + IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) + .Build(); + + IPResponseLite actual = client.IPApi.GetDetails(ip); + + Assert.Equal("2001:0:c000:200::0:255:1", actual.IP); + Assert.True(actual.Bogon); + } + + [Fact] + public void TestNonBogonIPV4() + { + string ip = "1.1.1.1"; + IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) + .Build(); + + IPResponseLite actual = client.IPApi.GetDetails(ip); + + Assert.Equal("1.1.1.1", actual.IP); + Assert.False(actual.Bogon); + } + + [Fact] + public void TestNonBogonIPV6() + { + string ip = "2a03:2880:f10a:83:face:b00c:0:25de"; + IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN")) + .Build(); + + IPResponseLite actual = client.IPApi.GetDetails(ip); + + Assert.Equal("2a03:2880:f10a:83:face:b00c:0:25de", actual.IP); + Assert.False(actual.Bogon); + } + } +} diff --git a/src/IPinfo/Apis/IPApiLite.cs b/src/IPinfo/Apis/IPApiLite.cs new file mode 100644 index 0000000..2af2932 --- /dev/null +++ b/src/IPinfo/Apis/IPApiLite.cs @@ -0,0 +1,131 @@ +using System.Collections.Generic; +using System.Net; +using System.Text; +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 +{ + /// + /// IPApiLite. + /// + public sealed class IPApiLite : BaseApi + { + /// + /// Initializes a new instance of the class. + /// + /// httpClient. + /// token. + internal IPApi(IHttpClient httpClient, string token, CacheHandler cacheHandler) + : base(httpClient, token, cacheHandler) + { + this.BaseUrl = "https://api.ipinfo.io/lite/" + } + + /// + /// Retrieves details of an IP address. + /// + /// The IP address of the user to retrieve details for. + /// Returns the Models.IPResponseLite response from the API call. + public Models.IPResponseLite 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.IPResponseLite response from the API call. + public Models.IPResponseLite 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.IPResponseLite 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.IPResponseLite response from the API call. + public async Task GetDetailsAsync( + string ipAddress = "", + CancellationToken cancellationToken = default) + { + if (ipAddress == null) + { + ipAddress = ""; + } + // first check the data in the cache if cache is available + IPResponseLite ipResponse = (IPResponseLite)GetFromCache(ipAddress); + if (ipResponse != null) + { + return ipResponse; + } + + if (BogonHelper.IsBogon(ipAddress)) + { + ipResponse = new IPResponseLite() + { + IP = ipAddress, + Bogon = true + }; + return ipResponse; + } + + // Determine the base URI based on IP version + string baseUri = this.BaseUrl; + + // prepare query string for API call. + StringBuilder queryBuilder = new StringBuilder(baseUri); + queryBuilder.Append("{ip_address}"); + // process optional template parameters. + ApiHelper.AppendUrlWithTemplateParameters(queryBuilder, new Dictionary() + { + { "ip_address", ipAddress }, + }); + + // prepare the API call request to fetch the response. + HttpRequest httpRequest = this.CreateGetRequest(queryBuilder.ToString()); + + // 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.ParseIPResponseLite(response.Body); + + SetInCache(ipAddress, responseModel); + return responseModel; + } + } +} diff --git a/src/IPinfo/IPinfo.csproj b/src/IPinfo/IPinfo.csproj index aa5a277..20e7c2b 100644 --- a/src/IPinfo/IPinfo.csproj +++ b/src/IPinfo/IPinfo.csproj @@ -40,8 +40,9 @@ - <_Parameter1>IPinfo.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a3e25284f42d5a26fdeb222c8fb9ac5df0b7421bdf350a8e828c18f72f1ec06fec81eff098d3610ebd64ec3c3b9b66f3801fc3d38fde5968d04c46b319896074cdae04c20a5c4d2dc5438e259265617106567f7357b6daf0fbf301fbe43df5019c53c5668b231423e23411e6e4c4cfae2b0b2e0f6e1333e0fe2cd691c26717d4 + <_Parameter1>IPinfo.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef0be87bac040789cce848898328b49097b01bb34dbda41bc7b1e2e7a1a70e36f78efd463235911bf94fec1ec8ba23c8681997668d299b115571221f88483dbaa8417fe1ccb17ff70dfd3c08c4dd3191fe479b67b755e93bab7ae0db4787e4edfab7bac2144918c980e59ab3ac40a960ba70c502c0b5ee4a1862e54e9f6df1a1 + diff --git a/src/IPinfo/IPinfoClientLite.cs b/src/IPinfo/IPinfoClientLite.cs new file mode 100644 index 0000000..364416a --- /dev/null +++ b/src/IPinfo/IPinfoClientLite.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 SDK. This class holds the configuration of the SDK. + /// + public sealed class IPinfoClientLite + { + private readonly IHttpClient _httpClient; + private readonly CacheHandler _cacheHandler; + private readonly Lazy _ipApi; + + private IPinfoClient( + string accessToken, + IHttpClient httpClient, + CacheHandler cacheHandler, + IHttpClientConfiguration httpClientConfiguration) + { + this._httpClient = httpClient; + this._cacheHandler = cacheHandler; + this.HttpClientConfiguration = httpClientConfiguration; + + this._ipApi = new Lazy( + () => new IPApiLite(this._httpClient, accessToken, cacheHandler)); + } + + /// + /// Gets IPApiLite. + /// + public IPApiLite 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 IPinfoClientLite using the values provided for the builder. + /// + /// IPinfoClientLite. + public IPinfoClientLite Build() + { + this._httpClient = new HttpClientWrapper(this._httpClientConfig.Build()); + + return new IPinfoClientLite( + this._accessToken, + this._httpClient, + this._cacheHandler, + this._httpClientConfig.Build()); + } + } + } +} diff --git a/src/IPinfo/Models/IPResponseLite.cs b/src/IPinfo/Models/IPResponseLite.cs new file mode 100644 index 0000000..915f670 --- /dev/null +++ b/src/IPinfo/Models/IPResponseLite.cs @@ -0,0 +1,43 @@ +using System.Text.Json.Serialization; + +namespace IPinfo.Models +{ + public class IPResponseLite + { + [JsonInclude] + public bool Anycast { get; private set; } + + [JsonInclude] + public bool Bogon { get; internal set; } + + [JsonInclude] + public string Country { get; private set; } + + [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; } + + public Continent Continent { get; internal set; } + + [JsonInclude] + public string IP { get; internal set; } + + [JsonInclude] + public string Asn { get; private set; } + + [JsonInclude] + public string AsName {get; private set; } + + [JsonInclude] + public string AsDomain {get; private set; } + } +} diff --git a/src/IPinfo/Utilities/JsonHelper.cs b/src/IPinfo/Utilities/JsonHelper.cs index fff5ac6..201d126 100644 --- a/src/IPinfo/Utilities/JsonHelper.cs +++ b/src/IPinfo/Utilities/JsonHelper.cs @@ -25,18 +25,18 @@ internal static T Deserialize(string json, JsonSerializerOptions options = nu { return default; } - + if(options is null) { options = new JsonSerializerOptions { PropertyNameCaseInsensitive = DefaultCaseInsensitive - }; + }; } - return JsonSerializer.Deserialize(json, options); + return JsonSerializer.Deserialize(json, options); } - + /// /// JSON Deserialization of a given json string. /// @@ -93,7 +93,23 @@ internal static IPResponse ParseIPResponse(string response){ responseModel.CountryCurrency = CountryHelper.GetCountryCurrency(responseModel.Country); responseModel.Continent = CountryHelper.GetContinent(responseModel.Country); responseModel.CountryFlagURL = CountryFlagURL + responseModel.Country + ".svg"; - + + return responseModel; + } + + /// + /// IPResponseLite object with extra manual parsing. + /// + /// The json string to be parsed. + /// The deserialized IPResponseLite object with extra parsing for country being done. + internal static IPResponseLite ParseIPResponseLite(string response) { + IPResponseLite responseModel = JsonHelper.Deserialize(response); + responseModel.CountryName = CountryHelper.GetCountry(responseModel.CountryCode); + responseModel.IsEU = CountryHelper.IsEU(responseModel.CountryCode); + responseModel.CountryFlag = CountryHelper.GetCountryFlag(responseModel.CountryCode); + responseModel.CountryCurrency = CountryHelper.GetCountryCurrency(responseModel.CountryCode); + responseModel.Continent = CountryHelper.GetContinent(responseModel.CountryCode); + responseModel.CountryFlagURL = CountryFlagURL + responseModel.CountryCode + ".svg"; return responseModel; } } From 10f4112c67662e93cb08f1c546f63b1867ae8009 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 18:56:38 +0200 Subject: [PATCH 03/12] Add workflow to run tests on PRs and push on main --- .github/workflows/test.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3290d2a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Run tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: "6.x" + + - name: Create Strong Name Keypair + run: echo "${{ secrets.SNK_BASE64 }}" | base64 --decode > sgKeyIPinfoStrongName.snk + + - name: Restore dependencies + run: dotnet restore + + - name: Test + run: dotnet test + env: + IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }} + + - name: Build + run: dotnet build --configuration Release /p:AssemblyOriginatorKeyFile=sgKeyIPinfoStrongName.snk From 74c70bfe414933dd84128aad54fcf35fdf0b45a2 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 19:00:39 +0200 Subject: [PATCH 04/12] Add missing semicolon --- src/IPinfo/Apis/IPApiLite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IPinfo/Apis/IPApiLite.cs b/src/IPinfo/Apis/IPApiLite.cs index 2af2932..02648d7 100644 --- a/src/IPinfo/Apis/IPApiLite.cs +++ b/src/IPinfo/Apis/IPApiLite.cs @@ -26,7 +26,7 @@ public sealed class IPApiLite : BaseApi internal IPApi(IHttpClient httpClient, string token, CacheHandler cacheHandler) : base(httpClient, token, cacheHandler) { - this.BaseUrl = "https://api.ipinfo.io/lite/" + this.BaseUrl = "https://api.ipinfo.io/lite/"; } /// From 9fc6c64e06bfaabe23463701bd39300dd3ffc8be Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 19:01:54 +0200 Subject: [PATCH 05/12] Revert PublicKey in project file --- src/IPinfo/IPinfo.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IPinfo/IPinfo.csproj b/src/IPinfo/IPinfo.csproj index 20e7c2b..4e6dbb1 100644 --- a/src/IPinfo/IPinfo.csproj +++ b/src/IPinfo/IPinfo.csproj @@ -40,7 +40,7 @@ - <_Parameter1>IPinfo.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ef0be87bac040789cce848898328b49097b01bb34dbda41bc7b1e2e7a1a70e36f78efd463235911bf94fec1ec8ba23c8681997668d299b115571221f88483dbaa8417fe1ccb17ff70dfd3c08c4dd3191fe479b67b755e93bab7ae0db4787e4edfab7bac2144918c980e59ab3ac40a960ba70c502c0b5ee4a1862e54e9f6df1a1 + <_Parameter1>IPinfo.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a3e25284f42d5a26fdeb222c8fb9ac5df0b7421bdf350a8e828c18f72f1ec06fec81eff098d3610ebd64ec3c3b9b66f3801fc3d38fde5968d04c46b319896074cdae04c20a5c4d2dc5438e259265617106567f7357b6daf0fbf301fbe43df5019c53c5668b231423e23411e6e4c4cfae2b0b2e0f6e1333e0fe2cd691c26717d4 From 6fbb874a1241f33c5546f4f7f2e899f2839623dc Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 19:04:09 +0200 Subject: [PATCH 06/12] Properly name constructors --- src/IPinfo/Apis/IPApiLite.cs | 2 +- src/IPinfo/IPinfoClientLite.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IPinfo/Apis/IPApiLite.cs b/src/IPinfo/Apis/IPApiLite.cs index 02648d7..e4b9647 100644 --- a/src/IPinfo/Apis/IPApiLite.cs +++ b/src/IPinfo/Apis/IPApiLite.cs @@ -23,7 +23,7 @@ public sealed class IPApiLite : BaseApi /// /// httpClient. /// token. - internal IPApi(IHttpClient httpClient, string token, CacheHandler cacheHandler) + internal IPApiLite(IHttpClient httpClient, string token, CacheHandler cacheHandler) : base(httpClient, token, cacheHandler) { this.BaseUrl = "https://api.ipinfo.io/lite/"; diff --git a/src/IPinfo/IPinfoClientLite.cs b/src/IPinfo/IPinfoClientLite.cs index 364416a..d39e620 100644 --- a/src/IPinfo/IPinfoClientLite.cs +++ b/src/IPinfo/IPinfoClientLite.cs @@ -16,7 +16,7 @@ public sealed class IPinfoClientLite private readonly CacheHandler _cacheHandler; private readonly Lazy _ipApi; - private IPinfoClient( + private IPinfoClientLite( string accessToken, IHttpClient httpClient, CacheHandler cacheHandler, From 7e4880191b8cb44cccd5460e249c6bd18dfb48c3 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 19:06:37 +0200 Subject: [PATCH 07/12] Fix BaseUrl assignment --- src/IPinfo/Apis/BaseApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IPinfo/Apis/BaseApi.cs b/src/IPinfo/Apis/BaseApi.cs index aee89a6..54e7e2b 100644 --- a/src/IPinfo/Apis/BaseApi.cs +++ b/src/IPinfo/Apis/BaseApi.cs @@ -60,7 +60,7 @@ internal BaseApi( /// /// Gets base url values. /// - internal string BaseUrl => DefaultBaseUrl; + internal string BaseUrl {get; protected set; } = DefaultBaseUrl; internal string BaseUrlIPv6 => DefaultBaseUrlIPv6; /// From 635aeca8c3ff84dda7d544e9ffd1ae0067690103 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 8 Jul 2025 19:09:55 +0200 Subject: [PATCH 08/12] Fix accessibility modified for BaseUrl --- src/IPinfo/Apis/BaseApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IPinfo/Apis/BaseApi.cs b/src/IPinfo/Apis/BaseApi.cs index 54e7e2b..6830a4f 100644 --- a/src/IPinfo/Apis/BaseApi.cs +++ b/src/IPinfo/Apis/BaseApi.cs @@ -60,7 +60,7 @@ internal BaseApi( /// /// Gets base url values. /// - internal string BaseUrl {get; protected set; } = DefaultBaseUrl; + protected string BaseUrl { get; set; } = DefaultBaseUrl; internal string BaseUrlIPv6 => DefaultBaseUrlIPv6; /// From be68ab067cb3c004941cf1cce19db003aa4edb4e Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 15 Jul 2025 15:09:28 +0200 Subject: [PATCH 09/12] Remove unnecessary uri string builder --- src/IPinfo/Apis/IPApiLite.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/IPinfo/Apis/IPApiLite.cs b/src/IPinfo/Apis/IPApiLite.cs index e4b9647..6104c7a 100644 --- a/src/IPinfo/Apis/IPApiLite.cs +++ b/src/IPinfo/Apis/IPApiLite.cs @@ -100,20 +100,8 @@ public Models.IPResponseLite GetDetails( return ipResponse; } - // Determine the base URI based on IP version - string baseUri = this.BaseUrl; - - // prepare query string for API call. - StringBuilder queryBuilder = new StringBuilder(baseUri); - queryBuilder.Append("{ip_address}"); - // process optional template parameters. - ApiHelper.AppendUrlWithTemplateParameters(queryBuilder, new Dictionary() - { - { "ip_address", ipAddress }, - }); - // prepare the API call request to fetch the response. - HttpRequest httpRequest = this.CreateGetRequest(queryBuilder.ToString()); + HttpRequest httpRequest = this.CreateGetRequest(this.BaseUrl + ipAddress); // invoke request and get response. HttpStringResponse response = await this.GetClientInstance().ExecuteAsStringAsync(httpRequest, cancellationToken).ConfigureAwait(false); From 2fe8d6cb6c0c4ff74e13a4488dc448c11ab5b249 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 15 Jul 2025 15:15:37 +0200 Subject: [PATCH 10/12] Use the correct endpoint when IP is not set --- src/IPinfo/Apis/IPApiLite.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/IPinfo/Apis/IPApiLite.cs b/src/IPinfo/Apis/IPApiLite.cs index 6104c7a..3e6c994 100644 --- a/src/IPinfo/Apis/IPApiLite.cs +++ b/src/IPinfo/Apis/IPApiLite.cs @@ -79,9 +79,9 @@ public Models.IPResponseLite GetDetails( string ipAddress = "", CancellationToken cancellationToken = default) { - if (ipAddress == null) + if (string.IsNullOrEmpty(ipAddress)) { - ipAddress = ""; + ipAddress = "me"; } // first check the data in the cache if cache is available IPResponseLite ipResponse = (IPResponseLite)GetFromCache(ipAddress); @@ -102,7 +102,6 @@ public Models.IPResponseLite GetDetails( // 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); From 4370ba73b3715f9868b4c93fdd246261b877091d Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 15 Jul 2025 15:49:08 +0200 Subject: [PATCH 11/12] Fix response deserialization --- src/IPinfo/Models/IPResponseLite.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/IPinfo/Models/IPResponseLite.cs b/src/IPinfo/Models/IPResponseLite.cs index 915f670..4c83275 100644 --- a/src/IPinfo/Models/IPResponseLite.cs +++ b/src/IPinfo/Models/IPResponseLite.cs @@ -13,6 +13,7 @@ public class IPResponseLite [JsonInclude] public string Country { get; private set; } + [JsonPropertyName("country_code")] [JsonInclude] public string CountryCode { get; private set; } @@ -34,9 +35,11 @@ public class IPResponseLite [JsonInclude] public string Asn { get; private set; } + [JsonPropertyName("as_name")] [JsonInclude] public string AsName {get; private set; } + [JsonPropertyName("as_domain")] [JsonInclude] public string AsDomain {get; private set; } } From c98043084d4895abfaea7de46af872f8d176dc35 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Tue, 15 Jul 2025 17:34:32 +0200 Subject: [PATCH 12/12] Update README.md --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e44f01..f8c7d4c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ You'll need an IPinfo API access token, which you can get by signing up for a fr The free plan is limited to 50,000 requests per month, and doesn't include some of the data fields such as IP type and company data. To enable all the data fields and additional request volumes see [https://ipinfo.io/pricing](https://ipinfo.io/pricing) -⚠️ Note: This SDK does not currently support our newest free API https://ipinfo.io/lite. If you’d like to use IPinfo Lite, you can call the [endpoint directly](https://ipinfo.io/developers/lite-api) using your preferred HTTP client. Developers are also welcome to contribute support for Lite by submitting a pull request. +The library also supports the Lite API, see the [Lite API section](#lite-api) for more info. ### Installation @@ -126,6 +126,33 @@ Console.WriteLine($"IPResponse.Continent.Code: {ipResponse.Continent.Code}"); Console.WriteLine($"IPResponse.Continent.Name: {ipResponse.Continent.Name}"); ``` +### Lite API + +The library gives the possibility to use the [Lite API](https://ipinfo.io/developers/lite-api) too, authentication with your token is still required. + +The returned details are slightly different from the Core API. + +```csharp +// namespace +using IPinfo; +using IPinfo.Models; + +// initializing IPinfo client for Lite API +string token = "MY_TOKEN"; +IPinfoClientLite client = new IPinfoClientLite.Builder() + .AccessToken(token) + .Build(); + +// making API call +string ip = "216.239.36.21"; +IPResponseLite ipResponse = await client.IPApi.GetDetailsAsync(ip); + +// accessing details from response +Console.WriteLine($"IPResponse.IP: {ipResponse.IP}"); +Console.WriteLine($"IPResponse.Country: {ipResponse.Country}"); +Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}"); +``` + ### Caching In-memory caching of data is provided by default. Custom implementation of the cache can also be provided by implementing the `ICache` interface.