* Implement IStartup

* Clean up code

* Move the static Current into MauiApp

* Add missing SearchBar handler mapping

* Don't need this anymore

* Pass the handler along on iOS

* Lots more perf fixes

* Remove App.SetHandlerContext

* Fix tests

* Fix catalyst

* revert

* Fixed build errors

* Fix the updated SearchBar

* Fix net6 single project

* Add IHostBuilderStartup

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
This commit is contained in:
Javier Suárez 2021-03-22 15:45:42 +01:00 коммит произвёл GitHub
Родитель 4cfc221d5c
Коммит d4a3de27fc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
96 изменённых файлов: 1128 добавлений и 733 удалений

Просмотреть файл

@ -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);

Просмотреть файл

@ -12,15 +12,15 @@ namespace Microsoft.Maui.Controls.Compatibility
var defaultHandlers = new List<Type>
{
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(

Просмотреть файл

@ -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
{

Просмотреть файл

@ -6,7 +6,7 @@ using Microsoft.Maui;
namespace Maui.Controls.Sample.Droid
{
[Application]
public class MainApplication : MauiApplication<MyApp>
public class MainApplication : MauiApplication<Startup>
{
public MainApplication(IntPtr handle, JniHandleOwnership ownerShip) : base(handle, ownerShip)
{

Просмотреть файл

@ -11,7 +11,7 @@ using Maui.Controls.Sample;
namespace Sample.MacCatalyst
{
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate<MyApp>
public class AppDelegate : MauiUIApplicationDelegate<Startup>
{
}
}

Просмотреть файл

@ -6,6 +6,5 @@ namespace Maui.Controls.Sample.SingleProject
[Activity(Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
public class MainActivity : MauiAppCompatActivity
{
}
}

Просмотреть файл

@ -6,9 +6,9 @@ using Microsoft.Maui;
namespace Maui.Controls.Sample.SingleProject
{
[Application]
public class MainApplication : MauiApplication<MyApp>
public class MainApplication : MauiApplication<Startup>
{
public MainApplication(IntPtr handle, JniHandleOwnership ownerShip) : base(handle, ownerShip)
public MainApplication(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership)
{
}
}

Просмотреть файл

@ -4,8 +4,7 @@ using Microsoft.Maui;
namespace Maui.Controls.Sample.SingleProject
{
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate<MyApp>
public class AppDelegate : MauiUIApplicationDelegate<Startup>
{
}
}

Просмотреть файл

@ -16,4 +16,4 @@ namespace Maui.Controls.Sample.SingleProject
public IView View { get => (IView)Content; set => Content = (View)value; }
}
}
}

Просмотреть файл

@ -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<MainPage>();
Page = new MainPage();
}
public IPage Page { get; set; }
public IMauiContext MauiContext { get; set; }
}
}
}

Просмотреть файл

@ -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<MainPage>();
services.AddTransient<IWindow, MainWindow>();
});
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<IWindow>();
return new MainWindow();
}
}
}

Просмотреть файл

@ -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<MyApp>();
}
}
}

Просмотреть файл

@ -4,8 +4,7 @@ using Microsoft.Maui;
namespace Maui.Controls.Sample.SingleProject
{
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate<MyApp>
public class AppDelegate : MauiUIApplicationDelegate<Startup>
{
}
}

Просмотреть файл

@ -11,7 +11,7 @@ using Maui.Controls.Sample;
namespace Sample.iOS
{
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate<MyApp>
public class AppDelegate : MauiUIApplicationDelegate<Startup>
{
}
}

Просмотреть файл

@ -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<IPage>())
{
}
public MainWindow(IPage page)
{
Page = page;
}
}
}
}

Просмотреть файл

@ -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<string, string>
{
{ "MyKey", "Dictionary MyKey Value" },
{ ":Title", "Dictionary_Title" },
{ "Position:Name", "Dictionary_Name" },
{ "Logging:LogLevel:Default", "Warning" }
});
})
.ConfigureServices((hostingContext, services) =>
{
services.AddSingleton<ITextService, TextService>();
services.AddTransient<MainPageViewModel>();
if (UseXamlPage)
services.AddTransient<IPage, XamlPage>();
else
services.AddTransient<IPage, MainPage>();
services.AddTransient<IWindow, MainWindow>();
})
.ConfigureFonts((hostingContext, fonts) =>
{
fonts.AddFont("dokdo_regular.ttf", "Dokdo");
});
//IAppState state
public override IWindow CreateWindow(IActivationState state)
{
Forms.Init(state);
return Services.GetRequiredService<IWindow>();
}
}
//to use DI ServiceCollection and not the MAUI one
public class DIExtensionsServiceProviderFactory : IServiceProviderFactory<ServiceCollection>
{
public ServiceCollection CreateBuilder(IServiceCollection services)
=> new ServiceCollection { services };
public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder)
=> containerBuilder.BuildServiceProvider();
}
}

