Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion src/Core/src/MauiContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ public MauiContext(IServiceProvider services, Android.Content.Context context)

public MauiContext(IServiceProvider services)
{
_services = new WrappedServiceProvider(services ?? throw new ArgumentNullException(nameof(services)));
_ = services ?? throw new ArgumentNullException(nameof(services));
_services = services is IKeyedServiceProvider
? new KeyedWrappedServiceProvider(services)
: new WrappedServiceProvider(services);

_handlers = new Lazy<IMauiHandlersFactory>(() => _services.GetRequiredService<IMauiHandlersFactory>());
#if ANDROID
_context = new Lazy<Android.Content.Context?>(() => _services.GetService<Android.Content.Context>());
Expand Down Expand Up @@ -73,5 +77,27 @@ public void AddSpecific(Type type, Func<object, object?> getter, object state)
_scopeStatic[type] = (state, getter);
}
}

class KeyedWrappedServiceProvider : WrappedServiceProvider, IKeyedServiceProvider
{
public KeyedWrappedServiceProvider(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}

public object? GetKeyedService(Type serviceType, object? serviceKey)
{
if (Inner is IKeyedServiceProvider provider)
return provider.GetKeyedService(serviceType, serviceKey);
Comment thread
mattleibow marked this conversation as resolved.

// we know this won't work, but we need to call it to throw the right exception
return Inner.GetRequiredKeyedService(serviceType, serviceKey);
}

public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
{
return Inner.GetRequiredKeyedService(serviceType, serviceKey);
}
}
}
}
122 changes: 122 additions & 0 deletions src/Core/tests/UnitTests/MauiContextTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Hosting.Internal;
using Xunit;

Expand Down Expand Up @@ -99,6 +101,126 @@ public void CloneCanOverrideIncludeService()
Assert.Same(obj2, second.Services.GetService<TestThing>());
}

[Fact]
public void MauiContextSupportsKeyedServices()
Comment thread
mattleibow marked this conversation as resolved.
{
var collection = new ServiceCollection();
collection.AddKeyedTransient<IFooService, FooService>("foo");
collection.AddKeyedTransient<IFooService, FooService2>("foo2");
var services = collection.BuildServiceProvider();

var context = new MauiContext(services);

var foo = context.Services.GetRequiredKeyedService<IFooService>("foo");
Assert.IsType<FooService>(foo);

var foo2 = context.Services.GetRequiredKeyedService<IFooService>("foo2");
Assert.IsType<FooService2>(foo2);
}

[Fact]
public void MauiContextSupportsKeyedServicesUsingAttributes()
{
var collection = new ServiceCollection();
collection.AddKeyedTransient<IFooService, FooService>("foo");
collection.AddKeyedTransient<IBarService, BarService>("bar");
collection.AddTransient<IFooBarService, FooBarKeyedService>();
var services = collection.BuildServiceProvider();

var context = new MauiContext(services);

var foobar = context.Services.GetRequiredService<IFooBarService>();
var keyed = Assert.IsType<FooBarKeyedService>(foobar);
Assert.NotNull(keyed.Foo);
Assert.NotNull(keyed.Bar);
}
[Fact]
public void NonKeyedProviderStaysNonKeyed()
{
var builder = MauiApp.CreateBuilder(useDefaults: false);
builder.ConfigureContainer(new KeyedOrNonKeyedProviderFactory(false));
var mauiApp = builder.Build();

var context = new MauiContext(mauiApp.Services);

Assert.IsAssignableFrom<IServiceProvider>(context.Services);
Assert.IsNotAssignableFrom<IKeyedServiceProvider>(context.Services);

var context2 = new MauiContext(context.Services);

Assert.IsAssignableFrom<IServiceProvider>(context2.Services);
Assert.IsNotAssignableFrom<IKeyedServiceProvider>(context2.Services);
}

[Fact]
public void KeyedProviderStaysKeyed()
{
var builder = MauiApp.CreateBuilder(useDefaults: false);
builder.ConfigureContainer(new KeyedOrNonKeyedProviderFactory(true));
var mauiApp = builder.Build();

var context = new MauiContext(mauiApp.Services);

Assert.IsAssignableFrom<IServiceProvider>(context.Services);
Assert.IsAssignableFrom<IKeyedServiceProvider>(context.Services);

var context2 = new MauiContext(context.Services);

Assert.IsAssignableFrom<IServiceProvider>(context2.Services);
Assert.IsAssignableFrom<IKeyedServiceProvider>(context2.Services);
}

private class KeyedOrNonKeyedProviderFactory : IServiceProviderFactory<ServiceCollection>
{
public KeyedOrNonKeyedProviderFactory(bool keyed)
{
Keyed = keyed;
}

public bool Keyed { get; }

public ServiceCollection CreateBuilder(IServiceCollection services) =>
new() { services };

public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder)
{
var real = containerBuilder.BuildServiceProvider();
return Keyed ? new KeyedProvider(real) : new NonKeyedProvider(real);
}
}

private class NonKeyedProvider : IServiceProvider
{
public NonKeyedProvider(ServiceProvider provider)
{
Provider = provider;
}

public ServiceProvider Provider { get; }

public object GetService(Type serviceType) =>
Provider.GetService(serviceType);
}

private class KeyedProvider : IServiceProvider, IKeyedServiceProvider
{
public KeyedProvider(ServiceProvider provider)
{
Provider = provider;
}

public ServiceProvider Provider { get; }

public object GetKeyedService(Type serviceType, object serviceKey) =>
Provider.GetKeyedService(serviceType, serviceKey);

public object GetRequiredKeyedService(Type serviceType, object serviceKey) =>
Provider.GetRequiredKeyedService(serviceType, serviceKey);

public object GetService(Type serviceType) =>
Provider.GetService(serviceType);
}

class TestThing
{
}
Expand Down
14 changes: 14 additions & 0 deletions src/Core/tests/UnitTests/TestClasses/TestServices.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Maui.UnitTests
{
Expand Down Expand Up @@ -64,6 +65,19 @@ public FooBarService(IFooService foo, IBarService bar)
public IBarService Bar { get; }
}

class FooBarKeyedService : IFooBarService
{
public FooBarKeyedService([FromKeyedServices("foo")] IFooService foo, [FromKeyedServices("bar")] IBarService bar)
{
Foo = foo;
Bar = bar;
}

public IFooService Foo { get; }

public IBarService Bar { get; }
}

class FooTrioConstructor : IFooBarService
{
public FooTrioConstructor()
Expand Down