Giter Site home page Giter Site logo

matt-goldman / maui.plugins.pageresolver Goto Github PK

View Code? Open in Web Editor NEW
127.0 7.0 11.0 256 KB

A simple and lightweight page resolver for use in .NET MAUI projects

License: MIT License

C# 97.54% PowerShell 2.46%
dotnet dotnetmaui dependency-injection source-generator

maui.plugins.pageresolver's Introduction

NuGet Status Nuget

Watch the video:

Watch the video

MAUI PageResolver

A simple and lightweight page resolver for use in .NET MAUI projects.

If you want a simple page resolver with DI without using a full MVVM framework (or if you want to use MVU), this package will let you navigate to fully resolved pages, with view models and dependencies, by calling:

await Navigation.PushAsync<MyPage>();

Advanced features

Additional features supported by PageReolver:

  • Paramaterised navigation - pass page parameters
await Navigation.PushAsync<MyPage>(myPageParam1, "bob", 4);
  • Paramaterised navigation - pass ViewModel parameters (.NET 8 version only)
await Navigation.PushAsync<MyPage>(myViewModelParam1, "bob", 4);
  • Source generator - automatically register dependencies in IServiceCollection with generated code (.NET 8 version only)
using Maui.Plugins.PageResolver;
using DemoProject;
using DemoProject.Pages;
using DemoProject.ViewModels;
using DemoProject.Services;
// ---------------
// <auto-generated>
//   Generated by the MauiPageResolver Auto-registration module.
//   https://github.com/matt-goldman/Maui.Plugins.PageResolver
// </auto-generated>
// ---------------

namespace DemoProject;

public static class PageResolverExtensions
{

    public static MauiAppBuilder UseAutodependencies(this MauiAppBuilder builder)
    {
         var ViewModelMappings = new Dictionary<Type, Type>();

         // pages
         builder.Services.AddTransient<MainPage>();


         // ViewModels
         builder.Services.AddTransient<MainViewModel>();


         // Services
         builder.Services.AddSingleton<IDefaultScopedService, DefaultScopedService>();
         builder.Services.AddTransient<ICustomScopedService, CustomScopedService>();


         // ViewModel to Page mappings
         ViewModelMappings.Add(typeof(MainPage), typeof(MainViewModel));


         // Initialisation
         builder.Services.UsePageResolver(ViewModelMappings);
         return builder;
    }
}
  • Lifetime attributes - override convention-based service lifetimes (singleton for services, transient for pages and ViewModels) in the source generator (.NET 8 version only)
[Transient]
public class CustomScopedService : ICustomScopedService
{
[...]

Getting Started

Check out the full instructions in the wiki on using PageResolver

maui.plugins.pageresolver's People

Contributors

bakerhillpins avatar guysymonds avatar ieuanwalker avatar jeancollas avatar matt-goldman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

maui.plugins.pageresolver's Issues

Example of how to implement with FlyoutPage rather than Shell

Hi, I'm trying to understand how to use this without using Shell for Navigation, specifically when using a FlyoutPage as MainPage

Would be great if you could advise how to achieve this, perhaps with a Demo project.

Coming from using Prism with XF where it was nice and simple, and being unable to use it with Maui, it's confusing how to achieve navigation, DI and MVVM combined for this use case....specifically how to set the MainPage as the FlyoutPage with the Detail as a ContentPage while injecting services and ViewModels into both the FlyoutPage and the Detail page.

Hope that makes sense and appreciate any help you can offer.

Use IMauiInitializeService interface in conjunction with UsePageResolver startup extension.

This was to be a Feature Request but it appears there's no distinction between the 2 even though it's an option on the "New Issue" page.

I've been working through a bunch of different Navigation and startup things over the past few days and I believe that you could register a resolver initialization class as an IMauiInitializeService in the startup extension method and this would allow it to be placed anywhere in the builder chain as it will be called within the MauiAppBuilder.Build method. It could even be setup so that it supports multiple calls in case the code is referenced from multiple libraries.

Describe the solution you'd like

Create a class that IMauiInitializeService that's registered which initializes the Resolver during the MauiAppBuilder.Build method call.


