simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 153 forks source link

@inject unsupported in ASP.NET Core Razor pages? #860

Closed Plasma closed 3 years ago

Plasma commented 4 years ago

Hello,

I came across https://github.com/simpleinjector/SimpleInjector/issues/442#issuecomment-334430299 mentioning how @inject IMyService myService in Razor ASP.NET Core was not supported.

This comment was a while ago, and so wanted to double-check to see if .NET Core 3.x had any improvements to allow SimpleInjector to work with @inject?

Index.cshtml

@inject IMyService myService

Hello from @myService.TestProperty
InvalidOperationException: No service for type 'MyNamespace.IMyService' has been registered.
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator+<>c__DisplayClass8_0.<CreateActivateInfo>b__1(ViewContext context)
Microsoft.Extensions.Internal.PropertyActivator<TContext>.Activate(object instance, TContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator.Activate(object page, ViewContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.Activate(IRazorPage page, ViewContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
...

This is a nicer way to inject a context object for the page, instead of wrapping in a ViewBag for example.

Thank you

dotnetjunkie commented 4 years ago

UPDATE: Please see the Blazor integration guide for the latest information on integrating with Blazor.

To my knowledge, nothing has changed in the .NET Core 3.x space that would improve the situation. Improvements are planned in .NET Core 5.0.

Plasma commented 4 years ago

Thanks for the update. Would this be fixable by having SI be delegated to from the native dependency resolver? I thought it was supposed to be queried by the native DI framework if it couldn’t resolve something, is there a hook point for that?

I know it already crosswires the other way but I want the inverse I think

dotnetjunkie commented 4 years ago

Inverse cross wiring is not possible, because MS.DI doesn't allow unregistered type resolution. You can, of course, register individual delegates into MS.DI that cal back into Simple Injector, but you'll have to do that for every dependency you need to resolve from within your Razor page.

dotnetjunkie commented 4 years ago

With ASP.NET Core 5.0, the creation of Razor components can be intercepted and forwarded to Simple Injector. Here is how to do it:

// Custom component activator
public class SimpleInjectorComponentActivator : IComponentActivator
{
    private readonly Container container;   
    public SimpleInjectorComponentActivator(Container container) => this.container = container;    
    public IComponent CreateInstance(Type type) => (IComponent)this.container.GetInstance(type);
}

// Add the custom component activator to ASP.NET Core:
services.AddSingleton<IComponentActivator>(
    new SimpleInjectorComponentActivator(container));

Also make sure to register all your components up-front, e.g.:

foreach (Type type in container.GetTypesToRegister<IComponent>(typeof(MyComponent).Assembly))
{
    container.Register(type);
}

In case you wish your components to be initializable with properties (note: you should prefer using Constructor Injection), you can add a custom property injection behavior to inject properties marked with InjectAttribute, as described here.

Plasma commented 4 years ago

Thank you for this

Bouke commented 3 years ago

I'm using a custom RazorPage<TModel> which I'd like to have dependencies injected into. It needs a parameterless constructor for MVC, meaning I have to use property injection. From the context of this ticket I assumed the following would work, but it doesn't. Is this a different issue?

# MyRazorPage.cs
public abstract class MyRazorPage<TModel> : RazorPage<TModel>
{
    [Import]
    public ITenantContextProvider<TenantContext> tenantProvider { get; init; }

    public TenantContext? Tenant => tenantProvider.GetContext();
}
#_ViewImports.cshtml
@inherits MyRazorPage<TModel>

I'm assuming IPropertySelectionBehavior and IComponentActivator would work here as well, but it doesn't. The property remains null. Furthermore if I use RazorInject I'm getting an exception from Microsoft DI that ITenantContextProvider<TenantContext> is not a registered service:

System.InvalidOperationException: No service for type 'ITenantContextProvider`1[TenantContext]' has been registered.
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator.<>c__DisplayClass8_0.<CreateActivateInfo>b__1(ViewContext context)
   at Microsoft.Extensions.Internal.PropertyActivator`1.Activate(Object instance, TContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator.Activate(Object page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator.Activate(IRazorPage page, ViewContext context)
dotnetjunkie commented 3 years ago

Hi @Bouke, if I'm not mistaken, this question is more about razor pages, rather than Razor components. That deserves a new thread, but I haven't got a quick answer for you. I will have to look into that, which might take some time.

Bouke commented 3 years ago

Thank you for looking into this, I have created simpleinjector/SimpleInjector.Integration.AspNetCore#25 for this.