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
34.91k stars 9.86k forks source link

Improving type-safety of RazorComponentResult<T> #51923

Open KennethHoff opened 8 months ago

KennethHoff commented 8 months ago

Background and Motivation

ASP.Net Core 8 introduced the RazorComponentResult<T> which allows a developer to render Razor Components from normal endpoints. This is very useful for for example HTMX as it allows you to create very simple endpoints like this, which will return a pre-rendered Razor Component as raw HTML over the wire.

image

However, there is one huge problem with the ergonomics here when it comes to parameter passing, as you have to pass in dynamic objects, like the following:

image

Without any further context you might not realize the problem, but here's the component:

image

As you can see, this will not function as you'd expect - in fact, it will throw at runtime. The reason is that I passed in AuthorId when I should've passed in UserId. You might think, "Oh, I'll just pass in a model of type SomeRandomComponent" (I certainly did..), but that will give you this compiler error (https://learn.microsoft.com/en-us/aspnet/core/diagnostics/bl0005):

image image

error BL0005: Component parameter 'UserId' should not be set outside of its component. 

So the only solution we have is to pass in an anonymous object and pray we did it right.

This is not only a problem for when you forget to add a prop, but also when you forget to remove a prop, as if the component doesn't have a prop with the name you specified, you also get a runtime error:

InvalidOperationException: Object of type 'MyNamespace.SomeRandomComponent' does not have a property matching the name 'AuthorId'.

There is also the problem with the fact that - as you can see in the third image (The one with the Component code), I have [EditorRequired] on the parameter, but the component renders just fine - no warning or anything - despite me not passing it in (in the first example)

Proposed API

I don't have any specific APIs in mind, but in an ideal world I'd love for there to be an overload that works something like this:

image

Usage Examples

image

Alternative Designs

Risks

The proposed API presumably cannot be implemented; I just mentioned what would I consider more-or-less ideal. Assuming we go that direction, then the component parameter assignment logic would presumably need to be rewritten somewhat, which could be quite a huge undertaking.

javiercn commented 8 months ago

@KennethHoff thanks for contacting us.

If we wanted, we could do this with an analyzer that simply checks the properties you are passing match those expected by the component. No need to do runtime changes if the editing experience is what we are looking to match.

The other way to do this would be to take an expression in the form of

() => new CustomerComponent
{
    Value = a,
    Other = b
}

and rewrite it to just emit new { Value = a, Other = b }

ghost commented 7 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.

FrodeRennemo commented 3 months ago

@KennethHoff Regards to BL0005: Component parameter should not be set outside of its component: Component parameters should be assigned to as part of the component initialization or as part of SetParametersAsync. Assigning a value to a parameter from an external source results in the value being overwritten the next time the component renders.

Given that you render the HTML on the server side, is that warning actually relevant? I've tested the same approach with HTMX and see no runtime issues myself.