question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Proposal: Remove Maui's Dependency on Extensions.Hosting

See original GitHub issue

Description

The default startup logic in a Maui app uses MauiAppBuilder to register DI services, including configuration sources and logging providers. This design follows a similar programming model in ASP.NET apps - both server side and Blazor WASM apps. The default “dotnet new maui” template generates MauiProgram.cs file that looks like:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        return builder.Build();
    }
}

This makes sense and is familiar to ASP.NET developers.

However, one public API hanging off of the MauiAppBuilder class is a public IHostBuilder Host property. This comes from the Microsoft.Extensions.Hosting.Abstractions library. Microsoft.Extensions.Hosting is an “app model” itself. It defines a certain order in which a “Hosted Application” is built. For example:

  1. There is a “Host Configuration” step, which can be used to configure the host itself. Or it can be used to read “phase-one” configuration that can be used to initialize more configuration in a later step - “App Configuration” (phase-two).
  2. Initialization of the HostEnvironment and HostBuilderContext.
  3. An “App Configuration” step which you can add your application configuration sources to.
  4. A step to configure any DI services in the Hosted Application.
  5. Finally, a way to replace which DI container implementation is used - by default it uses Microsoft.Extensions.DependencyInjection, but apps can opt into using Unity, Autofac, LightInject, etc.

A HostBuilder builds a “Host”, which can then be “Started” and “Stopped”. Starting and stopping a Host will start and stop any registered “Hosted Services”.

Digging into what capabilities the Microsoft.Extensions.Hosting provides, I do not believe it is a good fit for mobile / client applications. Even a default ASP.NET Blazor Web Assembly application doesn’t use Microsoft.Extensions.Hosting. I assume it is the for similar reasons I list below.

  1. Extensions.Hosting is really meant for long running processes (like a Windows service, web server, background process, etc). It has a “Lifetime” concept, but it is really simplistic - it has Started, Stopping, and Stopped. This can’t model the App lifecycle needed for a client application.

  2. Extensions.Hosting has an “Environment” concept, which are things like “Development”, “Staging”, and “Production”. And while this concept could map to client apps, the way these environments change in Hosted Apps is through Configuration - ex. environment variable, command line parameter, appsettings.json file, etc. My understanding is that a Mobile app doesn’t get a chance to change these kinds of configurations once it is built. How do you change a .json file in your already built .apk file?

  3. Extensions.Hosting has a “ContentRoot”, which for a web application points to the folder where it serves files from - like index.html, etc. Maui applications don’t have “Content” files that get served / shown by the application. Instead the Views are embedded in the app itself. So this concept is useless in a default Maui app.

  4. Extensions.Hosting’s main concept is its “Hosted Services” or BackgroundService. While this is definitely a useful concept in a client application, the abstraction isn’t correct for client apps. Hosted Services get “Start” and “Stop”. But what if the app is getting deactivated? Can the service tell the difference between that and the app being closed? Should all the Hosted Services get resumed when the app is resumed? These are all scenarios that would need to work and the Hosted Service abstraction isn’t designed for these scenarios. Maui apps that need background service functionality need to use something that is designed for client apps.

  5. We aren’t using the Host that is built today. Start isn’t even called on the object. And neither do we call Stop on it to shut it down properly if we did. Again this gets into the same lifetime questions as the Hosted Services. Should the Host be “Stopped” during deactivation? Extensions.Hosting isn’t designed to be re-startable. Once it is stopped, it disposes of its services and you need to start over with the Builder to create a new one.

  6. Lastly, Extensions.Hosting’s API IHostBuilder has a “deferred execution” model. When ConfigureAppConfiguration or ConfigureServices is called, the code passed in doesn’t run in-line. Instead the delegate passed is saved and only executed later during the Build() process. This isn’t the easiest model for developers to learn and reason about how their code executes. And you can see us going away from these kind of API models with “ASP.NET Minimal APIs” and Maui is following the same pattern. When MauiProgram.CreateMauiApp() is executed, the code is executed in a more traditional “top down” approach. When you add a Service to the builder, the service is added now. After that line executes, the service is in the collection. The same for adding Configuration. There isn’t this “two-phase configuration” step that we have in Extensions.Hosting. When you add a config source to the builder, you can immediately read config values from it. However, since MauiAppBuilder is trying to interact with the HostBuilder, it has a lot of workaround code to make sure this “deferred execution” model works correctly with the inline approach Maui. In case you are interested in the internals, check out here, here, and here.

With all this in mind, I propose we remove the dependency on Microsoft.Extensions.Hosting from Maui.

Public API Changes

