Skip to content

Introduce ComponentTagHelper #14592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 11, 2019
Merged

Conversation

pranavkm
Copy link
Contributor

Fixes #13726

@pranavkm pranavkm requested a review from rynowak September 30, 2019 22:45
@Eilon Eilon added the area-blazor Includes: Blazor, Razor Components label Sep 30, 2019
<component type="typeof(ComponentWithParameters)"
render-mode="ServerPrerendered"
parameter-Param1="ComponentWithParameters.TestModelValues"
parameter-Param2="ComponentWithParameters.DerivedModelValue"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda an interesting case. It's interesting to think about because the same is not true with Param1. If you put an instance of DerivedModel in that list, it wouldn't roundtrip.

I think is is probably the right thing to do but I'm sure it's going to come up. This use case for serialization is really different from our usage elsewhere. We'll have to make sure to write good docs for this - the main thing is that it's not really possible for people to marshal some kinds of data (like polymorphic collections) without doing a lot of S.T.J extensibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. I wanted to at least capture some of the ordinary cases. The experience is generally going to be bad with polymorphism. Dictionary<string, object> for instance are going to round trip as JsonElement instances. I'll make sure to specifically say that the implementation uses S.T.J to marshal the values so we recommend simple values that are easy to round trip, don't exceed the size limits, etc

/// <summary>
/// A <see cref="TagHelper"/> that renders a Blazor component.
/// </summary>
[HtmlTargetElement("component", Attributes = ComponentTypeName)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we enforce the style here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Razor component" vs. "Blazor component"?
Technically, as it is right now, they're Razor Components.

}

var componentRenderer = ViewContext.HttpContext.RequestServices.GetRequiredService<IComponentRenderer>();
var result = await componentRenderer.RenderComponentAsync(ViewContext, ComponentType, RenderMode, Parameters);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to try and prevent the dictionary allocation here?

/// <summary>
/// Extensions for rendering components.
/// </summary>
public static class HtmlHelperComponentExtensions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

um.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a folder move, ok.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this new namespace? .ViewFeatures is already kinda a junk namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we could clean it up. I thought everything else in that directory was intentionally under the namespace, but it's just this type: https://github.com/aspnet/AspNetCore/blob/master/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs#L18


namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents
{
internal interface IComponentRenderer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we get out of this? If we just called the HTML helper from the tag helper, what are the problmes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we at least add the non-generic overload to the HTML helper's existing method right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the things as extensions makes it really difficult to test. Also the extension method is just a place to dump code. Outside of using the ViewContext, none of the IHtmlHelper is used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we at least get rid of the interface and use an abstract class instead?

Copy link
Contributor

@ryanbrandenburg ryanbrandenburg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat.

@pranavkm pranavkm force-pushed the prkrishn/component-tag-helper branch from 38c7eb7 to 15aabfe Compare October 1, 2019 20:06
@pranavkm pranavkm requested review from dougbu and a team as code owners October 1, 2019 20:06
@pranavkm pranavkm force-pushed the prkrishn/component-tag-helper branch from e62dbbd to ccf5a3f Compare October 1, 2019 20:15
@pranavkm pranavkm changed the base branch from javiercn/server-component-parameters to release/3.1 October 1, 2019 20:16
@pranavkm pranavkm changed the base branch from release/3.1 to release/3.1-preview1 October 1, 2019 20:18
@pranavkm pranavkm force-pushed the prkrishn/component-tag-helper branch 2 times, most recently from cf8ddd0 to fe83fa0 Compare October 1, 2019 20:23
@pranavkm pranavkm changed the base branch from release/3.1-preview1 to javiercn/server-component-parameters October 1, 2019 20:23
@pranavkm pranavkm changed the base branch from javiercn/server-component-parameters to release/3.1-preview1 October 1, 2019 23:11
@pranavkm pranavkm force-pushed the prkrishn/component-tag-helper branch 2 times, most recently from 02a7d8d to 27556bb Compare October 2, 2019 20:04
@pranavkm pranavkm changed the base branch from release/3.1-preview1 to release/3.1 October 2, 2019 22:21
@pranavkm pranavkm added this to the 3.1.0-preview2 milestone Oct 2, 2019
var componentRenderer = viewContext.HttpContext.RequestServices.GetRequiredService<IComponentRenderer>();
return componentRenderer.RenderComponentAsync(viewContext, typeof(TComponent), renderMode, parameters);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add non-generic overloads. The HTML helper is a little too limiting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it, but in general we wouldn't encourage users to use the html helper if there's an equally capable tag helper available. Do you feel strongly about adding the additional overload?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really care that much about the TagHelper, but it should be uber cheap to add this, so I'm with @rynowak on this. There's not even need to add or change tests for them, simply chain the implementations. I trust you know what to do :)

/// A <see cref="TagHelper"/> that renders a Razor component.
/// </summary>
[HtmlTargetElement("component", Attributes = ComponentTypeName, TagStructure = TagStructure.WithoutEndTag)]
public class ComponentTagHelper : TagHelper
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we seal? Would this do something reasonable if someone inherited? I don't have a strong feeling either way.

throw new ArgumentNullException(nameof(output));
}

var componentRenderer = ViewContext.HttpContext.RequestServices.GetRequiredService<IComponentRenderer>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a constructor parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to make the utility type public as yet, at least until we got enough feedback about how users want to use it and how it fits for the client scenarios that were punted until 5.1. This forces the interface to not be a ctor parameter.

@pranavkm pranavkm force-pushed the prkrishn/component-tag-helper branch from cb99d62 to 968d5e5 Compare October 9, 2019 19:34
@pranavkm
Copy link
Contributor Author

pranavkm commented Oct 9, 2019

This is updated

}

[Fact]
public void PassingParametersToComponentsWorks()
Copy link
Member

@javiercn javiercn Oct 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already an E2E test for this. Could you rename it to make it clear that this is testing inheritance?

var parameter2 = Browser.FindElement(By.CssSelector(".Param2"));
Assert.Equal("Value Derived-Value", parameter2.Text);

// This check verifies CaptureUnmatchedValues works
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we validating this here, it feels completely unrelated to the feature or to the ability to pass parameters to the root component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Steve noted in the other PR - #14465 (comment), that we want to ensure we have the ability to pass CaptureUnmatchedValues around. I wanted to make sure we covered that in the context of using the tag helper.

<p>Some content after</p>
</div>
</div>
<div id="container">
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.Server, new { Name = "Albert" }))
@(await Html.RenderComponentAsync<GreeterComponent>(RenderMode.ServerPrerendered, new { Name = "Abraham" }))
<component type="typeof(GreeterComponent)" render-mode="Server" param-name='"Albert"' />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave some HtmlHelper instances around so that we can validate they keep working.

@@ -1,13 +1,14 @@
@page
@using BasicTestApp.RouterTest

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unnecessary white-space.

//
services.TryAddScoped<IComponentRenderer, ComponentRenderer>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get rid of the interface an use an internal abstract class with a public virtual method instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular benefit to changing this? Given it's internal, we should be free to modify the contract in the future if we need to.


namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
internal class ComponentRenderer : IComponentRenderer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any changes to what was there in the HtmlHelper?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this was pretty much copied from the old code as-is.

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of comments, but otherwise looks solid. The main 3 things are:

  • Get rid of IComponentRenderer.
  • Add the non-generic HtmlHelper overloads.
  • Leave around some HtmlHelper calls in the E2E tests to make sure they keep working E2E.

@pranavkm pranavkm force-pushed the prkrishn/component-tag-helper branch from 968d5e5 to 79a1769 Compare October 11, 2019 20:47
@pranavkm pranavkm merged commit d299ae2 into release/3.1 Oct 11, 2019
@pranavkm pranavkm deleted the prkrishn/component-tag-helper branch October 11, 2019 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants