Skip to content

Commit

Permalink
Merge pull request #3239 from chowarth/feature/containerprovider
Browse files Browse the repository at this point in the history
Add ContainerProvider for resolving dependencies in XAML
  • Loading branch information
dansiegel committed Aug 27, 2024
2 parents 66b1ab3 + a43fcc7 commit ec09b17
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 0 deletions.
67 changes: 67 additions & 0 deletions src/Maui/Prism.Maui/Ioc/ContainerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#nullable enable
namespace Prism.IoC;

/// <summary>
/// Provides Types and Services registered with the Container
/// </summary>
/// <typeparam name="T">The type to Resolve</typeparam>
/// <example>
/// We can use this to build better types such as ValueConverters with full dependency injection
/// <code>
/// public class MyValueConverter : IValueConverter
/// {
/// private readonly ILogger _logger { get; }
///
/// public MyValueConverter(ILogger logger)
/// {
/// _logger = logger;
/// }
///
/// public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
/// {
/// _logger.Log($"Converting {value.GetType().Name} to {targetType.Name}", Category.Debug, Priority.None);
/// // do stuff
/// }
///
/// public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
/// {
/// _logger.Log($"Converting back from {value.GetType().Name} to {targetType.Name}", Category.Debug, Priority.None);
/// return null;
/// }
/// }
/// </code>
/// We can then simply use our ValueConverter or other class directly in XAML
/// <![CDATA[
/// <ContentPage xmlns:prism="clr-namespace:Prism.Ioc;assembly=Prism.Maui">
/// <ContentPage.Resources>
/// <ResourceDictionary>
/// <prism:ContainerProvider x:TypeArguments="MyValueConverter" x:Key="myValueConverter" />
/// <ResourceDictionary>
/// <ContentPage.Resources>
/// <Label Text="{Binding SomeProp, Converter={StaticResource myValueConverter}}" />
/// </ContentPage>
/// ]]>
/// </example>
public class ContainerProvider<T>
{
/// <summary>
/// The Name used to register the type with the Container
/// </summary>
public string? Name { get; set; }

/// <summary>
/// Resolves the specified type from the Application's Container
/// </summary>
/// <param name="containerProvider"></param>
public static implicit operator T(ContainerProvider<T> containerProvider)
{
var container = ContainerLocator.Container;
if (container == null) return default(T);
if (string.IsNullOrWhiteSpace(containerProvider.Name))
{
return container.Resolve<T>();
}

return container.Resolve<T>(containerProvider.Name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Prism.DryIoc.Maui.Tests.Mocks;
using Prism.DryIoc.Maui.Tests.Mocks.Events;
using Prism.DryIoc.Maui.Tests.Mocks.ViewModels;
using Prism.DryIoc.Maui.Tests.Mocks.Views;
using Prism.Events;
using Prism.IoC;

namespace Prism.DryIoc.Maui.Tests.Fixtures.IoC;

public class ContainerProviderTests(ITestOutputHelper testOutputHelper) : TestBase(testOutputHelper)
{
[Fact]
public void CanResolveUnamedType()
{
var builder = CreateBuilder(prism => { });
var app = builder.Build();
var containerProvider = new ContainerProvider<ConcreteTypeMock>();

ConcreteTypeMock type = (ConcreteTypeMock)containerProvider;

Assert.NotNull(type);
Assert.IsType<ConcreteTypeMock>(type);
}

[Fact]
public void CanResolvedNamedType()
{
var builder = CreateBuilder(prism => { });
var app = builder.Build();
var containerProvider = new ContainerProvider<ConcreteTypeMock>
{
Name = ConcreteTypeMock.Key
};

ConcreteTypeMock vm = (ConcreteTypeMock)containerProvider;

Assert.NotNull(vm);
Assert.IsType<ConcreteTypeMock>(vm);
}

[Fact]
public async Task ProvidesValueFromResourceDictionary()
{
var builder = CreateBuilder(prism =>
{
prism.RegisterTypes(containerRegistry =>
{
containerRegistry.RegisterForNavigation<MockXamlView, MockXamlViewViewModel>();
})
.CreateWindow(n =>
n.CreateBuilder()
.AddSegment<MockViewAViewModel>()
.NavigateAsync());
});
var app = builder.Build();

var ea = app.Services.GetService<IEventAggregator>();
var events = new List<string>();
ea.GetEvent<TestActionEvent>().Subscribe((m) => events.Add(m));

var navigation = app.Services.GetService<INavigationService>();
await navigation.CreateBuilder()
.AddSegment<MockXamlViewViewModel>()
.NavigateAsync();

Assert.Contains(events, e => e == "Convert");
var window = GetWindow(app);
Assert.NotNull(window.Page);

var xamlView = window.Page as MockXamlView;
var viewModel = xamlView.BindingContext as MockXamlViewViewModel;

xamlView.TestEntry.Text = "Foo Bar";
Assert.Contains(events, e => e == "ConvertBack");
}
}
6 changes: 6 additions & 0 deletions tests/Maui/Prism.DryIoc.Maui.Tests/Mocks/ConcreteTypeMock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Prism.DryIoc.Maui.Tests.Mocks;

internal class ConcreteTypeMock
{
public const string Key = "ConcreteTypeMock";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Globalization;
using Prism.DryIoc.Maui.Tests.Mocks.Events;
using Prism.Events;

namespace Prism.DryIoc.Maui.Tests.Mocks.Converters;

internal class MockValueConverter : IValueConverter
{
private IEventAggregator _eventAggreator { get; }

public MockValueConverter(IEventAggregator eventAggreator)
{
_eventAggreator = eventAggreator;
}

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
_eventAggreator.GetEvent<TestActionEvent>().Publish("Convert");
return value;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
_eventAggreator.GetEvent<TestActionEvent>().Publish("ConvertBack");
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Prism.Events;

namespace Prism.DryIoc.Maui.Tests.Mocks.Events;

internal class TestActionEvent : PubSubEvent<string>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Prism.DryIoc.Maui.Tests.Mocks.ViewModels;

internal class MockXamlViewViewModel : BindableBase
{
private string _test = "Initial Value";
public string Test
{
get => _test;
set => SetProperty(ref _test, value);
}
}
23 changes: 23 additions & 0 deletions tests/Maui/Prism.DryIoc.Maui.Tests/Mocks/Views/MockXamlView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.IoC;assembly=Prism.Maui"
xmlns:converters="clr-namespace:Prism.DryIoc.Maui.Tests.Mocks.Converters"
x:Class="Prism.DryIoc.Maui.Tests.Mocks.Views.MockXamlView"
Title="MockXamlView">

<ContentPage.Resources>
<ResourceDictionary>
<prism:ContainerProvider
x:TypeArguments="converters:MockValueConverter"
x:Key="mockValueConverter" />
</ResourceDictionary>
</ContentPage.Resources>

<StackLayout>
<Entry x:Name="testEntry"
Text="{Binding Test,Converter={StaticResource mockValueConverter}}" />
</StackLayout>

</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Prism.DryIoc.Maui.Tests.Mocks.Views;

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MockXamlView : ContentPage
{
public MockXamlView()
{
InitializeComponent();
}

public Entry TestEntry => testEntry;
}

0 comments on commit ec09b17

Please sign in to comment.