环境
- dotnet
❯ dotnet --list-sdks
7.0.400 [C:\Program Files\dotnet\sdk]
8.0.100-preview.7.23376.3 [C:\Program Files\dotnet\sdk]
- Microsoft Visual Studio Professional 2022 (64-bit) - Current Version 17.7.2
准备
目前.net8
还是preview
状态,需要在VS
中开启,否则无法选用.net8
版本的SDK。
Sample
Kx.Toolx.Clicky.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- AOT -->
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
<InvariantGlobalization>true</InvariantGlobalization>
<!-- NuGet -->
<PackAsTool>true</PackAsTool>
<ToolCommandName>clicky</ToolCommandName>
<RootNamespace>Kx.Toolx.Clicky</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.7.23375.6" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.2.0-dev-00918" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.18-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
目前AOT还存在不少限制,具体见Native AOT deployment overview # Limitations
目前选项模式与AOT的兼容不是很好,发布时勾选裁剪也要注意,可能会读取配置为空。
选项模式的简易替代方案
直接使用选项模式会报警告:
原因是Configure
方法已经标了RequiresDynamicCode
特性:
[RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
=> services.Configure<TOptions>(Options.Options.DefaultName, config);
跟进代码后发现核心代码如下:
[RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string? name, IConfiguration config, Action<BinderOptions>? configureBinder)
where TOptions : class
{
ThrowHelper.ThrowIfNull(services);
ThrowHelper.ThrowIfNull(config);
services.AddOptions();
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}
真正受影响的是NamedConfigureFromConfigurationOptions
这个类:
[RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
public NamedConfigureFromConfigurationOptions(string? name, IConfiguration config, Action<BinderOptions>? configureBinder)
: base(name, options => config.Bind(options, configureBinder))
{
ThrowHelper.ThrowIfNull(config);
}
其核心作用是将config
中的配置信息填充给被注入的options
对象。
我们做如下替换:
- 初始化
await Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// 服务注册
services.AddHostedService<HostedService>();
// Options
// 原配置
// services.Configure<ClickyConfig>(hostContext.Configuration.GetSection(ClickyConfig.KEY));
// 替换后
services.AddOptions();
var config = hostContext.Configuration.GetSection(ClickyConfig.KEY);
services.AddSingleton<IOptionsChangeTokenSource<ClickyConfig>>(new ConfigurationChangeTokenSource<ClickyConfig>(string.Empty, config));
// 使用 ClickyConfigureNamedOptions 替换 NamedConfigureFromConfigurationOptions
services.AddSingleton<IConfigureOptions<ClickyConfig>>(new ClickyConfigureNamedOptions(string.Empty, config));
})
.UseConsoleLifetime()
.Build()
.RunAsync()
;
ClickyConfigureNamedOptions
using System.Drawing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace Kx.Toolx.Clicky;
internal class ClickyConfigureNamedOptions : ConfigureNamedOptions<ClickyConfig>
{
public ClickyConfigureNamedOptions(string? name, IConfiguration configuration)
: base(name, cc => Bind(cc, configuration))
{
}
private static void Bind(ClickyConfig config, IConfiguration configuration)
{
if (config == null)
{
return;
}
if (configuration == null)
{
return;
}
// 填充config的具体字段
config.Hwnd = configuration[nameof(config.Hwnd)] ?? string.Empty;
config.TimerPeriod = double.TryParse(configuration[nameof(config.TimerPeriod)], out var tp) ? tp : 5;
config.Points = configuration.GetSection(nameof(config.Points))
.GetChildren()
.Select(p => new Point((int.TryParse(p["X"], out var x) ? x : 0), (int.TryParse(p["Y"], out var y) ? y : 0)))
.ToList()
;
}
}
相当于手动解析了一下配置,目前项目配置不复杂还可以接受,复杂的话就够呛。
不过我相信官方不久就应该要解决这个问题了,毕竟选型模式挺好用且挺好用的。躺着等就好。