diff --git a/src/BootstrapBlazor.Server/Startup.cs b/src/BootstrapBlazor.Server/Startup.cs
index 8f54b6e40..e4c0a8d2e 100644
--- a/src/BootstrapBlazor.Server/Startup.cs
+++ b/src/BootstrapBlazor.Server/Startup.cs
@@ -3,18 +3,10 @@
// Website: https://www.blazor.zone or https://argozhang.github.io/
using BootstrapBlazor.Components;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
+using System.Text;
namespace BootstrapBlazor.Server
{
@@ -52,7 +44,8 @@ namespace BootstrapBlazor.Server
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
- services.AddLogging(builder => builder.AddConsole());
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+
services.AddCors();
services.AddResponseCompression();
diff --git a/src/BootstrapBlazor.Shared/Locales/en.json b/src/BootstrapBlazor.Shared/Locales/en.json
index 9cecd5abb..14f2c71bc 100644
--- a/src/BootstrapBlazor.Shared/Locales/en.json
+++ b/src/BootstrapBlazor.Shared/Locales/en.json
@@ -379,6 +379,7 @@
"GroupBox": "GroupBox",
"HandwrittenPage": "HandwrittenPage",
"ListView": "ListView",
+ "Locator": "Locator",
"Popover": "Popover",
"QRCode": "QRCode",
"Search": "Search",
diff --git a/src/BootstrapBlazor.Shared/Locales/zh.json b/src/BootstrapBlazor.Shared/Locales/zh.json
index 93ea4bbd0..c575488a9 100644
--- a/src/BootstrapBlazor.Shared/Locales/zh.json
+++ b/src/BootstrapBlazor.Shared/Locales/zh.json
@@ -379,6 +379,7 @@
"GroupBox": "集合 GroupBox",
"HandwrittenPage": "手写组件 HandwrittenPage",
"ListView": "列表组件 ListView",
+ "Locator": "位置定位 Locator",
"Popover": "弹出窗 Popover",
"QRCode": "二维码 QRCode",
"Search": "搜索框 Search",
diff --git a/src/BootstrapBlazor.Shared/Pages/Samples/Client.razor.cs b/src/BootstrapBlazor.Shared/Pages/Samples/Client.razor.cs
index f460c7a02..d92816b43 100644
--- a/src/BootstrapBlazor.Shared/Pages/Samples/Client.razor.cs
+++ b/src/BootstrapBlazor.Shared/Pages/Samples/Client.razor.cs
@@ -14,9 +14,6 @@ namespace BootstrapBlazor.Shared.Pages
///
public partial class Client
{
- ///
- ///
- ///
[Inject]
[NotNull]
private WebClientService? ClientService { get; set; }
diff --git a/src/BootstrapBlazor.Shared/Pages/Samples/Locator.razor b/src/BootstrapBlazor.Shared/Pages/Samples/Locator.razor
new file mode 100644
index 000000000..fa1b747d5
--- /dev/null
+++ b/src/BootstrapBlazor.Shared/Pages/Samples/Locator.razor
@@ -0,0 +1,58 @@
+@page "/locator"
+
+
获取 IP 地理位置
+
+多用系统日志跟踪
+
+
+ 用法介绍
+
+
组件中使用注入服务 IIPLocatorProvider
调用 Locate
方法
+
[Inject]
+[NotNull]
+private IIPLocatorProvider? IPLocator { get; set; }
+
+
+
+ 由于地理位置查询接口返回字符集可能是其他字符集如 gbk
,程序会报错;
+ 解决办法:
+ Startup
文件中 ConfigureServices
方法内增加下面这句话即可解决
+
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)
+ IP 测试数据
+ 112.224.74.239
山东省济南市 联通
+ 183.160.236.53
安徽省合肥市 电信
+
+ 扩展自定义地理位置查询接口
+
+
1. 实现自定义定位器
+
private class CustomerLocator : IIPLocator
+{
+ public Task<string> Locate(IPLocatorOption option)
+ {
+ throw new NotImplementedException();
+ }
+}
+
2. 配置自定义定位器
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddBootstrapBlazor(locatorAction: option =>
+ {
+ option.LocatorName = "CustomerLocator";
+ option.LocatorFactory = name => new CustomerLocator();
+ option.Url = "http://apis.juhe.cn/ip/ipNew?key=f57102d1b9fadd3f4a1c29072d0c0206&ip=";
+ });
+}
+
通过 AddBootstrapBlazor
方法的回调委托参数进行自定义定位器 CustomerLocator
配置
+
+
diff --git a/src/BootstrapBlazor.Shared/Pages/Samples/Locator.razor.cs b/src/BootstrapBlazor.Shared/Pages/Samples/Locator.razor.cs
new file mode 100644
index 000000000..dc047e286
--- /dev/null
+++ b/src/BootstrapBlazor.Shared/Pages/Samples/Locator.razor.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using BootstrapBlazor.Components;
+using Microsoft.AspNetCore.Components;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading.Tasks;
+
+namespace BootstrapBlazor.Shared.Pages
+{
+ ///
+ ///
+ ///
+ public partial class Locator
+ {
+ [Inject]
+ [NotNull]
+ private WebClientService? ClientService { get; set; }
+
+ [Inject]
+ [NotNull]
+ private IIPLocatorProvider? IPLocator { get; set; }
+
+ private string? Ip { get; set; }
+
+ private string? Location { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ await base.OnAfterRenderAsync(firstRender);
+
+ if (firstRender)
+ {
+ await ClientService.RetrieveRemoteInfo();
+ Ip = ClientService.Ip;
+ StateHasChanged();
+ }
+ }
+
+ private async Task OnClick()
+ {
+ if (!string.IsNullOrEmpty(Ip))
+ {
+ Location = await IPLocator.Locate(Ip);
+ }
+ }
+ }
+}
diff --git a/src/BootstrapBlazor.Shared/Shared/NavMenu.razor.cs b/src/BootstrapBlazor.Shared/Shared/NavMenu.razor.cs
index 15017e297..82255701c 100644
--- a/src/BootstrapBlazor.Shared/Shared/NavMenu.razor.cs
+++ b/src/BootstrapBlazor.Shared/Shared/NavMenu.razor.cs
@@ -435,6 +435,12 @@ namespace BootstrapBlazor.Shared.Shared
Url = "listviews"
},
new()
+ {
+ IsNew = true,
+ Text = Localizer["Locator"],
+ Url = "locator"
+ },
+ new()
{
Text = Localizer["QRCode"],
Url = "qrcodes"
diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj
index 413942b84..4efbaf931 100644
--- a/src/BootstrapBlazor/BootstrapBlazor.csproj
+++ b/src/BootstrapBlazor/BootstrapBlazor.csproj
@@ -1,13 +1,14 @@
- 5.7.3
+ 5.7.4
+
@@ -16,6 +17,7 @@
+
diff --git a/src/BootstrapBlazor/Components/IPLocator/BaiDuIPLocator.cs b/src/BootstrapBlazor/Components/IPLocator/BaiDuIPLocator.cs
new file mode 100644
index 000000000..1d6748041
--- /dev/null
+++ b/src/BootstrapBlazor/Components/IPLocator/BaiDuIPLocator.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace BootstrapBlazor.Components
+{
+ ///
+ ///
+ ///
+ internal class BaiDuIPLocator : DefaultIPLocator
+ {
+ ///
+ /// 详细地址信息
+ ///
+ public IEnumerable? Data { get; set; }
+
+ ///
+ /// 结果状态返回码
+ ///
+ public string? Status { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override Task Locate(IPLocatorOption option) => Locate(option);
+
+ ///
+ ///
+ ///
+ ///
+ public override string ToString()
+ {
+ return Status == "0" ? (Data?.FirstOrDefault()?.Location ?? "XX XX") : "Error";
+ }
+ }
+
+ ///
+ ///
+ ///
+ public class LocationInfo
+ {
+ ///
+ ///
+ ///
+ public string? Location { get; set; }
+ }
+}
diff --git a/src/BootstrapBlazor/Components/IPLocator/DefaultIPLocator.cs b/src/BootstrapBlazor/Components/IPLocator/DefaultIPLocator.cs
new file mode 100644
index 000000000..b3397c307
--- /dev/null
+++ b/src/BootstrapBlazor/Components/IPLocator/DefaultIPLocator.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using Microsoft.Extensions.Logging;
+using System;
+using System.Net.Http.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace BootstrapBlazor.Components
+{
+ ///
+ ///
+ ///
+ internal class DefaultIPLocator : IIPLocator
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual Task Locate(IPLocatorOption option) => Task.FromResult(string.Empty);
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected static async Task Locate(IPLocatorOption option) where T : class
+ {
+ string? ret = null;
+ try
+ {
+ if (!string.IsNullOrEmpty(option.Url) && !string.IsNullOrEmpty(option.IP) && option.HttpClient != null)
+ {
+ var url = string.Format(option.Url, option.IP);
+ using var token = new CancellationTokenSource(option.RequestTimeout);
+ var result = await option.HttpClient.GetFromJsonAsync(url, token.Token);
+ ret = result?.ToString();
+ }
+ }
+ catch (Exception ex)
+ {
+ option.Logger?.LogError(ex, option.Url, option.IP);
+ }
+ return ret ?? string.Empty;
+ }
+ }
+}
diff --git a/src/BootstrapBlazor/Components/IPLocator/DefaultIPLocatorProvider.cs b/src/BootstrapBlazor/Components/IPLocator/DefaultIPLocatorProvider.cs
new file mode 100644
index 000000000..96d00aa48
--- /dev/null
+++ b/src/BootstrapBlazor/Components/IPLocator/DefaultIPLocatorProvider.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using Microsoft.Extensions.Logging;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace BootstrapBlazor.Components
+{
+ ///
+ /// 默认 IP 地理位置定位器
+ ///
+ internal class DefaultIPLocatorProvider : IIPLocatorProvider
+ {
+ private readonly IPLocatorOption _option = new();
+
+ ///
+ /// 构造函数
+ ///
+ ///
+ ///
+ public DefaultIPLocatorProvider(IHttpClientFactory factory, ILogger logger)
+ {
+ _option.HttpClient = factory.CreateClient();
+ _option.Logger = logger;
+ }
+
+ ///
+ /// 定位方法
+ ///
+ ///
+ ///
+ public async Task Locate(string ip)
+ {
+ string? ret = null;
+
+ // 解析本机地址
+ if (string.IsNullOrEmpty(ip) || _option.Localhosts.Any(p => p == ip))
+ {
+ ret = "本地连接";
+ }
+ else
+ {
+ // IP定向器地址未设置
+ _option.IP = ip;
+ if (string.IsNullOrEmpty(_option.Url))
+ {
+ ret = string.Empty;
+ }
+ var locator = _option.LocatorFactory(_option.LocatorName);
+ ret = await locator.Locate(_option);
+ }
+ return ret ?? string.Empty;
+ }
+ }
+}
diff --git a/src/BootstrapBlazor/Components/IPLocator/IIPLocator.cs b/src/BootstrapBlazor/Components/IPLocator/IIPLocator.cs
new file mode 100644
index 000000000..25c7bc70a
--- /dev/null
+++ b/src/BootstrapBlazor/Components/IPLocator/IIPLocator.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using System.Threading.Tasks;
+
+namespace BootstrapBlazor.Components
+{
+ ///
+ ///
+ ///
+ public interface IIPLocator
+ {
+ ///
+ ///
+ ///
+ /// 定位器配置信息
+ /// 定位器定位结果
+ Task Locate(IPLocatorOption option);
+ }
+}
diff --git a/src/BootstrapBlazor/Components/IPLocator/IIPLocatorProvider.cs b/src/BootstrapBlazor/Components/IPLocator/IIPLocatorProvider.cs
new file mode 100644
index 000000000..27aa5f805
--- /dev/null
+++ b/src/BootstrapBlazor/Components/IPLocator/IIPLocatorProvider.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using System.Threading.Tasks;
+
+namespace BootstrapBlazor.Components
+{
+ ///
+ /// IP 地址定位服务
+ ///
+ public interface IIPLocatorProvider
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task Locate(string ip);
+ }
+}
diff --git a/src/BootstrapBlazor/Components/IPLocator/IPLocatorOption.cs b/src/BootstrapBlazor/Components/IPLocator/IPLocatorOption.cs
new file mode 100644
index 000000000..798f11710
--- /dev/null
+++ b/src/BootstrapBlazor/Components/IPLocator/IPLocatorOption.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using Microsoft.Extensions.Logging;
+
+namespace BootstrapBlazor.Components
+{
+ ///
+ /// IP定位器配置项
+ ///
+ public class IPLocatorOption
+ {
+ ///
+ /// 获得/设置 定位器名称
+ ///
+ public string LocatorName { get; set; } = nameof(BaiDuIPLocator);
+
+ ///
+ /// 获得/设置 定位器创建方法未设置使用内部定位器
+ ///
+ public Func LocatorFactory { get; set; } = _ => new BaiDuIPLocator();
+
+ ///
+ /// 获得/设置 IP定位器请求地址
+ ///
+ public string Url { get; set; } = "https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?resource_id=6006&query={0}";
+
+ ///
+ /// 获得/设置 IP地址请求超时时间 默认为 3000 毫秒
+ ///
+ public int RequestTimeout { get; set; } = 3000;
+
+ ///
+ /// 获得 本机地址列表
+ ///
+ public List Localhosts { get; } = new List(new string[] { "::1", "127.0.0.1" });
+
+ ///
+ /// 获得/设置 IP地址
+ ///
+ internal string? IP { get; set; }
+
+ ///
+ /// 获得/设置 HttpClient 实体类
+ ///
+ internal HttpClient? HttpClient { get; set; }
+
+ ///
+ /// 获得/设置 ILogger 实体类
+ ///
+ internal ILogger? Logger { get; set; }
+ }
+}
diff --git a/src/BootstrapBlazor/Components/Logger/BlazorLogger.cs b/src/BootstrapBlazor/Components/Logger/BlazorLogger.cs
index 5bc804104..d538e8a95 100644
--- a/src/BootstrapBlazor/Components/Logger/BlazorLogger.cs
+++ b/src/BootstrapBlazor/Components/Logger/BlazorLogger.cs
@@ -88,7 +88,7 @@ namespace BootstrapBlazor.Components
logger.AppendLine();
}
- logger.AppendFormat("{0}: {1}", "Exception:", exception.Message);
+ logger.AppendFormat("{0}: {1}", "Exception", exception.Message);
logger.AppendLine();
logger.Append(new string('*', 45));
diff --git a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs
index 6b0fddc9f..d5f1651ad 100644
--- a/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs
+++ b/src/BootstrapBlazor/Extensions/BootstrapBlazorServiceCollectionExtensions.cs
@@ -24,12 +24,13 @@ namespace Microsoft.Extensions.DependencyInjection
///
///
///
- ///
+ ///
+ ///
///
- public static IServiceCollection AddBootstrapBlazor(this IServiceCollection services, Action? configureOptions = null, Action? setupAction = null)
+ public static IServiceCollection AddBootstrapBlazor(this IServiceCollection services, Action? configureOptions = null, Action? localizationAction = null, Action? locatorAction = null)
{
services.AddAuthorizationCore();
- services.AddJsonLocalization(setupAction);
+ services.AddJsonLocalization(localizationAction);
services.TryAddScoped();
services.TryAddScoped();
services.TryAddScoped(typeof(IDataService<>), typeof(NullDataService<>));
@@ -47,6 +48,13 @@ namespace Microsoft.Extensions.DependencyInjection
{
configureOptions?.Invoke(options);
});
+
+ services.AddHttpClient();
+ services.TryAddSingleton();
+ services.Configure(options =>
+ {
+ locatorAction?.Invoke(options);
+ });
return services;
}
diff --git a/src/BootstrapBlazor/Components/Logger/ConfigurationExtensions.cs b/src/BootstrapBlazor/Extensions/ConfigurationExtensions.cs
similarity index 100%
rename from src/BootstrapBlazor/Components/Logger/ConfigurationExtensions.cs
rename to src/BootstrapBlazor/Extensions/ConfigurationExtensions.cs
diff --git a/src/BootstrapBlazor/Extensions/StringExtensions.cs b/src/BootstrapBlazor/Extensions/StringExtensions.cs
new file mode 100644
index 000000000..fb1340bb1
--- /dev/null
+++ b/src/BootstrapBlazor/Extensions/StringExtensions.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Website: https://www.blazor.zone or https://argozhang.github.io/
+
+using System;
+using System.Collections.Generic;
+
+namespace BootstrapBlazor.Components
+{
+ ///
+ ///
+ ///
+ internal static class StringExtensions
+ {
+ ///
+ /// SpanSplit 扩展方法
+ ///
+ /// 源数组
+ /// 分隔符数组 分割规则作为整体
+ /// StringSplitOptions 选项
+ /// 分割后的字符串数组
+ public static List SpanSplit(this string source, string? splitStr = null, StringSplitOptions stringSplitOptions = StringSplitOptions.None)
+ {
+ var ret = new List();
+ if (string.IsNullOrEmpty(source))
+ {
+ return ret;
+ }
+
+ if (string.IsNullOrEmpty(splitStr))
+ {
+ splitStr = Environment.NewLine;
+ }
+
+ var sourceSpan = source.AsSpan();
+ var splitSpan = splitStr.AsSpan();
+
+ do
+ {
+ var n = sourceSpan.IndexOf(splitSpan);
+ if (n == -1)
+ {
+ n = sourceSpan.Length;
+ }
+
+ ret.Add(stringSplitOptions == StringSplitOptions.None
+ ? sourceSpan.Slice(0, n).ToString()
+ : sourceSpan.Slice(0, n).Trim().ToString());
+ sourceSpan = sourceSpan[Math.Min(sourceSpan.Length, n + splitSpan.Length)..];
+ }
+ while (sourceSpan.Length > 0);
+ return ret;
+ }
+
+ ///
+ /// SpanSplit 扩展方法
+ ///
+ /// 源数组
+ /// 分隔符数组 分割规则是任意一个
+ /// StringSplitOptions 选项
+ /// 分割后的字符串数组
+ public static List SpanSplitAny(this string source, string splitStr, StringSplitOptions stringSplitOptions = StringSplitOptions.None)
+ {
+ var ret = new List();
+ if (string.IsNullOrEmpty(source))
+ {
+ return ret;
+ }
+
+ if (string.IsNullOrEmpty(splitStr))
+ {
+ ret.Add(source);
+ return ret;
+ }
+
+ var sourceSpan = source.AsSpan();
+ var splitSpan = splitStr.AsSpan();
+
+ do
+ {
+ var n = sourceSpan.IndexOfAny(splitSpan);
+ if (n == -1)
+ {
+ n = sourceSpan.Length;
+ }
+
+ if (n > 0)
+ {
+ ret.Add(stringSplitOptions == StringSplitOptions.None
+ ? sourceSpan.Slice(0, n).ToString()
+ : sourceSpan.Slice(0, n).Trim().ToString());
+ }
+
+ sourceSpan = sourceSpan[Math.Min(sourceSpan.Length, n + 1)..];
+ }
+ while (sourceSpan.Length > 0);
+ return ret;
+ }
+ }
+}
diff --git a/src/BootstrapBlazor/Extensions/ServiceProviderHelper.cs b/src/BootstrapBlazor/Utils/ServiceProviderHelper.cs
similarity index 100%
rename from src/BootstrapBlazor/Extensions/ServiceProviderHelper.cs
rename to src/BootstrapBlazor/Utils/ServiceProviderHelper.cs