-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Injection into custom RazorPage? #25
Comments
Can you post a somewhat more elaborate example that I can use to reproduce the issue? |
Sure, the repository is here: https://github.com/Bouke/SimpleInjectorIssue928, and this is the relevant commit: Bouke/SimpleInjectorIssue928@be4b430. Thank you. |
Hi Bouke, I've done some tinkering, and as far as I can see, the problem lies in the default
Here's a custom public class SimpleInjectorRazorpageActivator : IRazorPageActivator
{
private readonly ConcurrentDictionary<Type, Registration> registrations = new();
private readonly RazorPageActivator activator;
private readonly Container container;
// This implementation depends on the default RazorPageActivator, because initialization
// of framework dependencies is required for activation to succeed.
public SimpleInjectorRazorpageActivator(RazorPageActivator activator, Container container)
{
this.activator = activator;
this.container = container;
}
public void Activate(IRazorPage page, ViewContext context)
{
this.activator.Activate(page, context);
var reg = this.registrations.GetOrAdd(
page.GetType(),
type => Lifestyle.Transient.CreateRegistration(type, this.container));
reg.InitializeInstance(page);
}
} You can wire everything together as follows: public class Startup
{
private readonly Container _container = new();
public Startup(IConfiguration configuration)
{
Configuration = configuration;
// Change property injection behavior
_container.Options.PropertySelectionBehavior =
new ImportAttributePropertySelectionBehavior();
}
public class ImportAttributePropertySelectionBehavior : IPropertySelectionBehavior
{
public bool SelectProperty(Type _, PropertyInfo propertyInfo) =>
propertyInfo.GetCustomAttribute<ImportAttribute>() != null;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSimpleInjector(_container, options =>
{
options.AddAspNetCore()
.AddControllerActivation()
.AddViewComponentActivation()
.AddPageModelActivation()
.AddTagHelperActivation();
});
// Replace default IRazorPageActivator
services.AddSingleton<RazorPageActivator>();
services.AddSingleton<IRazorPageActivator, SimpleInjectorRazorpageActivator>();
InitializeContainer();
}
// same old, same old
} When you implement your public abstract class MyRazorPage<TModel> : RazorPage<TModel>
{
[Import] public SomeDependency SomeDependency { get; set; }
} I hope this helps. |
Hi Steven, Thank you for the investigation and the example code. This appears to be working fine. |
I'm dipping my toes into Blazor, which uses a RazorPage for bootstrapping ( public abstract class MyPage : Page
{
[Import] public Container Container { get; set; }
} Being used like so: @page "/"
@inherits MyPage
<component type="typeof(App)" render-mode="ServerPrerendered" /> When debugging, I found that This is the stack trace leading up to
|
How does the RazorPageAdapter contain your page? Is there a property of some sort? What's the relationship between the two. How do you get from the adapter to the page? |
You can see the RazorPageAdapter containing a
Good question, however I'm not familiar with the design choices that went into Razor and Blazor Pages. I assume they have the adapter in place so they can share some logic. You can see the Blazor |
So the adapter is passed on by the framework to the |
The page inside the adapter is already activated before being passed to the adapter. That is handled here through DefaultPageActivatorProvider (internal) and RazorPagePropertyActivator: https://github.com/dotnet/aspnetcore/blob/ae1a6cbe225b99c0bf38b7e31bf60cb653b73a52/src/Mvc/Mvc.RazorPages/src/Infrastructure/DefaultPageFactoryProvider.cs#L63-L67. Armed with this knowledge I've come up with a custom public class SimpleInjectorPageActivatorProvider : IPageActivatorProvider
{
private readonly ConcurrentDictionary<Type, Registration> registrations = new();
private readonly IPageActivatorProvider pageActivatorProvider;
private readonly Container container;
// This implementation depends on the default RazorPageActivator, because initialization
// of framework dependencies is required for activation to succeed.
public SimpleInjectorPageActivatorProvider(IPageActivatorProvider pageActivatorProvider, Container container)
{
this.pageActivatorProvider = pageActivatorProvider;
this.container = container;
}
public Func<PageContext, ViewContext, object> CreateActivator(CompiledPageActionDescriptor descriptor)
{
var activator = pageActivatorProvider.CreateActivator(descriptor);
return (context, viewContext) =>
{
var page = (PageBase)activator(context, viewContext);
var reg = registrations.GetOrAdd(
page.GetType(),
type => Lifestyle.Transient.CreateRegistration(type, container));
reg.InitializeInstance(page);
return page;
};
}
public Action<PageContext, ViewContext, object> CreateReleaser(CompiledPageActionDescriptor descriptor)
{
return pageActivatorProvider.CreateReleaser(descriptor);
}
} Registration is cumbersome as the default activator is internal: var defaultPageActivatorProvider = typeof(IPageActivatorProvider).Assembly.GetType(
"Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageActivatorProvider")!;
services.AddSingleton(defaultPageActivatorProvider);
services.AddSingleton<IPageActivatorProvider>(
provider => new SimpleInjectorPageActivatorProvider(
(IPageActivatorProvider)provider.GetRequiredService(defaultPageActivatorProvider),
provider.GetRequiredService<Container>())); |
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.I'm assuming
IPropertySelectionBehavior
andIComponentActivator
would work here as well, but it doesn't. The property remainsnull
. Furthermore if I useRazorInject
I'm getting an exception from Microsoft DI thatITenantContextProvider<TenantContext>
is not a registered service:(split from simpleinjector/SimpleInjector#860 (comment))
The text was updated successfully, but these errors were encountered: