AvaloniaUI 默认使用的IoC是reactiveui/splat
使用惯了 微软的那套, 就一定要用趁手的。
改造 Main:
[STAThread]
public static void Main(string[] args)
{
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
//.net8.0
var hb = Host.CreateEmptyApplicationBuilder(null);
//自定义的 LoggingProvider, Log4NET 不支持 AOT
hb.Logging.AddSimpleFile();
//加载各个模块输出的配置
var dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Cfgs");
if (Directory.Exists(dir))
{
var fs = Directory.GetFiles(dir, "*.json");
foreach (var f in fs)
hb.Configuration.AddJsonFile(f, true, true);
}
//用 SourceGenerator 生成的方法
Regist.RegistService(hb.Services);
Regist.RegistCfg(hb.Services, hb.Configuration);
///
hb.Services.UseMicrosoftDependencyResolver();
//Build 会报错, 不 Build 运行也没有问题。
//using var host = hb.Build();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
注意:
-
CreateEmptyApplicationBuilder
是 .NET8 里提供的 - Log4net 不支持 AOT, 运行会报错。
- 引用
Splat.Microsoft.Extensions.DependencyInjection
, 并调用hb.Services.UseMicrosoftDependencyResolver();
-
HostingApplicatonBuilder
不要执行Build
方法。 - 这里不要用 GetService(...) 去获取 注册为
Singleton
的实例。因为在这里 GetService 返回一个新实例, 在Avalonia 里获取又会返回另一个新实例。所以,如果有需要在程序启动的时候进行初始化的东西,最好是放到App.OnFrameworkInitializationCompleted
方法里去执行。这样可以保证 注册为Singleton
的服务只实例化一次。
App 改造
App.xaml 需要注释掉 ViewLocator
, 放到代码里实现, 因为 ViewLocator 也要使用 IoC
<!--<Application.DataTemplates>
<local:ViewLocator />
</Application.DataTemplates>-->
App.xaml.cs
public override void OnFrameworkInitializationCompleted()
{
this.DataTemplates.Add(Locator.Current.GetService<ViewLocator>()!);
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = Locator.Current.GetService<MainWindowViewModel>()
};
}
//初始化
var initializers = Locator.Current.GetServices<IInitializer>();
var tsks = new List<Task>(initializers.Count());
foreach (var initializer in initializers)
{
var tsk = Task.Run(async () =>
{
try
{
await initializer.Init();
}
catch (Exception e)
{
Common.FileLogger.Log(e, "initialize");
}
});
tsks.Add(tsk);
}
Task.WaitAll(tsks.ToArray());
base.OnFrameworkInitializationCompleted();
}
可以看到需要初始化的东西, 我放到这里了。
Locator.Current
这个有违设计理念的东西, 只出现在这一个地方就够了. 后面的都交给够造注入了。
ViewLocator
因为要启用 AOT,使用反射来创建 View 实例的方法行不通, 所以, 这个 ViewLocator 需要把 View 也注册到 IoC 里。
/// <summary>
///
/// </summary>
[Regist(RegistMode.Singleton)]
public class ViewLocator : IDataTemplate
{
private readonly IServiceProvider sp;
/// <summary>
///
/// </summary>
/// <param name="sp"></param>
public ViewLocator(IServiceProvider sp)
{
this.sp = sp;
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public Control Build(object? data)
{
if (data == null)
return new TextBlock { Text = $"ViewModel is null" };
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var view = (Control?)this.sp.GetKeyedService<IView>(name);
if (view == null)
{
return new TextBlock { Text = "Not Found: " + name };
}
else
return view;
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public bool Match(object? data)
{
return data is BaseVM;
}
}
为了方便,这里了 .NET8 里的命名服务
: KeyedService
. 之前我自己也实现过类似的命名服务注册.NET5 IoC 按名称注册,但是 .NET8 里现在自带了
//SourceGenerator 生成的
RegistService(sc, typeof(CNB.PubTools.Views.AboutView), "RegistViewAttribute", "CNB.PubTools.Views.AboutView");
。。。
private static void RegistService(IServiceCollection sc, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, string tag, string? name)
{
switch (tag)
{
。。。
case "RegistViewAttribute":
sc.TryAddKeyedTransient(typeof(IView), name, targetType);
break;
}
}
下一篇讲一下 SourceGenerator.
网友评论