namespace Microsoft.Maui.Hosting
{
	public sealed class MauiAppBuilder
	{
-		public IHostBuilder Host { get; }
	}

-	public sealed class MauiApp : IHost
+	public sealed class MauiApp
	{
-		public IHostEnvironment Environment { get; }
-		public IHostApplicationLifetime Lifetime { get; }

-		public Task StartAsync(CancellationToken cancellationToken = default)
-		public Task StopAsync(CancellationToken cancellationToken = default)
-               void IDisposable.Dispose()
	}
}

The one scenario that still needs to be supported is replacing the DI container implementation with another implementation (for example Autofac, Unity, etc.). In order to keep supporting this, I propose we add the same method that WebAssemblyHostBuilder uses to enable this scenario:

namespace Microsoft.Maui.Hosting
{
	public sealed class MauiAppBuilder
	{
+		public void ConfigureContainer<TBuilder>(IServiceProviderFactory<TBuilder> factory, Action<TBuilder>? configure = null) where TBuilder : notnull
	}
}

Intended Use-Case

As part of reducing startup cost and size of Maui applications, removing dependencies and code that aren’t necessary is an easy way to improve startup and size.

I prototyped removing Extensions.Hosting, and disabling Logging by default. These two combined changes resulted in a 13% reduction in time to start a dotnet new maui app on my Android emulator.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:9
  • Comments:12 (11 by maintainers)

github_iconTop GitHub Comments

5reactions
SteveSandersonMScommented, Jan 31, 2022

WASM apps don’t use Extensions.Hosting. I’m not the best to answer “why” they don’t, but I believe @SteveSandersonMS should be able to.

Two main reasons come to mind:

  • Blazor WebAssembly has always needed to provide the most lean and streamlined runtime environment that it can. If we didn’t strictly need to use a specific bit of infrastructure, we’d prefer not to.
  • Blazor WebAssembly runs in an environment that imposes different constraints than you’d get on server/desktop. For example, a server/desktop app can synchronously look at the filesystem and load a .json config file if it’s there. Whereas for Blazor WebAssembly any such I/O would become an async network call and potential delay the startup process, so it has its own patterns that combine or parallelize such steps.

More generally, the more high-level a feature area is, the less universal it can be. Hosting abstractions are pretty high-level (compared with, say, JSON serialization) and it’s not clear that it’s desirable or wise to insist on a single hosting abstraction for all possible app types and runtime environments. I can’t think of any cases where Blazor WebAssembly developers have expressed that it’s a problem that it doesn’t use Hosting.Extensions.

3reactions
Redthcommented, Jan 29, 2022
  1. Extensions.Hosting has an “Environment” concept, which are things like “Development”, “Staging”, and “Production”. And while this concept could map to client apps, the way these environments change in Hosted Apps is through Configuration - ex. environment variable, command line parameter, appsettings.json file, etc. My understanding is that a Mobile app doesn’t get a chance to change these kinds of configurations once it is built. How do you change a .json file in your already built .apk file?

While you’re right that the settings effectively happen at build time, I think there’s still value in providing some implementation of appsettings.json as it’s a familiar pattern and is useful for debug vs release environments. It’s sort of typical to use an [EmbeddedResource] of a json file for this purpose… I’ve done it and so has James we even have a community PR on this.

The problem is, this isn’t a super efficient thing to do, load an embedded resource, and parse the json at startup for values which are effectively static.

I keep thinking that some sort of source generator or build task could bridge the performance gap here, but that’s not necessarily a small amount of effort to accomplish. Is there something we could add in the short term that’s less efficient of an implementation up front, but can be replaced with something source generator or build task based without changing behaviour or API between RC and GA?

Alternatively it is something that can be deferred until .NET 7 and left out for now. Adding later is always easier than removing or fixing a half thought through implementation.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Is Maui dead on arrival? : r/dotnet
Uno platform seems to be a dependency hell for me. I never managed to export from Figma to their Xaml code so I...
Read more >
Announcing .NET MAUI Preview 13
NET MAUI developers. In this release, we've made another adjustment removing Microsoft.Extensions.Hosting in favor of a faster app startup ...
Read more >
Getting started with Microsoft .NET MAUI
The process for installing MAUI consists of some prerequisites being installed first in addition to some extensions and workloads.
Read more >
Beach Management Plan For Maui
With public beaches steadily disappearing and the continued reliance on private shoreline protection structures for erosion control, management authorities are ...
Read more >
Uninstall extension does not handle increased font size
When attempting to uninstall an extension pack that is a dependency of another extension, the dialog buttons are unreadable. It appears that this...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found