diff --git a/eng/scripts/profile-android.ps1 b/eng/scripts/profile-android.ps1 index a3ac1f286bb8..49be1dd02d48 100644 --- a/eng/scripts/profile-android.ps1 +++ b/eng/scripts/profile-android.ps1 @@ -38,9 +38,6 @@ The $(Configuration) MSBuild property. Defaults to 'Debug'. Additional command-line arguments to pass to MSBuild. For example, '-extra /p:AotAssemblies=True' would enable AOT. -.PARAMETER xamarinformsversion -The Xamarin.Forms version to run hello world on - .PARAMETER sleep The number of seconds to wait between each app launch. Defaults to 3, @@ -89,8 +86,7 @@ param [string] $package, [string] $configuration = 'Debug', [string] $extra, - [string] $xamarinformsversion = '4.7.0.1351', - [string] $androidapi = 'android-28', + [string] $androidapi = 'android-30', [int] $sleep = 3, [int] $iterations = 10 ) @@ -172,7 +168,7 @@ Function Build-App{ $isFlutter = -not $project.EndsWith("csproj") if(-not $isFlutter) { - & $msbuild $project /v:minimal /nologo /restore /t:Clean,Install /p:Configuration=$configuration /p:XamarinFormsVersion=$xamarinformsversion $extra + & $msbuild $project /v:minimal /nologo /restore /t:Clean,Install /p:Configuration=$configuration $extra } else { diff --git a/src/Compatibility/Core/src/Android/AppCompat/Platform.cs b/src/Compatibility/Core/src/Android/AppCompat/Platform.cs index 9526a854eba4..0883d9fc558c 100644 --- a/src/Compatibility/Core/src/Android/AppCompat/Platform.cs +++ b/src/Compatibility/Core/src/Android/AppCompat/Platform.cs @@ -313,6 +313,7 @@ internal static IVisualElementRenderer CreateRenderer(VisualElement element, Con try { handler = Forms.MauiContext.Handlers.GetHandler(element.GetType()); + handler.SetMauiContext(Forms.MauiContext); } catch { @@ -340,10 +341,7 @@ internal static IVisualElementRenderer CreateRenderer(VisualElement element, Con 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/iOS/Platform.cs b/src/Compatibility/Core/src/iOS/Platform.cs index ed59a0a6e8c2..53ccb15a4f2c 100644 --- a/src/Compatibility/Core/src/iOS/Platform.cs +++ b/src/Compatibility/Core/src/iOS/Platform.cs @@ -239,6 +239,7 @@ public static IVisualElementRenderer CreateRenderer(VisualElement element) 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 b189420703d4..f00ed5d2400f 100644 --- a/src/Controls/samples/Controls.Sample.Droid/MainApplication.cs +++ b/src/Controls/samples/Controls.Sample.Droid/MainApplication.cs @@ -6,7 +6,7 @@ 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.iOS/AppDelegate.cs b/src/Controls/samples/Controls.Sample.iOS/AppDelegate.cs index c9cbbb72c117..989b85de2394 100644 --- a/src/Controls/samples/Controls.Sample.iOS/AppDelegate.cs +++ b/src/Controls/samples/Controls.Sample.iOS/AppDelegate.cs @@ -11,7 +11,7 @@ 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 928e1ccde4e1..5059d7ab1539 100644 --- a/src/Controls/samples/Controls.Sample/MainWindow.cs +++ b/src/Controls/samples/Controls.Sample/MainWindow.cs @@ -1,18 +1,13 @@ using Maui.Controls.Sample.Controls; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui; 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 be5cb9e55ea4..743edb9038c6 100644 --- a/src/Controls/samples/Controls.Sample/MyApp.cs +++ b/src/Controls/samples/Controls.Sample/MyApp.cs @@ -1,3 +1,5 @@ +using System; +using Maui.Controls.Sample.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui; using Microsoft.Maui.Controls.Compatibility; @@ -6,7 +8,11 @@ namespace Maui.Controls.Sample { public class MyApp : MauiApp { - // IAppState state + public MyApp(ITextService textService) + { + Console.WriteLine($"The injected text service had a message: '{textService.GetText()}'"); + } + public override IWindow CreateWindow(IActivationState state) { Forms.Init(state); diff --git a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs index 889bcbfac4c6..789a9fe889e5 100644 --- a/src/Controls/samples/Controls.Sample/Pages/MainPage.cs +++ b/src/Controls/samples/Controls.Sample/Pages/MainPage.cs @@ -1,5 +1,4 @@ using Maui.Controls.Sample.ViewModel; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui; using Microsoft.Maui.Controls; @@ -9,14 +8,10 @@ public class MainPage : ContentPage, IPage { MainPageViewModel _viewModel; - public MainPage() : this(App.Current.Services.GetService()) - { - - } - public MainPage(MainPageViewModel viewModel) { BindingContext = _viewModel = viewModel; + SetupMauiLayout(); //SetupCompatibilityLayout(); } @@ -89,7 +84,7 @@ void SetupMauiLayout() }; 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" }); @@ -120,7 +115,10 @@ void SetupMauiLayout() 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 index ba35789eba2e..8322d51e23ff 100644 --- a/src/Controls/samples/Controls.Sample/Startup.cs +++ b/src/Controls/samples/Controls.Sample/Startup.cs @@ -1,13 +1,14 @@ 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; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Configuration; -using System.Collections.Generic; namespace Maui.Controls.Sample { @@ -18,7 +19,8 @@ public class Startup : IStartup public void Configure(IAppHostBuilder appBuilder) { appBuilder - //.RegisterCompatibilityRenderers() + .RegisterCompatibilityRenderers() + .UseMauiApp() .ConfigureAppConfiguration((hostingContext, config) => { config.AddInMemoryCollection(new Dictionary @@ -29,7 +31,8 @@ public void Configure(IAppHostBuilder appBuilder) {"Logging:LogLevel:Default", "Warning"} }); }) - .UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) + .UseMauiServiceProviderFactory(true) + //.UseServiceProviderFactory(new DIExtensionsServiceProviderFactory()) .ConfigureServices((hostingContext, services) => { services.AddSingleton(); diff --git a/src/Controls/samples/Controls.Sample/ViewModel/MainPageViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModel/MainPageViewModel.cs index b61d6830625d..ce04425fff13 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() }) - { - } + readonly IConfiguration _configuration; + readonly ITextService _textService; + string _text; - public MainPageViewModel(IEnumerable textServices) + public MainPageViewModel(IConfiguration configuration, ITextService textService) { - Configuration = App.Current.Services.GetService(); + _configuration = configuration; + _textService = textService; - //var logger = App.Current.Services.GetService>(); + Console.WriteLine($"Value from config: {_configuration["MyKey"]}"); - //logger.LogInformation("hello"); - - textService = textServices.FirstOrDefault(); - Text = textService.GetText(); + Text = _textService.GetText(); } - //public MainPageViewModel(ITextService textService) - //{ - // Text = textService.GetText(); - //} - - string _text; 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 new file mode 100644 index 000000000000..99cdc3c009be --- /dev/null +++ b/src/Controls/src/Core/HandlerImpl/SearchBar.Impl.cs @@ -0,0 +1,6 @@ +namespace Microsoft.Maui.Controls +{ + public partial class SearchBar : ISearchBar + { + } +} \ No newline at end of file diff --git a/src/Controls/src/Core/SearchBar.cs b/src/Controls/src/Core/SearchBar.cs index 42e948be7646..a2c4b5121724 100644 --- a/src/Controls/src/Core/SearchBar.cs +++ b/src/Controls/src/Core/SearchBar.cs @@ -5,7 +5,7 @@ namespace Microsoft.Maui.Controls { - public class SearchBar : InputView, IFontElement, ITextAlignmentElement, ISearchBarController, IElementConfiguration + public partial class SearchBar : InputView, IFontElement, ITextAlignmentElement, ISearchBarController, IElementConfiguration { public static readonly BindableProperty SearchCommandProperty = BindableProperty.Create("SearchCommand", typeof(ICommand), typeof(SearchBar), null, propertyChanged: OnCommandChanged); diff --git a/src/Core/src/App.cs b/src/Core/src/App.cs index 0d9fb9138252..6c7f4e96b00d 100644 --- a/src/Core/src/App.cs +++ b/src/Core/src/App.cs @@ -1,36 +1,14 @@ using System; -using Microsoft.Extensions.DependencyInjection; 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; + public IServiceProvider? Services { get; private set; } internal void SetServiceProvider(IServiceProvider provider) { - _serviceProvider = provider; - SetHandlerContext(provider.GetService()); - } - - internal void SetHandlerContext(IMauiContext? context) - { - _context = context; + Services = provider; } } } \ 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 b3242fc51d58..3d6b3d769433 100644 --- a/src/Core/src/Handlers/Button/ButtonHandler.Android.cs +++ b/src/Core/src/Handlers/Button/ButtonHandler.Android.cs @@ -55,9 +55,9 @@ public static void MapTextColor(ButtonHandler handler, IButton button) 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 1ffafa85b8f8..8c0cf2ab7f0b 100644 --- a/src/Core/src/Handlers/Button/ButtonHandler.cs +++ b/src/Core/src/Handlers/Button/ButtonHandler.cs @@ -15,7 +15,7 @@ public ButtonHandler() : base(ButtonMapper) } - 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 4137c8097063..bc22f5bed81b 100644 --- a/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs +++ b/src/Core/src/Handlers/Button/ButtonHandler.iOS.cs @@ -61,9 +61,9 @@ public static void MapPadding(ButtonHandler handler, IButton button) 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 b64426da7ef1..924df939fc4d 100644 --- a/src/Core/src/Handlers/Editor/EditorHandler.cs +++ b/src/Core/src/Handlers/Editor/EditorHandler.cs @@ -12,7 +12,7 @@ public EditorHandler() : base(EditorMapper) } - 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 011fc493d502..c54f7cd18a7f 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.Android.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.Android.cs @@ -62,9 +62,9 @@ public static void MapPlaceholder(EntryHandler handler, IEntry entry) 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 a943c8dc1fe7..169700e4c3ac 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.cs @@ -18,7 +18,7 @@ public EntryHandler() : base(EntryMapper) } - 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 c3248b3c4708..5341702c3974 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs @@ -93,9 +93,9 @@ void OnTextChanged() 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 c3e53f6f03fc..ebd9cda8d59a 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 3a6115f23975..96b0dc207920 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 e7d00d78db31..b8904d25e1c3 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.Android.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.Android.cs @@ -64,9 +64,9 @@ public static void MapTextDecorations(LabelHandler handler, ILabel label) 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 0ad39e91075d..dd085f990600 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.cs @@ -20,7 +20,7 @@ public LabelHandler() : base(LabelMapper) } - 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 c3b673201d68..e78a6aded478 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.iOS.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.iOS.cs @@ -50,9 +50,9 @@ public static void MapTextDecorations(LabelHandler handler, ILabel label) 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 33e1cbe5163f..8bd23d8f78b7 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs @@ -29,14 +29,14 @@ public override void SetVirtualView(IView view) _ = 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 @@ public void Add(IView child) { _ = 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 5e49fc1b16bf..d52cffa5e4f6 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.cs @@ -15,7 +15,7 @@ public LayoutHandler() : base(LayoutMapper) } - 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 994db20b5d3a..a242444c10be 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.iOS.cs @@ -32,14 +32,14 @@ public override void SetVirtualView(IView view) _ = 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 @@ public void Add(IView child) { _ = 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 9a52c7d6ae46..6b9c36390a57 100644 --- a/src/Core/src/Handlers/ProgressBar/ProgressBarHandler.cs +++ b/src/Core/src/Handlers/ProgressBar/ProgressBarHandler.cs @@ -17,7 +17,7 @@ public ProgressBarHandler() : base(ProgressMapper) } - public ProgressBarHandler(PropertyMapper mapper) : base(mapper ?? ProgressMapper) + public ProgressBarHandler(PropertyMapper? mapper = null) : base(mapper ?? ProgressMapper) { } diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs index 70a65304dda0..e39d4462bd3d 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs @@ -14,7 +14,7 @@ public SearchBarHandler() : base(SearchBarMapper) } - public SearchBarHandler(PropertyMapper mapper) : base(mapper ?? SearchBarMapper) + public SearchBarHandler(PropertyMapper? mapper = null) : base(mapper ?? SearchBarMapper) { } diff --git a/src/Core/src/Handlers/Slider/SliderHandler.cs b/src/Core/src/Handlers/Slider/SliderHandler.cs index cdfd63db53ad..b535a9e2699f 100644 --- a/src/Core/src/Handlers/Slider/SliderHandler.cs +++ b/src/Core/src/Handlers/Slider/SliderHandler.cs @@ -17,7 +17,7 @@ public SliderHandler() : base(SliderMapper) } - 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 f87cd4b12473..3c4947b6c30a 100644 --- a/src/Core/src/Handlers/Switch/SwitchHandler.cs +++ b/src/Core/src/Handlers/Switch/SwitchHandler.cs @@ -14,7 +14,7 @@ public SwitchHandler() : base(SwitchMapper) } - 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 2f9069c36d0d..19921a47465d 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 56fab321773b..9d2f9fb034db 100644 --- a/src/Core/src/Handlers/View/AbstractViewHandler.cs +++ b/src/Core/src/Handlers/View/AbstractViewHandler.cs @@ -41,6 +41,12 @@ protected AbstractViewHandler(PropertyMapper mapper) 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 3c633635958d..233d3d0d8cf7 100644 --- a/src/Core/src/Hosting/AppHostBuilder.cs +++ b/src/Core/src/Hosting/AppHostBuilder.cs @@ -19,7 +19,7 @@ public class AppHostBuilder 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; @@ -28,7 +28,6 @@ public class AppHostBuilder IServiceCollection? _services; IConfiguration? _hostConfiguration; IConfiguration? _appConfiguration; - App? _app; public AppHostBuilder() { @@ -46,19 +45,33 @@ public static IAppHostBuilder CreateDefaultAppBuilder() return builder; } - public IHost Build() - => InternalBuild(); - - public void SetServiceProvider(IApp app) + public IAppHost Build() { - _app = app as MauiApp; + _services = _serviceColectionFactory(); + + if (_hostBuilt) + throw new InvalidOperationException("Build can only be called once."); + + _hostBuilt = true; + + // the order is important here + BuildHostConfiguration(); + CreateHostingEnvironment(); + CreateHostBuilderContext(); + BuildAppConfiguration(); + + if (_services == null) + throw new InvalidOperationException("The ServiceCollection cannot be null"); + + ConfigureHandlers(_services); + CreateServiceProvider(_services); if (_serviceProvider == null) throw new InvalidOperationException($"The ServiceProvider cannot be null"); - //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); + BuildFontRegistrar(_serviceProvider); + + return new AppHost(_serviceProvider, null); } public IAppHostBuilder ConfigureAppConfiguration(Action configureDelegate) @@ -113,39 +126,6 @@ public IAppHostBuilder UseServiceProviderFactory(Func(Action(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 ce1a513d1c10..685b8f5c9a3a 100644 --- a/src/Core/src/Hosting/AppHostBuilderExtensions.cs +++ b/src/Core/src/Hosting/AppHostBuilderExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui.Handlers; +using Microsoft.Maui.Hosting.Internal; namespace Microsoft.Maui.Hosting { @@ -36,14 +37,15 @@ public static IAppHostBuilder UseMauiHandlers(this IAppHostBuilder builder) { builder.RegisterHandlers(new Dictionary { - { typeof(IButton), typeof(ButtonHandler) }, - { typeof(IEditor), typeof(EditorHandler) }, - { typeof(IEntry), typeof(EntryHandler) }, - { typeof(ILayout), typeof(LayoutHandler) }, - { typeof(ILabel), typeof(LabelHandler) }, - { typeof(IProgress), typeof(ProgressBarHandler) }, - { typeof(ISlider), typeof(SliderHandler) }, - { typeof(ISwitch), typeof(SwitchHandler) } + { typeof(IButton), typeof(ButtonHandler) }, + { typeof(IEditor), typeof(EditorHandler) }, + { typeof(IEntry), typeof(EntryHandler) }, + { typeof(ILayout), typeof(LayoutHandler) }, + { typeof(ILabel), typeof(LabelHandler) }, + { typeof(IProgress), typeof(ProgressBarHandler) }, + { typeof(ISearchBar), typeof(SearchBarHandler) }, + { typeof(ISlider), typeof(SliderHandler) }, + { typeof(ISwitch), typeof(SwitchHandler) } }); return builder; @@ -59,5 +61,31 @@ public static IAppHostBuilder UseFonts(this IAppHostBuilder builder) }); 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 000000000000..f90226508cd2 --- /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 000000000000..98f81b4f163b --- /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 3cca82cd99fb..f7fea08592c2 100644 --- a/src/Core/src/Hosting/IAppHostBuilder.cs +++ b/src/Core/src/Hosting/IAppHostBuilder.cs @@ -17,6 +17,6 @@ public interface IAppHostBuilder : IHostBuilder 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. - void SetServiceProvider(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 8005b019646c..8d43f328c6bd 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 @@ public async ValueTask DisposeAsync() } } } -} +} \ 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 aec7a6022cb4..19fd5174f70b 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 @@ public IServiceCollection CreateBuilder(IServiceCollection services) 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 a4cfc7c9d6b8..fcec77cbf84c 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 @@ public MauiHandlersServiceProvider(IMauiServiceCollection collection) : base(col 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 5992cc28d295..8e1f1e45ce87 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 @@ public MauiServiceProvider(IMauiServiceCollection collection) 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 @@ public MauiServiceProvider(IMauiServiceCollection collection) 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 2ab95f887fe5..35f50fe00359 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 f3ec261e5f0b..46917604a77b 100644 --- a/src/Core/src/MauiApp.cs +++ b/src/Core/src/MauiApp.cs @@ -1,12 +1,19 @@ +using System; + 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 69dc63b79bff..712f4d1083b6 100644 --- a/src/Core/src/Platform/Android/HandlerExtensions.cs +++ b/src/Core/src/Platform/Android/HandlerExtensions.cs @@ -21,8 +21,7 @@ public static AView ToNative(this IView view, IMauiContext context) 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 3d8e04832914..620fda8aea3c 100644 --- a/src/Core/src/Platform/Android/MauiAppCompatActivity.cs +++ b/src/Core/src/Platform/Android/MauiAppCompatActivity.cs @@ -21,10 +21,10 @@ protected override void OnCreate(Bundle? savedInstanceState) { 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 @@ protected override void OnCreate(Bundle? savedInstanceState) 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 4d6c138221dc..fe4a8a19fdae 100644 --- a/src/Core/src/Platform/Android/MauiApplication.cs +++ b/src/Core/src/Platform/Android/MauiApplication.cs @@ -6,19 +6,16 @@ namespace Microsoft.Maui { - public class MauiApplication : Android.App.Application - where TStartup : IStartup - where TApplication : MauiApp + public class MauiApplication : Android.App.Application + where TStartup : IStartup, new() { public MauiApplication(IntPtr handle, JniHandleOwnership ownerShip) : base(handle, ownerShip) { - } public override void OnCreate() { - if (!(Activator.CreateInstance(typeof(TStartup)) is TStartup startup)) - throw new InvalidOperationException($"We weren't able to create the Startup {typeof(TStartup)}"); + var startup = new TStartup(); var appBuilder = AppHostBuilder .CreateDefaultAppBuilder() @@ -26,12 +23,14 @@ public override void OnCreate() startup.Configure(appBuilder); - appBuilder.Build(); + var host = appBuilder.Build(); + if (host.Services == null) + throw new InvalidOperationException("App was not intialized"); - if (!(Activator.CreateInstance(typeof(TApplication)) is TApplication app)) - throw new InvalidOperationException($"We weren't able to create the App {typeof(TApplication)}"); + var services = host.Services; - appBuilder.SetServiceProvider(app); + var app = services.GetRequiredService(); + host.SetServiceProvider(app); base.OnCreate(); } @@ -39,7 +38,6 @@ public override void OnCreate() // 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 b336d934e3c1..4f7f24e44b91 100644 --- a/src/Core/src/Platform/iOS/HandlerExtensions.cs +++ b/src/Core/src/Platform/iOS/HandlerExtensions.cs @@ -20,6 +20,8 @@ public static UIView ToNative(this IView view, IMauiContext context) 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 78e2a3b3972f..e6010bc7bac4 100644 --- a/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs +++ b/src/Core/src/Platform/iOS/MauiUIApplicationDelegate.cs @@ -7,9 +7,8 @@ namespace Microsoft.Maui { - public class MauiUIApplicationDelegate : UIApplicationDelegate, IUIApplicationDelegate - where TStartup : IStartup - where TApplication : MauiApp + public class MauiUIApplicationDelegate : UIApplicationDelegate, IUIApplicationDelegate + where TStartup : IStartup, new() { public override UIWindow? Window { @@ -19,8 +18,7 @@ public class MauiUIApplicationDelegate : UIApplicationDe public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { - if (!(Activator.CreateInstance(typeof(TStartup)) is TStartup startup)) - throw new InvalidOperationException($"We weren't able to create the Startup {typeof(TStartup)}"); + var startup = new TStartup(); var appBuilder = AppHostBuilder .CreateDefaultAppBuilder() @@ -28,24 +26,20 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l startup.Configure(appBuilder); - appBuilder.Build(); - - if (!(Activator.CreateInstance(typeof(TApplication)) is TApplication app)) - throw new InvalidOperationException($"We weren't able to create the App {typeof(TApplication)}"); + var host = appBuilder.Build(); + if (host.Services == null) + throw new InvalidOperationException("App was not intialized"); - appBuilder.SetServiceProvider(app); + var services = host.Services; - if (app == null || app.Services == null) - throw new InvalidOperationException("App was not intialized"); + var app = services.GetRequiredService(); + host.SetServiceProvider(app); - var mauiContext = new MauiContext(app.Services); + 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.SetHandlerContext(window.MauiContext); - var content = (window.Page as IView) ?? window.Page.View; Window = new UIWindow @@ -63,7 +57,6 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l 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 cb559c4d88dc..b4dabe6f95b0 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 { - AppStub _app; + IAppHost _host; Registrar _registrar; @@ -16,18 +16,9 @@ public class GetHandlersBenchmarker [GlobalSetup(Target = nameof(GetHandlerUsingDI))] public void SetupForDI() { - var startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder(); - - startup.Configure(appBuilder); - - appBuilder.Build(); - - _app = new AppStub(); - - appBuilder.SetServiceProvider(_app); + _host = AppHostBuilder + .CreateDefaultAppBuilder() + .Build(); } [GlobalSetup(Target = nameof(GetHandlerUsingRegistrar))] @@ -42,7 +33,7 @@ public void GetHandlerUsingDI() { for (int i = 0; i < N; i++) { - _app.Context.Handlers.GetHandler(); + _host.Handlers.GetHandler(); } } @@ -55,4 +46,4 @@ public void GetHandlerUsingRegistrar() } } } -} +} \ 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 000000000000..0e284747448c --- /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 b05420a3170d..35b3d3af9fd7 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 eff3e7e3c5fc..5cf2316642e2 100644 --- a/src/Core/tests/Benchmarks/Program.cs +++ b/src/Core/tests/Benchmarks/Program.cs @@ -6,7 +6,8 @@ class Program { 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/StartupStub.cs b/src/Core/tests/Benchmarks/Stubs/StartupStub.cs deleted file mode 100644 index 56affa5739cd..000000000000 --- a/src/Core/tests/Benchmarks/Stubs/StartupStub.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Maui.Hosting; - -namespace Microsoft.Maui.Handlers.Benchmarks -{ - public class StartupStub : IStartup - { - public void Configure(IAppHostBuilder appBuilder) - { - appBuilder - .ConfigureServices(ConfigureNativeServices); - } - - void ConfigureNativeServices(HostBuilderContext ctx, IServiceCollection services) - { - services.AddSingleton(provider => new HandlersContextStub(provider)); - } - } -} \ 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 000000000000..6dee7b46102c --- /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/DeviceTests/Handlers/Button/ButtonHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs index 4ad8eb946a9c..c2efa4224938 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/Editor/EditorHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs index ef3766c2b5c0..081ffc32a767 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 32085afefd83..4ad88dedb156 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 98f511d950dc..911d47dc1881 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs @@ -8,8 +8,7 @@ public partial class HandlerTestBase 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 b1278ebe2eae..08e12f2857bf 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs @@ -2,23 +2,48 @@ 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 3f8629b01cac..add0fa52a286 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs @@ -11,6 +11,8 @@ public partial class HandlerTestBase 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 40076dc7dbec..000000000000 --- 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 de93165e18bb..000000000000 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestFixture.cs +++ /dev/null @@ -1,54 +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() - { - StartupStub startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder() - .ConfigureFonts((ctx, fonts) => - { - fonts.AddFont("dokdo_regular.ttf", "Dokdo"); - }) - .ConfigureServices((ctx, services) => - { - services.AddSingleton(_context); - }); - - startup.Configure(appBuilder); - - _host = appBuilder.Build(); - - _app = new AppStub(); - - appBuilder.SetServiceProvider(_app); - - _context = new ContextStub(_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 24d814ec194a..d5e5c9fa78a4 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 056a75aa6170..5ef3894003a9 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/ProgressBar/ProgressBarTests.cs b/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarTests.cs index d54b4764a999..04c9631229fa 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 6c2f8b8e7354..0b22249f2b77 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 1957123e0ce3..c752debaf02a 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/Switch/SwitchHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.cs index 1569d4dc89df..cf96ee8c4524 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/Stubs/AppStub.cs b/src/Core/tests/DeviceTests/Stubs/AppStub.cs index 59b1be8a88db..3ae8ad7fe95f 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) - { - return new WindowStub(); - } - - 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 b4e68665fa79..75ab5aec3c46 100644 --- a/src/Core/tests/DeviceTests/Stubs/ContextStub.cs +++ b/src/Core/tests/DeviceTests/Stubs/ContextStub.cs @@ -3,29 +3,20 @@ 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/DeviceTests/Stubs/StartupStub.cs b/src/Core/tests/DeviceTests/Stubs/StartupStub.cs deleted file mode 100644 index b5f3a3e0a7ee..000000000000 --- a/src/Core/tests/DeviceTests/Stubs/StartupStub.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Maui.Hosting; - -namespace Microsoft.Maui.DeviceTests.Stubs -{ - public class StartupStub : IStartup - { - public void Configure(IAppHostBuilder appBuilder) - { - - } - } -} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Stubs/WindowStub.cs b/src/Core/tests/DeviceTests/Stubs/WindowStub.cs deleted file mode 100644 index 90b7764a7d67..000000000000 --- a/src/Core/tests/DeviceTests/Stubs/WindowStub.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Microsoft.Maui.DeviceTests.Stubs -{ - 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/UnitTests/ApplicationTests.cs b/src/Core/tests/UnitTests/ApplicationTests.cs deleted file mode 100644 index 0e9420efd939..000000000000 --- a/src/Core/tests/UnitTests/ApplicationTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Microsoft.Maui.Tests; -using Xunit; - -namespace Microsoft.Maui.UnitTests -{ - [Category(TestCategory.Core, TestCategory.Lifecycle)] - public class ApplicationTests : IDisposable - { - [Fact] - public void CanCreateApplication() - { - var application = new AppStub(); - - Assert.NotNull(App.Current); - Assert.Equal(App.Current, application); - } - - [Fact] - public void ShouldntCreateMultipleApp() - { - var application = new AppStub(); - - Assert.Throws(() => new AppStub()); - } - - public void Dispose() - { - (App.Current as AppStub)?.ClearApp(); - } - } -} \ 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 000000000000..64e290ff5ef0 --- /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 000000000000..f56b69a793ca --- /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 ae43d877ae56..000000000000 --- a/src/Core/tests/UnitTests/HostBuilderTests.cs +++ /dev/null @@ -1,228 +0,0 @@ -using Microsoft.Maui.Handlers; -using System; -using System.Collections.Generic; -using Microsoft.Maui.Tests; -using Microsoft.Maui.Hosting; -using Xunit; - -namespace Microsoft.Maui.UnitTests -{ - [Category(TestCategory.Core, TestCategory.Hosting)] - public class HostBuilderTests : IDisposable - { - [Fact] - public void CanBuildAHost() - { - var host = AppHostBuilder.CreateDefaultAppBuilder().Build(); - Assert.NotNull(host); - } - - [Fact] - public void CanGetServices() - { - var application = CreateDefaultApp(); - - Assert.NotNull(application.Services); - } - - [Fact] - public void CanGetStaticServices() - { - var application = CreateDefaultApp(); - - Assert.NotNull(App.Current.Services); - Assert.Equal(application.Services, App.Current.Services); - } - - [Fact] - public void HandlerContextNullBeforeBuild() - { - var application = CreateDefaultApp(false); - - Assert.NotNull(application); - - var handlerContext = App.Current.Context; - - Assert.Null(handlerContext); - } - - [Fact] - public void HandlerContextAfterBuild() - { - var application = CreateDefaultApp(); - - Assert.NotNull(application); - - var handlerContext = App.Current.Context; - - Assert.NotNull(handlerContext); - } - - [Fact] - public void CanHandlerProviderContext() - { - var application = CreateDefaultApp(); - - Assert.NotNull(application); - - var handlerContext = App.Current.Context; - - Assert.IsAssignableFrom(handlerContext.Handlers); - } - - [Fact] - public void CanRegisterAndGetHandler() - { - var startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder() - .RegisterHandler(); - - startup.Configure(appBuilder); - - appBuilder.Build(); - - var application = new AppStub(); - - appBuilder.SetServiceProvider(application); - - Assert.NotNull(application); - - var handler = App.Current.Context.Handlers.GetHandler(typeof(IViewStub)); - - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void CanRegisterAndGetHandlerWithDictionary() - { - var startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder() - .RegisterHandlers(new Dictionary - { - { typeof(IViewStub), typeof(ViewHandlerStub) } - }); - - startup.Configure(appBuilder); - - appBuilder.Build(); - - var application = new AppStub(); - - appBuilder.SetServiceProvider(application); - - Assert.NotNull(application); - - var handler = App.Current.Context.Handlers.GetHandler(typeof(IViewStub)); - - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void CanRegisterAndGetHandlerForType() - { - var startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder() - .RegisterHandler(); - - startup.Configure(appBuilder); - - appBuilder.Build(); - - var application = new AppStub(); - - appBuilder.SetServiceProvider(application); - - Assert.NotNull(application); - - var handler = App.Current.Context.Handlers.GetHandler(typeof(ViewStub)); - - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void DefaultHandlersAreRegistered() - { - var startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder(); - - startup.Configure(appBuilder); - - appBuilder.Build(); - - var application = new AppStub(); - - appBuilder.SetServiceProvider(application); - - Assert.NotNull(application); - - var handler = App.Current.Context.Handlers.GetHandler(typeof(IButton)); - - Assert.NotNull(handler); - Assert.IsType(handler); - } - - [Fact] - public void CanSpecifyHandler() - { - var startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder() - .RegisterHandler(); - - startup.Configure(appBuilder); - - appBuilder.Build(); - - var application = new AppStub(); - - appBuilder.SetServiceProvider(application); - - Assert.NotNull(application); - - var defaultHandler = App.Current.Context.Handlers.GetHandler(typeof(IButton)); - var specificHandler = App.Current.Context.Handlers.GetHandler(typeof(ButtonStub)); - - Assert.NotNull(defaultHandler); - Assert.NotNull(specificHandler); - Assert.IsType(defaultHandler); - Assert.IsType(specificHandler); - } - - public void Dispose() - { - (App.Current as AppStub)?.ClearApp(); - } - - internal AppStub CreateDefaultApp(bool build = true) - { - var startup = new StartupStub(); - - var appBuilder = AppHostBuilder - .CreateDefaultAppBuilder(); - - startup.Configure(appBuilder); - - if (build) - appBuilder.Build(); - - var application = new AppStub(); - - if (build) - appBuilder.SetServiceProvider(application); - - return application; - } - } -} \ No newline at end of file diff --git a/src/Core/tests/UnitTests/TestClasses/AppStub.cs b/src/Core/tests/UnitTests/TestClasses/AppStub.cs index 043dfd2eed2e..31c879f4fda8 100644 --- a/src/Core/tests/UnitTests/TestClasses/AppStub.cs +++ b/src/Core/tests/UnitTests/TestClasses/AppStub.cs @@ -1,17 +1,6 @@ -using Microsoft.Maui.UnitTests.TestClasses; - namespace Microsoft.Maui.Tests { - class AppStub : MauiApp + class AppStub : App { - 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/StartupStub.cs b/src/Core/tests/UnitTests/TestClasses/StartupStub.cs deleted file mode 100644 index 87709f740b3b..000000000000 --- a/src/Core/tests/UnitTests/TestClasses/StartupStub.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Maui.Hosting; - -namespace Microsoft.Maui.Tests -{ - public class StartupStub : IStartup - { - public void Configure(IAppHostBuilder appBuilder) - { - appBuilder.ConfigureServices(ConfigureServices); - } - - public void ConfigureServices(HostBuilderContext ctx, IServiceCollection services) - { - services.AddSingleton(provider => new HandlersContextStub(provider)); - services.AddTransient(); - } - } -} \ 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 16cdf5186fd1..26f93aca9a8b 100644 --- a/src/Core/tests/UnitTests/TestClasses/TestServices.cs +++ b/src/Core/tests/UnitTests/TestClasses/TestServices.cs @@ -8,6 +8,10 @@ interface IBarService { } + interface IFooBarService + { + } + class FooService : IFooService { } @@ -15,4 +19,54 @@ class FooService : IFooService 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 b39c45f912bf..7bc3d848405e 100644 --- a/src/Core/tests/UnitTests/TestClasses/ViewHandlerStub.cs +++ b/src/Core/tests/UnitTests/TestClasses/ViewHandlerStub.cs @@ -13,7 +13,7 @@ public ViewHandlerStub() : base(MockViewMapper) } - 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 03c76cf3b04a..000000000000 --- a/src/Core/tests/UnitTests/TestClasses/WindowStub.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Microsoft.Maui.UnitTests.TestClasses -{ - class WindowStub : IWindow - { - public IMauiContext MauiContext { get; set; } - public IPage Page { get; set; } - } -} \ No newline at end of file