diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj
index 9e9a9b32164..4f7700e9aba 100644
--- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj
+++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj
@@ -42,6 +42,7 @@
+
diff --git a/src/BootstrapBlazor.Server/Extensions/ServiceCollectionSharedExtensions.cs b/src/BootstrapBlazor.Server/Extensions/ServiceCollectionSharedExtensions.cs
index 479c7981f93..d34dd4a48df 100644
--- a/src/BootstrapBlazor.Server/Extensions/ServiceCollectionSharedExtensions.cs
+++ b/src/BootstrapBlazor.Server/Extensions/ServiceCollectionSharedExtensions.cs
@@ -82,6 +82,9 @@ public static IServiceCollection AddBootstrapBlazorServices(this IServiceCollect
// 增加离线 IP 定位服务
services.AddBootstrapBlazorIP2RegionfService();
+ // 增加 JuHe 定位服务
+ services.AddBootstrapBlazorJuHeIpLocatorService();
+
// 增加 PetaPoco ORM 数据服务操作类
// 需要时打开下面代码
//services.AddPetaPoco(option =>
diff --git a/src/BootstrapBlazor.Server/Locales/en-US.json b/src/BootstrapBlazor.Server/Locales/en-US.json
index fae7a9e2562..e1dae3769ac 100644
--- a/src/BootstrapBlazor.Server/Locales/en-US.json
+++ b/src/BootstrapBlazor.Server/Locales/en-US.json
@@ -4142,7 +4142,7 @@
"LocatorsNormalInputText": "IpAddress",
"LocatorsNormalDisplayText": "Geographical location",
"LocatorsNormalButtonText": "Locating",
- "LocatorsProviderDesc": "
The component library has two built-in free online geolocation locators, BaiduIpLocatorProvider BaiduIpLocatorProviderV2
The component library has a built-in paid online geolocation locator JuHeIpLocatorProvider Official website address
The component library has a built-in free offline geolocation locator BootstrapBlazor.IP2Region Nuget package
"
+ "LocatorsProviderDesc": "The component library has two built-in free online geolocation locators, BaiduIpLocatorProvider BaiduIpLocatorProviderV2
The component library has a built-in paid online geolocation locator BootstrapBlazor.JuHeIpLocatorProvider Nuget package Official website address
The component library has a built-in free offline geolocation locator BootstrapBlazor.IP2Region Nuget package
"
},
"BootstrapBlazor.Server.Components.Samples.Print": {
"PrintsTitle": "Print",
diff --git a/src/BootstrapBlazor.Server/Locales/zh-CN.json b/src/BootstrapBlazor.Server/Locales/zh-CN.json
index dbbb087a331..1a56417b3b0 100644
--- a/src/BootstrapBlazor.Server/Locales/zh-CN.json
+++ b/src/BootstrapBlazor.Server/Locales/zh-CN.json
@@ -4142,7 +4142,7 @@
"LocatorsNormalInputText": "Ip 地址",
"LocatorsNormalDisplayText": "地理位置",
"LocatorsNormalButtonText": "定位",
- "LocatorsProviderDesc": "组件库内置两个免费在线地理位置定位器分别为 BaiduIpLocatorProvider BaiduIpLocatorProviderV2
组件库内置一个收费在线地理位置定位器 JuHeIpLocatorProvider 官网地址
组件库内置一个免费离线地理位置定位器 BootstrapBlazor.IP2Region Nuget 包
"
+ "LocatorsProviderDesc": "组件库内置两个免费在线地理位置定位器分别为 BaiduIpLocatorProvider BaiduIpLocatorProviderV2
组件库内置一个收费在线地理位置定位器 BootstrapBlazor.JuHeIpLocatorProvider Nuget 包 官网地址
组件库内置一个免费离线地理位置定位器 BootstrapBlazor.IP2Region Nuget 包
"
},
"BootstrapBlazor.Server.Components.Samples.Print": {
"PrintsTitle": "Print 打印按钮",
diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj
index 86ee6197a7c..6cb35269ddc 100644
--- a/src/BootstrapBlazor/BootstrapBlazor.csproj
+++ b/src/BootstrapBlazor/BootstrapBlazor.csproj
@@ -1,7 +1,7 @@
- 9.3.1-beta23
+ 9.3.1-beta24
diff --git a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs
index 163f66f6756..12af4f4ac80 100644
--- a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs
+++ b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs
@@ -47,10 +47,14 @@ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection serv
// IP 地理位置定位服务
services.TryAddSingleton();
- services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+#if NET8_0_OR_GREATER
+ services.AddKeyedSingleton(nameof(BaiduIpLocatorProvider));
+ services.AddKeyedSingleton(nameof(BaiduIpLocatorProviderV2));
+#endif
+
// 节日服务
services.TryAddSingleton();
diff --git a/src/BootstrapBlazor/Services/IPLocator/JuHeIpLocatorProvider.cs b/src/BootstrapBlazor/Services/IPLocator/JuHeIpLocatorProvider.cs
deleted file mode 100644
index 7a6dcab3943..00000000000
--- a/src/BootstrapBlazor/Services/IPLocator/JuHeIpLocatorProvider.cs
+++ /dev/null
@@ -1,203 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the Apache 2.0 License
-// See the LICENSE file in the project root for more information.
-// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
-
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using System.Net.Http.Json;
-using System.Text.Json.Serialization;
-
-namespace BootstrapBlazor.Components;
-
-///
-/// 聚合搜索引擎 IP 定位器
-///
-///
-///
-///
-///
-public class JuHeIpLocatorProvider(IHttpClientFactory httpClientFactory, IConfiguration configuration, IOptions options, ILogger logger) : DefaultIpLocatorProvider(options)
-{
- private HttpClient? _client;
-
- private string? _key;
-
- ///
- ///
- ///
- ///
- protected override async Task LocateByIp(string ip)
- {
- string? ret = null;
- _key ??= GetKey();
- var url = $"https://apis.juhe.cn/iplocation/ipv4dist/query?ip={ip}&key={_key}";
- try
- {
- _client ??= httpClientFactory.CreateClient();
- using var token = new CancellationTokenSource(3000);
- ret = await Fetch(url, _client, token.Token);
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "Url: {url}", url);
- }
- return ret;
- }
-
- private string GetKey()
- {
- var ret = configuration["JuHe:IpLocatorKey"];
- if (string.IsNullOrEmpty(ret))
- {
- throw new InvalidOperationException("JuHe:IpLocatorKey not value in appsettings configuration file. 未配置 JuHe:IpLocatorKey 请在 appsettings.json 中配置 JuHe:IpLocatorKey");
- }
- return ret;
- }
-
- ///
- /// 请求获得地理位置接口方法
- ///
- ///
- ///
- ///
- ///
- protected virtual async Task Fetch(string url, HttpClient client, CancellationToken token)
- {
- var result = await client.GetFromJsonAsync(url, token);
- if (result != null && result.ErrorCode != 0)
- {
- logger.LogError("ErrorCode: {ErrorCode} Reason: {Reason}", result.ErrorCode, result.Reason);
- }
- return result?.ToString();
- }
-
- ///
- /// LocationResult 结构体
- ///
- [ExcludeFromCodeCoverage]
- class LocationResult
- {
- ///
- /// 获得/设置 结果状态返回码 为 查询成功 时通讯正常
- ///
- public string? Reason { get; set; }
-
- ///
- /// 获得/设置 错误码
- ///
- [JsonPropertyName("error_code")]
- public int ErrorCode { get; set; }
-
- ///
- /// 获得/设置 定位信息
- ///
- public LocationData? Result { get; set; }
-
- ///
- ///
- ///
- ///
- public override string? ToString()
- {
- string? ret = null;
- if (ErrorCode == 0)
- {
- ret = Result?.Country == "中国"
- ? $"{Result?.Prov}{Result?.City}{Result?.District} {Result?.Isp}"
- : $"{Result?.Continent} {Result?.Country} {Result?.City}";
- }
- return ret;
- }
- }
-
-
- [ExcludeFromCodeCoverage]
- class LocationData
- {
- ///
- /// 获得/设置 州
- ///
- public string? Continent { get; set; }
-
- ///
- /// 获得/设置 国家
- ///
- public string? Country { get; set; }
-
- ///
- /// 获得/设置 邮编
- ///
- public string? ZipCode { get; set; }
-
- ///
- /// 获得/设置 时区
- ///
- public string? TimeZone { get; set; }
-
- ///
- /// 获得/设置 精度
- ///
- public string? Accuracy { get; set; }
-
- ///
- /// 获得/设置 所属
- ///
- public string? Owner { get; set; }
-
- ///
- /// 获得/设置 运营商
- ///
- public string? Isp { get; set; }
-
- ///
- /// 获得/设置 来源
- ///
- public string? Source { get; set; }
-
- ///
- /// 获得/设置 区号
- ///
- public string? AreaCode { get; set; }
-
- ///
- /// 获得/设置 行政区划代码
- ///
- public string? AdCode { get; set; }
-
- ///
- /// 获得/设置 国家代码
- ///
- public string? AsNumber { get; set; }
-
- ///
- /// 获得/设置 经度
- ///
- public string? Lat { get; set; }
-
- ///
- /// 获得/设置 纬度
- ///
- public string? Lng { get; set; }
-
- ///
- /// 获得/设置 半径
- ///
- public string? Radius { get; set; }
-
- ///
- /// 获得/设置 省份
- ///
- public string? Prov { get; set; }
-
- ///
- /// 获得/设置 城市
- ///
- public string? City { get; set; }
-
- ///
- /// 获得/设置 区县
- ///
- public string? District { get; set; }
- }
-}
diff --git a/test/UnitTest/Services/IpLocatorTest.cs b/test/UnitTest/Services/IpLocatorTest.cs
index c4aab8cdc5d..bc3ebd9b04e 100644
--- a/test/UnitTest/Services/IpLocatorTest.cs
+++ b/test/UnitTest/Services/IpLocatorTest.cs
@@ -61,34 +61,16 @@ public async Task BaiduIpLocatorProviderV2_Ok()
Assert.Equal("省份城市区县 测试", result);
}
- [Fact]
- public async Task JuHeIpLocatorProvider_Ok()
- {
- var factory = Context.Services.GetRequiredService();
- var option = Context.Services.GetRequiredService>();
- var logger = Context.Services.GetRequiredService>();
- var configuration = Context.Services.GetRequiredService();
- var provider = new MockJuHeProvider(factory, configuration, option, logger);
-
- var result = await provider.Locate("127.0.0.1");
- Assert.Equal("localhost", result);
-
- result = await provider.Locate("");
- Assert.Equal("localhost", result);
-
- // 河南省漯河市 移动
- result = await provider.Locate("223.91.188.112");
- Assert.Equal("省份城市区县 测试", result);
- }
-
[Fact]
public void Factory_Ok()
{
var factory = Context.Services.GetRequiredService();
Assert.NotNull(factory.Create("BaiduIpLocatorProviderV2"));
Assert.NotNull(factory.Create("BaiduIpLocatorProvider"));
- Assert.NotNull(factory.Create("JuHeIpLocatorProvider"));
Assert.NotNull(factory.Create());
+
+ Assert.NotNull(Context.Services.GetKeyedService("BaiduIpLocatorProvider"));
+ Assert.NotNull(Context.Services.GetKeyedService("BaiduIpLocatorProviderV2"));
}
[Fact]
@@ -111,12 +93,6 @@ public async Task Fetch_Error()
var cancelPprovider = new MockProviderFetchCancelError(factory, option, logger);
result = await cancelPprovider.Locate("223.91.188.112");
Assert.Null(result);
-
- var configuration = Context.Services.GetRequiredService();
- var juHeLogger = Context.Services.GetRequiredService>();
- var juHeProvider = new MockProviderJuHeFetchError(factory, configuration, option, juHeLogger);
- result = await juHeProvider.Locate("223.91.188.112");
- Assert.Null(result);
}
[Fact]
@@ -134,32 +110,6 @@ public async Task Fetch_Result_Fail()
var providerV2 = new MockBaiduProviderV2(factory, option, loggerV2);
result = await providerV2.Locate("223.91.188.112");
Assert.Null(result);
-
- var configuration = Context.Services.GetRequiredService();
- var loggerJuHe = Context.Services.GetRequiredService>();
- var providerJuHe = new MockJuHeNullProvider(factory, configuration, option, loggerJuHe);
- result = await providerJuHe.Locate("223.91.188.112");
- Assert.Null(result);
-
- var providerJuHeFailed = new MockJuHeFailProvider(factory, configuration, option, loggerJuHe);
- result = await providerJuHeFailed.Locate("223.91.188.112");
- Assert.Null(result);
- }
-
- [Fact]
- public async Task JuHe_Section_Error()
- {
- var factory = Context.Services.GetRequiredService();
- var option = Context.Services.GetRequiredService>();
- var logger = Context.Services.GetRequiredService>();
- var builder = new ConfigurationBuilder();
- builder.AddInMemoryCollection(new Dictionary
- {
- ["JuHe:IpLocatorKey"] = ""
- });
- var configuration = builder.Build();
- var provider = new MockJuHeNullProvider(factory, configuration, option, logger);
- await Assert.ThrowsAsync(async () => await provider.Locate("223.91.188.112"));
}
class MockProviderFetchError(IHttpClientFactory httpClientFactory, IOptions option, ILogger logger) : BaiduIpLocatorProvider(httpClientFactory, option, logger)
@@ -172,11 +122,6 @@ class MockProviderFetchCancelError(IHttpClientFactory httpClientFactory, IOption
protected override Task Fetch(string url, HttpClient client, CancellationToken token) => throw new TaskCanceledException();
}
- class MockProviderJuHeFetchError(IHttpClientFactory httpClientFactory, IConfiguration configuration, IOptions option, ILogger logger) : JuHeIpLocatorProvider(httpClientFactory, configuration, option, logger)
- {
- protected override Task Fetch(string url, HttpClient client, CancellationToken token) => throw new InvalidOperationException();
- }
-
class MockBaiduProvider(IHttpClientFactory httpClientFactory, IOptions option, ILogger logger) : BaiduIpLocatorProvider(httpClientFactory, option, logger)
{
protected override Task Fetch(string url, HttpClient client, CancellationToken token)
@@ -195,24 +140,6 @@ class MockBaiduProviderV2(IHttpClientFactory httpClientFactory, IOptions option, ILogger logger) : JuHeIpLocatorProvider(httpClientFactory, configuration, option, logger)
- {
- protected override Task Fetch(string url, HttpClient client, CancellationToken token)
- {
- client = new HttpClient(new MockHttpNullMessageHandler(), true);
- return base.Fetch(url, client, token);
- }
- }
-
- class MockJuHeFailProvider(IHttpClientFactory httpClientFactory, IConfiguration configuration, IOptions option, ILogger logger) : JuHeIpLocatorProvider(httpClientFactory, configuration, option, logger)
- {
- protected override Task Fetch(string url, HttpClient client, CancellationToken token)
- {
- client = new HttpClient(new MockHttpFailedMessageHandlerJuHe(), true);
- return base.Fetch(url, client, token);
- }
- }
-
class MockProvider(IHttpClientFactory httpClientFactory, IOptions option, ILogger logger) : BaiduIpLocatorProvider(httpClientFactory, option, logger)
{
protected override Task Fetch(string url, HttpClient client, CancellationToken token)
@@ -231,24 +158,6 @@ class MockProviderV2(IHttpClientFactory httpClientFactory, IOptions option, ILogger logger) : JuHeIpLocatorProvider(httpClientFactory, configuration, option, logger)
- {
- protected override Task Fetch(string url, HttpClient client, CancellationToken token)
- {
- client = new HttpClient(new MockHttpSuccessMessageHandlerJuHe(), true);
- return base.Fetch(url, client, token);
- }
- }
-
- class MockJuHeFailedProvider(IHttpClientFactory httpClientFactory, IConfiguration configuration, IOptions option, ILogger logger) : JuHeIpLocatorProvider(httpClientFactory, configuration, option, logger)
- {
- protected override Task Fetch(string url, HttpClient client, CancellationToken token)
- {
- client = new HttpClient(new MockHttpFailedMessageHandlerJuHe(), true);
- return base.Fetch(url, client, token);
- }
- }
-
class MockHttpNullMessageHandler : HttpMessageHandler
{
protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)