Conversation
|
Wow, does this actually work? Is the trick that it already pre-creates a scope during initialization? I wonder, are there any downsides to that? There are some random test failures, but also I see two legit ones where probably just the tests need to be updated: 1. CreatesServiceProviderByDefault Assert.IsType() Failure: Value is not the exact type Stack trace 2. ConfigureContainerCanReplaceIServiceProvider Assert.IsType() Failure: Value is not the exact type Stack trace |
|
As far as I understand, this PR enables scopes validation during the development of MAUI itself. Btw, does it make sense to include registered services validation (i.e. that all the dependencies are registered for all the services)? containerBuilder.BuildServiceProvider(new ServiceProviderOptions
{
ValidateScopes = true,
ValidateOnBuild = true
}); |
|
There's a potential issue with the current implementation. |
|
Indeed, @Dreamescaper points are I think both critical:
|
Yea, the first step here is just making sure that MAUI itself isn't violating the rules so if users opt in for the validation through the mechanisms that @Foda has shown in the PR that it'll work for users. |
|
/rebase |
56b6176 to
3f1ce7b
Compare
| : _services.BuildServiceProvider(); | ||
|
|
||
| MauiApp builtApplication = new MauiApp(serviceProvider); | ||
| var appScope = serviceProvider.CreateScope(); |
There was a problem hiding this comment.
Why are we always creating a scope for the application? That doesn't seem necessary.
There was a problem hiding this comment.
I feel like the core problem is really that some MAUI services have invalid scope dependencies (like a scoped service depending on a transient service or similar). And we never found that because we never had scope validation enabled. That's what my initial investigation from a long time ago revealed: #11457 (comment)
There was a problem hiding this comment.
This comes up for services that have the possibility to be scoped at the window or the application level.
Currently we only create scopes for each window.
The scenario that @Eilon articulated in the original issue is probably the only current case where this comes up.
The IDispatcher is a scoped service that returns an IDispatcherat the Application level and then a different IDispatcher at the window level, depending on where you are and what you are trying to do.
Before we've created any windows, the IDispatcher at the application level is used for scenarios that need a dispatcher. The exception that you see in @Eilon 's comment comes from us retrieving the Application level dispatcher before any windows have been created. The Application dispatcher is also relevant for scenarios where you might not be inside a window.
The other approach could be to setup a scoped container that we use specifically for app level scoped services. We can use that as our fall back so that the app level container remains the root container and not scoped. This would probably maintain the spirit/purpose of this validation a bit more accurately, whereas doing it this way would probably mask the validation.
There was a problem hiding this comment.
Pushed up a variation of this PR based on the notes I made above ^
There was a problem hiding this comment.
I'm not sure if there are any negative implications to this so I"ll need to defer to @eerhardt and @davidfowl regarding always creating a scope.
There was a problem hiding this comment.
So even though MauiApp.Services remains an unscoped provider, the root MauiContext.Services now always has an application-level scope? What about code already calling the public MauiContext ctor with MauiApp.Services? Also, did we miss a call to the MauiContext ctor in our own Tizen code?
If this is the direction we want to go, I think we should update the API to be clearer about where it's exposing an application-level scoped provider vs unscoped root provider. Or at least update the doc comments and make a breaking change announcement.
|
/rebase |
3f1ce7b to
011f4d5
Compare
Eilon
left a comment
There was a problem hiding this comment.
This looks fairly reasonable to me in terms of enabling scope validation.
In ASP.NET Core the system automatically enables scope validation in "Development" mode (this is orthogonal to Debug/Release). In MAUI we don't have such a concept, but we do have Debug/Release.
Would we consider a change to the default template code to have #if DEBUG di.EnableScopeValidation=true; ?
Obviously that isn't real code, but it would be nice if we:
- Make it very easy for people to enable (the current pattern has nearly zero discoverability, and is not easy)
- Enable it in the template for DEBUG mode. This way there's no breaking change, and it's template code, so very easy to change
Thoughts?
There was a problem hiding this comment.
Unless I'm missing something, resolving scoped services will always be "valid" after this change unless there are resolved using MauiApp.Services.GetService(type). Is this common? If not, I don't see the point of the additional complexity of exposing both unscoped and application scoped providers. We might as well make MauiApp.Services scoped too and forget about validation altogether.
| : _services.BuildServiceProvider(); | ||
|
|
||
| MauiApp builtApplication = new MauiApp(serviceProvider); | ||
| var appScope = serviceProvider.CreateScope(); |
There was a problem hiding this comment.
So even though MauiApp.Services remains an unscoped provider, the root MauiContext.Services now always has an application-level scope? What about code already calling the public MauiContext ctor with MauiApp.Services? Also, did we miss a call to the MauiContext ctor in our own Tizen code?
If this is the direction we want to go, I think we should update the API to be clearer about where it's exposing an application-level scoped provider vs unscoped root provider. Or at least update the doc comments and make a breaking change announcement.
| foreach (var instance in initServices) | ||
| { | ||
| instance.Initialize(builtApplication.Services); | ||
| instance.Initialize(appScope.ServiceProvider); |
There was a problem hiding this comment.
If we're passing a scoped service provider into IMauiInitializeService, what's the point of IMauiInitializeScopedService?
There was a problem hiding this comment.
Alright shifted things around a bit. I'm pretty sure the bits that were implementing IMauiInitializeService should have actually been implementingIMauiInitializeScopedService
@mattleibow ? I noticed we add the ScopedService version here #2862
There was a problem hiding this comment.
Hmmm though it looks like this change now causes requests for the IDispatcher via MauiApp.Services to fail because it's not initialized on the right thread.
So, I'll need to think on that for a bit or just go back to the path of having MauiApp.Services just be a scoped service.
The main scenario I think that validation will cover is if you resolve a singleton service with a scoped constructor argument. Something like this public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp
.CreateBuilder()
.UseMauiMaps()
.UseMauiApp<App>();
builder.ConfigureContainer(new DefaultServiceProviderFactory(new ServiceProviderOptions
{
ValidateOnBuild = true,
ValidateScopes = true,
}));
builder.Services.AddSingleton(typeof(Singleton));
builder.Services.AddScoped(typeof(ScopedService));
// crashes because the ctor on singleton has a scoped service
var app = builder.Build();
app.Services.CreateScope().ServiceProvider.GetServices<Singleton>();
return app;
}
}
public class ScopedService
{
}
public class Singleton
{
public Singleton(ScopedService service)
{
}
}
This was our initial approach but @eerhardt expressed concern so I took at stab at modifying this PR so the maui/src/Core/src/MauiContextExtensions.cs Lines 49 to 69 in 1f8b824 so it somewhat made sense to me that we could do the same with the all that being said :-) I don't quite see the difference between the complexity of this approach vs our first approach just making the The only difference I can think of there is that if someone were to do
if we keep
I moved the code for creating the app scope to |
|
/rebase |
Fix test
136624c to
6fcde0d
Compare
|
Trying a different approach here |
Description of Change
Validate service provider scopes
Issues Fixed
Fixes #11457