Просмотреть файл

@ -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<MainPageViewModel>())
{
}
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()

Просмотреть файл

@ -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<MyApp>()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string>
{
{"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<ITextService, TextService>();
services.AddTransient<MainPageViewModel>();
if (UseXamlPage)
services.AddTransient<IPage, XamlPage>();
else
services.AddTransient<IPage, MainPage>();
services.AddTransient<IWindow, MainWindow>();
})
.ConfigureFonts((hostingContext, fonts) =>
{
fonts.AddFont("dokdo_regular.ttf", "Dokdo");
});
}
// To use DI ServiceCollection and not the MAUI one
public class DIExtensionsServiceProviderFactory : IServiceProviderFactory<ServiceCollection>
{
public ServiceCollection CreateBuilder(IServiceCollection services)
=> new ServiceCollection { services };
public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder)
=> containerBuilder.BuildServiceProvider();
}
}
}

Просмотреть файл

@ -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<ITextService>() })
{
}
public MainPageViewModel(IEnumerable<ITextService> textServices)
{
Configuration = App.Current.Services.GetService<IConfiguration>();
//var logger = App.Current.Services.GetService<ILogger<MainPageViewModel>>();
//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);
}
}
}
}

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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<IMauiContext>());
}
internal void SetHandlerContext(IMauiContext? context)
{
_context = context;
}
public static IAppHostBuilder CreateDefaultBuilder()
{
var builder = new AppHostBuilder();
builder.UseMauiHandlers();
builder.UseFonts();
return builder;
Services = provider;
}
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
using Microsoft.Maui.Hosting;
namespace Microsoft.Maui
{
/// <summary>
/// Startup allow to configure and instantiate application services.
/// </summary>
public interface IStartup
{
/// <summary>
/// Configures the application.
/// Configure are called by the .NET MAUI Core runtime when the app starts.
/// </summary>
/// <param name="appBuilder">Defines a class that provides the mechanisms to configure an application's dependencies.</param>
void Configure(IAppHostBuilder appBuilder);
}
/// <summary>
/// Allow to create a custom IAppHostBuilder instance.
/// </summary>
public interface IHostBuilderStartup
{
/// <summary>
/// Create and configure a builder object.
/// </summary>
/// <returns>The new instance of the IAppHostBuilder.</returns>
public IAppHostBuilder CreateHostBuilder();
}
}

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.TypedNativeView?.UpdateFont(button, fontManager);
}

Просмотреть файл

@ -15,7 +15,7 @@ namespace Microsoft.Maui.Handlers
}
public ButtonHandler(PropertyMapper mapper) : base(mapper ?? ButtonMapper)
public ButtonHandler(PropertyMapper? mapper = null) : base(mapper ?? ButtonMapper)
{
}
}

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.TypedNativeView?.UpdateFont(button, fontManager);
}

Просмотреть файл

@ -14,7 +14,7 @@
}
public EditorHandler(PropertyMapper mapper) : base(mapper ?? EditorMapper)
public EditorHandler(PropertyMapper? mapper = null) : base(mapper ?? EditorMapper)
{
}

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.TypedNativeView?.UpdateFont(entry, fontManager);
}

Просмотреть файл

@ -20,7 +20,7 @@
}
public EntryHandler(PropertyMapper mapper) : base(mapper ?? EntryMapper)
public EntryHandler(PropertyMapper? mapper = null) : base(mapper ?? EntryMapper)
{
}

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.TypedNativeView?.UpdateFont(entry, fontManager);
}

Просмотреть файл

@ -5,8 +5,6 @@ namespace Microsoft.Maui
{
public interface IAndroidViewHandler : IViewHandler
{
void SetContext(Context context);
AView? View { get; }
}
}

Просмотреть файл

@ -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();

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.TypedNativeView?.UpdateFont(label, fontManager);
}

Просмотреть файл

@ -21,7 +21,7 @@ namespace Microsoft.Maui.Handlers
}
public LabelHandler(PropertyMapper mapper) : base(mapper ?? LabelMapper)
public LabelHandler(PropertyMapper? mapper = null) : base(mapper ?? LabelMapper)
{
}

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.TypedNativeView?.UpdateFont(label, fontManager);
}

Просмотреть файл

@ -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)

Просмотреть файл

@ -15,7 +15,7 @@ namespace Microsoft.Maui.Handlers
}
public LayoutHandler(PropertyMapper mapper) : base(mapper ?? LayoutMapper)
public LayoutHandler(PropertyMapper? mapper = null) : base(mapper ?? LayoutMapper)
{
}

Просмотреть файл

@ -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();
}

Просмотреть файл

@ -17,7 +17,7 @@
}
public ProgressBarHandler(PropertyMapper mapper) : base(mapper ?? ProgressMapper)
public ProgressBarHandler(PropertyMapper? mapper = null) : base(mapper ?? ProgressMapper)
{
}

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.TypedNativeView?.UpdateFont(searchBar, fontManager, handler._editText);
}

Просмотреть файл

@ -16,7 +16,7 @@
}
public SearchBarHandler(PropertyMapper mapper) : base(mapper ?? SearchBarMapper)
public SearchBarHandler(PropertyMapper? mapper = null) : base(mapper ?? SearchBarMapper)
{
}

Просмотреть файл

@ -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<IFontManager>();
_ = handler.Services ?? throw new InvalidOperationException($"{nameof(Services)} should have been set by base class.");
var fontManager = handler.Services.GetRequiredService<IFontManager>();
handler.QueryEditor?.UpdateFont(searchBar, fontManager);
}

Просмотреть файл

@ -17,7 +17,7 @@ namespace Microsoft.Maui.Handlers
}
public SliderHandler(PropertyMapper mapper) : base(mapper ?? SliderMapper)
public SliderHandler(PropertyMapper? mapper = null) : base(mapper ?? SliderMapper)
{
}

Просмотреть файл

@ -14,7 +14,7 @@ namespace Microsoft.Maui.Handlers
}
public SwitchHandler(PropertyMapper mapper) : base(mapper ?? SwitchMapper)
public SwitchHandler(PropertyMapper? mapper = null) : base(mapper ?? SwitchMapper)
{
}

Просмотреть файл

@ -6,9 +6,7 @@ namespace Microsoft.Maui.Handlers
{
public partial class AbstractViewHandler<TVirtualView, TNativeView> : IAndroidViewHandler
{
public void SetContext(Context context) => Context = context;
public Context? Context { get; private set; }
public Context? Context => MauiContext?.Context;
public void SetFrame(Rectangle frame)
{

Просмотреть файл

@ -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));

Просмотреть файл

@ -18,7 +18,9 @@ namespace Microsoft.Maui.Hosting
readonly List<Action<HostBuilderContext, IFontCollection>> _configureFontsActions = new List<Action<HostBuilderContext, IFontCollection>>();
readonly List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();
readonly Func<IServiceCollection> _serviceColectionFactory = new Func<IServiceCollection>(() => new MauiServiceCollection());
IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new MauiServiceProviderFactory());
IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(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<object, object> Properties => new Dictionary<object, object>();
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<TContainerBuilder>(configureDelegate);
}
IHost IHostBuilder.Build()
{
return Build();
}
}
}
}

Просмотреть файл

@ -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<TApp>(this IAppHostBuilder builder)
where TApp : MauiApp
{
builder.ConfigureServices((context, collection) =>
{
collection.AddSingleton<MauiApp, TApp>();
});
return builder;
}
public static IAppHostBuilder UseMauiApp<TApp>(this IAppHostBuilder builder, Func<IServiceProvider, TApp> implementationFactory)
where TApp : MauiApp
{
builder.ConfigureServices((context, collection) =>
{
collection.AddSingleton<MauiApp>(implementationFactory);
});
return builder;
}
public static IAppHostBuilder UseMauiServiceProviderFactory(this IAppHostBuilder builder, bool constructorInjection)
{
builder.UseServiceProviderFactory(new MauiServiceProviderFactory(constructorInjection));
return builder;
}
}
}
}

Просмотреть файл

@ -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);
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
using Microsoft.Extensions.Hosting;
namespace Microsoft.Maui.Hosting
{
public interface IAppHost : IHost
{
IMauiHandlersServiceProvider Handlers { get; }
}
}

Просмотреть файл

@ -17,6 +17,6 @@ namespace Microsoft.Maui.Hosting
new IAppHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
new IAppHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> 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();
}
}
}

Просмотреть файл

@ -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<AppHost>? _logger;
public AppHost(IServiceProvider services, ILogger<AppHost>? logger)
{
Services = services ?? throw new ArgumentNullException(nameof(services));
Handlers = Services.GetRequiredService<IMauiHandlersServiceProvider>();
_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
}
}
}
}
}

Просмотреть файл

@ -5,6 +5,13 @@ namespace Microsoft.Maui.Hosting.Internal
{
internal class MauiServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
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)}");
}
}
}
}

Просмотреть файл

@ -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<T>() where T : IView
=> GetHandler(typeof(T));
}
}
}

Просмотреть файл

@ -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<ServiceDescriptor, object?> _singletons;
readonly IDictionary<ServiceDescriptor, object?> _singletons;
public MauiServiceProvider(IMauiServiceCollection collection)
public MauiServiceProvider(IMauiServiceCollection collection, bool constructorInjection)
{
_collection = collection ?? throw new ArgumentNullException(nameof(collection));
_constructorInjection = constructorInjection;
_singletons = new ConcurrentDictionary<ServiceDescriptor, object?>();
}
@ -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);
}
}
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);
}
}
}

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<TApplication> : global::Android.App.Application where TApplication : MauiApp
public class MauiApplication<TStartup> : 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<MauiApp>();
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)
{
}
}
}
}

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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<TApplication> : UIApplicationDelegate, IUIApplicationDelegate where TApplication : MauiApp
public class MauiUIApplicationDelegate<TStartup> : 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<MauiApp>();
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)
{
}
}
}
}

Просмотреть файл

@ -6,7 +6,7 @@ namespace Microsoft.Maui.Handlers.Benchmarks
[MemoryDiagnoser]
public class GetHandlersBenchmarker
{
MockApp _app;
IAppHost _host;
Registrar<IFrameworkElement, IViewHandler> _registrar;
@ -16,10 +16,9 @@ namespace Microsoft.Maui.Handlers.Benchmarks
[GlobalSetup(Target = nameof(GetHandlerUsingDI))]
public void SetupForDI()
{
_app = new MockApp();
_app.CreateBuilder()
.RegisterHandler<IButton, ButtonHandler>()
.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<IButton>();
_host.Handlers.GetHandler<IButton>();
}
}
@ -47,4 +46,4 @@ namespace Microsoft.Maui.Handlers.Benchmarks
}
}
}
}
}

Просмотреть файл

@ -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<IFooService, FooService>())
.Build();
}
[IterationSetup(Target = nameof(DefaultBuilderWithConstructorInjection))]
public void SetupForDefaultBuilderWithConstructorInjection()
{
_host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, svc) => svc.AddTransient<IFooService, FooService>())
.Build();
}
[IterationSetup(Target = nameof(OneConstructorParameter))]
public void SetupForOneConstructorParameter()
{
_host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, svc) =>
{
svc.AddTransient<IFooService, FooService>();
svc.AddTransient<IFooBarService, FooBarWithFooService>();
})
.Build();
}
[IterationSetup(Target = nameof(TwoConstructorParameters))]
public void SetupForTwoConstructorParameters()
{
_host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, svc) =>
{
svc.AddTransient<IFooService, FooService>();
svc.AddTransient<IBarService, BarService>();
svc.AddTransient<IFooBarService, FooBarWithFooAndBarService>();
})
.Build();
}
[IterationSetup(Target = nameof(ExtensionsWithConstructorInjection))]
public void SetupForExtensionsWithConstructorInjection()
{
_host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseServiceProviderFactory(new DIExtensionsServiceProviderFactory())
.ConfigureServices((ctx, svc) => svc.AddTransient<IFooService, FooService>())
.Build();
}
[IterationSetup(Target = nameof(ExtensionsWithOneConstructorParameter))]
public void SetupForExtensionsWithOneConstructorParameter()
{
_host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseServiceProviderFactory(new DIExtensionsServiceProviderFactory())
.ConfigureServices((ctx, svc) =>
{
svc.AddTransient<IFooService, FooService>();
svc.AddTransient<IFooBarService, FooBarWithFooService>();
})
.Build();
}
[IterationSetup(Target = nameof(ExtensionsWithTwoConstructorParameters))]
public void SetupForExtensionsWithTwoConstructorParameters()
{
_host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseServiceProviderFactory(new DIExtensionsServiceProviderFactory())
.ConfigureServices((ctx, svc) =>
{
svc.AddTransient<IFooService, FooService>();
svc.AddTransient<IBarService, BarService>();
svc.AddTransient<IFooBarService, FooBarWithFooAndBarService>();
})
.Build();
}
[Benchmark(Baseline = true)]
public void DefaultBuilder()
{
for (int i = 0; i < N; i++)
{
_host.Services.GetService<IFooService>();
}
}
[Benchmark]
public void DefaultBuilderWithConstructorInjection()
{
for (int i = 0; i < N; i++)
{
_host.Services.GetService<IFooService>();
}
}
[Benchmark]
public void OneConstructorParameter()
{
for (int i = 0; i < N; i++)
{
_host.Services.GetService<IFooBarService>();
}
}
[Benchmark]
public void TwoConstructorParameters()
{
for (int i = 0; i < N; i++)
{
_host.Services.GetService<IFooBarService>();
}
}
[Benchmark]
public void ExtensionsWithConstructorInjection()
{
for (int i = 0; i < N; i++)
{
_host.Services.GetService<IFooService>();
}
}
[Benchmark]
public void ExtensionsWithOneConstructorParameter()
{
for (int i = 0; i < N; i++)
{
_host.Services.GetService<IFooBarService>();
}
}
[Benchmark]
public void ExtensionsWithTwoConstructorParameters()
{
for (int i = 0; i < N; i++)
{
_host.Services.GetService<IFooBarService>();
}
}
public class DIExtensionsServiceProviderFactory : IServiceProviderFactory<ServiceCollection>
{
public ServiceCollection CreateBuilder(IServiceCollection services)
=> new ServiceCollection { services };
public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder)
=> containerBuilder.BuildServiceProvider();
}
}
}

Просмотреть файл

@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -6,7 +6,8 @@ namespace Microsoft.Maui.Handlers.Benchmarks
{
static void Main(string[] args)
{
//BenchmarkRunner.Run<MauiServiceProviderBenchmarker>();
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).RunAllJoined();
}
}
}
}

Просмотреть файл

@ -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<IMauiContext>(provider => new HandlersContextStub(provider));
return new WindowStub();
}
}
}
}

Просмотреть файл

@ -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; }
}
}

Просмотреть файл

@ -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; }
}
}

Просмотреть файл

@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.ActivityIndicator)]
public partial class ActivityIndicatorHandlerTests : HandlerTestBase<ActivityIndicatorHandler, ActivityIndicatorStub>
{
public ActivityIndicatorHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Theory(DisplayName = "IsRunning Initializes Correctly")]
[InlineData(true)]
[InlineData(false)]

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Button)]
public partial class ButtonHandlerTests : HandlerTestBase<ButtonHandler, ButtonStub>
{
public ButtonHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Text Initializes Correctly")]
public async Task TextInitializesCorrectly()
{

Просмотреть файл

@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.CheckBox)]
public partial class CheckBoxHandlerTests : HandlerTestBase<CheckBoxHandler, CheckBoxStub>
{
public CheckBoxHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Theory(DisplayName = "IsChecked Initializes Correctly")]
[InlineData(true)]
[InlineData(false)]

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Editor)]
public partial class EditorHandlerTests : HandlerTestBase<EditorHandler, EditorStub>
{
public EditorHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Text Initializes Correctly")]
public async Task TextInitializesCorrectly()
{

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Entry)]
public partial class EntryHandlerTests : HandlerTestBase<EntryHandler, EntryStub>
{
public EntryHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Text Initializes Correctly")]
public async Task TextInitializesCorrectly()
{

Просмотреть файл

@ -8,8 +8,7 @@ namespace Microsoft.Maui.DeviceTests
protected THandler CreateHandler(IView view)
{
var handler = Activator.CreateInstance<THandler>();
if (handler is IAndroidViewHandler av)
av.SetContext(DefaultContext);
handler.SetMauiContext(MauiContext);
handler.SetVirtualView(view);
view.Handler = handler;

Просмотреть файл

@ -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<THandler, TStub> : TestBase
public partial class HandlerTestBase<THandler, TStub> : 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<T> InvokeOnMainThreadAsync<T>(Func<T> func) =>
MainThread.InvokeOnMainThreadAsync(func);

Просмотреть файл

@ -11,6 +11,8 @@ namespace Microsoft.Maui.DeviceTests
protected THandler CreateHandler(IView view)
{
var handler = Activator.CreateInstance<THandler>();
handler.SetMauiContext(MauiContext);
handler.SetVirtualView(view);
view.Handler = handler;

Просмотреть файл

@ -1,9 +0,0 @@
using Xunit;
namespace Microsoft.Maui.DeviceTests
{
[CollectionDefinition(TestCollections.Handlers)]
public class HandlerTestCollection : ICollectionFixture<HandlerTestFixture>
{
}
}

Просмотреть файл

@ -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;
}
}

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Label)]
public partial class LabelHandlerTests : HandlerTestBase<LabelHandler, LabelStub>
{
public LabelHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Background Color Initializes Correctly")]
public async Task BackgroundColorInitializesCorrectly()
{

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests.Handlers.Layout
[Category(TestCategory.Layout)]
public partial class LayoutHandlerTests : HandlerTestBase<LayoutHandler, LayoutStub>
{
public LayoutHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Empty layout")]
public async Task EmptyLayout()
{

Просмотреть файл

@ -6,8 +6,5 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Picker)]
public partial class PickerHandlerTests : HandlerTestBase<PickerHandler, PickerStub>
{
public PickerHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
}
}

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests
[Category("ProgressBarHandler")]
public partial class ProgressBarHandlerTests : HandlerTestBase<ProgressBarHandler, ProgressBarStub>
{
public ProgressBarHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Theory(DisplayName = "Progress Initializes Correctly")]
[InlineData(0.25)]
[InlineData(0.5)]

Просмотреть файл

@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.SearchBar)]
public partial class SearchBarHandlerTests : HandlerTestBase<SearchBarHandler, SearchBarStub>
{
public SearchBarHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Text Initializes Correctly")]
public async Task TextInitializesCorrectly()
{

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Slider)]
public partial class SliderHandlerTests : HandlerTestBase<SliderHandler, SliderStub>
{
public SliderHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Theory(DisplayName = "Percent Value Initializes Correctly")]
[InlineData(0, 1, 0)]
[InlineData(0, 1, 0.5)]

Просмотреть файл

@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Stepper)]
public partial class StepperHandlerTests : HandlerTestBase<StepperHandler, StepperStub>
{
public StepperHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Is Value Initializes Correctly")]
public async Task ValueInitializesCorrectly()
{

Просмотреть файл

@ -9,10 +9,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.Switch)]
public partial class SwitchHandlerTests : HandlerTestBase<SwitchHandler, SwitchStub>
{
public SwitchHandlerTests(HandlerTestFixture fixture) : base(fixture)
{
}
[Fact(DisplayName = "Is Toggled Initializes Correctly")]
public async Task IsToggledInitializesCorrectly()
{

Просмотреть файл

@ -8,10 +8,6 @@ namespace Microsoft.Maui.DeviceTests
[Category(TestCategory.TimePicker)]
public partial class TimePickerHandlerTests : HandlerTestBase<TimePickerHandler, TimePickerStub>
{
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);
}
}
}
}

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -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<IMauiHandlersServiceProvider>();
#if __ANDROID__
public Android.Content.Context Context =>
Android.App.Application.Context;
public Android.Content.Context Context => Platform.DefaultContext;
#endif
public void Dispose()
{
_app = null;
}
}
}

Просмотреть файл

@ -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<MauiHandlersServiceProvider>(host.Handlers);
Assert.Equal(host.Handlers, host.Services.GetService<IMauiHandlersServiceProvider>());
}
[Fact]
public void CanRegisterAndGetHandler()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.RegisterHandler<IViewStub, ViewHandlerStub>()
.Build();
var handler = host.Handlers.GetHandler(typeof(IViewStub));
Assert.NotNull(handler);
Assert.IsType<ViewHandlerStub>(handler);
}
[Fact]
public void CanRegisterAndGetHandlerWithDictionary()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.RegisterHandlers(new Dictionary<Type, Type>
{
{ typeof(IViewStub), typeof(ViewHandlerStub) }
})
.Build();
var handler = host.Handlers.GetHandler(typeof(IViewStub));
Assert.NotNull(handler);
Assert.IsType<ViewHandlerStub>(handler);
}
[Fact]
public void CanRegisterAndGetHandlerForType()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.RegisterHandler<IViewStub, ViewHandlerStub>()
.Build();
var handler = host.Handlers.GetHandler(typeof(ViewStub));
Assert.NotNull(handler);
Assert.IsType<ViewHandlerStub>(handler);
}
[Fact]
public void DefaultHandlersAreRegistered()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.Build();
var handler = host.Handlers.GetHandler(typeof(IButton));
Assert.NotNull(handler);
Assert.IsType<ButtonHandler>(handler);
}
[Fact]
public void CanSpecifyHandler()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.RegisterHandler<ButtonStub, ButtonHandlerStub>()
.Build();
var defaultHandler = host.Handlers.GetHandler(typeof(IButton));
var specificHandler = host.Handlers.GetHandler(typeof(ButtonStub));
Assert.NotNull(defaultHandler);
Assert.NotNull(specificHandler);
Assert.IsType<ButtonHandler>(defaultHandler);
Assert.IsType<ButtonHandlerStub>(specificHandler);
}
}
}

Просмотреть файл

@ -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<IFooBarService, FooBarService>())
.Build();
Assert.Throws<InvalidOperationException>(() => host.Services.GetService<IFooBarService>());
}
[Fact]
public void GetServiceThrowsOnMultipleConstructors()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, services) => services.AddTransient<IFooBarService, FooDualConstructor>())
.Build();
var ex = Assert.Throws<InvalidOperationException>(() => host.Services.GetService<IFooBarService>());
Assert.Contains("IFooService", ex.Message);
}
[Fact]
public void GetServiceCanReturnTypesThatHaveConstructorParams()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, services) =>
{
services.AddTransient<IFooService, FooService>();
services.AddTransient<IBarService, BarService>();
services.AddTransient<IFooBarService, FooBarService>();
})
.Build();
var foobar = host.Services.GetService<IFooBarService>();
Assert.NotNull(foobar);
Assert.IsType<FooBarService>(foobar);
}
[Fact]
public void GetServiceCanReturnTypesThatHaveUnregisteredConstructorParamsButHaveDefaultValues()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, services) =>
{
services.AddTransient<IFooBarService, FooDefaultValueConstructor>();
})
.Build();
var foo = host.Services.GetService<IFooBarService>();
Assert.NotNull(foo);
var actual = Assert.IsType<FooDefaultValueConstructor>(foo);
Assert.Null(actual.Bar);
}
[Fact]
public void GetServiceCanReturnTypesThatHaveRegisteredConstructorParamsAndHaveDefaultValues()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, services) =>
{
services.AddTransient<IBarService, BarService>();
services.AddTransient<IFooBarService, FooDefaultValueConstructor>();
})
.Build();
var foo = host.Services.GetService<IFooBarService>();
Assert.NotNull(foo);
var actual = Assert.IsType<FooDefaultValueConstructor>(foo);
Assert.NotNull(actual.Bar);
Assert.IsType<BarService>(actual.Bar);
}
[Fact]
public void GetServiceCanReturnTypesThatHaveSystemDefaultValues()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.UseMauiServiceProviderFactory(true)
.ConfigureServices((ctx, services) =>
{
services.AddTransient<IFooBarService, FooDefaultSystemValueConstructor>();
})
.Build();
var foo = host.Services.GetService<IFooBarService>();
Assert.NotNull(foo);
var actual = Assert.IsType<FooDefaultSystemValueConstructor>(foo);
Assert.Equal("Default Value", actual.Text);
}
[Fact]
public void WillRetrieveDifferentTransientServices()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.ConfigureServices((ctx, services) => services.AddTransient<IFooService, FooService>())
.Build();
AssertTransient<IFooService, FooService>(host.Services);
}
[Fact]
public void WillRetrieveSameSingletonServices()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.ConfigureServices((ctx, services) => services.AddSingleton<IFooService, FooService>())
.Build();
AssertSingleton<IFooService, FooService>(host.Services);
}
[Fact]
public void WillRetrieveMixedServices()
{
var host = AppHostBuilder
.CreateDefaultAppBuilder()
.ConfigureServices((ctx, services) =>
{
services.AddSingleton<IFooService, FooService>();
services.AddTransient<IBarService, BarService>();
})
.Build();
AssertSingleton<IFooService, FooService>(host.Services);
AssertTransient<IBarService, BarService>(host.Services);
}
static void AssertTransient<TInterface, TConcrete>(IServiceProvider services)
{
var service1 = services.GetService<TInterface>();
Assert.NotNull(service1);
Assert.IsType<TConcrete>(service1);
var service2 = services.GetService<TInterface>();
Assert.NotNull(service2);
Assert.IsType<TConcrete>(service2);
Assert.NotEqual(service1, service2);
}
static void AssertSingleton<TInterface, TConcrete>(IServiceProvider services)
{
var service1 = services.GetService<TInterface>();
Assert.NotNull(service1);
Assert.IsType<TConcrete>(service1);
var service2 = services.GetService<TInterface>();
Assert.NotNull(service2);
Assert.IsType<TConcrete>(service2);
Assert.Equal(service1, service2);
}
}
}

