-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
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" />