Aspnetcore: Can we talk about constructor injection now that partial classes are here?

Created on 2 Jan 2020  路  12Comments  路  Source: dotnet/aspnetcore

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.

affected-medium area-blazor enhancement severity-minor

Most helpful comment

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

All 12 comments

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

Thanks for that

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.

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.

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

Any updates on this? @pranavkm

Moving to backlog given this now be achieved using IComponentActivator.

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.

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

@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.

The implementation can be like that:

````csharp
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:

csharp 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)?

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.

Was this page helpful?
0 / 5 - 0 ratings