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