diff --git a/src/Compatibility/Core/src/Android/AppCompat/Platform.cs b/src/Compatibility/Core/src/Android/AppCompat/Platform.cs index 9526a854e..0883d9fc5 100644 --- a/src/Compatibility/Core/src/Android/AppCompat/Platform.cs +++ b/src/Compatibility/Core/src/Android/AppCompat/Platform.cs @@ -313,6 +313,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat try { handler = Forms.MauiContext.Handlers.GetHandler(element.GetType()); + handler.SetMauiContext(Forms.MauiContext); } catch { @@ -340,10 +341,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat else if (handler is IVisualElementRenderer ver) renderer = ver; else if (handler is IAndroidViewHandler vh) - { - vh.SetContext(context); renderer = new HandlerToRendererShim(vh); - } } renderer.SetElement(element); diff --git a/src/Compatibility/Core/src/AppHostBuilderExtensions.cs b/src/Compatibility/Core/src/AppHostBuilderExtensions.cs index 531d90188..2fb93aec7 100644 --- a/src/Compatibility/Core/src/AppHostBuilderExtensions.cs +++ b/src/Compatibility/Core/src/AppHostBuilderExtensions.cs @@ -12,15 +12,15 @@ namespace Microsoft.Maui.Controls.Compatibility var defaultHandlers = new List { typeof(Button), + typeof(ContentPage), typeof(Editor), typeof(Entry), - typeof(ContentPage), - typeof(Page), typeof(Label), + typeof(Page), + typeof(SearchBar), typeof(Slider), typeof(Stepper), typeof(Switch), - typeof(SearchBar) }; Forms.RegisterCompatRenderers( diff --git a/src/Compatibility/Core/src/iOS/Platform.cs b/src/Compatibility/Core/src/iOS/Platform.cs index ed59a0a6e..53ccb15a4 100644 --- a/src/Compatibility/Core/src/iOS/Platform.cs +++ b/src/Compatibility/Core/src/iOS/Platform.cs @@ -239,6 +239,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS try { handler = Forms.ActivationState.Context.Handlers.GetHandler(element.GetType()); + handler.SetMauiContext(Forms.ActivationState.Context); } catch { diff --git a/src/Controls/samples/Controls.Sample.Droid/MainApplication.cs b/src/Controls/samples/Controls.Sample.Droid/MainApplication.cs index b18942070..f00ed5d24 100644 --- a/src/Controls/samples/Controls.Sample.Droid/MainApplication.cs +++ b/src/Controls/samples/Controls.Sample.Droid/MainApplication.cs @@ -6,7 +6,7 @@ using Microsoft.Maui; namespace Maui.Controls.Sample.Droid { [Application] - public class MainApplication : MauiApplication + public class MainApplication : MauiApplication { public MainApplication(IntPtr handle, JniHandleOwnership ownerShip) : base(handle, ownerShip) { diff --git a/src/Controls/samples/Controls.Sample.MacCatalyst/AppDelegate.cs b/src/Controls/samples/Controls.Sample.MacCatalyst/AppDelegate.cs index 3bb178928..70e3b7ea9 100644 --- a/src/Controls/samples/Controls.Sample.MacCatalyst/AppDelegate.cs +++ b/src/Controls/samples/Controls.Sample.MacCatalyst/AppDelegate.cs @@ -11,7 +11,7 @@ using Maui.Controls.Sample; namespace Sample.MacCatalyst { [Register("AppDelegate")] - public class AppDelegate : MauiUIApplicationDelegate + public class AppDelegate : MauiUIApplicationDelegate { } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.SingleProject/Android/MainActivity.cs b/src/Controls/samples/Controls.Sample.SingleProject/Android/MainActivity.cs index b78b7af7d..60c8fa52c 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/Android/MainActivity.cs +++ b/src/Controls/samples/Controls.Sample.SingleProject/Android/MainActivity.cs @@ -6,6 +6,5 @@ namespace Maui.Controls.Sample.SingleProject [Activity(Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)] public class MainActivity : MauiAppCompatActivity { - } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.SingleProject/Android/MainApplication.cs b/src/Controls/samples/Controls.Sample.SingleProject/Android/MainApplication.cs index d929dd821..2e3b3e23c 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/Android/MainApplication.cs +++ b/src/Controls/samples/Controls.Sample.SingleProject/Android/MainApplication.cs @@ -6,9 +6,9 @@ using Microsoft.Maui; namespace Maui.Controls.Sample.SingleProject { [Application] - public class MainApplication : MauiApplication + public class MainApplication : MauiApplication { - public MainApplication(IntPtr handle, JniHandleOwnership ownerShip) : base(handle, ownerShip) + public MainApplication(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership) { } } diff --git a/src/Controls/samples/Controls.Sample.SingleProject/MacCatalyst/AppDelegate.cs b/src/Controls/samples/Controls.Sample.SingleProject/MacCatalyst/AppDelegate.cs index 531403ea4..96ec576cb 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/MacCatalyst/AppDelegate.cs +++ b/src/Controls/samples/Controls.Sample.SingleProject/MacCatalyst/AppDelegate.cs @@ -4,8 +4,7 @@ using Microsoft.Maui; namespace Maui.Controls.Sample.SingleProject { [Register("AppDelegate")] - public class AppDelegate : MauiUIApplicationDelegate + public class AppDelegate : MauiUIApplicationDelegate { - } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.SingleProject/MainPage.cs b/src/Controls/samples/Controls.Sample.SingleProject/MainPage.cs index a253dcbd1..80e31b9e7 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/MainPage.cs +++ b/src/Controls/samples/Controls.Sample.SingleProject/MainPage.cs @@ -16,4 +16,4 @@ namespace Maui.Controls.Sample.SingleProject public IView View { get => (IView)Content; set => Content = (View)value; } } -} +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.SingleProject/MainWindow.cs b/src/Controls/samples/Controls.Sample.SingleProject/MainWindow.cs index 16cd8e0b3..02d1d1074 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/MainWindow.cs +++ b/src/Controls/samples/Controls.Sample.SingleProject/MainWindow.cs @@ -1,16 +1,16 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Maui; +using Microsoft.Maui; namespace Maui.Controls.Sample.SingleProject { public class MainWindow : IWindow { - public IPage Page { get; set; } - public IMauiContext MauiContext { get; set; } - public MainWindow() { - Page = App.Current.Services.GetService(); + Page = new MainPage(); } + + public IPage Page { get; set; } + + public IMauiContext MauiContext { get; set; } } -} +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.SingleProject/MyApp.cs b/src/Controls/samples/Controls.Sample.SingleProject/MyApp.cs index b65fff38e..aeea4bf06 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/MyApp.cs +++ b/src/Controls/samples/Controls.Sample.SingleProject/MyApp.cs @@ -1,26 +1,17 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Maui; -using Microsoft.Maui.Hosting; +using Microsoft.Maui; namespace Maui.Controls.Sample.SingleProject { public class MyApp : MauiApp { - public override IAppHostBuilder CreateBuilder() => - base.CreateBuilder().ConfigureServices((ctx, services) => - { - services.AddTransient(); - services.AddTransient(); - }); - public override IWindow CreateWindow(IActivationState state) { -#if (ANDROID || IOS) - +#if ANDROID || IOS // This will probably go into a compatibility app or window Microsoft.Maui.Controls.Compatibility.Forms.Init(state); #endif - return Services.GetService(); + + return new MainWindow(); } } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.SingleProject/Startup.cs b/src/Controls/samples/Controls.Sample.SingleProject/Startup.cs new file mode 100644 index 000000000..435dea100 --- /dev/null +++ b/src/Controls/samples/Controls.Sample.SingleProject/Startup.cs @@ -0,0 +1,16 @@ +using Microsoft.Maui; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Hosting; + +namespace Maui.Controls.Sample.SingleProject +{ + public class Startup : IStartup + { + public void Configure(IAppHostBuilder appBuilder) + { + appBuilder + .RegisterCompatibilityRenderers() + .UseMauiApp(); + } + } +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.SingleProject/iOS/AppDelegate.cs b/src/Controls/samples/Controls.Sample.SingleProject/iOS/AppDelegate.cs index 531403ea4..96ec576cb 100644 --- a/src/Controls/samples/Controls.Sample.SingleProject/iOS/AppDelegate.cs +++ b/src/Controls/samples/Controls.Sample.SingleProject/iOS/AppDelegate.cs @@ -4,8 +4,7 @@ using Microsoft.Maui; namespace Maui.Controls.Sample.SingleProject { [Register("AppDelegate")] - public class AppDelegate : MauiUIApplicationDelegate + public class AppDelegate : MauiUIApplicationDelegate { - } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.iOS/AppDelegate.cs b/src/Controls/samples/Controls.Sample.iOS/AppDelegate.cs index c9cbbb72c..989b85de2 100644 --- a/src/Controls/samples/Controls.Sample.iOS/AppDelegate.cs +++ b/src/Controls/samples/Controls.Sample.iOS/AppDelegate.cs @@ -11,7 +11,7 @@ using Maui.Controls.Sample; namespace Sample.iOS { [Register("AppDelegate")] - public class AppDelegate : MauiUIApplicationDelegate + public class AppDelegate : MauiUIApplicationDelegate { } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/MainWindow.cs b/src/Controls/samples/Controls.Sample/MainWindow.cs index 65c748926..5059d7ab1 100644 --- a/src/Controls/samples/Controls.Sample/MainWindow.cs +++ b/src/Controls/samples/Controls.Sample/MainWindow.cs @@ -1,20 +1,13 @@ using Maui.Controls.Sample.Controls; -using Maui.Controls.Sample.Pages; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui; -using Microsoft.Maui.Controls; namespace Maui.Controls.Sample { public class MainWindow : Window { - public MainWindow() : this(App.Current.Services.GetRequiredService()) - { - } - public MainWindow(IPage page) { Page = page; } } -} +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/MyApp.cs b/src/Controls/samples/Controls.Sample/MyApp.cs index 940b7fbcc..743edb903 100644 --- a/src/Controls/samples/Controls.Sample/MyApp.cs +++ b/src/Controls/samples/Controls.Sample/MyApp.cs @@ -1,65 +1,22 @@ using System; -using System.Collections.Generic; -using Maui.Controls.Sample.Pages; using Maui.Controls.Sample.Services; -using Maui.Controls.Sample.ViewModel; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Maui; -using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Compatibility; -using Microsoft.Maui.Hosting; namespace Maui.Controls.Sample { public class MyApp : MauiApp { - public readonly static bool UseXamlPage = false; + public MyApp(ITextService textService) + { + Console.WriteLine($"The injected text service had a message: '{textService.GetText()}'"); + } - public override IAppHostBuilder CreateBuilder() => - base.CreateBuilder() - .RegisterCompatibilityRenderers() - .ConfigureAppConfiguration((hostingContext, config) => - { - config.AddInMemoryCollection(new Dictionary - { - { "MyKey", "Dictionary MyKey Value" }, - { ":Title", "Dictionary_Title" }, - { "Position:Name", "Dictionary_Name" }, - { "Logging:LogLevel:Default", "Warning" } - }); - }) - .ConfigureServices((hostingContext, services) => - { - services.AddSingleton(); - services.AddTransient(); - if (UseXamlPage) - services.AddTransient(); - else - services.AddTransient(); - services.AddTransient(); - }) - .ConfigureFonts((hostingContext, fonts) => - { - fonts.AddFont("dokdo_regular.ttf", "Dokdo"); - }); - - //IAppState state public override IWindow CreateWindow(IActivationState state) { Forms.Init(state); return Services.GetRequiredService(); } } - - //to use DI ServiceCollection and not the MAUI one - public class DIExtensionsServiceProviderFactory : IServiceProviderFactory - { - public ServiceCollection CreateBuilder(IServiceCollection services) - => new ServiceCollection { services }; - - public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder) - => containerBuilder.BuildServiceProvider(); - } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs index b26afbe88..0ab4afbfb 100644 --- a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs +++ b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Maui.Controls.Sample.ViewModel; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui; using Microsoft.Maui.Controls; @@ -11,14 +10,10 @@ namespace Maui.Controls.Sample.Pages { MainPageViewModel _viewModel; - public MainPage() : this(App.Current.Services.GetService()) - { - - } - public MainPage(MainPageViewModel viewModel) { BindingContext = _viewModel = viewModel; + SetupMauiLayout(); //SetupCompatibilityLayout(); } @@ -98,8 +93,7 @@ namespace Maui.Controls.Sample.Pages }; verticalStack.Add(entry); - - verticalStack.Add(new Entry { Text = "Entry", TextColor = Color.DarkRed }); + verticalStack.Add(new Entry { Text = "Entry", TextColor = Color.DarkRed, FontFamily = "Dokdo" }); verticalStack.Add(new Entry { IsPassword = true, TextColor = Color.Black }); verticalStack.Add(new Entry { IsTextPredictionEnabled = false }); verticalStack.Add(new Entry { Placeholder = "This should be placeholder text" }); @@ -152,7 +146,10 @@ namespace Maui.Controls.Sample.Pages verticalStack.Add(new Image() { Source = "dotnet_bot.png" }); - Content = verticalStack; + Content = new ScrollView + { + Content = verticalStack + }; } void SetupCompatibilityLayout() diff --git a/src/Controls/samples/Controls.Sample/Startup.cs b/src/Controls/samples/Controls.Sample/Startup.cs new file mode 100644 index 000000000..8322d51e2 --- /dev/null +++ b/src/Controls/samples/Controls.Sample/Startup.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using Maui.Controls.Sample.Pages; +using Maui.Controls.Sample.Services; +using Maui.Controls.Sample.ViewModel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Maui; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Hosting; + +namespace Maui.Controls.Sample +{ + public class Startup : IStartup + { + public readonly static bool UseXamlPage = false; + + public void Configure(IAppHostBuilder appBuilder) + { + appBuilder + .RegisterCompatibilityRenderers() + .UseMauiApp() + .ConfigureAppConfiguration((hostingContext, config) => + { + config.AddInMemoryCollection(new Dictionary + { + {"MyKey", "Dictionary MyKey Value"}, + {":Title", "Dictionary_Title"}, + {"Position:Name", "Dictionary_Name" }, + {"Logging:LogLevel:Default", "Warning"} + }); + }) + .UseMauiServiceProviderFactory(true) + //.UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) + .ConfigureServices((hostingContext, services) => + { + services.AddSingleton(); + services.AddTransient(); + + if (UseXamlPage) + services.AddTransient(); + else + services.AddTransient(); + + services.AddTransient(); + }) + .ConfigureFonts((hostingContext, fonts) => + { + fonts.AddFont("dokdo_regular.ttf", "Dokdo"); + }); + } + + // To use DI ServiceCollection and not the MAUI one + public class DIExtensionsServiceProviderFactory : IServiceProviderFactory + { + public ServiceCollection CreateBuilder(IServiceCollection services) + => new ServiceCollection { services }; + + public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder) + => containerBuilder.BuildServiceProvider(); + } + } +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample/ViewModel/MainPageViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModel/MainPageViewModel.cs index b61d68306..ce04425ff 100644 --- a/src/Controls/samples/Controls.Sample/ViewModel/MainPageViewModel.cs +++ b/src/Controls/samples/Controls.Sample/ViewModel/MainPageViewModel.cs @@ -1,44 +1,29 @@ -using System.Collections.Generic; -using System.Linq; +using System; using Maui.Controls.Sample.Services; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Maui; namespace Maui.Controls.Sample.ViewModel { public class MainPageViewModel : ViewModelBase { - private readonly IConfiguration Configuration; - ITextService textService; - - public MainPageViewModel() : this(new ITextService[] { App.Current.Services.GetService() }) - { - } - - public MainPageViewModel(IEnumerable textServices) - { - Configuration = App.Current.Services.GetService(); - - //var logger = App.Current.Services.GetService>(); - - //logger.LogInformation("hello"); - - textService = textServices.FirstOrDefault(); - Text = textService.GetText(); - } - - //public MainPageViewModel(ITextService textService) - //{ - // Text = textService.GetText(); - //} - + readonly IConfiguration _configuration; + readonly ITextService _textService; string _text; + + public MainPageViewModel(IConfiguration configuration, ITextService textService) + { + _configuration = configuration; + _textService = textService; + + Console.WriteLine($"Value from config: {_configuration["MyKey"]}"); + + Text = _textService.GetText(); + } + public string Text { get => _text; set => SetProperty(ref _text, value); } } -} +} \ No newline at end of file diff --git a/src/Controls/src/Core/HandlerImpl/SearchBar.Impl.cs b/src/Controls/src/Core/HandlerImpl/SearchBar.Impl.cs index 41d89509e..7ac8d035e 100644 --- a/src/Controls/src/Core/HandlerImpl/SearchBar.Impl.cs +++ b/src/Controls/src/Core/HandlerImpl/SearchBar.Impl.cs @@ -4,17 +4,6 @@ { Font? _font; - Font IText.Font - { - get - { - if (_font == null) - { - _font = Font.OfSize(FontFamily, FontSize).WithAttributes(FontAttributes); - } - - return _font.Value; - } - } + Font IText.Font => _font ??= Font.OfSize(FontFamily, FontSize).WithAttributes(FontAttributes); } } \ No newline at end of file diff --git a/src/Core/src/App.cs b/src/Core/src/App.cs index 273c5c23b..6c7f4e96b 100644 --- a/src/Core/src/App.cs +++ b/src/Core/src/App.cs @@ -1,50 +1,14 @@ using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Maui.Hosting; namespace Microsoft.Maui { public abstract class App : IApp { - IServiceProvider? _serviceProvider; - IMauiContext? _context; - - protected App() - { - if (Current != null) - throw new InvalidOperationException($"Only one {nameof(App)} instance is allowed"); - Current = this; - } - - public static App? Current { get; internal set; } - - public IServiceProvider? Services => _serviceProvider; - - public IMauiContext? Context => _context; - - //move to abstract - public virtual IAppHostBuilder CreateBuilder() => CreateDefaultBuilder(); + public IServiceProvider? Services { get; private set; } internal void SetServiceProvider(IServiceProvider provider) { - _serviceProvider = provider; - SetHandlerContext(provider.GetService()); - } - - internal void SetHandlerContext(IMauiContext? context) - { - _context = context; - } - - public static IAppHostBuilder CreateDefaultBuilder() - { - var builder = new AppHostBuilder(); - - builder.UseMauiHandlers(); - builder.UseFonts(); - - return builder; + Services = provider; } } -} +} \ No newline at end of file diff --git a/src/Core/src/Core/IStartup.cs b/src/Core/src/Core/IStartup.cs new file mode 100644 index 000000000..575f752bd --- /dev/null +++ b/src/Core/src/Core/IStartup.cs @@ -0,0 +1,29 @@ +using Microsoft.Maui.Hosting; + +namespace Microsoft.Maui +{ + /// + /// Startup allow to configure and instantiate application services. + /// + public interface IStartup + { + /// + /// Configures the application. + /// Configure are called by the .NET MAUI Core runtime when the app starts. + /// + /// Defines a class that provides the mechanisms to configure an application's dependencies. + void Configure(IAppHostBuilder appBuilder); + } + + /// + /// Allow to create a custom IAppHostBuilder instance. + /// + public interface IHostBuilderStartup + { + /// + /// Create and configure a builder object. + /// + /// The new instance of the IAppHostBuilder. + public IAppHostBuilder CreateHostBuilder(); + } +} \ No newline at end of file diff --git a/src/Core/src/Handlers/Button/ButtonHandler.Android.cs b/src/Core/src/Handlers/Button/ButtonHandler.Android.cs index b3242fc51..3d6b3d769 100644 --- a/src/Core/src/Handlers/Button/ButtonHandler.Android.cs +++ b/src/Core/src/Handlers/Button/ButtonHandler.Android.cs @@ -55,9 +55,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(ButtonHandler handler, IButton button) { - var services = App.Current?.Services - ?? throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.TypedNativeView?.UpdateFont(button, fontManager); } diff --git a/src/Core/src/Handlers/Button/ButtonHandler.cs b/src/Core/src/Handlers/Button/ButtonHandler.cs index 1ffafa85b..8c0cf2ab7 100644 --- a/src/Core/src/Handlers/Button/ButtonHandler.cs +++ b/src/Core/src/Handlers/Button/ButtonHandler.cs @@ -15,7 +15,7 @@ namespace Microsoft.Maui.Handlers } - public ButtonHandler(PropertyMapper mapper) : base(mapper ?? ButtonMapper) + public ButtonHandler(PropertyMapper? mapper = null) : base(mapper ?? ButtonMapper) { } } diff --git a/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs b/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs index 4137c8097..bc22f5bed 100644 --- a/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs +++ b/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs @@ -61,9 +61,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(ButtonHandler handler, IButton button) { - var services = App.Current?.Services ?? - throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.TypedNativeView?.UpdateFont(button, fontManager); } diff --git a/src/Core/src/Handlers/Editor/EditorHandler.cs b/src/Core/src/Handlers/Editor/EditorHandler.cs index ecb7d6991..a0dd68df6 100644 --- a/src/Core/src/Handlers/Editor/EditorHandler.cs +++ b/src/Core/src/Handlers/Editor/EditorHandler.cs @@ -14,7 +14,7 @@ } - public EditorHandler(PropertyMapper mapper) : base(mapper ?? EditorMapper) + public EditorHandler(PropertyMapper? mapper = null) : base(mapper ?? EditorMapper) { } diff --git a/src/Core/src/Handlers/Entry/EntryHandler.Android.cs b/src/Core/src/Handlers/Entry/EntryHandler.Android.cs index f1c84c4f4..f3048df44 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.Android.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.Android.cs @@ -67,9 +67,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(EntryHandler handler, IEntry entry) { - var services = App.Current?.Services - ?? throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.TypedNativeView?.UpdateFont(entry, fontManager); } diff --git a/src/Core/src/Handlers/Entry/EntryHandler.cs b/src/Core/src/Handlers/Entry/EntryHandler.cs index aa1e935c9..2a42051b9 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.cs @@ -20,7 +20,7 @@ } - public EntryHandler(PropertyMapper mapper) : base(mapper ?? EntryMapper) + public EntryHandler(PropertyMapper? mapper = null) : base(mapper ?? EntryMapper) { } diff --git a/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs b/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs index aaa9e4922..cbdc80235 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs @@ -103,9 +103,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(EntryHandler handler, IEntry entry) { - var services = App.Current?.Services - ?? throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.TypedNativeView?.UpdateFont(entry, fontManager); } diff --git a/src/Core/src/Handlers/IViewHandler.Android.cs b/src/Core/src/Handlers/IViewHandler.Android.cs index c3e53f6f0..ebd9cda8d 100644 --- a/src/Core/src/Handlers/IViewHandler.Android.cs +++ b/src/Core/src/Handlers/IViewHandler.Android.cs @@ -5,8 +5,6 @@ namespace Microsoft.Maui { public interface IAndroidViewHandler : IViewHandler { - void SetContext(Context context); - AView? View { get; } } } \ No newline at end of file diff --git a/src/Core/src/Handlers/IViewHandler.cs b/src/Core/src/Handlers/IViewHandler.cs index 3a6115f23..96b0dc207 100644 --- a/src/Core/src/Handlers/IViewHandler.cs +++ b/src/Core/src/Handlers/IViewHandler.cs @@ -1,9 +1,11 @@ +using System; using Microsoft.Maui; namespace Microsoft.Maui { public interface IViewHandler { + void SetMauiContext(IMauiContext mauiContext); void SetVirtualView(IView view); void UpdateValue(string property); void DisconnectHandler(); diff --git a/src/Core/src/Handlers/Label/LabelHandler.Android.cs b/src/Core/src/Handlers/Label/LabelHandler.Android.cs index 7a01a2132..0ff342b81 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.Android.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.Android.cs @@ -68,9 +68,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(LabelHandler handler, ILabel label) { - var services = App.Current?.Services - ?? throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.TypedNativeView?.UpdateFont(label, fontManager); } diff --git a/src/Core/src/Handlers/Label/LabelHandler.cs b/src/Core/src/Handlers/Label/LabelHandler.cs index fd79632c4..820346779 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.cs @@ -21,7 +21,7 @@ namespace Microsoft.Maui.Handlers } - public LabelHandler(PropertyMapper mapper) : base(mapper ?? LabelMapper) + public LabelHandler(PropertyMapper? mapper = null) : base(mapper ?? LabelMapper) { } diff --git a/src/Core/src/Handlers/Label/LabelHandler.iOS.cs b/src/Core/src/Handlers/Label/LabelHandler.iOS.cs index 1175168c5..fc8c9c3a9 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.iOS.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.iOS.cs @@ -53,9 +53,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(LabelHandler handler, ILabel label) { - var services = App.Current?.Services ?? - throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.TypedNativeView?.UpdateFont(label, fontManager); } diff --git a/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs b/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs index 33e1cbe51..8bd23d8f7 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs @@ -29,14 +29,14 @@ namespace Microsoft.Maui.Handlers _ = TypedNativeView ?? throw new InvalidOperationException($"{nameof(TypedNativeView)} should have been set by base class."); _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); - _ = MauiApp.Current?.Context ?? throw new InvalidOperationException($"The MauiApp.Current.Context can't be null."); + _ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); TypedNativeView.CrossPlatformMeasure = VirtualView.Measure; TypedNativeView.CrossPlatformArrange = VirtualView.Arrange; foreach (var child in VirtualView.Children) { - TypedNativeView.AddView(child.ToNative(MauiApp.Current.Context)); + TypedNativeView.AddView(child.ToNative(MauiContext)); } } @@ -44,9 +44,9 @@ namespace Microsoft.Maui.Handlers { _ = TypedNativeView ?? throw new InvalidOperationException($"{nameof(TypedNativeView)} should have been set by base class."); _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); - _ = MauiApp.Current?.Context ?? throw new InvalidOperationException($"The MauiApp.Current.Context can't be null."); + _ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); - TypedNativeView.AddView(child.ToNative(MauiApp.Current.Context!), 0); + TypedNativeView.AddView(child.ToNative(MauiContext), 0); } public void Remove(IView child) diff --git a/src/Core/src/Handlers/Layout/LayoutHandler.cs b/src/Core/src/Handlers/Layout/LayoutHandler.cs index 5e49fc1b1..d52cffa5e 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.cs @@ -15,7 +15,7 @@ namespace Microsoft.Maui.Handlers } - public LayoutHandler(PropertyMapper mapper) : base(mapper ?? LayoutMapper) + public LayoutHandler(PropertyMapper? mapper = null) : base(mapper ?? LayoutMapper) { } diff --git a/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs b/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs index 994db20b5..a242444c1 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs @@ -32,14 +32,14 @@ namespace Microsoft.Maui.Handlers _ = TypedNativeView ?? throw new InvalidOperationException($"{nameof(TypedNativeView)} should have been set by base class."); _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); - _ = MauiApp.Current?.Context ?? throw new InvalidOperationException($"The MauiApp.Current.Context can't be null."); + _ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); TypedNativeView.CrossPlatformMeasure = VirtualView.Measure; TypedNativeView.CrossPlatformArrange = VirtualView.Arrange; foreach (var child in VirtualView.Children) { - TypedNativeView.AddSubview(child.ToNative(MauiApp.Current.Context)); + TypedNativeView.AddSubview(child.ToNative(MauiContext)); } } @@ -47,9 +47,9 @@ namespace Microsoft.Maui.Handlers { _ = TypedNativeView ?? throw new InvalidOperationException($"{nameof(TypedNativeView)} should have been set by base class."); _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); - _ = MauiApp.Current?.Context ?? throw new InvalidOperationException($"The MauiApp.Current.Context can't be null."); + _ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); - TypedNativeView.AddSubview(child.ToNative(MauiApp.Current.Context)); + TypedNativeView.AddSubview(child.ToNative(MauiContext)); TypedNativeView.SetNeedsLayout(); } diff --git a/src/Core/src/Handlers/ProgressBar/ProgressBarHandler.cs b/src/Core/src/Handlers/ProgressBar/ProgressBarHandler.cs index 9a52c7d6a..6b9c36390 100644 --- a/src/Core/src/Handlers/ProgressBar/ProgressBarHandler.cs +++ b/src/Core/src/Handlers/ProgressBar/ProgressBarHandler.cs @@ -17,7 +17,7 @@ } - public ProgressBarHandler(PropertyMapper mapper) : base(mapper ?? ProgressMapper) + public ProgressBarHandler(PropertyMapper? mapper = null) : base(mapper ?? ProgressMapper) { } diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs index da507a547..98f49729a 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs @@ -32,9 +32,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(SearchBarHandler handler, ISearchBar searchBar) { - var services = App.Current?.Services - ?? throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.TypedNativeView?.UpdateFont(searchBar, fontManager, handler._editText); } diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs index ee7ff98a4..d56db9e51 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs @@ -16,7 +16,7 @@ } - public SearchBarHandler(PropertyMapper mapper) : base(mapper ?? SearchBarMapper) + public SearchBarHandler(PropertyMapper? mapper = null) : base(mapper ?? SearchBarMapper) { } diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs index b16401180..cfc88bfc9 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs @@ -31,9 +31,9 @@ namespace Microsoft.Maui.Handlers public static void MapFont(SearchBarHandler handler, ISearchBar searchBar) { - var services = App.Current?.Services ?? - throw new InvalidOperationException($"Unable to find service provider, the App.Current.Services was null."); - var fontManager = services.GetRequiredService(); + _ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class."); + + var fontManager = handler.Services.GetRequiredService(); handler.QueryEditor?.UpdateFont(searchBar, fontManager); } diff --git a/src/Core/src/Handlers/Slider/SliderHandler.cs b/src/Core/src/Handlers/Slider/SliderHandler.cs index cdfd63db5..b535a9e26 100644 --- a/src/Core/src/Handlers/Slider/SliderHandler.cs +++ b/src/Core/src/Handlers/Slider/SliderHandler.cs @@ -17,7 +17,7 @@ namespace Microsoft.Maui.Handlers } - public SliderHandler(PropertyMapper mapper) : base(mapper ?? SliderMapper) + public SliderHandler(PropertyMapper? mapper = null) : base(mapper ?? SliderMapper) { } diff --git a/src/Core/src/Handlers/Switch/SwitchHandler.cs b/src/Core/src/Handlers/Switch/SwitchHandler.cs index f87cd4b12..3c4947b6c 100644 --- a/src/Core/src/Handlers/Switch/SwitchHandler.cs +++ b/src/Core/src/Handlers/Switch/SwitchHandler.cs @@ -14,7 +14,7 @@ namespace Microsoft.Maui.Handlers } - public SwitchHandler(PropertyMapper mapper) : base(mapper ?? SwitchMapper) + public SwitchHandler(PropertyMapper? mapper = null) : base(mapper ?? SwitchMapper) { } diff --git a/src/Core/src/Handlers/View/AbstractViewHandler.Android.cs b/src/Core/src/Handlers/View/AbstractViewHandler.Android.cs index 2f9069c36..19921a474 100644 --- a/src/Core/src/Handlers/View/AbstractViewHandler.Android.cs +++ b/src/Core/src/Handlers/View/AbstractViewHandler.Android.cs @@ -6,9 +6,7 @@ namespace Microsoft.Maui.Handlers { public partial class AbstractViewHandler : IAndroidViewHandler { - public void SetContext(Context context) => Context = context; - - public Context? Context { get; private set; } + public Context? Context => MauiContext?.Context; public void SetFrame(Rectangle frame) { diff --git a/src/Core/src/Handlers/View/AbstractViewHandler.cs b/src/Core/src/Handlers/View/AbstractViewHandler.cs index 56fab3217..9d2f9fb03 100644 --- a/src/Core/src/Handlers/View/AbstractViewHandler.cs +++ b/src/Core/src/Handlers/View/AbstractViewHandler.cs @@ -41,6 +41,12 @@ namespace Microsoft.Maui.Handlers public object? NativeView => TypedNativeView; + public IServiceProvider? Services => MauiContext?.Services; + + public IMauiContext? MauiContext { get; private set; } + + public void SetMauiContext(IMauiContext mauiContext) => MauiContext = mauiContext; + public virtual void SetVirtualView(IView view) { _ = view ?? throw new ArgumentNullException(nameof(view)); diff --git a/src/Core/src/Hosting/AppHostBuilder.cs b/src/Core/src/Hosting/AppHostBuilder.cs index 56e1ddee7..233d3d0d8 100644 --- a/src/Core/src/Hosting/AppHostBuilder.cs +++ b/src/Core/src/Hosting/AppHostBuilder.cs @@ -18,7 +18,9 @@ namespace Microsoft.Maui.Hosting readonly List> _configureFontsActions = new List>(); readonly List _configureContainerActions = new List(); readonly Func _serviceColectionFactory = new Func(() => new MauiServiceCollection()); - IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter(new MauiServiceProviderFactory()); + + IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter(new MauiServiceProviderFactory(false)); + bool _hostBuilt; HostBuilderContext? _hostBuilderContext; IHostEnvironment? _hostEnvironment; @@ -26,7 +28,6 @@ namespace Microsoft.Maui.Hosting IServiceCollection? _services; IConfiguration? _hostConfiguration; IConfiguration? _appConfiguration; - App? _app; public AppHostBuilder() { @@ -34,13 +35,17 @@ namespace Microsoft.Maui.Hosting } public IDictionary Properties => new Dictionary(); - public IHost Build(IApp app) + public static IAppHostBuilder CreateDefaultAppBuilder() { - _app = app as App; - return Build(); + var builder = new AppHostBuilder(); + + builder.UseMauiHandlers(); + builder.UseFonts(); + + return builder; } - public IHost Build() + public IAppHost Build() { _services = _serviceColectionFactory(); @@ -66,10 +71,6 @@ namespace Microsoft.Maui.Hosting BuildFontRegistrar(_serviceProvider); - //we do this here because we can't inject the provider on the App ctor - //before we register the user ConfigureServices should this live in IApp ? - _app?.SetServiceProvider(_serviceProvider); - return new AppHost(_serviceProvider, null); } @@ -125,7 +126,6 @@ namespace Microsoft.Maui.Hosting } #pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint - void BuildHostConfiguration() { var configBuilder = new ConfigurationBuilder(); @@ -275,5 +275,10 @@ namespace Microsoft.Maui.Hosting { return ConfigureContainer(configureDelegate); } + + IHost IHostBuilder.Build() + { + return Build(); + } } -} +} \ No newline at end of file diff --git a/src/Core/src/Hosting/AppHostBuilderExtensions.cs b/src/Core/src/Hosting/AppHostBuilderExtensions.cs index 4edb18ea1..26965ea1c 100644 --- a/src/Core/src/Hosting/AppHostBuilderExtensions.cs +++ b/src/Core/src/Hosting/AppHostBuilderExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui.Handlers; +using Microsoft.Maui.Hosting.Internal; namespace Microsoft.Maui.Hosting { @@ -41,15 +42,15 @@ namespace Microsoft.Maui.Hosting { typeof(ICheckBox), typeof(CheckBoxHandler) }, { typeof(IEditor), typeof(EditorHandler) }, { typeof(IEntry), typeof(EntryHandler) }, - { typeof(ILayout), typeof(LayoutHandler) }, { typeof(ILabel), typeof(LabelHandler) }, + { typeof(ILayout), typeof(LayoutHandler) }, { typeof(IPicker), typeof(PickerHandler) }, { typeof(IProgress), typeof(ProgressBarHandler) }, { typeof(ISearchBar), typeof(SearchBarHandler) }, { typeof(ISlider), typeof(SliderHandler) }, { typeof(IStepper), typeof(StepperHandler) }, { typeof(ISwitch), typeof(SwitchHandler) }, - { typeof(ITimePicker), typeof(TimePickerHandler) } + { typeof(ITimePicker), typeof(TimePickerHandler) }, }); return builder; @@ -65,5 +66,31 @@ namespace Microsoft.Maui.Hosting }); return builder; } + + public static IAppHostBuilder UseMauiApp(this IAppHostBuilder builder) + where TApp : MauiApp + { + builder.ConfigureServices((context, collection) => + { + collection.AddSingleton(); + }); + return builder; + } + + public static IAppHostBuilder UseMauiApp(this IAppHostBuilder builder, Func implementationFactory) + where TApp : MauiApp + { + builder.ConfigureServices((context, collection) => + { + collection.AddSingleton(implementationFactory); + }); + return builder; + } + + public static IAppHostBuilder UseMauiServiceProviderFactory(this IAppHostBuilder builder, bool constructorInjection) + { + builder.UseServiceProviderFactory(new MauiServiceProviderFactory(constructorInjection)); + return builder; + } } -} +} \ No newline at end of file diff --git a/src/Core/src/Hosting/AppHostExtensions.cs b/src/Core/src/Hosting/AppHostExtensions.cs new file mode 100644 index 000000000..f90226508 --- /dev/null +++ b/src/Core/src/Hosting/AppHostExtensions.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Maui.Hosting +{ + public static class AppHostExtensions + { + public static void SetServiceProvider(this IAppHost host, App app) + { + //we do this here because we can't inject the provider on the App ctor + //before we register the user ConfigureServices should this live in IApp ? + app?.SetServiceProvider(host.Services); + } + } +} \ No newline at end of file diff --git a/src/Core/src/Hosting/IAppHost.cs b/src/Core/src/Hosting/IAppHost.cs new file mode 100644 index 000000000..98f81b4f1 --- /dev/null +++ b/src/Core/src/Hosting/IAppHost.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Hosting; + +namespace Microsoft.Maui.Hosting +{ + public interface IAppHost : IHost + { + IMauiHandlersServiceProvider Handlers { get; } + } +} diff --git a/src/Core/src/Hosting/IAppHostBuilder.cs b/src/Core/src/Hosting/IAppHostBuilder.cs index 872b3367a..f7fea0859 100644 --- a/src/Core/src/Hosting/IAppHostBuilder.cs +++ b/src/Core/src/Hosting/IAppHostBuilder.cs @@ -17,6 +17,6 @@ namespace Microsoft.Maui.Hosting new IAppHostBuilder UseServiceProviderFactory(IServiceProviderFactory factory); new IAppHostBuilder UseServiceProviderFactory(Func> factory); #pragma warning restore CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. - IHost Build(IApp app); + new IAppHost Build(); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hosting/Internal/AppHost.cs b/src/Core/src/Hosting/Internal/AppHost.cs index 8005b0196..8d43f328c 100644 --- a/src/Core/src/Hosting/Internal/AppHost.cs +++ b/src/Core/src/Hosting/Internal/AppHost.cs @@ -1,23 +1,27 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.Maui.Hosting.Internal { - internal class AppHost : IHost, IAsyncDisposable + internal class AppHost : IAppHost, IAsyncDisposable { readonly ILogger? _logger; public AppHost(IServiceProvider services, ILogger? logger) { Services = services ?? throw new ArgumentNullException(nameof(services)); + Handlers = Services.GetRequiredService(); + _logger = logger; } public IServiceProvider Services { get; } + public IMauiHandlersServiceProvider Handlers { get; } + public async Task StartAsync(CancellationToken cancellationToken = default) { _logger?.Starting(); @@ -51,4 +55,4 @@ namespace Microsoft.Maui.Hosting.Internal } } } -} +} \ No newline at end of file diff --git a/src/Core/src/Hosting/Internal/MauiServiceProviderFactory.cs b/src/Core/src/Hosting/Internal/MauiServiceProviderFactory.cs index aec7a6022..19fd5174f 100644 --- a/src/Core/src/Hosting/Internal/MauiServiceProviderFactory.cs +++ b/src/Core/src/Hosting/Internal/MauiServiceProviderFactory.cs @@ -5,6 +5,13 @@ namespace Microsoft.Maui.Hosting.Internal { internal class MauiServiceProviderFactory : IServiceProviderFactory { + readonly bool _constructorInjection; + + public MauiServiceProviderFactory(bool constructorInjection) + { + _constructorInjection = constructorInjection; + } + public IServiceCollection CreateBuilder(IServiceCollection services) { if (services is IMauiServiceCollection mauiServiceCollection) @@ -21,10 +28,9 @@ namespace Microsoft.Maui.Hosting.Internal public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) { if (containerBuilder is IMauiServiceCollection mauiServiceCollection) - return mauiServiceCollection.BuildServiceProvider(); + return mauiServiceCollection.BuildServiceProvider(_constructorInjection); else throw new InvalidCastException($"{nameof(containerBuilder)} is not {nameof(IMauiServiceCollection)}"); - } } -} +} \ No newline at end of file diff --git a/src/Core/src/Hosting/MauiHandlersServiceProvider.cs b/src/Core/src/Hosting/MauiHandlersServiceProvider.cs index a4cfc7c9d..fcec77cbf 100644 --- a/src/Core/src/Hosting/MauiHandlersServiceProvider.cs +++ b/src/Core/src/Hosting/MauiHandlersServiceProvider.cs @@ -4,7 +4,8 @@ namespace Microsoft.Maui.Hosting { class MauiHandlersServiceProvider : MauiServiceProvider, IMauiHandlersServiceProvider { - public MauiHandlersServiceProvider(IMauiServiceCollection collection) : base(collection) + public MauiHandlersServiceProvider(IMauiServiceCollection collection) + : base(collection, false) { } @@ -14,4 +15,4 @@ namespace Microsoft.Maui.Hosting public IViewHandler? GetHandler() where T : IView => GetHandler(typeof(T)); } -} +} \ No newline at end of file diff --git a/src/Core/src/Hosting/MauiServiceProvider.cs b/src/Core/src/Hosting/MauiServiceProvider.cs index 5992cc28d..8e1f1e45c 100644 --- a/src/Core/src/Hosting/MauiServiceProvider.cs +++ b/src/Core/src/Hosting/MauiServiceProvider.cs @@ -1,20 +1,23 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Maui.Hosting { class MauiServiceProvider : IMauiServiceProvider { - IMauiServiceCollection _collection; + readonly IMauiServiceCollection _collection; + readonly bool _constructorInjection; // TODO: do this properly and support scopes - IDictionary _singletons; + readonly IDictionary _singletons; - public MauiServiceProvider(IMauiServiceCollection collection) + public MauiServiceProvider(IMauiServiceCollection collection, bool constructorInjection) { _collection = collection ?? throw new ArgumentNullException(nameof(collection)); + _constructorInjection = constructorInjection; _singletons = new ConcurrentDictionary(); } @@ -66,7 +69,12 @@ namespace Microsoft.Maui.Hosting object? CreateInstance(ServiceDescriptor item) { if (item.ImplementationType != null) - return Activator.CreateInstance(item.ImplementationType); + { + if (_constructorInjection) + return CreateInstance(item.ImplementationType); + else + return Activator.CreateInstance(item.ImplementationType); + } if (item.ImplementationInstance != null) return item.ImplementationInstance; @@ -76,5 +84,47 @@ namespace Microsoft.Maui.Hosting throw new InvalidOperationException($"You need to provide an {nameof(item.ImplementationType)}, an {nameof(item.ImplementationFactory)} or an {nameof(item.ImplementationInstance)}."); } + + object? CreateInstance(Type implementationType) + { + (ConstructorInfo Constructor, ParameterInfo[] Parameters) match = default; + var constructors = implementationType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + for (var i = 0; i < constructors.Length; i++) + { + var ctor = constructors[i]; + if (!ctor.IsFamily && !ctor.IsPrivate) + { + var parameters = ctor.GetParameters(); + if (match.Parameters == null || parameters.Length > match.Parameters.Length) + match = (ctor, parameters); + } + } + + if (match.Constructor == null) + throw new InvalidOperationException($"The type '{implementationType.Name}' did not have any public or internal constructors."); + + var paramCount = match.Parameters!.Length; + + if (paramCount == 0) + return match.Constructor.Invoke(null); + + var paramValues = new object?[paramCount]; + + for (var i = 0; i < paramCount; i++) + { + var param = match.Parameters[i]; + var value = GetService(param.ParameterType); + if (value == null) + { + if (!param.HasDefaultValue) + throw new InvalidOperationException($"No service for type '{param.ParameterType}' has been registered."); + else + value = param.DefaultValue; + } + paramValues[i] = value; + } + + return match.Constructor.Invoke(paramValues); + } } } \ No newline at end of file diff --git a/src/Core/src/Hosting/ServiceProviderExtensions.cs b/src/Core/src/Hosting/ServiceProviderExtensions.cs index 2ab95f887..35f50fe00 100644 --- a/src/Core/src/Hosting/ServiceProviderExtensions.cs +++ b/src/Core/src/Hosting/ServiceProviderExtensions.cs @@ -4,8 +4,8 @@ namespace Microsoft.Maui.Hosting { public static class ServiceProviderExtensions { - internal static IServiceProvider BuildServiceProvider(this IMauiServiceCollection serviceCollection) - => new MauiServiceProvider(serviceCollection); + internal static IServiceProvider BuildServiceProvider(this IMauiServiceCollection serviceCollection, bool constructorInjection) + => new MauiServiceProvider(serviceCollection, constructorInjection); internal static IMauiHandlersServiceProvider BuildHandlersServiceProvider(this IMauiServiceCollection serviceCollection) => new MauiHandlersServiceProvider(serviceCollection); diff --git a/src/Core/src/MauiApp.cs b/src/Core/src/MauiApp.cs index 55db69d68..46917604a 100644 --- a/src/Core/src/MauiApp.cs +++ b/src/Core/src/MauiApp.cs @@ -1,15 +1,19 @@ using System; -using System.Collections.Generic; namespace Microsoft.Maui { public abstract class MauiApp : App { - public abstract IWindow CreateWindow(IActivationState state); - - public MauiApp() + protected MauiApp() { + if (Current != null) + throw new InvalidOperationException($"Only one {nameof(App)} instance is allowed."); + Current = this; } + + public static MauiApp? Current { get; internal set; } + + public abstract IWindow CreateWindow(IActivationState state); } -} +} \ No newline at end of file diff --git a/src/Core/src/Platform/Android/HandlerExtensions.cs b/src/Core/src/Platform/Android/HandlerExtensions.cs index 69dc63b79..712f4d108 100644 --- a/src/Core/src/Platform/Android/HandlerExtensions.cs +++ b/src/Core/src/Platform/Android/HandlerExtensions.cs @@ -21,8 +21,7 @@ namespace Microsoft.Maui if (handler == null) throw new Exception($"Handler not found for view {view}"); - if (handler is IAndroidViewHandler ahandler) - ahandler.SetContext(context.Context); + handler.SetMauiContext(context); view.Handler = handler; } diff --git a/src/Core/src/Platform/Android/MauiAppCompatActivity.cs b/src/Core/src/Platform/Android/MauiAppCompatActivity.cs index 3d8e04832..620fda8ae 100644 --- a/src/Core/src/Platform/Android/MauiAppCompatActivity.cs +++ b/src/Core/src/Platform/Android/MauiAppCompatActivity.cs @@ -21,10 +21,10 @@ namespace Microsoft.Maui { base.OnCreate(savedInstanceState); - if (App.Current as MauiApp == null) + if (MauiApp.Current == null) throw new InvalidOperationException($"App is not {nameof(MauiApp)}"); - var mauiApp = (MauiApp)App.Current; + var mauiApp = MauiApp.Current; if (mauiApp.Services == null) throw new InvalidOperationException("App was not initialized"); @@ -35,9 +35,6 @@ namespace Microsoft.Maui window.MauiContext = mauiContext; - //Hack for now we set this on the App Static but this should be on IFrameworkElement - App.Current.SetHandlerContext(window.MauiContext); - var content = (window.Page as IView) ?? window.Page.View; diff --git a/src/Core/src/Platform/Android/MauiApplication.cs b/src/Core/src/Platform/Android/MauiApplication.cs index cd175c63a..def8bc944 100644 --- a/src/Core/src/Platform/Android/MauiApplication.cs +++ b/src/Core/src/Platform/Android/MauiApplication.cs @@ -2,30 +2,54 @@ using System; using Android.Runtime; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Maui.Hosting; namespace Microsoft.Maui { - public class MauiApplication : global::Android.App.Application where TApplication : MauiApp + public class MauiApplication : Android.App.Application + where TStartup : IStartup, new() { public MauiApplication(IntPtr handle, JniHandleOwnership ownerShip) : base(handle, ownerShip) { - } - IHost? _host; + public override void OnCreate() { - if (!(Activator.CreateInstance(typeof(TApplication)) is TApplication app)) - throw new InvalidOperationException($"We weren't able to create the App {typeof(TApplication)}"); + var startup = new TStartup(); - _host = app.CreateBuilder().ConfigureServices(ConfigureNativeServices).Build(app); + IAppHostBuilder appBuilder; + + if (startup is IHostBuilderStartup hostBuilderStartup) + { + appBuilder = hostBuilderStartup + .CreateHostBuilder(); + } + else + { + appBuilder = AppHostBuilder + .CreateDefaultAppBuilder(); + } + + appBuilder. + ConfigureServices(ConfigureNativeServices); + + startup.Configure(appBuilder); + + var host = appBuilder.Build(); + if (host.Services == null) + throw new InvalidOperationException("App was not intialized"); + + var services = host.Services; + + var app = services.GetRequiredService(); + host.SetServiceProvider(app); - //_host.Start(); base.OnCreate(); } - //configure native services like HandlersContext, ImageSourceHandlers etc.. + // Configure native services like HandlersContext, ImageSourceHandlers etc.. void ConfigureNativeServices(HostBuilderContext ctx, IServiceCollection services) { } } -} +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/HandlerExtensions.cs b/src/Core/src/Platform/iOS/HandlerExtensions.cs index b336d934e..4f7f24e44 100644 --- a/src/Core/src/Platform/iOS/HandlerExtensions.cs +++ b/src/Core/src/Platform/iOS/HandlerExtensions.cs @@ -20,6 +20,8 @@ namespace Microsoft.Maui if (handler == null) throw new Exception($"Handler not found for view {view}"); + handler.SetMauiContext(context); + view.Handler = handler; } diff --git a/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs b/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs index 8c52a1408..ebd7faf26 100644 --- a/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs +++ b/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using Foundation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -9,7 +7,8 @@ using UIKit; namespace Microsoft.Maui { - public class MauiUIApplicationDelegate : UIApplicationDelegate, IUIApplicationDelegate where TApplication : MauiApp + public class MauiUIApplicationDelegate : UIApplicationDelegate, IUIApplicationDelegate + where TStartup : IStartup, new() { public override UIWindow? Window { @@ -19,23 +18,40 @@ namespace Microsoft.Maui public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { - if (!(Activator.CreateInstance(typeof(TApplication)) is TApplication app)) - throw new InvalidOperationException($"We weren't able to create the App {typeof(TApplication)}"); + var startup = new TStartup(); - var host = app.CreateBuilder().ConfigureServices(ConfigureNativeServices).Build(app); + IAppHostBuilder appBuilder; - if (MauiApp.Current == null || MauiApp.Current.Services == null) + if (startup is IHostBuilderStartup hostBuilderStartup) + { + appBuilder = hostBuilderStartup + .CreateHostBuilder(); + } + else + { + appBuilder = AppHostBuilder + .CreateDefaultAppBuilder(); + } + + appBuilder. + ConfigureServices(ConfigureNativeServices); + + startup.Configure(appBuilder); + + var host = appBuilder.Build(); + if (host.Services == null) throw new InvalidOperationException("App was not intialized"); + var services = host.Services; - var mauiContext = new MauiContext(MauiApp.Current.Services); + var app = services.GetRequiredService(); + host.SetServiceProvider(app); + + var mauiContext = new MauiContext(services); var window = app.CreateWindow(new ActivationState(mauiContext)); window.MauiContext = mauiContext; - //Hack for now we set this on the App Static but this should be on IFrameworkElement - App.Current.SetHandlerContext(window.MauiContext); - var content = (window.Page as IView) ?? window.Page.View; Window = new UIWindow @@ -53,7 +69,6 @@ namespace Microsoft.Maui void ConfigureNativeServices(HostBuilderContext ctx, IServiceCollection services) { - } } -} +} \ No newline at end of file diff --git a/src/Core/tests/Benchmarks/Benchmarks/GetHandlersBenchmarker.cs b/src/Core/tests/Benchmarks/Benchmarks/GetHandlersBenchmarker.cs index 7d99943f3..b4dabe6f9 100644 --- a/src/Core/tests/Benchmarks/Benchmarks/GetHandlersBenchmarker.cs +++ b/src/Core/tests/Benchmarks/Benchmarks/GetHandlersBenchmarker.cs @@ -6,7 +6,7 @@ namespace Microsoft.Maui.Handlers.Benchmarks [MemoryDiagnoser] public class GetHandlersBenchmarker { - MockApp _app; + IAppHost _host; Registrar _registrar; @@ -16,10 +16,9 @@ namespace Microsoft.Maui.Handlers.Benchmarks [GlobalSetup(Target = nameof(GetHandlerUsingDI))] public void SetupForDI() { - _app = new MockApp(); - _app.CreateBuilder() - .RegisterHandler() - .Build(_app); + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .Build(); } [GlobalSetup(Target = nameof(GetHandlerUsingRegistrar))] @@ -34,7 +33,7 @@ namespace Microsoft.Maui.Handlers.Benchmarks { for (int i = 0; i < N; i++) { - _app.Context.Handlers.GetHandler(); + _host.Handlers.GetHandler(); } } @@ -47,4 +46,4 @@ namespace Microsoft.Maui.Handlers.Benchmarks } } } -} +} \ No newline at end of file diff --git a/src/Core/tests/Benchmarks/Benchmarks/MauiServiceProviderBenchmarker.cs b/src/Core/tests/Benchmarks/Benchmarks/MauiServiceProviderBenchmarker.cs new file mode 100644 index 000000000..0e2847474 --- /dev/null +++ b/src/Core/tests/Benchmarks/Benchmarks/MauiServiceProviderBenchmarker.cs @@ -0,0 +1,176 @@ +using System; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Maui.Hosting; + +namespace Microsoft.Maui.Handlers.Benchmarks +{ + [MemoryDiagnoser] + public class MauiServiceProviderBenchmarker + { + IAppHost _host; + + [Params(100_000)] + public int N { get; set; } + + [IterationSetup(Target = nameof(DefaultBuilder))] + public void SetupForDefaultBuilder() + { + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .ConfigureServices((ctx, svc) => svc.AddTransient()) + .Build(); + } + + [IterationSetup(Target = nameof(DefaultBuilderWithConstructorInjection))] + public void SetupForDefaultBuilderWithConstructorInjection() + { + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, svc) => svc.AddTransient()) + .Build(); + } + + [IterationSetup(Target = nameof(OneConstructorParameter))] + public void SetupForOneConstructorParameter() + { + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, svc) => + { + svc.AddTransient(); + svc.AddTransient(); + }) + .Build(); + } + + [IterationSetup(Target = nameof(TwoConstructorParameters))] + public void SetupForTwoConstructorParameters() + { + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, svc) => + { + svc.AddTransient(); + svc.AddTransient(); + svc.AddTransient(); + }) + .Build(); + } + + [IterationSetup(Target = nameof(ExtensionsWithConstructorInjection))] + public void SetupForExtensionsWithConstructorInjection() + { + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) + .ConfigureServices((ctx, svc) => svc.AddTransient()) + .Build(); + } + + [IterationSetup(Target = nameof(ExtensionsWithOneConstructorParameter))] + public void SetupForExtensionsWithOneConstructorParameter() + { + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) + .ConfigureServices((ctx, svc) => + { + svc.AddTransient(); + svc.AddTransient(); + }) + .Build(); + } + + [IterationSetup(Target = nameof(ExtensionsWithTwoConstructorParameters))] + public void SetupForExtensionsWithTwoConstructorParameters() + { + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) + .ConfigureServices((ctx, svc) => + { + svc.AddTransient(); + svc.AddTransient(); + svc.AddTransient(); + }) + .Build(); + } + + [Benchmark(Baseline = true)] + public void DefaultBuilder() + { + for (int i = 0; i < N; i++) + { + _host.Services.GetService(); + } + } + + [Benchmark] + public void DefaultBuilderWithConstructorInjection() + { + for (int i = 0; i < N; i++) + { + _host.Services.GetService(); + } + } + + [Benchmark] + public void OneConstructorParameter() + { + for (int i = 0; i < N; i++) + { + _host.Services.GetService(); + } + } + + [Benchmark] + public void TwoConstructorParameters() + { + for (int i = 0; i < N; i++) + { + _host.Services.GetService(); + } + } + + [Benchmark] + public void ExtensionsWithConstructorInjection() + { + for (int i = 0; i < N; i++) + { + _host.Services.GetService(); + } + } + + [Benchmark] + public void ExtensionsWithOneConstructorParameter() + { + for (int i = 0; i < N; i++) + { + _host.Services.GetService(); + } + } + + [Benchmark] + public void ExtensionsWithTwoConstructorParameters() + { + for (int i = 0; i < N; i++) + { + _host.Services.GetService(); + } + } + + public class DIExtensionsServiceProviderFactory : IServiceProviderFactory + { + public ServiceCollection CreateBuilder(IServiceCollection services) + => new ServiceCollection { services }; + + public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder) + => containerBuilder.BuildServiceProvider(); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/Benchmarks/Core.Benchmarks.csproj b/src/Core/tests/Benchmarks/Core.Benchmarks.csproj index b05420a31..35b3d3af9 100644 --- a/src/Core/tests/Benchmarks/Core.Benchmarks.csproj +++ b/src/Core/tests/Benchmarks/Core.Benchmarks.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Core/tests/Benchmarks/Program.cs b/src/Core/tests/Benchmarks/Program.cs index eff3e7e3c..5cf231664 100644 --- a/src/Core/tests/Benchmarks/Program.cs +++ b/src/Core/tests/Benchmarks/Program.cs @@ -6,7 +6,8 @@ namespace Microsoft.Maui.Handlers.Benchmarks { static void Main(string[] args) { + //BenchmarkRunner.Run(); BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).RunAllJoined(); } } -} +} \ No newline at end of file diff --git a/src/Core/tests/Benchmarks/Stubs/AppStub.cs b/src/Core/tests/Benchmarks/Stubs/AppStub.cs index f17cc4c07..c88b69203 100644 --- a/src/Core/tests/Benchmarks/Stubs/AppStub.cs +++ b/src/Core/tests/Benchmarks/Stubs/AppStub.cs @@ -1,20 +1,10 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Maui.Hosting; - namespace Microsoft.Maui.Handlers.Benchmarks { - class MockApp : App + class AppStub : MauiApp { - public override IAppHostBuilder CreateBuilder() + public override IWindow CreateWindow(IActivationState state) { - return base.CreateBuilder() - .ConfigureServices(ConfigureNativeServices); - } - - void ConfigureNativeServices(HostBuilderContext ctx, IServiceCollection services) - { - services.AddSingleton(provider => new HandlersContextStub(provider)); + return new WindowStub(); } } -} +} \ No newline at end of file diff --git a/src/Core/tests/Benchmarks/Stubs/TestServices.cs b/src/Core/tests/Benchmarks/Stubs/TestServices.cs new file mode 100644 index 000000000..6dee7b461 --- /dev/null +++ b/src/Core/tests/Benchmarks/Stubs/TestServices.cs @@ -0,0 +1,82 @@ +namespace Microsoft.Maui.Handlers.Benchmarks +{ + interface IFooService + { + } + + interface IBarService + { + } + + interface IFooBarService + { + } + + class FooService : IFooService + { + } + + class BarService : IBarService + { + } + + class FooBarWithFooService : IFooBarService + { + public FooBarWithFooService(IFooService foo) + { + Foo = foo; + } + + public IFooService Foo { get; } + } + + class FooBarWithFooAndBarService : IFooBarService + { + public FooBarWithFooAndBarService(IFooService foo, IBarService bar) + { + Foo = foo; + Bar = bar; + } + + public IFooService Foo { get; } + + public IBarService Bar { get; } + } + + class FooDualConstructor : IFooBarService + { + public FooDualConstructor(IFooService foo) + { + Foo = foo; + } + + public FooDualConstructor(IBarService bar) + { + Bar = bar; + } + + public IFooService Foo { get; } + + public IBarService Bar { get; } + } + + class FooDefaultValueConstructor : IFooBarService + { + public FooDefaultValueConstructor(IBarService bar = null) + { + Bar = bar; + } + + public IBarService Bar { get; } + } + + class FooDefaultSystemValueConstructor : IFooBarService + { + public FooDefaultSystemValueConstructor(string text = "Default Value") + { + Text = text; + } + + public string Text { get; } + } +} \ No newline at end of file diff --git a/src/Core/tests/Benchmarks/Stubs/WindowStub.cs b/src/Core/tests/Benchmarks/Stubs/WindowStub.cs new file mode 100644 index 000000000..b642f085b --- /dev/null +++ b/src/Core/tests/Benchmarks/Stubs/WindowStub.cs @@ -0,0 +1,10 @@ +using System; + +namespace Microsoft.Maui.Handlers.Benchmarks +{ + public class WindowStub : IWindow + { + public IMauiContext MauiContext { get; set; } + public IPage Page { get; set; } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.cs index 4a53ff7e7..b67ddb0bc 100644 --- a/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.cs @@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.ActivityIndicator)] public partial class ActivityIndicatorHandlerTests : HandlerTestBase { - public ActivityIndicatorHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Theory(DisplayName = "IsRunning Initializes Correctly")] [InlineData(true)] [InlineData(false)] diff --git a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs index 4ad8eb946..c2efa4224 100644 --- a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Button)] public partial class ButtonHandlerTests : HandlerTestBase { - public ButtonHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Text Initializes Correctly")] public async Task TextInitializesCorrectly() { diff --git a/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.cs index 14a7fa4f8..3624353b6 100644 --- a/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.cs @@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.CheckBox)] public partial class CheckBoxHandlerTests : HandlerTestBase { - public CheckBoxHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Theory(DisplayName = "IsChecked Initializes Correctly")] [InlineData(true)] [InlineData(false)] diff --git a/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs index 2215b5680..d4d4f7fc3 100644 --- a/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Editor)] public partial class EditorHandlerTests : HandlerTestBase { - public EditorHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Text Initializes Correctly")] public async Task TextInitializesCorrectly() { diff --git a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs index 32085afef..4ad88dedb 100644 --- a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Entry)] public partial class EntryHandlerTests : HandlerTestBase { - public EntryHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Text Initializes Correctly")] public async Task TextInitializesCorrectly() { diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs index 98f511d95..911d47dc1 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs @@ -8,8 +8,7 @@ namespace Microsoft.Maui.DeviceTests protected THandler CreateHandler(IView view) { var handler = Activator.CreateInstance(); - if (handler is IAndroidViewHandler av) - av.SetContext(DefaultContext); + handler.SetMauiContext(MauiContext); handler.SetVirtualView(view); view.Handler = handler; diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs index b1278ebe2..08e12f285 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs @@ -2,23 +2,48 @@ using System; using System.Threading.Tasks; using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Essentials; +using Microsoft.Maui.Hosting; using Xunit; namespace Microsoft.Maui.DeviceTests { - [Collection(TestCollections.Handlers)] - public partial class HandlerTestBase : TestBase + public partial class HandlerTestBase : TestBase, IDisposable where THandler : IViewHandler where TStub : StubBase, IView, new() { - readonly HandlerTestFixture _fixture; + AppStub _app; + IAppHost _host; + IMauiContext _context; - public HandlerTestBase(HandlerTestFixture fixture) + public HandlerTestBase() { - _fixture = fixture; + var appBuilder = AppHostBuilder + .CreateDefaultAppBuilder() + .ConfigureFonts((ctx, fonts) => + { + fonts.AddFont("dokdo_regular.ttf", "Dokdo"); + }); + + _host = appBuilder.Build(); + + _app = new AppStub(); + + _host.SetServiceProvider(_app); + + _context = new ContextStub(_host.Services); } - public IApp App => _fixture.App; + public void Dispose() + { + _host.Dispose(); + _host = null; + _app = null; + _context = null; + } + + public IApp App => _app; + + public IMauiContext MauiContext => _context; public Task InvokeOnMainThreadAsync(Func func) => MainThread.InvokeOnMainThreadAsync(func); diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs index 3f8629b01..add0fa52a 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs @@ -11,6 +11,8 @@ namespace Microsoft.Maui.DeviceTests protected THandler CreateHandler(IView view) { var handler = Activator.CreateInstance(); + handler.SetMauiContext(MauiContext); + handler.SetVirtualView(view); view.Handler = handler; diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestCollection.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestCollection.cs deleted file mode 100644 index 40076dc7d..000000000 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestCollection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Xunit; - -namespace Microsoft.Maui.DeviceTests -{ - [CollectionDefinition(TestCollections.Handlers)] - public class HandlerTestCollection : ICollectionFixture - { - } -} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestFixture.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestFixture.cs deleted file mode 100644 index fafabbcc1..000000000 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestFixture.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Maui.DeviceTests.Stubs; -using Microsoft.Maui.Hosting; - -namespace Microsoft.Maui.DeviceTests -{ - public class HandlerTestFixture : IDisposable - { - AppStub _app; - IHost _host; - IMauiContext _context; - - public HandlerTestFixture() - { - _app = new AppStub(); - _context = new ContextStub(_app); - _host = _app - .CreateBuilder() - .ConfigureFonts((ctx, fonts) => - { - fonts.AddFont("dokdo_regular.ttf", "Dokdo"); - }) - .ConfigureServices((ctx, services) => - { - services.AddSingleton(_context); - }) - .Build(_app); - } - - public void Dispose() - { - _host.Dispose(); - _host = null; - - _app.Dispose(); - _app = null; - - _context = null; - } - - public IApp App => _app; - } -} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.cs index 24d814ec1..d5e5c9fa7 100644 --- a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Label)] public partial class LabelHandlerTests : HandlerTestBase { - public LabelHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Background Color Initializes Correctly")] public async Task BackgroundColorInitializesCorrectly() { diff --git a/src/Core/tests/DeviceTests/Handlers/Layout/LayoutHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Layout/LayoutHandlerTests.cs index 056a75aa6..5ef389400 100644 --- a/src/Core/tests/DeviceTests/Handlers/Layout/LayoutHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Layout/LayoutHandlerTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests.Handlers.Layout [Category(TestCategory.Layout)] public partial class LayoutHandlerTests : HandlerTestBase { - public LayoutHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Empty layout")] public async Task EmptyLayout() { diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs index 9a8986094..c6d09f0db 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.cs @@ -6,8 +6,5 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Picker)] public partial class PickerHandlerTests : HandlerTestBase { - public PickerHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarTests.cs b/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarTests.cs index d54b4764a..04c963122 100644 --- a/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests [Category("ProgressBarHandler")] public partial class ProgressBarHandlerTests : HandlerTestBase { - public ProgressBarHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Theory(DisplayName = "Progress Initializes Correctly")] [InlineData(0.25)] [InlineData(0.5)] diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs index e1d703937..e9d962107 100644 --- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs @@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.SearchBar)] public partial class SearchBarHandlerTests : HandlerTestBase { - public SearchBarHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Text Initializes Correctly")] public async Task TextInitializesCorrectly() { diff --git a/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.cs index 1957123e0..c752debaf 100644 --- a/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Slider)] public partial class SliderHandlerTests : HandlerTestBase { - public SliderHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Theory(DisplayName = "Percent Value Initializes Correctly")] [InlineData(0, 1, 0)] [InlineData(0, 1, 0.5)] diff --git a/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.cs index bd57e1df7..0a0da6cd9 100644 --- a/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.cs @@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Stepper)] public partial class StepperHandlerTests : HandlerTestBase { - public StepperHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Is Value Initializes Correctly")] public async Task ValueInitializesCorrectly() { diff --git a/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.cs index 1569d4dc8..cf96ee8c4 100644 --- a/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.Switch)] public partial class SwitchHandlerTests : HandlerTestBase { - public SwitchHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Is Toggled Initializes Correctly")] public async Task IsToggledInitializesCorrectly() { diff --git a/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.cs index 0a225c6ae..86d2a6d9a 100644 --- a/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.cs @@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests [Category(TestCategory.TimePicker)] public partial class TimePickerHandlerTests : HandlerTestBase { - public TimePickerHandlerTests(HandlerTestFixture fixture) : base(fixture) - { - } - [Fact(DisplayName = "Time Initializes Correctly")] public async Task IsToggledInitializesCorrectly() { @@ -22,4 +18,4 @@ namespace Microsoft.Maui.DeviceTests await ValidateTime(timePickerStub, () => timePickerStub.Time = time); } } -} +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Stubs/AppStub.cs b/src/Core/tests/DeviceTests/Stubs/AppStub.cs index 5f48a1097..3ae8ad7fe 100644 --- a/src/Core/tests/DeviceTests/Stubs/AppStub.cs +++ b/src/Core/tests/DeviceTests/Stubs/AppStub.cs @@ -1,17 +1,6 @@ -using System; - namespace Microsoft.Maui.DeviceTests.Stubs { - class AppStub : MauiApp, IDisposable + class AppStub : App { - public override IWindow CreateWindow(IActivationState state) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - Current = null; - } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Stubs/ContextStub.cs b/src/Core/tests/DeviceTests/Stubs/ContextStub.cs index b4e68665f..75ab5aec3 100644 --- a/src/Core/tests/DeviceTests/Stubs/ContextStub.cs +++ b/src/Core/tests/DeviceTests/Stubs/ContextStub.cs @@ -3,29 +3,20 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Maui.DeviceTests.Stubs { - class ContextStub : IMauiContext, IDisposable + class ContextStub : IMauiContext { - AppStub _app; - - public ContextStub(AppStub app) + public ContextStub(IServiceProvider services) { - _app = app; + Services = services; } - public IServiceProvider Services => - _app.Services; + public IServiceProvider Services { get; } public IMauiHandlersServiceProvider Handlers => Services.GetRequiredService(); #if __ANDROID__ - public Android.Content.Context Context => - Android.App.Application.Context; + public Android.Content.Context Context => Platform.DefaultContext; #endif - - public void Dispose() - { - _app = null; - } } } \ No newline at end of file diff --git a/src/Core/tests/UnitTests/HostBuilderHandlerTests.cs b/src/Core/tests/UnitTests/HostBuilderHandlerTests.cs new file mode 100644 index 000000000..64e290ff5 --- /dev/null +++ b/src/Core/tests/UnitTests/HostBuilderHandlerTests.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Hosting; +using Microsoft.Maui.Tests; +using Xunit; + +namespace Microsoft.Maui.UnitTests +{ + [Category(TestCategory.Core, TestCategory.Hosting)] + public class HostBuilderHandlerTests + { + [Fact] + public void CanBuildAHost() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .Build(); + + Assert.NotNull(host); + } + + [Fact] + public void CanGetIMauiHandlersServiceProviderFromServices() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .Build(); + + Assert.NotNull(host); + Assert.NotNull(host.Services); + Assert.NotNull(host.Handlers); + Assert.IsType(host.Handlers); + Assert.Equal(host.Handlers, host.Services.GetService()); + } + + [Fact] + public void CanRegisterAndGetHandler() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .RegisterHandler() + .Build(); + + var handler = host.Handlers.GetHandler(typeof(IViewStub)); + + Assert.NotNull(handler); + Assert.IsType(handler); + } + + [Fact] + public void CanRegisterAndGetHandlerWithDictionary() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .RegisterHandlers(new Dictionary + { + { typeof(IViewStub), typeof(ViewHandlerStub) } + }) + .Build(); + + var handler = host.Handlers.GetHandler(typeof(IViewStub)); + + Assert.NotNull(handler); + Assert.IsType(handler); + } + + [Fact] + public void CanRegisterAndGetHandlerForType() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .RegisterHandler() + .Build(); + + var handler = host.Handlers.GetHandler(typeof(ViewStub)); + + Assert.NotNull(handler); + Assert.IsType(handler); + } + + [Fact] + public void DefaultHandlersAreRegistered() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .Build(); + + var handler = host.Handlers.GetHandler(typeof(IButton)); + + Assert.NotNull(handler); + Assert.IsType(handler); + } + + [Fact] + public void CanSpecifyHandler() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .RegisterHandler() + .Build(); + + var defaultHandler = host.Handlers.GetHandler(typeof(IButton)); + var specificHandler = host.Handlers.GetHandler(typeof(ButtonStub)); + + Assert.NotNull(defaultHandler); + Assert.NotNull(specificHandler); + Assert.IsType(defaultHandler); + Assert.IsType(specificHandler); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/UnitTests/HostBuilderServicesTests.cs b/src/Core/tests/UnitTests/HostBuilderServicesTests.cs new file mode 100644 index 000000000..f56b69a79 --- /dev/null +++ b/src/Core/tests/UnitTests/HostBuilderServicesTests.cs @@ -0,0 +1,202 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.Hosting; +using Microsoft.Maui.Tests; +using Xunit; + +namespace Microsoft.Maui.UnitTests +{ + [Category(TestCategory.Core, TestCategory.Hosting)] + public class HostBuilderServicesTests + { + [Fact] + public void CanGetServices() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .Build(); + + Assert.NotNull(host); + Assert.NotNull(host.Services); + } + + [Fact] + public void GetServiceThrowsWhenConstructorParamTypesWereNotRegistered() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, services) => services.AddTransient()) + .Build(); + + Assert.Throws(() => host.Services.GetService()); + } + + [Fact] + public void GetServiceThrowsOnMultipleConstructors() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, services) => services.AddTransient()) + .Build(); + + var ex = Assert.Throws(() => host.Services.GetService()); + + Assert.Contains("IFooService", ex.Message); + } + + [Fact] + public void GetServiceCanReturnTypesThatHaveConstructorParams() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, services) => + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + }) + .Build(); + + var foobar = host.Services.GetService(); + + Assert.NotNull(foobar); + Assert.IsType(foobar); + } + + [Fact] + public void GetServiceCanReturnTypesThatHaveUnregisteredConstructorParamsButHaveDefaultValues() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, services) => + { + services.AddTransient(); + }) + .Build(); + + var foo = host.Services.GetService(); + + Assert.NotNull(foo); + + var actual = Assert.IsType(foo); + + Assert.Null(actual.Bar); + } + + [Fact] + public void GetServiceCanReturnTypesThatHaveRegisteredConstructorParamsAndHaveDefaultValues() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, services) => + { + services.AddTransient(); + services.AddTransient(); + }) + .Build(); + + var foo = host.Services.GetService(); + + Assert.NotNull(foo); + + var actual = Assert.IsType(foo); + + Assert.NotNull(actual.Bar); + Assert.IsType(actual.Bar); + } + + [Fact] + public void GetServiceCanReturnTypesThatHaveSystemDefaultValues() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .UseMauiServiceProviderFactory(true) + .ConfigureServices((ctx, services) => + { + services.AddTransient(); + }) + .Build(); + + var foo = host.Services.GetService(); + + Assert.NotNull(foo); + + var actual = Assert.IsType(foo); + + Assert.Equal("Default Value", actual.Text); + } + + [Fact] + public void WillRetrieveDifferentTransientServices() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .ConfigureServices((ctx, services) => services.AddTransient()) + .Build(); + + AssertTransient(host.Services); + } + + [Fact] + public void WillRetrieveSameSingletonServices() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .ConfigureServices((ctx, services) => services.AddSingleton()) + .Build(); + + AssertSingleton(host.Services); + } + + [Fact] + public void WillRetrieveMixedServices() + { + var host = AppHostBuilder + .CreateDefaultAppBuilder() + .ConfigureServices((ctx, services) => + { + services.AddSingleton(); + services.AddTransient(); + }) + .Build(); + + AssertSingleton(host.Services); + AssertTransient(host.Services); + } + + static void AssertTransient(IServiceProvider services) + { + var service1 = services.GetService(); + + Assert.NotNull(service1); + Assert.IsType(service1); + + var service2 = services.GetService(); + + Assert.NotNull(service2); + Assert.IsType(service2); + + Assert.NotEqual(service1, service2); + } + + static void AssertSingleton(IServiceProvider services) + { + var service1 = services.GetService(); + + Assert.NotNull(service1); + Assert.IsType(service1); + + var service2 = services.GetService(); + + Assert.NotNull(service2); + Assert.IsType(service2); + + Assert.Equal(service1, service2); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/UnitTests/HostBuilderTests.cs b/src/Core/tests/UnitTests/HostBuilderTests.cs deleted file mode 100644 index ccfaa2d57..000000000 --- a/src/Core/tests/UnitTests/HostBuilderTests.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Maui.Handlers; -using Microsoft.Maui.Hosting; -using Microsoft.Maui.Tests; -using Xunit; - -namespace Microsoft.Maui.UnitTests -{ - [Category(TestCategory.Core, TestCategory.Hosting)] - public partial class HostBuilderTests : IDisposable - { - [Fact] - public void CanBuildAHost() - { - var host = App.CreateDefaultBuilder().Build(); - Assert.NotNull(host); - } - - [Fact] - public void CanGetStaticApp() - { - var app = new AppStub(); - app.CreateBuilder().Build(app); - - Assert.NotNull(MauiApp.Current); - Assert.Equal(MauiApp.Current, app); - } - - [Fact] - public void ShouldntCreateMultipleApp() - { - var app = new AppStub(); - Assert.Throws(() => new AppStub()); - } - - [Fact] - public void CanGetServices() - { - var app = new AppStub(); - app.CreateBuilder().Build(app); - - Assert.NotNull(app.Services); - } - - [Fact] - public void CanGetStaticServices() - { - var app = new AppStub(); - app.CreateBuilder().Build(app); - - Assert.NotNull(MauiApp.Current.Services); - Assert.Equal(app.Services, MauiApp.Current.Services); - } - - [Fact] - public void HandlerContextNullBeforeBuild() - { - var app = new AppStub(); - app.CreateBuilder(); - - var handlerContext = MauiApp.Current.Context; - - Assert.Null(handlerContext); - } - - [Fact] - public void HandlerContextAfterBuild() - { - var app = new AppStub(); - app.CreateBuilder().Build(app); - - var handlerContext = MauiApp.Current.Context; - - Assert.NotNull(handlerContext); - } - - [Fact] - public void CanHandlerProviderContext() - { - var app = new AppStub(); - app.CreateBuilder().Build(app); - - var handlerContext = MauiApp.Current.Context; - - Assert.IsAssignableFrom(handlerContext.Handlers); - } - - [Fact] - public void CanRegisterAndGetHandler() - { - var app = new AppStub(); - app.CreateBuilder() - .RegisterHandler() - .Build(app); - - var handler = MauiApp.Current.Context.Handlers.GetHandler(typeof(IViewStub)); - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void CanRegisterAndGetHandlerWithDictionary() - { - var app = new AppStub(); - app.CreateBuilder() - .RegisterHandlers(new Dictionary - { - { typeof(IViewStub), typeof(ViewHandlerStub) } - }) - .Build(app); - - var handler = MauiApp.Current.Context.Handlers.GetHandler(typeof(IViewStub)); - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void CanRegisterAndGetHandlerForType() - { - var app = new AppStub(); - app.CreateBuilder() - .RegisterHandler() - .Build(app); - - var handler = MauiApp.Current.Context.Handlers.GetHandler(typeof(ViewStub)); - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void DefaultHandlersAreRegistered() - { - var app = new AppStub(); - app.CreateBuilder().Build(app); - - var handler = MauiApp.Current.Context.Handlers.GetHandler(typeof(IButton)); - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void CanSpecifyHandler() - { - var app = new AppStub(); - app.CreateBuilder() - .RegisterHandler() - .Build(app); - - var defaultHandler = MauiApp.Current.Context.Handlers.GetHandler(typeof(IButton)); - var specificHandler = MauiApp.Current.Context.Handlers.GetHandler(typeof(ButtonStub)); - Assert.NotNull(defaultHandler); - Assert.NotNull(specificHandler); - Assert.IsType(defaultHandler); - Assert.IsType(specificHandler); - } - - [Fact] - public void WillRetrieveDifferentTransientServices() - { - var app = new AppStub(); - app.CreateBuilder() - .ConfigureServices((ctx, services) => services.AddTransient()) - .Build(app); - - AssertTransient(app); - } - - [Fact] - public void WillRetrieveSameSingletonServices() - { - var app = new AppStub(); - app.CreateBuilder() - .ConfigureServices((ctx, services) => services.AddSingleton()) - .Build(app); - - AssertSingleton(app); - } - - [Fact] - public void WillRetrieveMixedServices() - { - var app = new AppStub(); - app.CreateBuilder() - .ConfigureServices((ctx, services) => - { - services.AddSingleton(); - services.AddTransient(); - }) - .Build(app); - - AssertSingleton(app); - AssertTransient(app); - } - - public void Dispose() - { - (App.Current as AppStub)?.ClearApp(); - } - - static void AssertTransient(AppStub app) - { - var service1 = app.Services.GetService(); - - Assert.NotNull(service1); - Assert.IsType(service1); - - var service2 = app.Services.GetService(); - - Assert.NotNull(service2); - Assert.IsType(service2); - - Assert.NotEqual(service1, service2); - } - - static void AssertSingleton(AppStub app) - { - var service1 = app.Services.GetService(); - - Assert.NotNull(service1); - Assert.IsType(service1); - - var service2 = app.Services.GetService(); - - Assert.NotNull(service2); - Assert.IsType(service2); - - Assert.Equal(service1, service2); - } - } -} diff --git a/src/Core/tests/UnitTests/TestClasses/AppStub.cs b/src/Core/tests/UnitTests/TestClasses/AppStub.cs index 3a971942c..31c879f4f 100644 --- a/src/Core/tests/UnitTests/TestClasses/AppStub.cs +++ b/src/Core/tests/UnitTests/TestClasses/AppStub.cs @@ -1,32 +1,6 @@ -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Maui.Hosting; -using Microsoft.Maui.UnitTests.TestClasses; - namespace Microsoft.Maui.Tests { - class AppStub : MauiApp + class AppStub : App { - public void ConfigureServices(HostBuilderContext ctx, IServiceCollection services) - { - services.AddSingleton(provider => new HandlersContextStub(provider)); - services.AddTransient(); - } - - public override IAppHostBuilder CreateBuilder() - { - return base.CreateBuilder().ConfigureServices(ConfigureServices); - } - - public override IWindow CreateWindow(IActivationState state) - { - return new WindowStub(); - } - - internal void ClearApp() - { - Current = null; - } } -} +} \ No newline at end of file diff --git a/src/Core/tests/UnitTests/TestClasses/TestServices.cs b/src/Core/tests/UnitTests/TestClasses/TestServices.cs index 16cdf5186..26f93aca9 100644 --- a/src/Core/tests/UnitTests/TestClasses/TestServices.cs +++ b/src/Core/tests/UnitTests/TestClasses/TestServices.cs @@ -8,6 +8,10 @@ { } + interface IFooBarService + { + } + class FooService : IFooService { } @@ -15,4 +19,54 @@ class BarService : IBarService { } -} + + class FooBarService : IFooBarService + { + public FooBarService(IFooService foo, IBarService bar) + { + Foo = foo; + Bar = bar; + } + + public IFooService Foo { get; } + + public IBarService Bar { get; } + } + + class FooDualConstructor : IFooBarService + { + public FooDualConstructor(IFooService foo) + { + Foo = foo; + } + + public FooDualConstructor(IBarService bar) + { + Bar = bar; + } + + public IFooService Foo { get; } + + public IBarService Bar { get; } + } + + class FooDefaultValueConstructor : IFooBarService + { + public FooDefaultValueConstructor(IBarService bar = null) + { + Bar = bar; + } + + public IBarService Bar { get; } + } + + class FooDefaultSystemValueConstructor : IFooBarService + { + public FooDefaultSystemValueConstructor(string text = "Default Value") + { + Text = text; + } + + public string Text { get; } + } +} \ No newline at end of file diff --git a/src/Core/tests/UnitTests/TestClasses/ViewHandlerStub.cs b/src/Core/tests/UnitTests/TestClasses/ViewHandlerStub.cs index b39c45f91..7bc3d8484 100644 --- a/src/Core/tests/UnitTests/TestClasses/ViewHandlerStub.cs +++ b/src/Core/tests/UnitTests/TestClasses/ViewHandlerStub.cs @@ -13,7 +13,7 @@ namespace Microsoft.Maui.Tests } - public ViewHandlerStub(PropertyMapper mapper) : base(mapper ?? MockViewMapper) + public ViewHandlerStub(PropertyMapper mapper = null) : base(mapper ?? MockViewMapper) { } diff --git a/src/Core/tests/UnitTests/TestClasses/WindowStub.cs b/src/Core/tests/UnitTests/TestClasses/WindowStub.cs deleted file mode 100644 index 6709ab076..000000000 --- a/src/Core/tests/UnitTests/TestClasses/WindowStub.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Maui.UnitTests.TestClasses -{ - class WindowStub : IWindow - { - public IPage Page { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public IMauiContext MauiContext { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - } -}