The Wayback Machine - https://web.archive.org/web/20210504080645/https://github.com/dotnet/aspnetcore/issues/18088
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

Can we talk about constructor injection now that partial classes are here? #18088

Open
SQL-MisterMagoo opened this issue Jan 2, 2020 · 14 comments
Open

Comments

@SQL-MisterMagoo
Copy link
Contributor

@SQL-MisterMagoo SQL-MisterMagoo commented Jan 2, 2020

Is your feature request related to a problem? Please describe.

Constructor injection in Components was ruled out previously as "we don't plan to do this" but now that we have partials is there any change in this attitude?
see #15779 , #5497

Describe the solution you'd like

If Blazor could be modified to optionally use DI to source components, then constructor injection would be possible,

If you would be open to at least reviewing a PR, I'm sure the community would do the groundwork to make it happen.

@pranavkm
Copy link
Contributor

@pranavkm pranavkm commented Jan 2, 2020

Thanks for your issue report. We'll consider whether to address this during our next milestone planning.

@SQL-MisterMagoo
Copy link
Contributor Author

@SQL-MisterMagoo SQL-MisterMagoo commented Jan 2, 2020

Thanks for that

@nvmkpk
Copy link

@nvmkpk nvmkpk commented Apr 9, 2020

This is a must have. Property based injection prevents us from consuming them within the constructor there by preventing us from making some (other) fields read only.

@YairHalberstadt
Copy link

@YairHalberstadt YairHalberstadt commented May 26, 2020

Comment from a duplicate issue I opened:

Currently blazor only allows injecting services into components using property injection via the [Inject] attribute.

However this has a number of disadvantages over constructor injection

  1. NRTs
    The compiler warns if you declare you service as non-nullable, and nullable reference types are enabled. You have to manually suppress the warning.
  2. Immutablility
    You cannot declare the services readonly, since they need to be set by the injector.
  3. Manual construction
    If you create the component manually (perhaps for unit testing), there is nothing to guarentee you will set the services. Even worse - you often can't since the properties are usually private. You have to declare two constructors - one parameterless, and one which sets all the services. This is needless code duplication, and means that test and app code go through different paths, which is undesirable.
  4. Validation
    The constructor is a place where you can validate all your services, and do transformations on them. Perhaps, you don't want to store your services, but just call one service with another service, and store the result. Constructors are a good place to do that, with property injection this is tricky.

Proposal

If there is a single constructor, use it for dependency injection. It is an error if any of the parameters cannot be resolved. It is an error if there are multiple constructors, none of which are parameterless, unless one of them is marked with [Inject]. After the construction, property injection takes place.

@nvmkpk
Copy link

@nvmkpk nvmkpk commented May 27, 2020

InjectAttribute cannot be set on non-properties. There is already ActivatorUtilitiesConstructorAttribute available for decorating a constructor to be used by DI.

@janissimsons
Copy link

@janissimsons janissimsons commented Jul 23, 2020

Any updates on this? @pranavkm

@mkArtakMSFT
Copy link
Contributor

@mkArtakMSFT mkArtakMSFT commented Jul 23, 2020

Moving to backlog given this now be achieved using IComponentActivator.

@msftbot
Copy link
Contributor

@msftbot msftbot bot commented Jul 23, 2020

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@janissimsons
Copy link

@janissimsons janissimsons commented Jul 23, 2020

@mkArtakMSFT how can it be achieved using IComponentActivator? Could you please give an example?

@hikalkan
Copy link

@hikalkan hikalkan commented Aug 30, 2020

@janissimsons,

As I can see, IComponentActivator is a service that is used when the renderer needs to create a new component instance.
The default implementation is DefaultComponentActivator which uses the Activator.CreateInstance.

So, you can just implement the IComponentActivator, inject the IServiceProvider into your implementation and use the .GetService(Type) method to create the compoent. I suggest to fallback the Activator.CreateInstance if GetService returns null (that means the component hasn't registered to the DI).

Then you need to register your components to the DI.

@hikalkan
Copy link

@hikalkan hikalkan commented Oct 22, 2020

The implementation can be like that:

using System;
using Microsoft.AspNetCore.Components;

namespace MyProject
{
    public class ServiceProviderComponentActivator : IComponentActivator
    {
        public IServiceProvider ServiceProvider { get; }

        public ServiceProviderComponentActivator(IServiceProvider serviceProvider)
        {
            ServiceProvider = serviceProvider;
        }

        public IComponent CreateInstance(Type componentType)
        {
            var instance = ServiceProvider.GetService(componentType);

            if (instance == null)
            {
                instance = Activator.CreateInstance(componentType);
            }

            if (!(instance is IComponent component))
            {
                throw new ArgumentException($"The type {componentType.FullName} does not implement {nameof(IComponent)}.", nameof(componentType));
            }

            return component;
        }
    }
}

Then replace the IComponentActivator service:

services.Replace(ServiceDescriptor.Transient<IComponentActivator, ServiceProviderComponentActivator>());

@mkArtakMSFT is there any problem with this implementation? Does it cause a memory leak (I don't know how the IServiceProvider scope is managed for this case)?

@nvmkpk
Copy link

@nvmkpk nvmkpk commented Oct 22, 2020

Keep in mind that this only works in .net 5. I wish this was the done by default. But this will do it for me.

@TanvirArjel
Copy link

@TanvirArjel TanvirArjel commented Feb 23, 2021

Constructor DI should be default in Blazor and it can be a very good feature for 6.0.0

@TanvirArjel
Copy link

@TanvirArjel TanvirArjel commented Mar 16, 2021

I have created a library called TanvirArjel.Blazor which enables constructor dependency injection support for Blazor Components with a single line of code as follows:

Blazor Server:

using TanvirArjel.Blazor.DependencyInjection;
    
services.AddComponents();

Blazor Web Assembly:

using TanvirArjel.Blazor.DependencyInjection;
    
builder.Services.AddComponents();

Install the TanvirArjel.Blazor nuget package into your Blazor app as follows:

PMC:

 Install-Package TanvirArjel.Blazor

.NET CLI:

 dotnet add package TanvirArjel.Blazor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
9 participants