File: Hosting\MauiAppBuilder.cs
Web Access
Project: src\src\Core\src\Core.csproj (Microsoft.Maui)
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.LifecycleEvents;
 
namespace Microsoft.Maui.Hosting
{
	/// <summary>
	/// A builder for .NET MAUI cross-platform applications and services.
	/// </summary>
	public sealed class MauiAppBuilder
	{
		private readonly ServiceCollection _services = new();
		private Func<IServiceProvider>? _createServiceProvider;
		private readonly Lazy<ConfigurationManager> _configuration;
		private ILoggingBuilder? _logging;
 
		internal MauiAppBuilder(bool useDefaults)
		{
			// Lazy-load the ConfigurationManager, so it isn't created if it is never used.
			// Don't capture the 'this' variable in AddSingleton, so MauiAppBuilder can be GC'd.
			var configuration = new Lazy<ConfigurationManager>(() => new ConfigurationManager());
			Services.AddSingleton<IConfiguration>(sp => configuration.Value);
 
			_configuration = configuration;
 
			if (useDefaults)
			{
				// Register required services
				this.ConfigureMauiHandlers(configureDelegate: null);
 
				this.ConfigureFonts();
				this.ConfigureImageSources();
				this.ConfigureAnimations();
				this.ConfigureCrossPlatformLifecycleEvents();
				this.ConfigureWindowEvents();
				this.ConfigureDispatching();
 
				this.UseEssentials();
 
#if WINDOWS
				this.Services.TryAddEnumerable(ServiceDescriptor.Transient<IMauiInitializeService, MauiCoreInitializer>());
#endif
			}
		}
 
		class MauiCoreInitializer : IMauiInitializeService
		{
			public void Initialize(IServiceProvider services)
			{
#if WINDOWS
				// WORKAROUND: use the MAUI dispatcher instead of the OS dispatcher to
				// avoid crashing: https://github.com/microsoft/WindowsAppSDK/issues/2451
				var dispatcher = services.GetRequiredApplicationDispatcher();
				if (dispatcher.IsDispatchRequired)
					dispatcher.Dispatch(() => SetupResources());
				else
					SetupResources();
 
				static void SetupResources()
				{
					if (UI.Xaml.Application.Current?.Resources is not UI.Xaml.ResourceDictionary resources)
						return;
 
					// WinUI
					resources.AddLibraryResources<UI.Xaml.Controls.XamlControlsResources>();
 
					// Microsoft.Maui
					resources.AddLibraryResources("MicrosoftMauiCoreIncluded", "ms-appx:///Microsoft.Maui/Platform/Windows/Styles/Resources.xbf");
				}
#endif
			}
		}
 
		/// <summary>
		/// A collection of services for the application to compose. This is useful for adding user provided or framework provided services.
		/// </summary>
		public IServiceCollection Services => _services;
 
		/// <summary>
		/// A collection of configuration providers for the application to compose. This is useful for adding new configuration sources and providers.
		/// </summary>
		public ConfigurationManager Configuration => _configuration.Value;
 
		/// <summary>
		/// A collection of logging providers for the application to compose. This is useful for adding new logging providers.
		/// </summary>
		public ILoggingBuilder Logging
		{
			get
			{
				return _logging ??= InitializeLogging();
 
				ILoggingBuilder InitializeLogging()
				{
					// if someone accesses the Logging builder, ensure Logging has been initialized.
					Services.AddLogging();
					return new LoggingBuilder(Services);
				}
			}
		}
 
		/// <summary>
		/// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
		/// </summary>
		/// <param name="factory">The <see cref="IServiceProviderFactory{TBuilder}" />.</param>
		/// <param name="configure">
		/// A delegate used to configure the <typeparamref T="TBuilder" />. This can be used to configure services using
		/// APIS specific to the <see cref="IServiceProviderFactory{TBuilder}" /> implementation.
		/// </param>
		/// <typeparam name="TBuilder">The type of builder provided by the <see cref="IServiceProviderFactory{TBuilder}" />.</typeparam>
		/// <remarks>
		/// <para>
		/// <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> is called by <see cref="Build"/>
		/// and so the delegate provided by <paramref name="configure"/> will run after all other services have been registered.
		/// </para>
		/// <para>
		/// Multiple calls to <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> will replace
		/// the previously stored <paramref name="factory"/> and <paramref name="configure"/> delegate.
		/// </para>
		/// </remarks>
		public void ConfigureContainer<TBuilder>(IServiceProviderFactory<TBuilder> factory, Action<TBuilder>? configure = null) where TBuilder : notnull
		{
			if (factory == null)
			{
				throw new ArgumentNullException(nameof(factory));
			}
 
			_createServiceProvider = () =>
			{
				var container = factory.CreateBuilder(Services);
				configure?.Invoke(container);
				return factory.CreateServiceProvider(container);
			};
		}
 
		/// <summary>
		/// Builds the <see cref="MauiApp"/>.
		/// </summary>
		/// <returns>A configured <see cref="MauiApp"/>.</returns>
		public MauiApp Build()
		{
			ConfigureDefaultLogging();
 
			IServiceProvider serviceProvider = _createServiceProvider != null
				? _createServiceProvider()
				: _services.BuildServiceProvider();
 
			// Mark the service collection as read-only to prevent future modifications
			_services.MakeReadOnly();
 
			MauiApp builtApplication = new MauiApp(serviceProvider);
 
			// Initialize any singleton/app services, for example the OS hooks
			builtApplication.InitializeAppServices();
 
			return builtApplication;
		}
 
		private sealed class LoggingBuilder : ILoggingBuilder
		{
			public LoggingBuilder(IServiceCollection services)
			{
				Services = services;
			}
 
			public IServiceCollection Services { get; }
		}
 
		private void ConfigureDefaultLogging()
		{
			// By default, if no one else has configured logging, add a "no-op" LoggerFactory
			// and Logger services with no providers. This way when components try to get an
			// ILogger<> from the IServiceProvider, they don't get 'null'.
			Services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, NullLoggerFactory>());
			Services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(NullLogger<>)));
		}
 
		private sealed class NullLoggerFactory : ILoggerFactory
		{
			public void AddProvider(ILoggerProvider provider) { }
 
			public ILogger CreateLogger(string categoryName) => NullLogger.Instance;
 
			public void Dispose() { }
		}
 
		private sealed class NullLogger<T> : ILogger<T>, IDisposable
		{
			public IDisposable BeginScope<TState>(TState state) where TState : notnull => this;
 
			public void Dispose() { }
 
			public bool IsEnabled(LogLevel logLevel) => false;
 
			public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
			{
			}
		}
	}
}