Support more expressions in @page
directive
#11805
Open
+714
−66
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Closes #7519
Details
The issue asks about being able to provide constant expressions into the page directive, possibly including constant string interpolations, string concatenations, or mixes of the two.
This PR provides a primitive solution to that by accepting either a double-quoted string, or an identifier (single or qualified), or a razor expression. It is not required to place a @ before the identifier, but the razor expression requires that the expression be placed inside
@()
.To support this new syntax, a new token kind had to be created, IdentifierOrExpressionOrString. This follows the logic of its components, IdentifierOrExpression and String. The parser tries to first parse a razor expression, else an identifier, else a string, otherwise fails. While directly using a non-simple string literal is not supported, there exists the workaround of placing the non-simple string literal into a razor expression (like so:
@($"{AppRoutes.Forms}/{{id:int}}")
)Retrieving the string value is not possible when a non-string literal expression is used, since we don't have the ability to look into the compilation while generating the files. So instead, we introduce a new public constant field in the generated page class, named
__RouteTemplate
, which is used to contain the compilable expression, in order to guarantee the expression is always validly evaluated. If we directly used that expression in the assembly attribute, resolving the actual expression would fail 99% of the time, as we'd be in the global namespace and the expression's top level identifier will possibly refer to a namespaced member.We can also leverage that technique when any expression other than a double-quoted string is used. This ensures we don't introduce custom parsing logic which would introduce the risk of deviating from the intended C# behavior. So, arbitrary razor expressions are also accepted, whose contents will be used for the instantiation of the generated constant.
The assembly attribute containing the page name will refer to the generated class's public field by fully qualifying the member. This is possible because we know the namespace and the class name of the generated class, and the public field's name is defined by us, which is placed in a constant. The field is only used when the route template isn't a simple string, and thus isn't known in Razor's context. If the field doesn't exist (because the
@page
directive has no defined template), no field is used and the behavior remains the same.Consequences
Risks
The parser becomes more complicated by having a new token kind. Especially with the string part of the new token kind not adhering to all C# rules.
The
@page
directive generates a new public constant field for every non-simple route template page. This bloats the generated code (very minorly).The
@page
directive is now designed to accept bothDirectiveTokenIntermediateNode
andLazyIntermediateToken
, aside from the simple double-quoted string literal, possibly evaluating both newly added properties in some cases. while the runtime overhead is negligible, code involving aPageDirective
instance must be aware of these changes.CC @chsienki @javiercn