simpleinjector / SimpleInjector.Integration.AspNetCore

MIT License
2 stars 3 forks source link

AddPageModelActivation() can't resolve Microsoft-specified types like LogoutModel<IdentityUser> #9

Open oleg-gochachko opened 5 years ago

oleg-gochachko commented 5 years ago

I follow https://simpleinjector.org/aspnetcore guide to integrate SI to my web site (standard template with authentication).

However, when I try to open login page - I recieve the folloving error:

System.InvalidOperationException: For the SimpleInjectorPageModelActivatorProvider to function properly, it requires all page models to be registered explicitly in Simple Injector, but a registration for LogoutModel<IdentityUser> is missing. To ensure all page models are registered properly, call the RegisterPageModels extension method on the Container from within your Startup.Configure method while supplying the IApplicationBuilder instance, e.g. "this.container.RegisterPageModels(app);". Full page model name: Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.LogoutModel`1[[Microsoft.AspNetCore.Identity.IdentityUser, Microsoft.Extensions.Identity.Stores, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].

Stack trace:

at SimpleInjector.Integration.AspNetCore.Mvc.SimpleInjectorPageModelActivatorProvider.CreateActivator(CompiledPageActionDescriptor descriptor)
at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageModelFactoryProvider.CreateModelFactory(CompiledPageActionDescriptor descriptor)
at Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerProvider.CreateCacheEntry(ActionInvokerProviderContext context, FilterItem[] cachedFilters)
at Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerProvider.OnProvidersExecuting(ActionInvokerProviderContext context)
at Microsoft.AspNetCore.Mvc.Internal.ActionInvokerFactory.CreateInvoker(ActionContext actionContext)
at Microsoft.AspNetCore.Mvc.Internal.MvcEndpointDataSource.<>c__DisplayClass21_0.<CreateEndpoint>b__0(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

After several hours of tries I'm able to start the project propery by using these configurations:

// add simple injector ...
services.AddSimpleInjector(Container, options =>
{
    // AddAspNetCore() wraps web requests in a Simple Injector scope.
    options.AddAspNetCore()
        // Ensure activation of a specific framework type to be created by
        // Simple Injector instead of the built-in configuration system.
        .AddControllerActivation()
        .AddViewComponentActivation()
        //.AddPageModelActivation()
        .AddTagHelperActivation();
});            

services.EnableSimpleInjectorCrossWiring(Container);            
services.AddSingleton<IPageModelActivatorProvider>(
    new SimpleInjectorPageModelActivatorProvider(Container));

and

app.UseSimpleInjector(Container, options =>
{
    //options.AutoCrossWireFrameworkComponents = false;

    // Add custom Simple Injector-created middleware to the ASP.NET pipeline.
    //options.UseMiddleware<CustomMiddleware1>(app);
    //options.UseMiddleware<CustomMiddleware2>(app);

    // Optionally, allow application components to depend on the
    // non-generic Microsoft.Extensions.Logging.ILogger abstraction.
    //options.UseLogging();
});

Container.RegisterPageModels(app);            
Container.AutoCrossWireAspNetComponents(app);

As you may see method .AddPageModelActivation() commented and obsolete method RegisterPageModels(app); and services.AddSingleton<IPageModelActivatorProvider> added.

I think AddPageModelActivation not enumerate some of Identity view models and should be fixed.

dotnetjunkie commented 5 years ago

Thank you for reporting this issue. This part of the ASP.NET Core stack is still something that might need improvement.

Your issue is a bit tricky, because there are a few things going on. First of all, the resolved type is a generic type defined internally inside the Microsoft libraries. That's why it hasn't been registered by Simple Injector. It only registers concrete, non-generic types that are part of application parts of type IApplicationPartTypeProvider.

The SimpleInjectorPageModelActivatorProvider requires types to be registered explicitly, which is similar to what the built-in MS.DI container requires. In the case of Page Models, however, Microsoft seems to circumvent that default behavior and circumvents going through the built-in container. Instead, the built-in IPageModelActivatorProvider has its own DI mechanism and only resolves Page Model's dependencies from the built-in container.

This is why the built-in behavior can resolve an Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal.LogoutModel<Microsoft.AspNetCore.Identity.IdentityUser>. The Simple Injector implementation, however, checks to see whether the type is registered. If this is not the case, it will fall back to using the built-in MS.DI container. That, unfortunately, won't work either, because that type isn't registered in MS.DI at all: it is the built-in IPageModelActivatorProvider that takes over.

We will have to figure out how to work with this scenario, without having to revert to weird hacks that prevent DI-container verification. A huge problem here is that the class in question is internal. This makes it very hard to register it by hand.

For now, you can work around the issue by doing the following:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        services.AddSimpleInjector(container, options =>
        {
            options.AddAspNetCore()
                .AddControllerActivation()
                .AddViewComponentActivation()
                .AddPageModelActivation()
                .AddTagHelperActivation();
        });

        services.AddSingleton<IPageModelActivatorProvider>(
            new FixedPageModelActivatorProvider(
                container,
                new SimpleInjectorPageModelActivatorProvider(container)));
    }

Here, FixedPageModelActivatorProvider is a custom class that is defined as follows:

public class FixedPageModelActivatorProvider : IPageModelActivatorProvider
{
    private readonly Container container;
    private readonly IPageModelActivatorProvider decoratee;

    public FixedPageModelActivatorProvider(Container container, IPageModelActivatorProvider decoratee)
    {
        this.container = container;
        this.decoratee = decoratee;
    }

    public Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor descriptor)
    {
        try
        {
            return this.decoratee.CreateActivator(descriptor);
        }
        catch (InvalidOperationException)
        {
            var producer = Lifestyle.Transient.CreateProducer(
                serviceType: descriptor.ModelTypeInfo.AsType(),
                implementationType: descriptor.ModelTypeInfo.AsType(),
                container: this.container);

            return _ => producer.GetInstance();
        }
    }

    public Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor descriptor) =>
        this.decoratee.CreateReleaser(descriptor);
}

This should fix your problem for now, until we have a good fix for this problem.

dotnetjunkie commented 5 years ago

feature-728 branch created.

dotnetjunkie commented 5 years ago

Hi @oleg-gochachko,

I'm currently trying to implement a fix for this issue. I, however, need to test my assumptions, but have little experience with PageModels myself. Would you be so kind to provide me with a small sample ASP.NET Core application that reproduces the issue?

dotnetjunkie commented 5 years ago

I postponed this feature until I have more information available. Feel free to provide me with information that helps me address this issue.