    public static void UsePageResolver(this IServiceCollection sc)
    {
        sc.AddSingleton<IMauiInitializeService, Resolver.Initializer>();
    }

    public static MauiAppBuilder UsePageResolver( this MauiAppBuilder builder )
    {
        builder.Services.AddSingleton<IMauiInitializeService, Resolver.Initializer>();

        return builder;
    }



    public static class Resolver
    {
        internal class Initializer : IMauiInitializeService
        {
#region Implementation of IMauiInitializeService

            /// <inheritdoc />
            public void Initialize( IServiceProvider services )
            {
                if ( Resolver.scope == null )
                {
                    Resolver.RegisterServiceProvider( services );
                }
            }

#endregion
        }
...
    }


Dynamic ViewModelLookup

Is your feature request related to a problem? Please describe.
Ability to add to Dictionary<Type, Type> ViewModelLookup in multiple places
https://github.com/matt-goldman/Maui.Plugins.PageResolver/blob/ff594dae8a38a20d365ca9748214a17df90aa052/src/Maui.Plugins.PageResolver/Resolver.cs#L13C5-L13C77

My Use case: We have an app with 100s possibly even a thousand pages, so to manage all the registration and configs, each area/ routes of the app has there own app builder to manage their section, f.e.
image

Currently in order to add the manual mappings they all need to be initialised at the same time, but in order to keep the code as maintainable as possible i'd like to keep the mapping next to the registrations.

Describe the solution you'd like
I see 3 possible solutions using the current code -

Typo in Wiki

Describe the bug
There is a small typo in the sample of your wiki-page in section 1.6 Option 3 (last line of code-sample).

To Reproduce
Steps to reproduce the behavior:

  1. Go to 'https://github.com/matt-goldman/Maui.Plugins.PageResolver/wiki/1-Using-the-PageResolver'
  2. Scroll down to Section 1.6, Option 3
  3. See error in code sample

Expected behavior

builder.Services.AddPageResolver(pageMappings);

Should be:

builder.Services.UsePageResolver(pageMappings);

Sorry for not opening a PR, thought it would be 2 mins work for you probably :)

System.InvalidCastException: 'Specified cast is not valid.'

Describe the bug
Getting a cast exception when trying to pass parameters -

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidCastException: Specified cast is not valid.
[mono-rt]    at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetServices(IServiceProvider provider, Type serviceType)
[mono-rt]    at Maui.Plugins.PageResolver.NavigationExtensions.IsRegisteredDependency(IServiceProvider serviceProvider, Type type)
[mono-rt]    at Maui.Plugins.PageResolver.NavigationExtensions.<>c__DisplayClass7_0.<ParametersMatchConstructors>b__0(ParameterInfo p)
[mono-rt]    at System.Linq.Enumerable.WhereArrayIterator`1[[System.Reflection.ParameterInfo, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
[mono-rt]    at System.Collections.Generic.LargeArrayBuilder`1[[System.Reflection.ParameterInfo, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].AddRange(IEnumerable`1 items)
[mono-rt]    at System.Collections.Generic.EnumerableHelpers.ToArray[ParameterInfo](IEnumerable`1 source)
[mono-rt]    at System.Linq.Enumerable.ToArray[ParameterInfo](IEnumerable`1 source)
[mono-rt]    at Maui.Plugins.PageResolver.NavigationExtensions.ParametersMatchConstructors(Type type, Object[] parameters)
[mono-rt]    at Maui.Plugins.PageResolver.NavigationExtensions.CreatePageWithViewModel[DisplayPage](IServiceProvider serviceProvider, Type viewModelType, Object[] parameters)
[mono-rt]    at Maui.Plugins.PageResolver.NavigationExtensions.ResolvePage[DisplayPage](Object[] parameters)
[mono-rt]    at Maui.Plugins.PageResolver.NavigationExtensions.<PushAsync>d__2`1[[App.Routes.WasteCalendar.Pages.DisplayPage, App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext()
[mono-rt]    at App.Routes.WasteCalendar.Pages.AddressSearchPage.BtnNext_Clicked(Object sender, EventArgs e) in D:\App\Maui\FCMPOCO\App\Routes\WasteCalendar\Pages\AddressSearchPage.xaml.cs:line 24
[mono-rt]    at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
[mono-rt]    at Android.App.SyncContext.<>c__DisplayClass2_0.<Post>b__0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:line 36
[mono-rt]    at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
[mono-rt]    at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Java.Lang.IRunnable.cs:line 84
[mono-rt]    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 22

To Reproduce
Navigating to a new page I am doing this -

await Navigation.PushAsync<DisplayPage>(_viewModel.SelectedAddress.Uprn, _viewModel.SelectedAddress.Address);

The new page has 2 injectable classes -
image

And the ViewModel for that page, has 1 injectable class + the 2 parameters being passed through -
image

All are being registered and the mapping is done manually -
image

Additional context
Removing the 2 parameters (double uprn, string address), I don't get the exception.

Use Navigation.PushModalAsync<T> also for TabbedPage

First of all; Thank you for this great library! It is really easy to use and saved me alot of time. I really think this should be a standard thing in .NET MAUI.

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

At the moment, it is not possible to use Navigation.PushModalAsync<T> for a TabbedPage. It only works when T is of type ContentPage.

Describe the solution you'd like
A clear and concise description of what you want to happen.

I would love to use this library for a TabbedPage in the same way I would use it with a ContentPage. So; Navigation.PushModalAsync<TabbedPage>().

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

I did not consider anything yet, but I am curious as to why T is only allowed to be a ContentPage?

Additional context
Add any other context or screenshots about the feature request here.

Fluent initialization override

Hi, could you add another override to StartupExtensions.cs so we can have one fluent startup builder

        public static MauiAppBuilder UsePageResolver(this MauiAppBuilder builder)
        {
            var sp = builder.Services.BuildServiceProvider();
            Resolver.RegisterServiceProvider(sp);
				
		    return builder;
        }

Not tested this, but this would allow chaining other startups stuff in one fluent call, at the moment this NuGet needs to be the last call on the builder chain or be on its own line.

Does this support shell pages?

I'm trying to use this with Shell and it doesn't seem to work. I may be using Shell wrong as it's my first time. This is the code I have for my shell page.

<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:pages="clr-namespace:Garsonix.LinkCheckerApp.Pages"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="Garsonix.LinkCheckerApp.MainPage">

    <FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
        <Tab Title="Report">
            <ShellContent ContentTemplate="{DataTemplate pages:ResourcesListPage}" />
        </Tab>
    </FlyoutItem>
</Shell>

My ResourcesListPage has one constructor injected dependency and the page and its dependency are both registered with DI.

So does PageResolver support shell and I'm doing it wrong, or does it not and I should be doing this a different way?

If it does support it, could you add an example to the instructions. If it doesn't, would it be possible for it to do so?

Make the Resolve method public

Is your feature request related to a problem? Please describe.
I'd like to be able to use the Resolve method directly in order to extend the functionality for things other than INavigation.

Describe the solution you'd like
Make certain methods public, or possibly even create a separate project for Mopups support and give it access to internal methods of this project.

Additional context
We use Mopups for our popups in our app. After a quick fiddle, I was able to get PageResolver to work with Mopups.

For example, I registered the popup and ViewModel like any other page -

builder.Services.AddTransient<AddPopup>();
builder.Services.AddTransient<AddReminderViewModel>();

builder.Services.UsePageResolver(new Dictionary<Type, Type>()
{
	{ typeof(AddPopup), typeof(AddReminderViewModel) }
});

I then made the Resolve<T> public, and was able to get this to work -

await MopupService.Instance.PushAsync(Resolver.Resolve<AddPopup>());

Obviously, I'd need to do quite a bit more work to get it to work with parameters.
Just seeing if you're open to something like this?

Use TryAddEnumerable to only add IMauiInitializeService once.

After continuing to use MAUI and MS DI I've noted that changing the way the IMauiInitializeService is registered will reduce memory use...

Using Services.TryAddEnumerable( ServiceDescriptor.Transient<IMauiInitializeService, Initializer>() ); will result in the IMauiInitializeService only being registered a single time. This is because the extension method validates that the Service AND the Implementation have not been previously registered before it does so.

This will result in a smaller memory footprint and fewer registered services during runtime when the initialization occurs multiple times from say multiple assemblies that reference it.

Abstract classes shouldn't be added during resolution.

Describe the bug
If a class is an abstract class it should not be added while generating the code.

To Reproduce
Steps to reproduce the behaviour:

  1. Create an abstract class
  2. build the solution
  3. Abstract class will be added in autogenerated code in UseAutodependencies function.

Expected behaviour
Abstract classes should not be added in UseAutodependencies function.

v2 cannot resolve a page with parameters using UsePageResolver

Describe the bug
I want to explicitly register dependencies using UsePageResolver, and one of my pages accepts a view model and a parameter.
After upgrading to v2 (v1.1.1 works fine), I get the following error:

System.InvalidOperationException: A suitable constructor for type 'DemoProject.PageParamPage' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.FindApplicableConstructor(Type instanceType, Type[] argumentTypes, ConstructorInfo& matchingConstructor, Nullable`1[]& matchingParameterMap)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance[PageParamPage](IServiceProvider provider, Object[] parameters)
at Maui.Plugins.PageResolver.NavigationExtensions.CreatePageWithoutViewModel[PageParamPage](IServiceProvider serviceProvider, Object[] parameters) in /Users/antonpolkanov/projects/Maui.Plugins.PageResolver/src/Maui.Plugins.PageResolver/NavigationExtensions.cs:line 89
at Maui.Plugins.PageResolver.NavigationExtensions.ResolvePage[PageParamPage](Object[] parameters) in /Users/antonpolkanov/projects/Maui.Plugins.PageResolver/src/Maui.Plugins.PageResolver/NavigationExtensions.cs:line 81
at Maui.Plugins.PageResolver.NavigationExtensions.d__21[[DemoProject.PageParamPage, DemoProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() in /Users/antonpolkanov/projects/Maui.Plugins.PageResolver/src/Maui.Plugins.PageResolver/NavigationExtensions.cs:line 54
at DemoProject.ViewModels.MainViewModel.GoToPageParamPage() in /Users/antonpolkanov/projects/Maui.Plugins.PageResolver/src/DemoProject/ViewModels/MainViewModel.cs:line 38
at DemoProject.ViewModels.MainViewModel.<get_GoToPageParamCommand>b__4_0() in /Users/antonpolkanov/projects/Maui.Plugins.PageResolver/src/DemoProject/ViewModels/MainViewModel.cs:line 12
at System.Threading.Tasks.Task.<>c.b__128_0(Object state)
at Android.App.SyncContext.<>c__DisplayClass2_0.b__0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:line 36
at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Java.Lang.IRunnable.cs:line 84
at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 26

To Reproduce
Steps to reproduce the behavior:

  1. Go to the DemoProject
  2. Replace UseAutodependencies with UsePageResolver
  3. Register 2 pages and a service for the app to run
            builder.Services.AddSingleton<INameService, NameService>();
            builder.Services.AddSingleton(typeof(MainPage));
            builder.Services.AddSingleton(typeof(MainViewModel));
            builder.Services.AddSingleton(typeof(PageParamPage));
            builder.Services.AddSingleton(typeof(PageParamViewModel));
  1. Run the app
  2. Tap "Click to go to page param page" button

Expected behavior
The page opens and passed parameter ("Maui") is displayed on the page

Screenshots
N/A

Desktop (please complete the following information):
N/A

Smartphone (please complete the following information):
N/A

Additional context
Add any other context about the problem here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.