From b9cdd5c0effc2a8f2a670090dff71fa4bbb826d1 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Sun, 30 Nov 2025 20:16:52 +0000 Subject: [PATCH 1/6] Add MAUI hosting extension for generic host Introduces the Extensions.Hosting.Maui project, providing integration between .NET MAUI and Microsoft.Extensions.Hosting. Includes interfaces and implementations for configuring MAUI applications, registering pages and shells, managing the MAUI application lifecycle, and supporting dependency injection and hosted services for MAUI apps. --- .../Extensions.Hosting.Maui.csproj | 29 +++ .../HostBuilderMauiExtensions.cs | 226 ++++++++++++++++++ src/Extensions.Hosting.Maui/IMauiBuilder.cs | 32 +++ src/Extensions.Hosting.Maui/IMauiContext.cs | 25 ++ src/Extensions.Hosting.Maui/IMauiService.cs | 19 ++ src/Extensions.Hosting.Maui/IMauiShell.cs | 10 + .../Internals/MauiBuilder.cs | 25 ++ .../Internals/MauiContext.cs | 24 ++ .../Internals/MauiThread.cs | 68 ++++++ .../MauiBuilderExtensions.cs | 73 ++++++ .../MauiHostedService.cs | 55 +++++ 11 files changed, 586 insertions(+) create mode 100644 src/Extensions.Hosting.Maui/Extensions.Hosting.Maui.csproj create mode 100644 src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs create mode 100644 src/Extensions.Hosting.Maui/IMauiBuilder.cs create mode 100644 src/Extensions.Hosting.Maui/IMauiContext.cs create mode 100644 src/Extensions.Hosting.Maui/IMauiService.cs create mode 100644 src/Extensions.Hosting.Maui/IMauiShell.cs create mode 100644 src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs create mode 100644 src/Extensions.Hosting.Maui/Internals/MauiContext.cs create mode 100644 src/Extensions.Hosting.Maui/Internals/MauiThread.cs create mode 100644 src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs create mode 100644 src/Extensions.Hosting.Maui/MauiHostedService.cs diff --git a/src/Extensions.Hosting.Maui/Extensions.Hosting.Maui.csproj b/src/Extensions.Hosting.Maui/Extensions.Hosting.Maui.csproj new file mode 100644 index 0000000..3e30467 --- /dev/null +++ b/src/Extensions.Hosting.Maui/Extensions.Hosting.Maui.csproj @@ -0,0 +1,29 @@ + + + + net8.0;net9.0;net10.0 + true + This extension adds MAUI support to generic host based applications. With this you can enhance your application with a UI, and use all the services provided by the generic host like DI, logging etc. + CP.Extensions.Hosting.Maui + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs b/src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs new file mode 100644 index 0000000..1e4989f --- /dev/null +++ b/src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs @@ -0,0 +1,226 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Maui.Controls; +using ReactiveMarbles.Extensions.Hosting.Maui.Internals; + +namespace ReactiveMarbles.Extensions.Hosting.Maui; + +/// +/// This contains the MAUI extensions for Microsoft.Extensions.Hosting. +/// +public static class HostBuilderMauiExtensions +{ + private const string MauiContextKey = nameof(MauiContext); + + /// + /// Defines that stopping the MAUI application also stops the host (application). + /// + /// IHostBuilder. + /// A IHostBuilder. + public static IHostBuilder? UseMauiLifetime(this IHostBuilder hostBuilder) => + hostBuilder?.ConfigureServices((_, __) => + { + TryRetrieveMauiContext(hostBuilder.Properties, out var mauiContext); + mauiContext.IsLifetimeLinked = true; + }); + + /// + /// Defines that stopping the MAUI application also stops the host (application). + /// + /// IHostApplicationBuilder. + /// The same IHostApplicationBuilder instance. + public static IHostApplicationBuilder UseMauiLifetime(this IHostApplicationBuilder hostBuilder) + { + ArgumentNullException.ThrowIfNull(hostBuilder); + + TryRetrieveMauiContext(hostBuilder.Properties, out var mauiContext); + mauiContext.IsLifetimeLinked = true; + return hostBuilder; + } + + /// + /// Configure an MAUI application. + /// + /// IHostBuilder. + /// Action to configure Maui. + /// A IHostBuilder. + public static IHostBuilder ConfigureMaui(this IHostBuilder hostBuilder, Action? configureDelegate = null) + { + ArgumentNullException.ThrowIfNull(hostBuilder); + + var mauiBuilder = new MauiBuilder(); + configureDelegate?.Invoke(mauiBuilder); + + hostBuilder.ConfigureServices((_, serviceCollection) => + { + if (!TryRetrieveMauiContext(hostBuilder.Properties, out var mauiContext)) + { + serviceCollection + .AddSingleton(mauiContext) + .AddSingleton(serviceProvider => new MauiThread(serviceProvider)) + .AddHostedService(); + } + + mauiBuilder.ConfigureContextAction?.Invoke(mauiContext); + }); + + if (mauiBuilder.ApplicationType != null) + { + // Check if the registered application does inherit Microsoft.Maui.Controls.Application + var baseApplicationType = typeof(Application); + if (!baseApplicationType.IsAssignableFrom(mauiBuilder.ApplicationType)) + { + throw new ArgumentException("The registered Application type must inherit Microsoft.Maui.Controls.Application", nameof(configureDelegate)); + } + + hostBuilder.ConfigureServices((_, serviceCollection) => + { + if (mauiBuilder.Application != null) + { + // Add existing Application + serviceCollection.AddSingleton(mauiBuilder.ApplicationType, mauiBuilder.Application); + } + else + { + serviceCollection.AddSingleton(mauiBuilder.ApplicationType); + } + + if (mauiBuilder.ApplicationType != baseApplicationType) + { + serviceCollection.AddSingleton(serviceProvider => (Application)serviceProvider.GetRequiredService(mauiBuilder.ApplicationType)); + } + }); + } + + if (mauiBuilder.PageTypes.Count > 0) + { + hostBuilder.ConfigureServices((_, serviceCollection) => + { + foreach (var mauiPageType in mauiBuilder.PageTypes) + { + serviceCollection.AddSingleton(mauiPageType); + + // Check if it also implements IMauiShell so we can register it as this + var shellInterfaceType = typeof(IMauiShell); + if (shellInterfaceType.IsAssignableFrom(mauiPageType)) + { + serviceCollection.AddSingleton(shellInterfaceType, serviceProvider => serviceProvider.GetRequiredService(mauiPageType)); + } + } + }); + } + + return hostBuilder; + } + + /// + /// Configure a MAUI application for the new builder API. + /// + /// The IHostApplicationBuilder. + /// Action to configure Maui. + /// The same IHostApplicationBuilder instance. + public static IHostApplicationBuilder ConfigureMaui(this IHostApplicationBuilder hostBuilder, Action? configureDelegate = null) + { + ArgumentNullException.ThrowIfNull(hostBuilder); + + var mauiBuilder = new MauiBuilder(); + configureDelegate?.Invoke(mauiBuilder); + + if (!TryRetrieveMauiContext(hostBuilder.Properties, out var mauiContext)) + { + hostBuilder.Services + .AddSingleton(mauiContext) + .AddSingleton(serviceProvider => new MauiThread(serviceProvider)) + .AddHostedService(); + } + + mauiBuilder.ConfigureContextAction?.Invoke(mauiContext); + + if (mauiBuilder.ApplicationType != null) + { + // Check if the registered application does inherit Microsoft.Maui.Controls.Application + var baseApplicationType = typeof(Application); + if (!baseApplicationType.IsAssignableFrom(mauiBuilder.ApplicationType)) + { + throw new ArgumentException("The registered Application type must inherit Microsoft.Maui.Controls.Application", nameof(configureDelegate)); + } + + if (mauiBuilder.Application != null) + { + // Add existing Application + hostBuilder.Services.AddSingleton(mauiBuilder.ApplicationType, mauiBuilder.Application); + } + else + { + hostBuilder.Services.AddSingleton(mauiBuilder.ApplicationType); + } + + if (mauiBuilder.ApplicationType != baseApplicationType) + { + hostBuilder.Services.AddSingleton(serviceProvider => (Application)serviceProvider.GetRequiredService(mauiBuilder.ApplicationType)); + } + } + + if (mauiBuilder.PageTypes.Count > 0) + { + foreach (var mauiPageType in mauiBuilder.PageTypes) + { + hostBuilder.Services.AddSingleton(mauiPageType); + + // Check if it also implements IMauiShell so we can register it as this + var shellInterfaceType = typeof(IMauiShell); + if (shellInterfaceType.IsAssignableFrom(mauiPageType)) + { + hostBuilder.Services.AddSingleton(shellInterfaceType, serviceProvider => serviceProvider.GetRequiredService(mauiPageType)); + } + } + } + + return hostBuilder; + } + + /// + /// Specify a shell, the primary Page, to start. + /// + /// IHostBuilder. + /// Type for the shell, must derive from Page and implement IMauiShell. + /// A IHostBuilder. + public static IHostBuilder? ConfigureMauiShell(this IHostBuilder hostBuilder) + where TShell : Page, IMauiShell + => hostBuilder?.ConfigureMaui(maui => maui.UsePage()); + + /// + /// Specify a shell, the primary Page, to start. (IHostApplicationBuilder). + /// + /// IHostApplicationBuilder. + /// Type for the shell, must derive from Page and implement IMauiShell. + /// The same IHostApplicationBuilder instance. + public static IHostApplicationBuilder ConfigureMauiShell(this IHostApplicationBuilder hostBuilder) + where TShell : Page, IMauiShell + => hostBuilder.ConfigureMaui(maui => maui.UsePage()); + + /// + /// Helper method to retrieve the IMauiContext. + /// + /// IDictionary. + /// IMauiContext out value. + /// bool if there was already an IMauiContext. + private static bool TryRetrieveMauiContext(this IDictionary properties, out IMauiContext mauiContext) + { + if (properties.TryGetValue(MauiContextKey, out var mauiContextAsObject)) + { + mauiContext = (IMauiContext)mauiContextAsObject; + return true; + } + + mauiContext = new MauiContext(); + properties[MauiContextKey] = mauiContext; + return false; + } +} diff --git a/src/Extensions.Hosting.Maui/IMauiBuilder.cs b/src/Extensions.Hosting.Maui/IMauiBuilder.cs new file mode 100644 index 0000000..5c76503 --- /dev/null +++ b/src/Extensions.Hosting.Maui/IMauiBuilder.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Maui.Controls; + +namespace ReactiveMarbles.Extensions.Hosting.Maui; + +/// +/// Interface used for configuring Maui. +/// +public interface IMauiBuilder +{ + /// + /// Gets or sets type of the application that will be used. + /// + Type? ApplicationType { get; set; } + /// + /// Gets or sets an existing application. + /// + Application? Application { get; set; } + /// + /// Gets type of the pages that will be used. + /// + IList PageTypes { get; } + /// + /// Gets or sets action to configure the Maui context. + /// + Action? ConfigureContextAction { get; set; } +} diff --git a/src/Extensions.Hosting.Maui/IMauiContext.cs b/src/Extensions.Hosting.Maui/IMauiContext.cs new file mode 100644 index 0000000..e5efb51 --- /dev/null +++ b/src/Extensions.Hosting.Maui/IMauiContext.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.Maui.Controls; +using Microsoft.Maui.Dispatching; +using ReactiveMarbles.Extensions.Hosting.UiThread; + +namespace ReactiveMarbles.Extensions.Hosting.Maui; + +/// +/// The MAUI context contains all information about the MAUI application and how it's started and stopped. +/// +public interface IMauiContext : IUiContext +{ + /// + /// Gets or sets the Application. + /// + Application? MauiApplication { get; set; } + + /// + /// Gets this MAUI dispatcher. + /// + IDispatcher? Dispatcher { get; } +} diff --git a/src/Extensions.Hosting.Maui/IMauiService.cs b/src/Extensions.Hosting.Maui/IMauiService.cs new file mode 100644 index 0000000..708275d --- /dev/null +++ b/src/Extensions.Hosting.Maui/IMauiService.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.Maui.Controls; + +namespace ReactiveMarbles.Extensions.Hosting.Maui; + +/// +/// This defines a service which is called before the message loop is started. +/// +public interface IMauiService +{ + /// + /// Do whatever you need to do to initialize MAUI, this is called from the UI thread. + /// + /// Application. + void Initialize(Application application); +} diff --git a/src/Extensions.Hosting.Maui/IMauiShell.cs b/src/Extensions.Hosting.Maui/IMauiShell.cs new file mode 100644 index 0000000..3577404 --- /dev/null +++ b/src/Extensions.Hosting.Maui/IMauiShell.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace ReactiveMarbles.Extensions.Hosting.Maui; + +/// +/// This marker interface defines the shell of the MAUI application. +/// +public interface IMauiShell; diff --git a/src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs b/src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs new file mode 100644 index 0000000..382eba9 --- /dev/null +++ b/src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Maui.Controls; + +namespace ReactiveMarbles.Extensions.Hosting.Maui.Internals; + +/// +internal class MauiBuilder : IMauiBuilder +{ + /// + public Type? ApplicationType { get; set; } + + /// + public Application? Application { get; set; } + + /// + public IList PageTypes { get; } = []; + + /// + public Action? ConfigureContextAction { get; set; } +} diff --git a/src/Extensions.Hosting.Maui/Internals/MauiContext.cs b/src/Extensions.Hosting.Maui/Internals/MauiContext.cs new file mode 100644 index 0000000..c01b1ab --- /dev/null +++ b/src/Extensions.Hosting.Maui/Internals/MauiContext.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Microsoft.Maui.Controls; +using Microsoft.Maui.Dispatching; + +namespace ReactiveMarbles.Extensions.Hosting.Maui.Internals; + +/// +internal class MauiContext : IMauiContext +{ + /// + public bool IsLifetimeLinked { get; set; } + + /// + public bool IsRunning { get; set; } + + /// + public Application? MauiApplication { get; set; } + + /// + public IDispatcher? Dispatcher => MauiApplication?.Dispatcher; +} diff --git a/src/Extensions.Hosting.Maui/Internals/MauiThread.cs b/src/Extensions.Hosting.Maui/Internals/MauiThread.cs new file mode 100644 index 0000000..409ab3a --- /dev/null +++ b/src/Extensions.Hosting.Maui/Internals/MauiThread.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.Controls; +using ReactiveMarbles.Extensions.Hosting.UiThread; + +namespace ReactiveMarbles.Extensions.Hosting.Maui.Internals; + +/// +/// This contains the logic for the MAUI thread. +/// +/// +/// Initializes a new instance of the class. +/// This will create the MauiThread. +/// +/// IServiceProvider. +public class MauiThread(IServiceProvider serviceProvider) : BaseUiThread(serviceProvider) +{ + /// + protected override void PreUiThreadStart() + { + // Create the new MAUI application + var mauiApplication = ServiceProvider.GetService() ?? new Application(); + + // Register to the MAUI application exit to stop the host application + mauiApplication.Dispatcher.Dispatch(() => mauiApplication.ModalPopping += (s, e) => HandleApplicationExit()); + + // Store the application for others to interact + UiContext!.MauiApplication = mauiApplication; + } + + /// + protected override void UiThreadStart() => + UiContext?.MauiApplication?.Dispatcher.Dispatch(() => + { + // Mark the application as running + UiContext.IsRunning = true; + + // Use the provided IMauiService + var mauiServices = ServiceProvider.GetServices(); + foreach (var mauiService in mauiServices) + { + mauiService.Initialize(UiContext.MauiApplication); + } + + // Set the main page to the shell + var shellPages = ServiceProvider.GetServices().Cast().ToList(); + + if (shellPages.Count == 1) + { + UiContext.MauiApplication.MainPage = shellPages[0]; + } + else if (shellPages.Count > 1) + { + // Perhaps use a NavigationPage or something + UiContext.MauiApplication.MainPage = new NavigationPage(shellPages[0]); + } + else + { + // No shell, perhaps throw or set a default + throw new InvalidOperationException("Please inherit from IMauiShell in a Page to use the required IMauiShell interface"); + } + }); +} diff --git a/src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs b/src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs new file mode 100644 index 0000000..0004426 --- /dev/null +++ b/src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using Microsoft.Maui.Controls; + +namespace ReactiveMarbles.Extensions.Hosting.Maui; + +/// +/// Extension methods to configure Maui. +/// +public static class MauiBuilderExtensions +{ + /// + /// Register a page, as a singleton. + /// + /// Type of the page, must inherit from Page. + /// IMauiBuilder. + /// A IMauiBuilder. + public static IMauiBuilder? UsePage(this IMauiBuilder mauiBuilder) + where TPage : Page + { + mauiBuilder?.PageTypes.Add(typeof(TPage)); + return mauiBuilder; + } + + /// + /// Register a type for the main application. + /// + /// Type of the application, must inherit from Application. + /// IMauiBuilder. + /// A IMauiBuilder. + public static IMauiBuilder UseApplication(this IMauiBuilder mauiBuilder) + where TApplication : Application + { + ArgumentNullException.ThrowIfNull(mauiBuilder); + + mauiBuilder.ApplicationType = typeof(TApplication); + return mauiBuilder; + } + + /// + /// Uses the current application. + /// + /// The type of the application. + /// The MAUI builder. + /// The current application. + /// A IMauiBuilder. + public static IMauiBuilder UseCurrentApplication(this IMauiBuilder mauiBuilder, TApplication currentApplication) + where TApplication : Application + { + ArgumentNullException.ThrowIfNull(mauiBuilder); + + mauiBuilder.ApplicationType = typeof(TApplication); + mauiBuilder.Application = currentApplication; + return mauiBuilder; + } + + /// + /// Register action to configure the Application. + /// + /// IMauiBuilder. + /// Action to configure the Application. + /// A IMauiBuilder. + public static IMauiBuilder ConfigureContext(this IMauiBuilder mauiBuilder, Action configureAction) + { + ArgumentNullException.ThrowIfNull(mauiBuilder); + + mauiBuilder.ConfigureContextAction = configureAction; + return mauiBuilder; + } +} diff --git a/src/Extensions.Hosting.Maui/MauiHostedService.cs b/src/Extensions.Hosting.Maui/MauiHostedService.cs new file mode 100644 index 0000000..dab45de --- /dev/null +++ b/src/Extensions.Hosting.Maui/MauiHostedService.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using ReactiveMarbles.Extensions.Hosting.Maui.Internals; + +namespace ReactiveMarbles.Extensions.Hosting.Maui; + +/// +/// This hosts a MAUI service, making sure the lifecycle is managed. +/// +/// +/// Initializes a new instance of the class. +/// The constructor which takes all the DI objects. +/// +/// ILogger. +/// MauiThread. +/// IMauiContext. +public class MauiHostedService(ILogger logger, MauiThread mauiThread, IMauiContext mauiContext) : IHostedService +{ + /// + public Task StartAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.CompletedTask; + } + + // Make the UI thread go + mauiThread.Start(); + return Task.CompletedTask; + } + + /// + public async Task StopAsync(CancellationToken cancellationToken) + { + if (mauiContext.IsRunning) + { + logger.LogDebug("Stopping MAUI due to application exit."); + + // Stop application + var completion = new TaskCompletionSource(); + mauiContext.Dispatcher?.Dispatch(() => + { + mauiContext.MauiApplication?.Quit(); + completion.SetResult(); + }); + await completion.Task; + } + } +} From f3d5a05c2677b218a098f5bdd63121768321c879 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 1 Dec 2025 00:27:02 +0000 Subject: [PATCH 2/6] Refactor Maui builder and app mixins for improved extensibility Introduces MauiAppBuilder to IMauiBuilder and MauiBuilder, updates extension methods to support MauiApp configuration, and refactors main page setup logic. Also modernizes AppMixins in example projects to use file-scoped namespaces and static classes. These changes improve flexibility and consistency in Maui app hosting and configuration. --- .../IUiContext.cs | 2 +- .../HostBuilderMauiExtensions.cs | 19 +++++- src/Extensions.Hosting.Maui/IMauiBuilder.cs | 9 +++ .../Internals/MauiBuilder.cs | 9 +++ .../Internals/MauiContext.cs | 2 +- .../Internals/MauiThread.cs | 38 ++++-------- .../MauiBuilderExtensions.cs | 22 +++++-- .../AppMixins.cs | 59 +++++++++---------- .../AppMixins.cs | 59 +++++++++---------- 9 files changed, 123 insertions(+), 96 deletions(-) diff --git a/src/Extensions.Hosting.MainUIThread/IUiContext.cs b/src/Extensions.Hosting.MainUIThread/IUiContext.cs index 579420d..65a4058 100644 --- a/src/Extensions.Hosting.MainUIThread/IUiContext.cs +++ b/src/Extensions.Hosting.MainUIThread/IUiContext.cs @@ -15,7 +15,7 @@ public interface IUiContext bool IsLifetimeLinked { get; set; } /// - /// Gets or sets a value indicating whether is the WPF application started and still running?. + /// Gets or sets a value indicating whether is the application started and still running?. /// bool IsRunning { get; set; } } diff --git a/src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs b/src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs index 1e4989f..223a13c 100644 --- a/src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs +++ b/src/Extensions.Hosting.Maui/HostBuilderMauiExtensions.cs @@ -57,6 +57,11 @@ public static IHostBuilder ConfigureMaui(this IHostBuilder hostBuilder, Action { if (!TryRetrieveMauiContext(hostBuilder.Properties, out var mauiContext)) @@ -100,7 +105,7 @@ public static IHostBuilder ConfigureMaui(this IHostBuilder hostBuilder, Action 0) { - hostBuilder.ConfigureServices((_, serviceCollection) => + hostBuilder.ConfigureServices(serviceCollection => { foreach (var mauiPageType in mauiBuilder.PageTypes) { @@ -132,6 +137,11 @@ public static IHostApplicationBuilder ConfigureMaui(this IHostApplicationBuilder var mauiBuilder = new MauiBuilder(); configureDelegate?.Invoke(mauiBuilder); + if (mauiBuilder.ApplicationType != null && mauiBuilder.Application == null && Application.Current?.GetType() == mauiBuilder.ApplicationType) + { + mauiBuilder.Application = Application.Current; + } + if (!TryRetrieveMauiContext(hostBuilder.Properties, out var mauiContext)) { hostBuilder.Services @@ -182,6 +192,9 @@ public static IHostApplicationBuilder ConfigureMaui(this IHostApplicationBuilder } } + var app = mauiBuilder.MauiAppBuilder.Build(); + hostBuilder.Services.AddSingleton(app); + return hostBuilder; } @@ -193,7 +206,7 @@ public static IHostApplicationBuilder ConfigureMaui(this IHostApplicationBuilder /// A IHostBuilder. public static IHostBuilder? ConfigureMauiShell(this IHostBuilder hostBuilder) where TShell : Page, IMauiShell - => hostBuilder?.ConfigureMaui(maui => maui.UsePage()); + => hostBuilder?.ConfigureMaui(maui => maui.AddSingletonPage()); /// /// Specify a shell, the primary Page, to start. (IHostApplicationBuilder). @@ -203,7 +216,7 @@ public static IHostApplicationBuilder ConfigureMaui(this IHostApplicationBuilder /// The same IHostApplicationBuilder instance. public static IHostApplicationBuilder ConfigureMauiShell(this IHostApplicationBuilder hostBuilder) where TShell : Page, IMauiShell - => hostBuilder.ConfigureMaui(maui => maui.UsePage()); + => hostBuilder.ConfigureMaui(maui => maui.AddSingletonPage()); /// /// Helper method to retrieve the IMauiContext. diff --git a/src/Extensions.Hosting.Maui/IMauiBuilder.cs b/src/Extensions.Hosting.Maui/IMauiBuilder.cs index 5c76503..02ab92c 100644 --- a/src/Extensions.Hosting.Maui/IMauiBuilder.cs +++ b/src/Extensions.Hosting.Maui/IMauiBuilder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.Maui.Controls; +using Microsoft.Maui.Hosting; namespace ReactiveMarbles.Extensions.Hosting.Maui; @@ -29,4 +30,12 @@ public interface IMauiBuilder /// Gets or sets action to configure the Maui context. /// Action? ConfigureContextAction { get; set; } + + /// + /// Gets the maui application builder. + /// + /// + /// The maui application builder. + /// + MauiAppBuilder MauiAppBuilder { get; } } diff --git a/src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs b/src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs index 382eba9..e879c7c 100644 --- a/src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs +++ b/src/Extensions.Hosting.Maui/Internals/MauiBuilder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.Maui.Controls; +using Microsoft.Maui.Hosting; namespace ReactiveMarbles.Extensions.Hosting.Maui.Internals; @@ -22,4 +23,12 @@ internal class MauiBuilder : IMauiBuilder /// public Action? ConfigureContextAction { get; set; } + + /// + /// Gets the maui application builder. + /// + /// + /// The maui application builder. + /// + public MauiAppBuilder MauiAppBuilder { get; } = MauiApp.CreateBuilder(); } diff --git a/src/Extensions.Hosting.Maui/Internals/MauiContext.cs b/src/Extensions.Hosting.Maui/Internals/MauiContext.cs index c01b1ab..7e9beb8 100644 --- a/src/Extensions.Hosting.Maui/Internals/MauiContext.cs +++ b/src/Extensions.Hosting.Maui/Internals/MauiContext.cs @@ -20,5 +20,5 @@ internal class MauiContext : IMauiContext public Application? MauiApplication { get; set; } /// - public IDispatcher? Dispatcher => MauiApplication?.Dispatcher; + public IDispatcher? Dispatcher => Application.Current?.Dispatcher; } diff --git a/src/Extensions.Hosting.Maui/Internals/MauiThread.cs b/src/Extensions.Hosting.Maui/Internals/MauiThread.cs index 409ab3a..e1ebc43 100644 --- a/src/Extensions.Hosting.Maui/Internals/MauiThread.cs +++ b/src/Extensions.Hosting.Maui/Internals/MauiThread.cs @@ -23,20 +23,22 @@ public class MauiThread(IServiceProvider serviceProvider) : BaseUiThread protected override void PreUiThreadStart() { - // Create the new MAUI application - var mauiApplication = ServiceProvider.GetService() ?? new Application(); - - // Register to the MAUI application exit to stop the host application - mauiApplication.Dispatcher.Dispatch(() => mauiApplication.ModalPopping += (s, e) => HandleApplicationExit()); - - // Store the application for others to interact - UiContext!.MauiApplication = mauiApplication; + // No initialization needed here } /// protected override void UiThreadStart() => - UiContext?.MauiApplication?.Dispatcher.Dispatch(() => + UiContext?.Dispatcher?.Dispatch(() => { + // Create the new MAUI application + var mauiApplication = ServiceProvider.GetService() ?? new Application(); + + // Register to the MAUI application exit to stop the host application + mauiApplication.ModalPopping += (s, e) => HandleApplicationExit(); + + // Store the application for others to interact + UiContext!.MauiApplication = mauiApplication; + // Mark the application as running UiContext.IsRunning = true; @@ -47,22 +49,6 @@ protected override void UiThreadStart() => mauiService.Initialize(UiContext.MauiApplication); } - // Set the main page to the shell - var shellPages = ServiceProvider.GetServices().Cast().ToList(); - - if (shellPages.Count == 1) - { - UiContext.MauiApplication.MainPage = shellPages[0]; - } - else if (shellPages.Count > 1) - { - // Perhaps use a NavigationPage or something - UiContext.MauiApplication.MainPage = new NavigationPage(shellPages[0]); - } - else - { - // No shell, perhaps throw or set a default - throw new InvalidOperationException("Please inherit from IMauiShell in a Page to use the required IMauiShell interface"); - } + // The main page will be set in CreateWindow }); } diff --git a/src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs b/src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs index 0004426..cbb780b 100644 --- a/src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs +++ b/src/Extensions.Hosting.Maui/MauiBuilderExtensions.cs @@ -4,6 +4,8 @@ using System; using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Hosting; +using Microsoft.Maui.Hosting; namespace ReactiveMarbles.Extensions.Hosting.Maui; @@ -18,7 +20,7 @@ public static class MauiBuilderExtensions /// Type of the page, must inherit from Page. /// IMauiBuilder. /// A IMauiBuilder. - public static IMauiBuilder? UsePage(this IMauiBuilder mauiBuilder) + public static IMauiBuilder? AddSingletonPage(this IMauiBuilder mauiBuilder) where TPage : Page { mauiBuilder?.PageTypes.Add(typeof(TPage)); @@ -30,13 +32,18 @@ public static class MauiBuilderExtensions /// /// Type of the application, must inherit from Application. /// IMauiBuilder. - /// A IMauiBuilder. - public static IMauiBuilder UseApplication(this IMauiBuilder mauiBuilder) + /// Configure action for the MauiAppBuilder. + /// + /// A IMauiBuilder. + /// + public static IMauiBuilder UseMauiApp(this IMauiBuilder mauiBuilder, Action? configureMauiApp = null) where TApplication : Application { ArgumentNullException.ThrowIfNull(mauiBuilder); mauiBuilder.ApplicationType = typeof(TApplication); + mauiBuilder.MauiAppBuilder.UseMauiApp(); + configureMauiApp?.Invoke(mauiBuilder.MauiAppBuilder); return mauiBuilder; } @@ -46,14 +53,19 @@ public static IMauiBuilder UseApplication(this IMauiBuilder mauiBu /// The type of the application. /// The MAUI builder. /// The current application. - /// A IMauiBuilder. - public static IMauiBuilder UseCurrentApplication(this IMauiBuilder mauiBuilder, TApplication currentApplication) + /// The configure maui application. + /// + /// A IMauiBuilder. + /// + public static IMauiBuilder UseMauiApp(this IMauiBuilder mauiBuilder, TApplication currentApplication, Action? configureMauiApp = null) where TApplication : Application { ArgumentNullException.ThrowIfNull(mauiBuilder); mauiBuilder.ApplicationType = typeof(TApplication); mauiBuilder.Application = currentApplication; + mauiBuilder.MauiAppBuilder.UseMauiApp(); + configureMauiApp?.Invoke(mauiBuilder.MauiAppBuilder); return mauiBuilder; } diff --git a/src/Extensions.Hosting.Reactive.Example/AppMixins.cs b/src/Extensions.Hosting.Reactive.Example/AppMixins.cs index 23018a7..6691f2c 100644 --- a/src/Extensions.Hosting.Reactive.Example/AppMixins.cs +++ b/src/Extensions.Hosting.Reactive.Example/AppMixins.cs @@ -7,39 +7,38 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace Extensions.Hosting.Reactive.Example +namespace Extensions.Hosting.Reactive.Example; + +/// +/// AppMixins. +/// +public static class AppMixins { - /// - /// AppMixins. - /// - public static class AppMixins - { - private const string HostSettingsFile = "hostsettings.json"; - private const string AppSettingsFilePrefix = "appsettings"; - private const string Prefix = "PREFIX_"; + private const string HostSettingsFile = "hostsettings.json"; + private const string AppSettingsFilePrefix = "appsettings"; + private const string Prefix = "PREFIX_"; - internal static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder) => - hostBuilder.ConfigureLogging((hostContext, configLogging) => - configLogging - .AddConfiguration(hostContext.Configuration.GetSection("Logging")) - .AddConsole() - .AddDebug()); + internal static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder) => + hostBuilder.ConfigureLogging((hostContext, configLogging) => + configLogging + .AddConfiguration(hostContext.Configuration.GetSection("Logging")) + .AddConsole() + .AddDebug()); - internal static IHostBuilder ConfigureConfiguration(this IHostBuilder hostBuilder, string[] args) => - hostBuilder.ConfigureHostConfiguration(configHost => configHost.SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile(HostSettingsFile, optional: true) - .AddEnvironmentVariables(prefix: Prefix) - .AddCommandLine(args)) - .ConfigureAppConfiguration((hostContext, configApp) => + internal static IHostBuilder ConfigureConfiguration(this IHostBuilder hostBuilder, string[] args) => + hostBuilder.ConfigureHostConfiguration(configHost => configHost.SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile(HostSettingsFile, optional: true) + .AddEnvironmentVariables(prefix: Prefix) + .AddCommandLine(args)) + .ConfigureAppConfiguration((hostContext, configApp) => + { + configApp.AddJsonFile(AppSettingsFilePrefix + ".json", optional: true); + if (!string.IsNullOrEmpty(hostContext.HostingEnvironment.EnvironmentName)) { - configApp.AddJsonFile(AppSettingsFilePrefix + ".json", optional: true); - if (!string.IsNullOrEmpty(hostContext.HostingEnvironment.EnvironmentName)) - { - configApp.AddJsonFile(AppSettingsFilePrefix + $".{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true); - } + configApp.AddJsonFile(AppSettingsFilePrefix + $".{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true); + } - configApp.AddEnvironmentVariables(prefix: Prefix) - .AddCommandLine(args); - }); - } + configApp.AddEnvironmentVariables(prefix: Prefix) + .AddCommandLine(args); + }); } diff --git a/src/Extensions.Hosting.Wpf.Example/AppMixins.cs b/src/Extensions.Hosting.Wpf.Example/AppMixins.cs index 086eb1e..086abb7 100644 --- a/src/Extensions.Hosting.Wpf.Example/AppMixins.cs +++ b/src/Extensions.Hosting.Wpf.Example/AppMixins.cs @@ -7,39 +7,38 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace Extensions.Hosting.Wpf.Example +namespace Extensions.Hosting.Wpf.Example; + +/// +/// AppMixins. +/// +public static class AppMixins { - /// - /// AppMixins. - /// - public static class AppMixins - { - private const string HostSettingsFile = "hostsettings.json"; - private const string AppSettingsFilePrefix = "appsettings"; - private const string Prefix = "PREFIX_"; + private const string HostSettingsFile = "hostsettings.json"; + private const string AppSettingsFilePrefix = "appsettings"; + private const string Prefix = "PREFIX_"; - internal static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder) => - hostBuilder.ConfigureLogging((hostContext, configLogging) => - configLogging - .AddConfiguration(hostContext.Configuration.GetSection("Logging")) - .AddConsole() - .AddDebug()); + internal static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder) => + hostBuilder.ConfigureLogging((hostContext, configLogging) => + configLogging + .AddConfiguration(hostContext.Configuration.GetSection("Logging")) + .AddConsole() + .AddDebug()); - internal static IHostBuilder ConfigureConfiguration(this IHostBuilder hostBuilder, string[] args) => - hostBuilder.ConfigureHostConfiguration(configHost => configHost.SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile(HostSettingsFile, optional: true) - .AddEnvironmentVariables(prefix: Prefix) - .AddCommandLine(args)) - .ConfigureAppConfiguration((hostContext, configApp) => + internal static IHostBuilder ConfigureConfiguration(this IHostBuilder hostBuilder, string[] args) => + hostBuilder.ConfigureHostConfiguration(configHost => configHost.SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile(HostSettingsFile, optional: true) + .AddEnvironmentVariables(prefix: Prefix) + .AddCommandLine(args)) + .ConfigureAppConfiguration((hostContext, configApp) => + { + configApp.AddJsonFile(AppSettingsFilePrefix + ".json", optional: true); + if (!string.IsNullOrEmpty(hostContext.HostingEnvironment.EnvironmentName)) { - configApp.AddJsonFile(AppSettingsFilePrefix + ".json", optional: true); - if (!string.IsNullOrEmpty(hostContext.HostingEnvironment.EnvironmentName)) - { - configApp.AddJsonFile(AppSettingsFilePrefix + $".{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true); - } + configApp.AddJsonFile(AppSettingsFilePrefix + $".{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true); + } - configApp.AddEnvironmentVariables(prefix: Prefix) - .AddCommandLine(args); - }); - } + configApp.AddEnvironmentVariables(prefix: Prefix) + .AddCommandLine(args); + }); } From 687c68b5064ffac3f7f6826f8d41afa8f59a7066 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 1 Dec 2025 00:27:26 +0000 Subject: [PATCH 3/6] Add MAUI example project with cross-platform setup Introduces a new Extensions.Hosting.Maui.Example project demonstrating usage of the hosting extensions in a .NET MAUI application. Includes platform-specific files for Android, iOS, MacCatalyst, and Windows, shared resources, styles, and example pages. Updates the solution file to include the new project. --- src/Extensions.Hosting.Maui.Example/App.xaml | 14 + .../App.xaml.cs | 30 ++ .../AppShell.xaml | 14 + .../AppShell.xaml.cs | 17 + .../ExampleMauiService.cs | 23 + .../Extensions.Hosting.Maui.Example.csproj | 77 ++++ .../MainPage.xaml | 42 ++ .../MainPage.xaml.cs | 38 ++ .../MauiProgram.cs | 66 +++ .../Platforms/Android/AndroidManifest.xml | 6 + .../Platforms/Android/MainActivity.cs | 13 + .../Platforms/Android/MainApplication.cs | 29 ++ .../Android/Resources/values/colors.xml | 6 + .../Platforms/MacCatalyst/AppDelegate.cs | 16 + .../Platforms/MacCatalyst/Entitlements.plist | 14 + .../Platforms/MacCatalyst/Info.plist | 40 ++ .../Platforms/MacCatalyst/Program.cs | 21 + .../Platforms/Windows/App.xaml | 8 + .../Platforms/Windows/App.xaml.cs | 24 + .../Platforms/Windows/Package.appxmanifest | 46 ++ .../Platforms/Windows/app.manifest | 17 + .../Platforms/iOS/AppDelegate.cs | 16 + .../Platforms/iOS/Info.plist | 32 ++ .../Platforms/iOS/Program.cs | 21 + .../iOS/Resources/PrivacyInfo.xcprivacy | 51 ++ .../Properties/launchSettings.json | 8 + .../Resources/AppIcon/appicon.svg | 4 + .../Resources/AppIcon/appiconfg.svg | 8 + .../Resources/Fonts/OpenSans-Regular.ttf | Bin 0 -> 107276 bytes .../Resources/Fonts/OpenSans-Semibold.ttf | Bin 0 -> 111204 bytes .../Resources/Images/dotnet_bot.png | Bin 0 -> 92532 bytes .../Resources/Raw/AboutAssets.txt | 15 + .../Resources/Splash/splash.svg | 8 + .../Resources/Styles/Colors.xaml | 44 ++ .../Resources/Styles/Styles.xaml | 434 ++++++++++++++++++ .../SecondPage.xaml | 19 + .../SecondPage.xaml.cs | 19 + src/Extensions.Hosting.sln | 15 +- 38 files changed, 1254 insertions(+), 1 deletion(-) create mode 100644 src/Extensions.Hosting.Maui.Example/App.xaml create mode 100644 src/Extensions.Hosting.Maui.Example/App.xaml.cs create mode 100644 src/Extensions.Hosting.Maui.Example/AppShell.xaml create mode 100644 src/Extensions.Hosting.Maui.Example/AppShell.xaml.cs create mode 100644 src/Extensions.Hosting.Maui.Example/ExampleMauiService.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Extensions.Hosting.Maui.Example.csproj create mode 100644 src/Extensions.Hosting.Maui.Example/MainPage.xaml create mode 100644 src/Extensions.Hosting.Maui.Example/MainPage.xaml.cs create mode 100644 src/Extensions.Hosting.Maui.Example/MauiProgram.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Android/AndroidManifest.xml create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Android/MainActivity.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Android/MainApplication.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Android/Resources/values/colors.xml create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/MacCatalyst/AppDelegate.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/MacCatalyst/Entitlements.plist create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/MacCatalyst/Info.plist create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/MacCatalyst/Program.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Windows/App.xaml create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Windows/App.xaml.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Windows/Package.appxmanifest create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/Windows/app.manifest create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/iOS/AppDelegate.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/iOS/Info.plist create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/iOS/Program.cs create mode 100644 src/Extensions.Hosting.Maui.Example/Platforms/iOS/Resources/PrivacyInfo.xcprivacy create mode 100644 src/Extensions.Hosting.Maui.Example/Properties/launchSettings.json create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/AppIcon/appicon.svg create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/AppIcon/appiconfg.svg create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/Fonts/OpenSans-Regular.ttf create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/Fonts/OpenSans-Semibold.ttf create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/Images/dotnet_bot.png create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/Raw/AboutAssets.txt create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/Splash/splash.svg create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/Styles/Colors.xaml create mode 100644 src/Extensions.Hosting.Maui.Example/Resources/Styles/Styles.xaml create mode 100644 src/Extensions.Hosting.Maui.Example/SecondPage.xaml create mode 100644 src/Extensions.Hosting.Maui.Example/SecondPage.xaml.cs diff --git a/src/Extensions.Hosting.Maui.Example/App.xaml b/src/Extensions.Hosting.Maui.Example/App.xaml new file mode 100644 index 0000000..578fd33 --- /dev/null +++ b/src/Extensions.Hosting.Maui.Example/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/Extensions.Hosting.Maui.Example/App.xaml.cs b/src/Extensions.Hosting.Maui.Example/App.xaml.cs new file mode 100644 index 0000000..7d4a25a --- /dev/null +++ b/src/Extensions.Hosting.Maui.Example/App.xaml.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Extensions.Hosting.Maui.Example; + +/// +/// App. +/// +/// +public partial class App : Application +{ + /// + /// Initializes a new instance of the class. + /// + public App() + { + InitializeComponent(); + + // Start the host + MauiProgram.HostApp.StartAsync(); + } + + /// + /// Creates the window. + /// + /// State of the activation. + /// A Window. + protected override Window CreateWindow(IActivationState? activationState) => new(new AppShell()); +} diff --git a/src/Extensions.Hosting.Maui.Example/AppShell.xaml b/src/Extensions.Hosting.Maui.Example/AppShell.xaml new file mode 100644 index 0000000..4f6a19d --- /dev/null +++ b/src/Extensions.Hosting.Maui.Example/AppShell.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/src/Extensions.Hosting.Maui.Example/AppShell.xaml.cs b/src/Extensions.Hosting.Maui.Example/AppShell.xaml.cs new file mode 100644 index 0000000..67ac278 --- /dev/null +++ b/src/Extensions.Hosting.Maui.Example/AppShell.xaml.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +namespace Extensions.Hosting.Maui.Example; + +/// +/// AppShell. +/// +/// +public partial class AppShell : Shell +{ + /// + /// Initializes a new instance of the class. + /// + public AppShell() => InitializeComponent(); +} diff --git a/src/Extensions.Hosting.Maui.Example/ExampleMauiService.cs b/src/Extensions.Hosting.Maui.Example/ExampleMauiService.cs new file mode 100644 index 0000000..9abf93d --- /dev/null +++ b/src/Extensions.Hosting.Maui.Example/ExampleMauiService.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved. +// ReactiveUI Association Incorporated licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using ReactiveMarbles.Extensions.Hosting.Maui; + +namespace Extensions.Hosting.Maui.Example; + +/// +/// Example MAUI service. +/// +public class ExampleMauiService : IMauiService +{ + /// + /// Initializes the specified application. + /// + /// The application. + public void Initialize(Microsoft.Maui.Controls.Application application) + { + // Example initialization + Console.WriteLine("MAUI application initialized via IMauiService"); + } +} diff --git a/src/Extensions.Hosting.Maui.Example/Extensions.Hosting.Maui.Example.csproj b/src/Extensions.Hosting.Maui.Example/Extensions.Hosting.Maui.Example.csproj new file mode 100644 index 0000000..352285b --- /dev/null +++ b/src/Extensions.Hosting.Maui.Example/Extensions.Hosting.Maui.Example.csproj @@ -0,0 +1,77 @@ + + + + net9.0-android;net9.0-ios;net9.0-maccatalyst + $(TargetFrameworks);net9.0-windows10.0.19041.0 + + + + + Exe + Extensions.Hosting.Maui.Example + true + true + enable + enable + + + Extensions.Hosting.Maui.Example + + + com.companyname.extensions.hosting.maui.example + + + 1.0 + 1 + + + None + + 15.0 + 15.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + NU1605;SA1633;SA1600;SA1601;SA1027;SA1137;SX1309;CA1805;CA1400;RCS1102;SA1400;SA1508;SA1512;SA1642;SX1101 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + + diff --git a/src/Extensions.Hosting.Maui.Example/MainPage.xaml b/src/Extensions.Hosting.Maui.Example/MainPage.xaml new file mode 100644 index 0000000..0a81bb6 --- /dev/null +++ b/src/Extensions.Hosting.Maui.Example/MainPage.xaml @@ -0,0 +1,42 @@ + + + + + + + +