-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avalonia and the generic host (Microsoft.Extensions) #5241
Comments
WinForms/WPF don't require to be initialized, they are available and ready to use even before generic host is configured. Avalonia requires a specific initialization procedure and specific platform-dependent startup and lifetime management (i. e. we don't control application lifetime when running on iOS or Android). You can investigate the possibilities of running on generic host, but that will require re-implementing AppBuilder on top of it. |
I have a worker service (using the generic host) that I wanted to add a UI for.
When bringing in Avalonia, I have ended up with code like the following in my main entry point. I have only tested this when running as a console app so far, so I am hoping it will work when running under systemd or windows service but that remains to be seen: private static Task _backgroundHostTask;
private static Task<int> _uiTask;
public static async Task<int> Main(string[] args)
{
_backgroundHostTask = CreateHostBuilder(args).Build().RunAsync();
_uiTask = Task.Run(() =>
{
var app = BuildAvaloniaApp();
return app.StartWithClassicDesktopLifetime(args, ShutdownMode.OnExplicitShutdown);
});
await _backgroundHostTask;
var lifetime = Application.Current.ApplicationLifetime as IControlledApplicationLifetime;
lifetime?.Shutdown(0);
var result = await _uiTask;
return result;
} I think there is an opportunity here for some better integration with the generic host, when you want to show a UI that is directly tied to the lifetime of the host. The convention of the generic host is that:-
Typically a public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// for DI etc.
}
public void Configure(IAppBuilder app)
{
// for configuring middleware if hosting web services etc.
}
public void ConfigureUI(AppBuilder avaloniaAppBuilder)
{
// avalonia stuff here?
}
} I think something like this would feel more natural to existing .net core projects wishing to add a UI e.g:
Something kind of extension methods like the ones demonstrated above would enable:
Forgive this pretty high level speculation around what the api's might look like, I don't have any concrete ideas but feel there is room for some possible enhancements in this area. |
@dazinator in https://github.com/dapplo/Dapplo.Microsoft.Extensions.Hosting I already made such UI workloads for WPF and Windows Forms possible on the generic host. I'm not so well acquainted with Avalonia yet, and have a big backlog for releasing the next Greenshot. I would love to have a sample project with Avalonia, maybe you like to fit it in with a PR? Have a look at https://github.com/dapplo/Dapplo.Microsoft.Extensions.Hosting/tree/master/src/Dapplo.Microsoft.Extensions.Hosting.Wpf how I made it possible for Wpf, the sample using this is here: https://github.com/dapplo/Dapplo.Microsoft.Extensions.Hosting/blob/master/samples/Dapplo.Hosting.Sample.WpfDemo/Program.cs#L60 |
Hi there, sorry for getting back to after such a long time. I actually stumbled across this issue when I tried to combine an GRPC Server with Avalonia. My solution consists of some simple extension methods: [SupportedOSPlatform("windows")]
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
public static IHostBuilder ConfigureAvaloniaAppBuilder<TApplication>(this IHostBuilder hostBuilder, Func<AppBuilder> appBuilderResolver, Action<AppBuilder> configureAppBuilder, IHostedLifetime? lifetime = null) where TApplication: Avalonia.Application
{
ArgumentNullException.ThrowIfNull(configureAppBuilder);
hostBuilder.ConfigureServices((ctx, s) => {
AppBuilder appBuilder = appBuilderResolver();
configureAppBuilder(appBuilder);
s.AddSingleton(appBuilder);
if (appBuilder.Instance is null)
{
appBuilder.SetupWithoutStarting();
}
s.AddSingleton<Avalonia.Application>((_) => appBuilder.Instance!);
s.AddSingleton<TApplication>((_) => (TApplication)appBuilder.Instance!);
s.AddSingleton<IHostedLifetime>(p => lifetime ?? HostedLifetime.Select(p.GetRequiredService<ILoggerFactory>(), p.GetRequiredService<Avalonia.Application>().ApplicationLifetime));
});
return hostBuilder;
} and [SupportedOSPlatform("windows")]
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
public async static Task<int> RunAvaloniaAppAsync(this IHost host, CancellationToken token = default)
{
IHostedLifetime lifetime = host.Services.GetRequiredService<IHostedLifetime>();
Avalonia.Application application = host.Services.GetRequiredService<Application>();
await host.StartAsync(token);
int result = await lifetime.StartAsync(application, token);
await host.StopAsync(token);
await host.WaitForShutdownAsync(token);
return result;
} whereas the Hence, my Program.Main.cs of the CommandSample now looks like this: [STAThread]
public static async Task<int> Main(string[] args)
{
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
{
return -1;
}
return await Host.CreateDefaultBuilder(args).ConfigureAvalonia<App>(
a =>
{
a.UsePlatformDetect().LogToTrace().UseReactiveUI();
a.SetupWithLifetime(
new ClassicDesktopStyleApplicationLifetime()
{
Args = args,
ShutdownMode = Avalonia.Controls.ShutdownMode.OnMainWindowClose,
}
);
}
)
.ConfigureServices(s => s.AttachLoggerToAvaloniaLogger())
.Build().RunAvaloniaAppAsync();
} I think this is a good starting point as it actually wraps Avalonia around the generic host builder. What do you think about this solution? Would you like to know more? Is this an acceptable way of running Avalonia with the generic host? Regards Carsten |
@carstencodes could you please also show the implementation of |
@ArcadeArchie Hi, Let me get back to you. I have talked to my manager about some points. We're currently in discussion. |
@carstencodes @ArcadeArchie for the most part you can integrate MsHosting in a way easier way: A couple of problems with other solutions:
Having these problems in mind, it's way easier to initialize Avalonia first, and then run Host Builder from it. Typically from OnFrameworkInitializationCompleted. |
I will try to add some sample description to make it a proper Avalonia.Samples project. But code itself is ready and can be used. |
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => HostBuilder.CreateDefaultBuilder(args).SetupAvaloniaApp<App>(BuildAvaloniaApp, a => a.UseClassicDesktopRuntime(args, Avalonia.Controls.ShutdownMode.OnMainWindowClose)).ConfigureServices(s => s.AttachLoggerToAvaloniaLogger())
.Build().RunAvaloniaApp();
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI(); At that point, there should not be a change to the behavior.
Yes, but if there are background services, it will be.
That's why I restricted the methods to desktop operating systems (see
I've experienced issues with this approach, but at that point I was not that experienced with Avalonia. But migrating this approach to Avalonia caused me much headaches. If the pitfalls are that high, how about adding a custom library helping developers to bypass these issues? Like |
Yep, there is nothing wrong with your approach since you know what actual target devices you need to run your app on. With little changes, it can work on macOS too, which would cover all desktop platforms. The problem is that it won't be really easily possible to have universal Avalonia.Hosting library that covers all supported platforms. Different lifetimes support, different Main method specifics (sync only on macOS, async only on Browser...). But if the community implements Avalonia.Hosting deeper integration, for desktop only or more, would be also useful. It can be also mentioned as an integrated alternative approach in Avalonia.Samples. |
@ArcadeArchie Sorry for coming back to you so late with this. I didn't forget about you (or this topic), but due to health conditions and many work, this moved a little to the bottom of the priority list. I've created a gist containing my implementation hoping you find it interesting and helpful. As @maxkatz6 stated, MacOS should use the sync revision, but I haven't tested it. @maxkatz6 Glad to hear, that there is nothing wrong with it. I am actually willing to do my part. So, if this is fine, maybe a first implementation only for desktop platforms can be set-up more easily from my implementation. I am actually struggling with the idea of starting the host within the XPF Application, as - to me - it doesn't really fit in there. The application should from my side be target and not source for DI, but that is a question of personal taste and opinions - as it is usually done in Software Development. |
An implementation of generic host for avaloniaui desktop app |
Is your feature request related to a problem? Please describe.
No, there is no problem.
Describe the solution you'd like
Make it possible to run Avalonia on the generic host
Describe alternatives you've considered
Not using the generic host.
Additional context
I've made a project hosting Windows Forms and WPF in the generic host, which you can find here: https://github.com/dapplo/Dapplo.Microsoft.Extensions.Hosting
I've added samples for CaliburnMicro and ReactiveUI, and wanted to add a sample for Avalonia to it, when I stumbled on #3538
@kekekeks can I find more details on why the generic host, Microsoft.Extensions, isn't suitable for Avalonia?
Is this an issue with the used IoC container, or the threading model, or what?
I'm not saying it's not possible, I just wouldn't like it if I ended up wrapping one "host" into another.
The text was updated successfully, but these errors were encountered: