[Spec] Microsoft.Extensions.Hosting and/or Microsoft.Extensions.DependencyInjection · Issue #24 · dotnet/maui (original) (raw)
Bake the features of Microsoft.Extensions.Hosting into .NET MAUI
https://montemagno.com/add-asp-net-cores-dependency-injection-into-xamarin-apps-with-hostbuilder/
Utilize the Generic Host structure that's setup with .netcore 3.0 to initialize .NET MAUI applications.
This will provide users with a very Microsoft experience and will bring a lot of our code in line with ASP.NET core
Deeply root IServiceProvider into .NET MAUI
Replace all instances of Activator.CreateInstance(Type) with IServiceProvider.Get()
For example if we change this out on ElementTemplate
LoadTemplate = () => Activator.CreateInstance(type); |
---|
Then any DataTemplate specified via type will take advantage of being created via constructor injection.
Examples
Host.CreateDefaultBuilder() .ConfigureHostConfiguration(c => { c.AddCommandLine(new string[] { $"ContentRoot={FileSystem.AppDataDirectory}" }); c.AddJsonFile(fullConfig); }) .ConfigureServices((c, x) => { nativeConfigureServices(c, x); ConfigureServices(c, x); }) .ConfigureLogging(l => l.AddConsole(o => { o.DisableColors = true; })) .Build();
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services) { if (ctx.HostingEnvironment.IsDevelopment()) { var world = ctx.Configuration["Hello"]; }
services.AddHttpClient();
services.AddTransient<IMainViewModel, MainViewModel>();
services.AddTransient<MainPage>();
services.AddSingleton<App>();
}
Shell Examples
Shell is already string based and just uses types to create everything so we can easily hook into DataTemplates and provide ServiceCollection extensions
static void ConfigureServices(HostBuilderContext ctx, IServiceCollection services) { services.RegisterRoute(typeof(MainPage)); services.RegisterRoute(typeof(SecondPage)); }
If all the DataTemplates are wired up through the IServiceProvider users could specify Interfaces on DataTemplates
<ShellContent ContentTemplate="{DataTemplate view:MainPage}"/ShellContent>
Baked in constructor injection
public class App { public App() { InitializeComponent(); MainPage = ServiceProvider.GetService(); } } public partial class MainPage : ContentPage { public MainPage(IMainViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; } }
public class MainViewModel { public MainViewModel(ILogger logger, IHttpClientFactory httpClientFactory) { var httpClient = httpClientFactory.CreateClient(); logger.LogCritical("Always be logging!"); Hello = "Hello from IoC"; } }
This will allow Shell to also have baked in Constructor Injection
Routing.RegisterRoute("MainPage", MainPage)
GotoAsync("MainPage") // this will use the ServiceProvider to create the type
All the ContentTemplates specified as part of Shell will be created via the IServiceProvider
<ShellContent
x:Name="login"
ContentTemplate="{DataTemplate MainPage}"
Route="login" />
Implementation Details to consider
Use Microsoft.Build to facilitate the startup pipeline
Pull in the host features to articulate a specific startup location where things are registered
https://montemagno.com/add-asp-net-cores-dependency-injection-into-xamarin-apps-with-hostbuilder/
This has the benefit of letting us tie into implementations of IoC containers that already work against asp.net core
Pros: This gives .NET developers a consistent experience.
Cons: Performance? Is this overkill for mobile?
DI Container options
Deprecate DependencyService in favor of Microsoft.Extensions.DependencyInjection
Xamarin.Forms currently has a very simple home grown dependency service that doesn't come with a lot of features. Growing the features of this service in the face of already available options doesn't make much sense. In order to align ourselves more appropriately with other Microsoft Platforms we can switch over to the container inside Microsoft.Extensions.DependencyInjection
.
Automatic registration of the DependencyService will be tied to the new registrar. If the user has opted in for the registrar to do assembly scanning than this will trigger the DependencyService to scan for assembly level attributes
One of the caveats of using this container is that types can't be registered on the fly once the app has started. You can only register types as part of the startup process and then once the IServicePRovider is constructed that's it. Registration is closed for business
Pros: It's a full featured container
Cons: Performance?
Convert our DependencyService to use IServiceCollection as an internal container and have it implement IServiceProvider
This would allow us to use a very slimmed down no featured container if people just want the best performance. We could probably use this as a default and then people could opt in for the more featured one if they want.
public class DependencyService : IServiceProvider { }
public static ServiceCollectionExtensions { public static DependencyService Create(this IServiceCollection); }
Considerations
Is this overall useful for a new users app experience? Do we really want to add the overhead of understanding a the build host startup loop for new users? It would probably be useful to just have a default setup for all of this that just uses Init and then new users can just easily do what they need to without having to setup settings files/configureservices/etc..
Performance
In my tests limited tests it takes about 25 ms for the Microsoft Hosting bits to startup. We'll probably want to dive deeper into those 25 ms to see if we can get around it or if that cost is already part of a different startup cost we will already incur
Backward Compatibility
- The current DependencyService scans for assembly level attributes which we will most likely shift to being opt in for .NET MAUI. The default will require you to register things via the Service Collection explicitly
Difficulty : Medium/Large
Existing work:
xamarin/Xamarin.Forms#8220