Skip to content
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

@inject support in .NET Core MVC views #990

Open
vkirienko opened this issue Feb 10, 2024 · 9 comments
Open

@inject support in .NET Core MVC views #990

vkirienko opened this issue Feb 10, 2024 · 9 comments
Labels

Comments

@vkirienko
Copy link

Hi,

I think this question was already asked before but reading through several threads I still don't have clear understanding if @Inject can be used with SimpleInjector. Clearly it does not work by default but is there any workaround?

For the record I went through these.

#860
#362
simpleinjector/SimpleInjector.Integration.AspNetCore#25

Thank you

@dotnetjunkie
Copy link
Collaborator

The discussion that you mentioned last actually contains code that, according to the OP, works.

@vkirienko
Copy link
Author

Thank you! That's what I wanted to confirm.

I just curios why it is not part of SimpleInjector default integration with .NET Core MVC?

@dotnetjunkie
Copy link
Collaborator

I just curios why it is not part of SimpleInjector default integration with .NET Core MVC?

Mainly because of two reasons:

  1. Due to the lack of good interception points within MVC, creating and maintaining such integration point for me as a library designer might be a lot of work. There might be tons of corner cases that have to be dealt with and I'm afraid this something that might come back to me as rework over and over again.
  2. I feel that injecting dependencies into your Razor pages isn't the best approach from a design perspective and Simple Injector was always designed in an opinionated way and promotes best practices. Instead, a Razor page, MVC view, WPF view or any sort of view should rather work with a Model object that contains all data required for that view. So instead of injecting an ITimeProvider into the page, return a model that contains a DateTime CurrentTime property or something similar.

I hope this makes sense.

@vkirienko
Copy link
Author

vkirienko commented Feb 10, 2024

Good points, especially #2. I'm about to migrate large application to .NET Core. It has clever framework on top of MVC with again clever code in MVC views. And I agree it would be better not to have it and keep views as simple as possible.

Thank you, again for great DI library!

@vkirienko
Copy link
Author

@dotnetjunkie thank you again for pointing to right direction. Code from this response works as intended and I was able to inject dependencies using [Import] attribute. It is nice to be able to do it as I have couple of properties in our base razor page class where we use service locator pattern and now we can use proper DI.

simpleinjector/SimpleInjector.Integration.AspNetCore#25 (comment)

But my question was not clear and in fact I was looking to way to inject dependency using @Inject directive.

Index,cshtml
@inject IDueToReminderInfoService dueToReminderInfoService

It still fails with error:

InvalidOperationException: No service for type 'MyApp.Services.Dates.IDueToReminderInfoService' has been registered.
Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator+<>c__DisplayClass8_0.<CreateActivateInfo>b__2(ViewContext context)
Microsoft.Extensions.Internal.PropertyActivator<TContext>.Activate(object instance, TContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator.Activate(object page, ViewContext context)
MyApp.Core.SimpleInjector.SimpleInjectorRazorpageActivator.Activate(IRazorPage page, ViewContext context) in SimpleInjectorRazorpageActivator.cs
+
            activator.Activate(page, 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)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

In generated Index.cshtml.cs file property I'm trying to inject RazorInject attribute

        [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
        public IDueToReminderInfoService dueToReminderInfoService { get; private set; } = default!;

@dotnetjunkie
Copy link
Collaborator

From the stack trace I understand that your custom SimpleInjectorRazorpageActivator calls into the Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator class. This means that your custom SimpleInjectorRazorpageActivator does not correctly differentiate between dependencies that should be resolved from Simple Injector and dependencies that should be resolved from the built-in DI infrastructure. Clearly, IDueToReminderInfoService is something that should be pulled in from Simple Injector. This means that in this stack trace, SimpleInjectorRazorpageActivator should not call RazorPagePropertyActivator, but rather call Simple Injector instead.

@vkirienko
Copy link
Author

I guess we have to call Microsoft.AspNetCore.Mvc.Razor.RazorPagePropertyActivator from SimpleInjectorRazorpageActivator. Comment in SimpleInjectorRazorpageActivator says:

    // This implementation depends on the default RazorPageActivator, because initialization
    // of framework dependencies is required for activation to succeed.

As far as I can see RazorPagePropertyActivator uses build-in service provider and not extendable,

var serviceProvider = context.HttpContext.RequestServices;
var value = serviceProvider.GetRequiredService(property.PropertyType);

@dotnetjunkie
Copy link
Collaborator

I think I'm starting to see the problem here. The implementation I proposed in 25, calls into the core behavior and let Simple Injector inject properties that are marked with the ImportAttribute. That would work great, but doesn't allow using the @Inject tag. With @Inject, Razor is generating an [Inject] attribute, but the framework's behavior is to resolve dependencies marked with inject. This is what is causing the exception in your case.

Since the core behavior isn't extendable, the only option is to skip calling into the core behavior. But that causes new problems, because there is a set of dependencies that seem to be injected through a different mechanism. Not initializing them might cause issues of its own.

It might be possible to work around these issues, but at this point I'm unsure how to proceed.

@vkirienko
Copy link
Author

vkirienko commented Feb 13, 2024

I don't have that many services used @Inject. So in my case I think reasonable workaround would be to register services used with @Inject in MS DI container and resolve them through SimpleInjector.

services.AddScoped<IDueToReminderInfoService>(c => SI.Container.GetInstance<IDueToReminderInfoService>());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants