Skip to content

Razor Pages equivalent of ViewComponents #63273

@Atulin

Description

@Atulin

Is there an existing issue for this?

  • I have searched the existing issues

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

ViewComponents are analogous to MVC with views, where one controller (viewcomponent) can have multiple views (views). For simple viewcomponents it creates quite some directory boilerplate, even if it uses just the Default view:

MyProject
├─ Pages
│  ├─ Shared
│  │  ├─ Components
│  │  │  ├─ FooViewComponent
│  │  │  │  └─ Default.cshtml
│  │  │  └─ FooViewComponent.cs
// FooViewComponent.cs
public class FooViewComponent(MyDbContext ctx) : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(int id)
    {
        var foo = await ctx.Foos
            .Where(f => f.Id == id)
            .ProjectToDto()
            .FirstOrDefaultAsync();
        return View(foo);
    }
}
<!-- Default.cshtml -->
@model FooDto

<h3>@Model.Name</h3>
<img src="@Model.Image" />

Describe the solution you'd like

I would like something closer to what Razor Pages offer, that is a simple .cshtml-.cshtml.cs pair.

MyProject
├─ Pages
│  ├─ Components
│  │  ├─ FooComponent.cshtml.cs
│  │  └─ FooComponent.cs
// FooComponent.cshtml.cs
public class FooComponent(MyDbContext ctx) : PageComponent
{
    public required FooDto Data { get; set; }

    public async Task<IPageComponentResult> InvokeAsync(int id)
    {
        Data = await ctx.Foos
            .Where(f => f.Id == id)
            .ProjectToDto()
            .FirstOrDefaultAsync();
        return Component();
    }
}
<!-- Default.cshtml -->
@model FooViewComponent

<h3>@Model.Data.Name</h3>
<img src="@Model.Data.Image" />

Additional context

Going a step further, the component would not use a duck-typed InvokeAsync, but rather it would be an abstract method on PageComponent. In that case, parameters would probably have to be declared either in the constructor, or by using some [Parameter] attribute. Some loose suggestions:

Constructor parameters?

// Perhaps it could be automatically detected, that the ctor params that don't
// exist in the DI container are component parameters, thus removing the need
// for an attibute?
public class FooComponent([Parameter] int id, MyDbContext ctx) : PageComponent
{
    public required FooDto Data { get; set; }

    public override async Task<IPageComponentResult> InvokeAsync()
    {
       ...
    }
}

Properties?

public class FooComponent(MyDbContext ctx) : PageComponent
{
    [Parameter]
    public required int Id { get; set; }
    public required FooDto Data { get; set; }

    public override async Task<IPageComponentResult> InvokeAsync()
    {
        ...
    }
}

Generic PageComponent with a T Data property that never gets treated as a parameter?

public class FooComponent(MyDbContext ctx) : PageComponent<FooDto>
{
    public required int Id { get; set; }

    public override async Task<IPageComponentResult> InvokeAsync()
    {
        Data = ...
    }
}

Could maybe make the PageComponent<T>.Data the @Model for easier access too:

@model FooViewComponent

<h3>@Model.Name</h3>
<img src="@Model.Image" />

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-ui-renderingIncludes: MVC Views/Pages, Razor Views/Pages

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions