Skip to content

Blazor (.NET9+): migrate property injection to constructor injection #1047

@Thieum

Description

@Thieum

Is your feature request related to a problem? Please describe.
Since .NET9, it is possible to have constructor injection in Blazor components, instead of property injection: https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-10.0#constructor-injection / https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-9.0#request-a-service-in-a-component

It would be nice to have an analyzer and a fix that helps detect and migrate property injection to constructor injection in Blazor components.

Describe the solution you'd like

Considering this code:

using Microsoft.AspNetCore.Components;

public partial class Component
{
    [Inject]
    protected NavigationManager Navigation { get; set; } = default!;

    private void HandleClick()
    {
        NavigationManager.NavigateTo("/counter");
    }
}

the injected dependency should be marked as a suggestion of fix, with the fix that would change the property to a constructor parameter like this:

using Microsoft.AspNetCore.Components;

public partial class Component(NavigationManager navigation)
{
    private void HandleClick()
    {
        navigation.NavigateTo("/counter");
    }
}

Describe alternatives you've considered
N/A

Additional context
I'm not sure if anyone would prefer the other way around? Detecting constructor injection to migrate to property injection? Constructor injection seems like an improvement (avoid defaut! and in somes cases, avoid more complicated init in OnInitializedAsync ).

The presence on an existing constructor in the class could complicate the fix, especially if async is involved :

partial class Home
{
    public Home(AdminClientService downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    int _numberOfDevices;
    string _version = "v.?";
    readonly AdminClientService _downstreamApi;

    [Inject] IWebHostEnvironment _env { get; set; } = default!;

    protected override async Task OnInitializedAsync()
    {
        ICollection<DeviceListDto> devices = await _downstreamApi.GetDeviceListAsync()
           .ConfigureAwait(true);
        _numberOfDevices = devices.Count;

        _version = await CloudVersionService.GetCloudVersionAsync(_env.WebRootPath)
            .ConfigureAwait(false) ?? "";
    }
}

would need to migrate to:

partial class Home
{
    public Home(AdminClientService downstreamApi,
                         IWebHostEnvironment env)
    {
        _downstreamApi = downstreamApi;
        _env = env;
    }

    int _numberOfDevices;
    string _version = "v.?";
    readonly AdminClientService _downstreamApi;
    readonly IWebHostEnvironment _env;

    protected override async Task OnInitializedAsync()
    {
        ICollection<DeviceListDto> devices = await _downstreamApi.GetDeviceListAsync()
           .ConfigureAwait(true);
        _numberOfDevices = devices.Count;

        _version = await CloudVersionService.GetCloudVersionAsync(_env.WebRootPath)
            .ConfigureAwait(false) ?? "";
    }
}

Then IDE0290 could complete the job to migrate to primary constructor to get close to the simpler example from the doc.

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions