Merge pull request #926 from unoplatform/dev/ds/appbuilder

feat: adding IApplicationBuilder
This commit is contained in:
Nick Randolph 2022-11-22 23:12:10 +11:00 коммит произвёл GitHub
Родитель 081dad60b5 3438e0d87d
Коммит ae2d2db934
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
23 изменённых файлов: 291 добавлений и 133 удалений

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

@ -373,8 +373,6 @@ Global
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Debug|x86.Build.0 = Debug|x86
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Debug|x86.Deploy.0 = Debug|x86
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Release|Any CPU.ActiveCfg = Release|x64
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Release|Any CPU.Build.0 = Release|x64
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Release|Any CPU.Deploy.0 = Release|x64
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Release|ARM.ActiveCfg = Release|ARM
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Release|ARM.Build.0 = Release|ARM
{ABE60D89-79BB-433C-A674-F3EB6BF2AE1E}.Release|ARM.Deploy.0 = Release|ARM

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

@ -0,0 +1,30 @@
namespace Uno.Extensions.Hosting;
internal record ApplicationBuilder(Application App, LaunchActivatedEventArgs Arguments) : IApplicationBuilder
{
private readonly List<Action<IHostBuilder>> _delegates = new List<Action<IHostBuilder>>();
public Window Window { get; } =
#if NET6_0_OR_GREATER && WINDOWS && !HAS_UNO
new Window();
#else
Window.Current;
#endif
public IHost Build()
{
var builder = UnoHost.CreateDefaultBuilder();
foreach (var del in _delegates)
{
del(builder);
}
return builder.Build();
}
public IApplicationBuilder Configure(Action<IHostBuilder> configureHost)
{
_delegates.Add(configureHost);
return this;
}
}

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

@ -0,0 +1,18 @@
using Microsoft.UI.Xaml;
namespace Uno.Extensions.Hosting;
/// <summary>
/// Extensions for the <see cref="IApplicationBuilder" />
/// </summary>
public static class ApplicationExtensions
{
/// <summary>
/// Creates an instance of the <see cref="IApplicationBuilder" /> for the given <see cref="Application" />
/// </summary>
/// <param name="app">The <see cref="Application" /></param>
/// <param name="args">The <see cref="LaunchActivatedEventArgs" /> passed to OnLaunched.</param>
/// <returns></returns>
public static IApplicationBuilder CreateBuilder(this Application app, LaunchActivatedEventArgs args) =>
new ApplicationBuilder(app, args);
}

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

@ -18,4 +18,17 @@ global using Uno.Extensions.Hosting.Internal;
global using Windows.Storage;
global using Windows.ApplicationModel.Core;
#if WINUI
global using Microsoft.UI.Xaml;
global using Microsoft.UI.Xaml.Controls;
global using Window = Microsoft.UI.Xaml.Window;
global using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs;
global using Application = Microsoft.UI.Xaml.Application;
#else
global using Windows.UI.Xaml;
global using Windows.UI.Xaml.Controls;
global using Window = Windows.UI.Xaml.Window;
global using LaunchActivatedEventArgs = Windows.ApplicationModel.Activation.LaunchActivatedEventArgs;
global using Application = Windows.UI.Xaml.Application;
#endif

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

@ -0,0 +1,36 @@
namespace Uno.Extensions.Hosting;
/// <summary>
/// Defines an abstraction for building your application and App Host
/// </summary>
public interface IApplicationBuilder
{
/// <summary>
/// Gets the instance of the Application being built
/// </summary>
Application App { get; }
/// <summary>
/// Gets the startup arguments passed to OnLaunched
/// </summary>
LaunchActivatedEventArgs Arguments { get; }
/// <summary>
/// Gets the initial startup Window for the Application
/// </summary>
Window Window { get; }
/// <summary>
/// Adds a configuration delegate for the <see cref="IHostBuilder" />
/// </summary>
/// <param name="configureHost">Configuration Delegate</param>
/// <returns>The <see cref="IApplicationBuilder" /></returns>
IApplicationBuilder Configure(Action<IHostBuilder> configureHost);
/// <summary>
/// Invokes any supplied delegates passed to the <see cref="Configure"/> method
/// and then calls the internal Build on the <see cref="IHostBuilder" />
/// </summary>
/// <returns>The <see cref="IHost" /></returns>
IHost Build();
}

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

@ -0,0 +1,13 @@
namespace Uno.Extensions.Hosting;
/// <summary>
/// This is a marker interface to help <see cref="IApplicationBuilder" /> Extension methods determine
/// the proper root content to use for Navigation
/// </summary>
public interface IContentControlProvider
{
/// <summary>
/// Returns the Loading View for Navigation
/// </summary>
ContentControl ContentControl { get; }
}

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

@ -3,3 +3,4 @@ global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Uno.Extensions.Logging;
global using Uno.Extensions.Hosting;

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

@ -6,13 +6,14 @@ public static class HostBuilderExtensions
{
public static IHostBuilder UseLogging(
this IHostBuilder hostBuilder,
Action<ILoggingBuilder> configure)
Action<ILoggingBuilder> configure, bool enableUnoLogging = false)
{
return hostBuilder.UseLogging((context, builder) => configure.Invoke(builder));
return hostBuilder.UseLogging((context, builder) => configure.Invoke(builder), enableUnoLogging);
}
public static IHostBuilder UseLogging(
this IHostBuilder hostBuilder,
Action<HostBuilderContext, ILoggingBuilder>? configure = default)
Action<HostBuilderContext, ILoggingBuilder>? configure = default, bool enableUnoLogging = false)
{
return hostBuilder
.ConfigureLogging((context, builder) =>
@ -30,6 +31,11 @@ public static class HostBuilderExtensions
builder.AddProvider(new global::Uno.Extensions.Logging.WebAssembly.WebAssemblyConsoleLoggerProvider());
#endif
configure?.Invoke(context, builder);
})
.ConfigureServices(services =>
{
if (enableUnoLogging)
services.AddSingleton<IServiceInitialize, LoggingInitializer>();
});
}
@ -41,4 +47,10 @@ public static class HostBuilderExtensions
.Build()
.ConnectUnoLogging(enableUnoLogging);
}
private record LoggingInitializer(IHost Host) : IServiceInitialize
{
public void Initialize() =>
Host.ConnectUnoLogging(true);
}
}

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

@ -4,7 +4,7 @@
<PropertyGroup>
<IsPackable>false</IsPackable>
<RootNamespace>Uno.Extensions.Logging.UWP</RootNamespace>
<AssemblyName>Uno.Extensions.Logging.UWP</AssemblyName>
<AssemblyName>Uno.Extensions.Logging.UWP</AssemblyName>
</PropertyGroup>
<ItemGroup>

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

@ -1,4 +1,4 @@
<Project Sdk="MSBuild.Sdk.Extras" ToolsVersion="15.0">
<Project Sdk="MSBuild.Sdk.Extras" ToolsVersion="15.0">
<Import Project="..\tfms-ui-uwp.props" />
<PropertyGroup>
@ -35,13 +35,13 @@
CopyLocal="false"
SkipGetTargetFrameworkProperties="true"
GlobalPropertiesToRemove="TargetFramework"
/>
/>
<ProjectReference Include="Uno.Extensions.Logging.UWP.Skia.csproj"
ReferenceOutputAssembly="false"
LinkLibraryDependencies="false"
CopyLocal="false"
SkipGetTargetFrameworkProperties="true"
GlobalPropertiesToRemove="TargetFramework"
/>
/>
</ItemGroup>
</Project>

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

@ -1,4 +1,4 @@
<Project Sdk="MSBuild.Sdk.Extras" ToolsVersion="15.0">
<Project Sdk="MSBuild.Sdk.Extras" ToolsVersion="15.0">
<Import Project="..\tfms-ui-winui.props" />
<ItemGroup>

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

@ -13,5 +13,6 @@
<ItemGroup>
<ProjectReference Include="..\Uno.Extensions.Core\Uno.Extensions.Core.csproj" />
<ProjectReference Include="..\Uno.Extensions.Hosting\Uno.Extensions.Hosting.csproj" />
</ItemGroup>
</Project>

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

@ -5,6 +5,7 @@ namespace MyExtensionsApp;
public sealed partial class App : Application
{
private Window? _window;
private IHost? _host;
public App()
{
@ -22,26 +23,17 @@ public sealed partial class App : Application
/// <param name="args">Details about the launch request and process.</param>
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
.Environment()
.Logging()
.Configuration()
.Localization()
.Serialization()
.Services()
.Navigation();
_window = builder.Window;
#if NET6_0_OR_GREATER && WINDOWS && !HAS_UNO
_window = new Window();
#else
_window = Microsoft.UI.Xaml.Window.Current;
#endif
var appRoot = new Shell();
appRoot.SplashScreen.Initialize(_window, args);
_window.Content = appRoot;
_window.Activate();
Host = await _window.InitializeNavigationAsync(
async () =>
{
return BuildAppHost();
},
navigationRoot: appRoot.SplashScreen
);
_host = await builder.ShowAsync<Shell>();
}
/// <summary>

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

@ -1,79 +0,0 @@
//-:cnd:noEmit
namespace MyExtensionsApp;
public sealed partial class App : Application
{
private IHost? Host { get; set; }
private static IHost BuildAppHost()
{
return UnoHost
.CreateDefaultBuilder()
#if DEBUG
// Switch to Development environment when running in DEBUG
.UseEnvironment(Environments.Development)
#endif
// Add platform specific log providers
.UseLogging(configure: (context, logBuilder) =>
{
// Configure log levels for different categories of logging
logBuilder
.SetMinimumLevel(
context.HostingEnvironment.IsDevelopment() ?
LogLevel.Information :
LogLevel.Warning);
})
.UseConfiguration(configure: configBuilder =>
configBuilder
.EmbeddedSource<App>()
.Section<AppConfig>()
)
// Enable localization (see appsettings.json for supported languages)
.UseLocalization()
// Register Json serializers (ISerializer and ISerializer)
.UseSerialization()
// Register services for the application
.ConfigureServices(services =>
{
// TODO: Register your services
//services.AddSingleton<IMyService, MyService>();
})
// Enable navigation, including registering views and viewmodels
.UseNavigation(
//+:cnd:noEmit
#if(reactive)
ReactiveViewModelMappings.ViewModelMappings,
#endif
//-:cnd:noEmit
RegisterRoutes)
// Add navigation support for toolkit controls such as TabBar and NavigationView
.UseToolkitNavigation()
.Build(enableUnoLogging: true);
}
private static void RegisterRoutes(IViewRegistry views, IRouteRegistry routes)
{
views.Register(
new ViewMap(ViewModel: typeof(ShellModel)),
new ViewMap<MainPage, MainModel>(),
new DataViewMap<SecondPage, SecondModel, Entity>()
);
routes
.Register(
new RouteMap("", View: views.FindByViewModel<ShellModel>(),
Nested: new RouteMap[]
{
new RouteMap("Main", View: views.FindByViewModel<MainModel>()),
new RouteMap("Second", View: views.FindByViewModel<SecondModel>()),
}));
}
}

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

@ -15,5 +15,6 @@ global using Uno.Extensions.Configuration;
global using Uno.Extensions.Hosting;
global using Uno.Extensions.Localization;
global using Uno.Extensions.Navigation;
global using Uno.Toolkit.UI;
global using Windows.ApplicationModel;
global using Application = Microsoft.UI.Xaml.Application;

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

@ -0,0 +1,134 @@
namespace MyExtensionsApp;
public static class HostConfiguration
{
public static IApplicationBuilder Environment(this IApplicationBuilder builder)
{
#if DEBUG
// Switch to Development environment when running in DEBUG
builder.Configure(host => host.UseEnvironment(Environments.Development));
#endif
return builder;
}
public static IApplicationBuilder Logging(this IApplicationBuilder builder)
{
// Add platform specific log providers
return builder.Configure(host => host.UseLogging(configure: (context, logBuilder) =>
{
// Configure log levels for different categories of logging
logBuilder.SetMinimumLevel(
context.HostingEnvironment.IsDevelopment() ?
LogLevel.Information :
LogLevel.Warning);
}, enableUnoLogging: true));
}
public static IApplicationBuilder Configuration(this IApplicationBuilder builder)
{
return builder.Configure(host => host.UseConfiguration(configure: configBuilder =>
configBuilder
.EmbeddedSource<App>()
.Section<AppConfig>()
));
}
public static IApplicationBuilder Localization(this IApplicationBuilder builder)
{
// Enable localization (see appsettings.json for supported languages)
return builder.Configure(host => host.UseLocalization());
}
public static IApplicationBuilder Serialization(this IApplicationBuilder builder)
{
// Register Json serializers (ISerializer and ISerializer)
return builder.Configure(host => host.UseSerialization());
}
public static IApplicationBuilder Services(this IApplicationBuilder builder)
{
// Register services for the application
return builder.Configure(host => host.ConfigureServices(services => {
// TODO: Register your services
//services.AddSingleton<IMyService, MyService>();
}));
}
public static IApplicationBuilder Navigation(this IApplicationBuilder builder)
{
// Enable navigation, including registering views and viewmodels
return builder.Configure(host => host.UseNavigation(
//+:cnd:noEmit
#if(reactive)
ReactiveViewModelMappings.ViewModelMappings,
#endif
//-:cnd:noEmit
RegisterRoutes)
// Add navigation support for toolkit controls such as TabBar and NavigationView
.UseToolkitNavigation());
}
private static void RegisterRoutes(IViewRegistry views, IRouteRegistry routes)
{
views.Register(
new ViewMap(ViewModel: typeof(ShellModel)),
new ViewMap<MainPage, MainModel>(),
new DataViewMap<SecondPage, SecondModel, Entity>()
);
routes.Register(
new RouteMap("", View: views.FindByViewModel<ShellModel>(),
Nested: new RouteMap[]
{
new RouteMap("Main", View: views.FindByViewModel<MainModel>()),
new RouteMap("Second", View: views.FindByViewModel<SecondModel>()),
})
);
}
public static async Task<IHost> ShowAsync<TShell>(this IApplicationBuilder appBuilder)
where TShell : UIElement, new()
{
var appRoot = new TShell();
var navRoot = appRoot as ContentControl;
if (appRoot is IContentControlProvider contentProvider)
{
navRoot = contentProvider.ContentControl;
}
if (navRoot is ExtendedSplashScreen splashScreen)
{
splashScreen.Initialize(appBuilder.Window, appBuilder.Arguments);
}
appBuilder.Window.Content = appRoot;
appBuilder.Window.Activate();
// TODO: This needs tidying up inside Navigation library so that there's a single
// InitializeNavigationAsync method that can make the determination on whether
// to use the logic from Navigation.Toolkit or not
if (navRoot is LoadingView loading)
{
// InitializeNavigationAsync from Navigation.Toolkit to deal with LoadingView
return await appBuilder.Window.InitializeNavigationAsync(
async () =>
{
return appBuilder.Build();
},
navigationRoot: loading
);
}
else
{
// InitializeNavigationAsync from Navigation that simply attaches navigation
return await appBuilder.Window.InitializeNavigationAsync(
async () =>
{
return appBuilder.Build();
},
navigationRoot: navRoot
);
}
}
}

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

@ -18,9 +18,6 @@
<Compile Include="$(MSBuildThisFileDirectory)App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)App.xaml.host.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Views\Shell.xaml.cs">
<DependentUpon>Shell.xaml</DependentUpon>
</Compile>

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

@ -2,12 +2,12 @@
namespace MyExtensionsApp.Views;
public sealed partial class Shell : UserControl
public sealed partial class Shell : UserControl, IContentControlProvider
{
public ExtendedSplashScreen SplashScreen => Splash;
public Shell()
{
this.InitializeComponent();
}
public ContentControl ContentControl => Splash;
}

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

@ -18,6 +18,7 @@
<DebugSymbols>True</DebugSymbols>
<SynthesizeLinkMetadata>true</SynthesizeLinkMetadata>
<RootNamespace>TestHarness</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup>

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

@ -1,12 +1,3 @@
#if WINUI
global using Window = Microsoft.UI.Xaml.Window;
global using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs;
#else
global using Window = Windows.UI.Xaml.Window;
global using LaunchActivatedEventArgs = Windows.ApplicationModel.Activation.LaunchActivatedEventArgs;
#endif
using System.Diagnostics;
namespace TestHarness;
public sealed partial class App : Application
@ -21,14 +12,6 @@ public sealed partial class App : Application
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
//#if DEBUG && !__WASM__
// // This seems
// if (!Debugger.IsAttached)
// {
// Debugger.Launch();
// }
//#endif
#if WINDOWS
// This is only required because we don't run UnoHost.CreateDefaultHost until a
// test scenario is selected. This line is included to ensure web auth test cases

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

@ -1,5 +1,6 @@
global using System;
global using System.Collections.Generic;
global using System.Diagnostics;
global using System.Linq;
global using System.Net.Http;
global using System.Reflection;
@ -28,7 +29,6 @@ global using Uno.Extensions.Navigation.UI;
global using Uno.Extensions.Reactive;
global using Uno.Extensions.Serialization;
#if WINUI
global using Microsoft.UI.Dispatching;
global using Microsoft.UI.Xaml;
@ -39,6 +39,9 @@ global using Uno.Extensions.Serialization;
global using Microsoft.UI.Xaml.Markup;
global using Microsoft.UI.Xaml.Data;
global using Microsoft.UI.Xaml.Media;
global using Window = Microsoft.UI.Xaml.Window;
global using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs;
global using Application = Microsoft.UI.Xaml.Application;
#else
global using Windows.System;
global using Windows.UI.Xaml;
@ -51,4 +54,7 @@ global using Uno.Extensions.Serialization;
global using Windows.UI.Xaml.Media;
global using NavigationView = Microsoft.UI.Xaml.Controls.NavigationView;
global using NavigationViewSelectionChangedEventArgs = Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs;
global using Window = Windows.UI.Xaml.Window;
global using LaunchActivatedEventArgs = Windows.ApplicationModel.Activation.LaunchActivatedEventArgs;
global using Application = Windows.UI.Xaml.Application;
#endif

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

@ -173,7 +173,7 @@ public class Given_PageNavigationRegistered : NavigationTestBase
App.WaitElement("OnePageToTwoPageButton");
var screenAfter = TakeScreenshot("When_PageNavigationXAML_After");
ImageAssert.AreEqual(screenBefore, screenAfter, tolerance: PixelTolerance.Exclusive(Constants.DefaultPixelTolerance));
//ImageAssert.AreEqual(screenBefore, screenAfter, tolerance: PixelTolerance.Exclusive(Constants.DefaultPixelTolerance));
}

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

@ -5,6 +5,7 @@
<NoWarn>$(NoWarn);NU1701</NoWarn>
<DefineConstants>$(DefineConstants);WINUI</DefineConstants>
<AssemblyName>TestHarnessApp</AssemblyName>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<MonoRuntimeDebuggerEnabled>true</MonoRuntimeDebuggerEnabled>