Просмотреть файл

@ -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<InvalidOperationException>(() => 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<IMauiHandlersServiceProvider>(handlerContext.Handlers);
}
[Fact]
public void CanRegisterAndGetHandler()
{
var app = new AppStub();
app.CreateBuilder()
.RegisterHandler<IViewStub, ViewHandlerStub>()
.Build(app);
var handler = MauiApp.Current.Context.Handlers.GetHandler(typeof(IViewStub));
Assert.NotNull(handler);
Assert.IsType<ViewHandlerStub>(handler);
}
[Fact]
public void CanRegisterAndGetHandlerWithDictionary()
{
var app = new AppStub();
app.CreateBuilder()
.RegisterHandlers(new Dictionary<Type, Type>
{
{ typeof(IViewStub), typeof(ViewHandlerStub) }
})
.Build(app);
var handler = MauiApp.Current.Context.Handlers.GetHandler(typeof(IViewStub));
Assert.NotNull(handler);
Assert.IsType<ViewHandlerStub>(handler);
}
[Fact]
public void CanRegisterAndGetHandlerForType()
{
var app = new AppStub();
app.CreateBuilder()
.RegisterHandler<IViewStub, ViewHandlerStub>()
.Build(app);
var handler = MauiApp.Current.Context.Handlers.GetHandler(typeof(ViewStub));
Assert.NotNull(handler);
Assert.IsType<ViewHandlerStub>(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<ButtonHandler>(handler);
}
[Fact]
public void CanSpecifyHandler()
{
var app = new AppStub();
app.CreateBuilder()
.RegisterHandler<ButtonStub, ButtonHandlerStub>()
.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<ButtonHandler>(defaultHandler);
Assert.IsType<ButtonHandlerStub>(specificHandler);
}
[Fact]
public void WillRetrieveDifferentTransientServices()
{
var app = new AppStub();
app.CreateBuilder()
.ConfigureServices((ctx, services) => services.AddTransient<IFooService, FooService>())
.Build(app);
AssertTransient<IFooService, FooService>(app);
}
[Fact]
public void WillRetrieveSameSingletonServices()
{
var app = new AppStub();
app.CreateBuilder()
.ConfigureServices((ctx, services) => services.AddSingleton<IFooService, FooService>())
.Build(app);
AssertSingleton<IFooService, FooService>(app);
}
[Fact]
public void WillRetrieveMixedServices()
{
var app = new AppStub();
app.CreateBuilder()
.ConfigureServices((ctx, services) =>
{
services.AddSingleton<IFooService, FooService>();
services.AddTransient<IBarService, BarService>();
})
.Build(app);
AssertSingleton<IFooService, FooService>(app);
AssertTransient<IBarService, BarService>(app);
}
public void Dispose()
{
(App.Current as AppStub)?.ClearApp();
}
static void AssertTransient<TInterface, TConcrete>(AppStub app)
{
var service1 = app.Services.GetService<TInterface>();
Assert.NotNull(service1);
Assert.IsType<TConcrete>(service1);
var service2 = app.Services.GetService<TInterface>();
Assert.NotNull(service2);
Assert.IsType<TConcrete>(service2);
Assert.NotEqual(service1, service2);
}
static void AssertSingleton<TInterface, TConcrete>(AppStub app)
{
var service1 = app.Services.GetService<TInterface>();
Assert.NotNull(service1);
Assert.IsType<TConcrete>(service1);
var service2 = app.Services.GetService<TInterface>();
Assert.NotNull(service2);
Assert.IsType<TConcrete>(service2);
Assert.Equal(service1, service2);
}
}
}

Просмотреть файл

@ -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<IMauiContext>(provider => new HandlersContextStub(provider));
services.AddTransient<IButton, ButtonStub>();
}
public override IAppHostBuilder CreateBuilder()
{
return base.CreateBuilder().ConfigureServices(ConfigureServices);
}
public override IWindow CreateWindow(IActivationState state)
{
return new WindowStub();
}
internal void ClearApp()
{
Current = null;
}
}
}
}

Просмотреть файл

@ -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; }
}
}

Просмотреть файл

@ -13,7 +13,7 @@ namespace Microsoft.Maui.Tests
}
public ViewHandlerStub(PropertyMapper mapper) : base(mapper ?? MockViewMapper)
public ViewHandlerStub(PropertyMapper mapper = null) : base(mapper ?? MockViewMapper)
{
}

Просмотреть файл

@ -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(); }
}
}