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.