Skip to content

Commit 7dbe853

Browse files
authored
Make sure we support keyed services (#20014)
1 parent 5519ab3 commit 7dbe853

File tree

3 files changed

+163
-1
lines changed

3 files changed

+163
-1
lines changed

src/Core/src/MauiContext.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ public MauiContext(IServiceProvider services, Android.Content.Context context)
2323

2424
public MauiContext(IServiceProvider services)
2525
{
26-
_services = new WrappedServiceProvider(services ?? throw new ArgumentNullException(nameof(services)));
26+
_ = services ?? throw new ArgumentNullException(nameof(services));
27+
_services = services is IKeyedServiceProvider
28+
? new KeyedWrappedServiceProvider(services)
29+
: new WrappedServiceProvider(services);
30+
2731
_handlers = new Lazy<IMauiHandlersFactory>(() => _services.GetRequiredService<IMauiHandlersFactory>());
2832
#if ANDROID
2933
_context = new Lazy<Android.Content.Context?>(() => _services.GetService<Android.Content.Context>());
@@ -73,5 +77,27 @@ public void AddSpecific(Type type, Func<object, object?> getter, object state)
7377
_scopeStatic[type] = (state, getter);
7478
}
7579
}
80+
81+
class KeyedWrappedServiceProvider : WrappedServiceProvider, IKeyedServiceProvider
82+
{
83+
public KeyedWrappedServiceProvider(IServiceProvider serviceProvider)
84+
: base(serviceProvider)
85+
{
86+
}
87+
88+
public object? GetKeyedService(Type serviceType, object? serviceKey)
89+
{
90+
if (Inner is IKeyedServiceProvider provider)
91+
return provider.GetKeyedService(serviceType, serviceKey);
92+
93+
// we know this won't work, but we need to call it to throw the right exception
94+
return Inner.GetRequiredKeyedService(serviceType, serviceKey);
95+
}
96+
97+
public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
98+
{
99+
return Inner.GetRequiredKeyedService(serviceType, serviceKey);
100+
}
101+
}
76102
}
77103
}

src/Core/tests/UnitTests/MauiContextTests.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.DependencyInjection.Extensions;
4+
using Microsoft.Maui.Hosting;
35
using Microsoft.Maui.Hosting.Internal;
46
using Xunit;
57

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

