diff --git a/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs
new file mode 100644
index 00000000..a5a1b77c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs
@@ -0,0 +1,212 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+
+namespace Snap.Hutao.Core;
+
+///
+/// 类型名称帮助类
+/// Directly copied from .NET Runtime library
+///
+internal static class TypeNameHelper
+{
+ private const char DefaultNestedTypeDelimiter = '+';
+
+ private static readonly Dictionary BuiltInTypeNames = new()
+ {
+ { typeof(void), "void" },
+ { typeof(bool), "bool" },
+ { typeof(byte), "byte" },
+ { typeof(char), "char" },
+ { typeof(decimal), "decimal" },
+ { typeof(double), "double" },
+ { typeof(float), "float" },
+ { typeof(int), "int" },
+ { typeof(long), "long" },
+ { typeof(object), "object" },
+ { typeof(sbyte), "sbyte" },
+ { typeof(short), "short" },
+ { typeof(string), "string" },
+ { typeof(uint), "uint" },
+ { typeof(ulong), "ulong" },
+ { typeof(ushort), "ushort" },
+ };
+
+ ///
+ /// 获取对象类型的显示名称
+ ///
+ /// 物品
+ /// 是否全名
+ /// 对象类型的显示名称
+ [return: NotNullIfNotNull(nameof(item))]
+ public static string? GetTypeDisplayName(object? item, bool fullName = true)
+ {
+ return item == null ? null : GetTypeDisplayName(item.GetType(), fullName);
+ }
+
+ ///
+ /// Pretty print a type name.
+ ///
+ /// The .
+ /// true to print a fully qualified name.
+ /// true to include generic parameter names.
+ /// true to include generic parameters.
+ /// Character to use as a delimiter in nested type names
+ /// The pretty printed type name.
+ public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter)
+ {
+ StringBuilder? builder = null;
+ string? name = ProcessType(ref builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter));
+ return name ?? builder?.ToString() ?? string.Empty;
+ }
+
+ private static string? ProcessType(ref StringBuilder? builder, Type type, in DisplayNameOptions options)
+ {
+ if (type.IsGenericType)
+ {
+ Type[] genericArguments = type.GetGenericArguments();
+ builder ??= new StringBuilder();
+ ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
+ }
+ else if (type.IsArray)
+ {
+ builder ??= new StringBuilder();
+ ProcessArrayType(builder, type, options);
+ }
+ else if (BuiltInTypeNames.TryGetValue(type, out string? builtInName))
+ {
+ if (builder is null)
+ {
+ return builtInName;
+ }
+
+ builder.Append(builtInName);
+ }
+ else if (type.IsGenericParameter)
+ {
+ if (options.IncludeGenericParameterNames)
+ {
+ if (builder is null)
+ {
+ return type.Name;
+ }
+
+ builder.Append(type.Name);
+ }
+ }
+ else
+ {
+ string name = options.FullName ? type.FullName! : type.Name;
+
+ if (builder is null)
+ {
+ if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter)
+ {
+ return name.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter);
+ }
+
+ return name;
+ }
+
+ builder.Append(name);
+ if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter)
+ {
+ builder.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter, builder.Length - name.Length, name.Length);
+ }
+ }
+
+ return null;
+ }
+
+ private static void ProcessArrayType(StringBuilder builder, Type type, in DisplayNameOptions options)
+ {
+ Type innerType = type;
+ while (innerType.IsArray)
+ {
+ innerType = innerType.GetElementType()!;
+ }
+
+ ProcessType(ref builder!, innerType, options);
+
+ while (type.IsArray)
+ {
+ builder.Append('[');
+ builder.Append(',', type.GetArrayRank() - 1);
+ builder.Append(']');
+ type = type.GetElementType()!;
+ }
+ }
+
+ private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, in DisplayNameOptions options)
+ {
+ int offset = 0;
+ if (type.IsNested)
+ {
+ offset = type.DeclaringType!.GetGenericArguments().Length;
+ }
+
+ if (options.FullName)
+ {
+ if (type.IsNested)
+ {
+ ProcessGenericType(builder, type.DeclaringType!, genericArguments, offset, options);
+ builder.Append(options.NestedTypeDelimiter);
+ }
+ else if (!string.IsNullOrEmpty(type.Namespace))
+ {
+ builder.Append(type.Namespace);
+ builder.Append('.');
+ }
+ }
+
+ int genericPartIndex = type.Name.IndexOf('`');
+ if (genericPartIndex <= 0)
+ {
+ builder.Append(type.Name);
+ return;
+ }
+
+ builder.Append(type.Name, 0, genericPartIndex);
+
+ if (options.IncludeGenericParameters)
+ {
+ builder.Append('<');
+ for (int i = offset; i < length; i++)
+ {
+ ProcessType(ref builder!, genericArguments[i], options);
+ if (i + 1 == length)
+ {
+ continue;
+ }
+
+ builder.Append(',');
+ if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
+ {
+ builder.Append(' ');
+ }
+ }
+
+ builder.Append('>');
+ }
+ }
+
+ private readonly struct DisplayNameOptions
+ {
+ public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeGenericParameters, char nestedTypeDelimiter)
+ {
+ FullName = fullName;
+ IncludeGenericParameters = includeGenericParameters;
+ IncludeGenericParameterNames = includeGenericParameterNames;
+ NestedTypeDelimiter = nestedTypeDelimiter;
+ }
+
+ public bool FullName { get; }
+
+ public bool IncludeGenericParameters { get; }
+
+ public bool IncludeGenericParameterNames { get; }
+
+ public char NestedTypeDelimiter { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs
index 00839239..e6c46e68 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs
@@ -14,7 +14,8 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
///
[HighQuality]
[UseDynamicSecret]
-[HttpClient(HttpClientConfiguration.XRpc, typeof(IUserClient))]
+[HttpClient(HttpClientConfiguration.XRpc)]
+[Injection(InjectAs.Transient, typeof(IUserClient))]
internal sealed class UserClient : IUserClient
{
private readonly HttpClient httpClient;
@@ -24,12 +25,13 @@ internal sealed class UserClient : IUserClient
///
/// 构造一个新的用户信息客户端
///
- /// http客户端
+ /// http客户端工厂
/// Json序列化选项
/// 日志器
- public UserClient(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
+ public UserClient(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger)
{
- this.httpClient = httpClient;
+ httpClient = httpClientFactory.CreateClient(nameof(UserClient));
+
this.options = options;
this.logger = logger;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs
index 76d0f237..4a706076 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs
@@ -13,7 +13,8 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
/// 用户信息客户端 Hoyolab版
///
[UseDynamicSecret]
-[HttpClient(HttpClientConfiguration.XRpc, typeof(IUserClient))]
+[HttpClient(HttpClientConfiguration.XRpc)]
+[Injection(InjectAs.Transient, typeof(IUserClient))]
internal sealed class UserClientOversea : IUserClient
{
private readonly HttpClient httpClient;
@@ -23,12 +24,13 @@ internal sealed class UserClientOversea : IUserClient
///
/// 构造一个新的用户信息客户端
///
- /// http客户端
+ /// http客户端工厂
/// Json序列化选项
/// 日志器
- public UserClientOversea(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
+ public UserClientOversea(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger)
{
- this.httpClient = httpClient;
+ httpClient = httpClientFactory.CreateClient(nameof(UserClientOversea));
+
this.options = options;
this.logger = logger;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs
index bf5019bc..ba9e1b50 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs
@@ -16,7 +16,8 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
///
[HighQuality]
[UseDynamicSecret]
-[HttpClient(HttpClientConfiguration.XRpc2, typeof(IPassportClient))]
+[HttpClient(HttpClientConfiguration.XRpc2)]
+[Injection(InjectAs.Transient, typeof(IPassportClient))]
internal sealed class PassportClient2 : IPassportClient
{
private readonly HttpClient httpClient;
@@ -26,12 +27,13 @@ internal sealed class PassportClient2 : IPassportClient
///
/// 构造一个新的通行证客户端
///
- /// http客户端
+ /// http客户端工厂
/// Json序列化选项
/// 日志器
- public PassportClient2(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
+ public PassportClient2(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger)
{
- this.httpClient = httpClient;
+ httpClient = httpClientFactory.CreateClient(nameof(PassportClient2));
+
this.options = options;
this.logger = logger;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs
index ecc4eb02..5f0d7b5c 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs
@@ -15,7 +15,8 @@ namespace Snap.Hutao.Web.Hoyolab.Passport;
///
/// 通行证客户端 XRPC 版
///
-[HttpClient(HttpClientConfiguration.XRpc3, typeof(IPassportClient))]
+[HttpClient(HttpClientConfiguration.XRpc3)]
+[Injection(InjectAs.Transient, typeof(IPassportClient))]
internal sealed class PassportClientOversea : IPassportClient
{
private readonly HttpClient httpClient;
@@ -25,12 +26,13 @@ internal sealed class PassportClientOversea : IPassportClient
///
/// 构造一个新的国际服通行证客户端
///
- /// http客户端
+ /// http客户端工厂
/// Json序列化选项
/// 日志器
- public PassportClientOversea(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
+ public PassportClientOversea(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger)
{
- this.httpClient = httpClient;
+ httpClient = httpClientFactory.CreateClient(nameof(PassportClientOversea));
+
this.options = options;
this.logger = logger;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs
index 8482fe22..d6114539 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs
@@ -16,8 +16,9 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
///
[HighQuality]
[UseDynamicSecret]
-[HttpClient(HttpClientConfiguration.XRpc, typeof(IGameRecordClient))]
+[HttpClient(HttpClientConfiguration.XRpc)]
[PrimaryHttpMessageHandler(UseCookies = false)]
+[Injection(InjectAs.Transient, typeof(IGameRecordClient))]
internal sealed class GameRecordClient : IGameRecordClient
{
private readonly IServiceProvider serviceProvider;
@@ -28,14 +29,14 @@ internal sealed class GameRecordClient : IGameRecordClient
///
/// 构造一个新的游戏记录提供器
///
- /// 请求器
+ /// 请求器工厂
/// 访问提供器
- public GameRecordClient(HttpClient httpClient, IServiceProvider serviceProvider)
+ public GameRecordClient(IHttpClientFactory httpClientFactory, IServiceProvider serviceProvider)
{
options = serviceProvider.GetRequiredService();
logger = serviceProvider.GetRequiredService>();
+ httpClient = httpClientFactory.CreateClient(nameof(GameRecordClient));
- this.httpClient = httpClient;
this.serviceProvider = serviceProvider;
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs
index 215e3b4a..4a27e8c9 100644
--- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs
@@ -15,8 +15,9 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
/// Hoyoverse game record provider
///
[UseDynamicSecret]
-[HttpClient(HttpClientConfiguration.XRpc3, typeof(IGameRecordClient))]
+[HttpClient(HttpClientConfiguration.XRpc3)]
[PrimaryHttpMessageHandler(UseCookies = false)]
+[Injection(InjectAs.Transient, typeof(IGameRecordClient))]
internal sealed class GameRecordClientOversea : IGameRecordClient
{
private readonly HttpClient httpClient;
@@ -26,12 +27,13 @@ internal sealed class GameRecordClientOversea : IGameRecordClient
///
/// 构造一个新的游戏记录提供器
///
- /// 请求器
+ /// 请求器工厂
/// json序列化选项
/// 日志器
- public GameRecordClientOversea(HttpClient httpClient, JsonSerializerOptions options, ILogger logger)
+ public GameRecordClientOversea(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger)
{
- this.httpClient = httpClient;
+ httpClient = httpClientFactory.CreateClient(nameof(GameRecordClientOversea));
+
this.options = options;
this.logger = logger;
}