dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.49k stars 10.04k forks source link

Blazor - Inherited CascadingTypeParameter #44019

Open datstarkey opened 2 years ago

datstarkey commented 2 years ago

Is there an existing issue for this?

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

CascadingTypeParameters work with nested components , but require you to specify the initial Type at the top level component, and can be inferred down, this is great except when you want to use an abstract or inherited component with a type parameter.

I've found quite often we wrap each of our pages in an inherited class, normally something to handle if this page or component has a model focus and how it gets and error handles.

as a very brief example something like this:

public abstract class ListPageBase<TListType> : ComponentBase
{
    public List<TListType> List { get; set; } = new();

    protected abstract Task<List<TListType>> Get();

    protected override async Task OnInitializedAsync()
    {
        List = await Get();
    }
}

You can then implement this in a very simple page like this: (Notice how we still have to specify T in the List Component

@page "/"
@inherits ListPageBase<UserModel>

<h1>Users List</h1>

<CascadingValue Value="this">

    <ListComponent TListType="UserModel">
        @context.Name - @context.Email
    </ListComponent>

    ...other components that utilize ListPageBase<TListType>

</CascadingValue>

@code {

    //Would normally be an api call to fetch data
    protected override Task<List<UserModel>> Get()
    {
        return Task.FromResult(new List<UserModel>()
        {
            new ()
            {
                Id = 1,
                Email = "someemail@email.com",
                Name = "some name"
            }
        });
    }
}

With an example of the ListComponent looking like this:

@typeparam TListType

<Virtualize TItem="TListType" Items="Page.List">
    <div class="row">
        @ChildContent(context)
    </div>
</Virtualize>

@code {
    [Parameter]
    public RenderFragment<TListType> ChildContent { get; set; } = default!;

    [CascadingParameter]
    public ListPageBase<TListType> Page { get; set; } = default!;
}

What would be good here is if there was a way to utilize the CascadingTypeParameter Attribute on the abstract base class.

Describe the solution you'd like

Solution 1: use CascadingTypeParameterAttribute on the abstract class and infer into the component

Example:

[CascadingTypeParameter(nameof(TListType))]
public abstract class ListPageBase<TListType> : ComponentBase
{
    public List<TListType> List { get; set; } = new();

    protected abstract Task<List<TListType>> Get();

    protected override async Task OnInitializedAsync()
    {
        List = await Get();
    }
}

This would then pass down in the razor compiler similar to how a nested component would and following models that use the same TypeParam name.

Solution 2: Specify CascadingTypeParameter Type yourself

@inherits ListPageBase<UserModel>
@attribute [CascadingTypeParameter("TListType", typeof(UserModel))]

<h1>Users List</h1>

<CascadingValue Value="this">

    <ListComponent>
        @context.Name - @context.Email
    </ListComponent>

    ...other components that utilize ListPageBase<TListType>

</CascadingValue>

Less elegant but would allow more control over passing cascading parameters down to multiple components without even having to inherit from base classes

Additional context

At the moment you can work around this by making a CascadingType Component or similar for each type name you want to cascade:

@typeparam TListType
@attribute [CascadingTypeParameter(nameof(TListType))]

@ChildContent

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

utilized like:

@page "/"
@inherits ListPageBase<UserModel>

<h1>Users List</h1>

<CascadingValue Value="this">

    <CascadingType TListType="UserModel">

        <ListComponent>
            @context.Name - @context.Email
        </ListComponent>

    @*...other components that utilize ListPageBase<TListType>*@

    </CascadingType>
</CascadingValue>

However you'd have to whack in every type name you might use and doesn't feel great to use.

Overall I feel like solution 1 would make the most sense and would fit in the current scheme of things, would feel like a natural extension when inheriting a base component.

ghost commented 2 years ago

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.

ghost commented 11 months ago

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.