104+
[Fact]
105+
public void MauiContextSupportsKeyedServices()
106+
{
107+
var collection = new ServiceCollection();
108+
collection.AddKeyedTransient<IFooService, FooService>("foo");
109+
collection.AddKeyedTransient<IFooService, FooService2>("foo2");
110+
var services = collection.BuildServiceProvider();
111+
112+
var context = new MauiContext(services);
113+
114+
var foo = context.Services.GetRequiredKeyedService<IFooService>("foo");
115+
Assert.IsType<FooService>(foo);
116+
117+
var foo2 = context.Services.GetRequiredKeyedService<IFooService>("foo2");
118+
Assert.IsType<FooService2>(foo2);
119+
}
120+
121+
[Fact]
122+
public void MauiContextSupportsKeyedServicesUsingAttributes()
123+
{
124+
var collection = new ServiceCollection();
125+
collection.AddKeyedTransient<IFooService, FooService>("foo");
126+
collection.AddKeyedTransient<IBarService, BarService>("bar");
127+
collection.AddTransient<IFooBarService, FooBarKeyedService>();
128+
var services = collection.BuildServiceProvider();
129+
130+
var context = new MauiContext(services);
131+
132+
var foobar = context.Services.GetRequiredService<IFooBarService>();
133+
var keyed = Assert.IsType<FooBarKeyedService>(foobar);
134+
Assert.NotNull(keyed.Foo);
135+
Assert.NotNull(keyed.Bar);
136+
}
137+
[Fact]
138+
public void NonKeyedProviderStaysNonKeyed()
139+
{
140+
var builder = MauiApp.CreateBuilder(useDefaults: false);
141+
builder.ConfigureContainer(new KeyedOrNonKeyedProviderFactory(false));
142+
var mauiApp = builder.Build();
143+
144+
var context = new MauiContext(mauiApp.Services);
145+
146+
Assert.IsAssignableFrom<IServiceProvider>(context.Services);
147+
Assert.IsNotAssignableFrom<IKeyedServiceProvider>(context.Services);
148+
149+
var context2 = new MauiContext(context.Services);
150+
151+
Assert.IsAssignableFrom<IServiceProvider>(context2.Services);
152+
Assert.IsNotAssignableFrom<IKeyedServiceProvider>(context2.Services);
153+
}
154+
155+
[Fact]
156+
public void KeyedProviderStaysKeyed()
157+
{
158+
var builder = MauiApp.CreateBuilder(useDefaults: false);
159+
builder.ConfigureContainer(new KeyedOrNonKeyedProviderFactory(true));
160+
var mauiApp = builder.Build();
161+
162+
var context = new MauiContext(mauiApp.Services);
163+
164+
Assert.IsAssignableFrom<IServiceProvider>(context.Services);
165+
Assert.IsAssignableFrom<IKeyedServiceProvider>(context.Services);
166+
167+
var context2 = new MauiContext(context.Services);
168+
169+
Assert.IsAssignableFrom<IServiceProvider>(context2.Services);
170+
Assert.IsAssignableFrom<IKeyedServiceProvider>(context2.Services);
171+
}
172+
173+
private class KeyedOrNonKeyedProviderFactory : IServiceProviderFactory<ServiceCollection>
174+
{
175+
public KeyedOrNonKeyedProviderFactory(bool keyed)
176+
{
177+
Keyed = keyed;
178+
}
179+
180+
public bool Keyed { get; }
181+
182+
public ServiceCollection CreateBuilder(IServiceCollection services) =>
183+
new() { services };
184+
185+
public IServiceProvider CreateServiceProvider(ServiceCollection containerBuilder)
186+
{
187+
var real = containerBuilder.BuildServiceProvider();
188+
return Keyed ? new KeyedProvider(real) : new NonKeyedProvider(real);
189+
}
190+
}
191+
192+
private class NonKeyedProvider : IServiceProvider
193+
{
194+
public NonKeyedProvider(ServiceProvider provider)
195+
{
196+
Provider = provider;
197+
}
198+
199+
public ServiceProvider Provider { get; }
200+
201+
public object GetService(Type serviceType) =>
202+
Provider.GetService(serviceType);
203+
}
204+
205+
private class KeyedProvider : IServiceProvider, IKeyedServiceProvider
206+
{
207+
public KeyedProvider(ServiceProvider provider)
208+
{
209+
Provider = provider;
210+
}
211+
212+
public ServiceProvider Provider { get; }
213+
214+
public object GetKeyedService(Type serviceType, object serviceKey) =>
215+
Provider.GetKeyedService(serviceType, serviceKey);
216+
217+
public object GetRequiredKeyedService(Type serviceType, object serviceKey) =>
218+
Provider.GetRequiredKeyedService(serviceType, serviceKey);
219+
220+
public object GetService(Type serviceType) =>
221+
Provider.GetService(serviceType);
222+
}
223+
102224
class TestThing
103225
{
104226
}

src/Core/tests/UnitTests/TestClasses/TestServices.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using Microsoft.Extensions.DependencyInjection;
23

34
namespace Microsoft.Maui.UnitTests
45
{
@@ -64,6 +65,19 @@ public FooBarService(IFooService foo, IBarService bar)
6465
public IBarService Bar { get; }
6566
}
6667

68+
class FooBarKeyedService : IFooBarService
69+
{
70+
public FooBarKeyedService([FromKeyedServices("foo")] IFooService foo, [FromKeyedServices("bar")] IBarService bar)
71+
{
72+
Foo = foo;
73+
Bar = bar;
74+
}
75+
76+
public IFooService Foo { get; }
77+
78+
public IBarService Bar { get; }
79+
}
80+
6781
class FooTrioConstructor : IFooBarService
6882
{
6983
public FooTrioConstructor()

0 commit comments

Comments
 (0)