-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Prerender select elements with value; move HtmlRenderer into Mvc.ViewFeatures #12996
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
Prerender select elements with value; move HtmlRenderer into Mvc.ViewFeatures #12996
Conversation
case RenderTreeFrameType.Attribute: | ||
return RenderAttributes(result, frames, position, 1); | ||
throw new InvalidOperationException($"Attributes should only be encountered within {nameof(RenderElement)}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is true, and all the tests do still pass. @javiercn, do you have any concerns about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test with more than 1 attribute? I thought we iterated over the attributes that way, but I might be wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new tests for prerendering <select>
put multiple attributes on a single element and work OK.
Components.Web depends on JSInterop, doesn't it? Not sure about how others feels about it. On one side, it's all the shared framework. On the other, we would have to explain customers why MVC has a reference to something they can't use. |
Whether they can use it depends on what parts of ASP.NET Core they have configured for use in their application. We have a lot of things like that, so it doesn't seem too problematic to me, but maybe we can cover this in our discussions about JSInterop. |
Update We agreed offline that we will move |
PR with the compiler changes dotnet/razor#952 |
4e1477f
to
3df6664
Compare
I've updated this quite a bit and expanded the scope of the PR to untangling
The one new bit of API we have to expose to enable this is changing |
This sounds great. I didn't like the shape of ComponentRenderedText and was planning to change it if we had time. It's better now as we can improve perf without causing breaking changes. |
@@ -247,6 +259,13 @@ private async Task<(int, ArrayRange<RenderTreeFrame>)> CreateInitialRenderAsync( | |||
|
|||
return (componentId, GetCurrentRenderTreeFrames(componentId)); | |||
} | |||
|
|||
private class HtmlRenderingContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only one per render, and if we make it a mutable struct we have to be careful about passing by ref everywhere. I'd be happier keeping it as class
for simplicity.
{ | ||
// There's no concept of nested <select> elements, so as soon as we're exiting one of them, | ||
// we can safely say there is no longer any value for this | ||
context.ClosestSelectValueAsString = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we try and detect this case and throw in that scenario? I'm concerned between the difference between prerender and non-prerender here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's any difference between prerender and non-prerender in any valid scenario. It's never valid to nest <select>
elements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great stuff!
I only had a small comment about detecting nested selects, I'll leave it up to you.
@@ -93,7 +93,7 @@ protected internal int AssignRootComponentId(IComponent component) | |||
/// </summary> | |||
/// <param name="componentId">The id for the component.</param> | |||
/// <returns>The <see cref="RenderTreeBuilder"/> representing the current render tree.</returns> | |||
private protected ArrayRange<RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId) => GetRequiredComponentState(componentId).CurrentRenderTree.GetFrames(); | |||
protected ArrayRange<RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId) => GetRequiredComponentState(componentId).CurrentRenderTree.GetFrames(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the expectation that if you get the current frames you can or can't hold on to them? Does this make a copy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't make a copy. The expectation is that you only access them synchronously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could rename to “ViewCurrentRenderTreeFrames” to clarify this. Longer term we could use the same technique we plan for ParameterView to throw if you access after the underlying store is mutated further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ViewCurrentRenderTreeFrames
I'm not actually going to rename it to that, because it's a strange name. The existing name GetCurrentRenderTreeFrames
is more conventional. We can add the "don't read after mutation" check in the future if we want.
This deals with #12667 in the case where the
<option>
element isn't embedded into a markup frame.The remainder of the fix for #12667 would be to change the Razor compiler's markup pass to avoid recursing into
<select>
elements, as per @rynowak's description. @ajaybhargavb, would it be possible for you to handle the remaining piece inside the Razor compiler? This is because I'm trying to maximise the amount of API-review changes I can get done in my remaining few days on preview 9 - not sure what your backlog currently looks like, @ajaybhargavb.Further API review thought: if there's any reasonable way we could move
HtmlRenderer
out of.Components
and into.Web
, that would be much better. This whole concept is completely web-specific. I understand the limit there is that the MVC prerendering feature depends on this. @javiercn / @rynowak, do you know if anything stops us from making MVC prerendering depend onM.A.Components.Web
?