-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
Add support for default parameter values in ViewComponent tag helper #32979
Conversation
fe90dfc
to
9792f12
Compare
src/Razor/Microsoft.AspNetCore.Razor.Language/src/BoundAttributeDescriptor.cs
Outdated
Show resolved
Hide resolved
src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/CodeWriterExtensions.cs
Outdated
Show resolved
Hide resolved
src/Razor/Microsoft.AspNetCore.Razor.Language/src/CodeGeneration/CodeWriterExtensions.cs
Outdated
Show resolved
Hide resolved
...es/IntegrationTests/CodeGenerationIntegrationTest/ViewComponentTagHelperOptionalParam.cshtml
Outdated
Show resolved
Hide resolved
...osoft.AspNetCore.Mvc.Razor.Extensions/test/IntegrationTests/CodeGenerationIntegrationTest.cs
Outdated
Show resolved
Hide resolved
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.
You'll also need to update two Json Converters:
- https://github.com/dotnet/aspnetcore/blob/1a2b3260c6161ae9b7f639de228a6eb0488a1075/src/Shared/RazorShared/TagHelperDescriptorJsonConverter.cs
- https://github.com/dotnet/aspnetcore-tooling/blob/main/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/TagHelperDescriptorJsonConverter.cs
TagHelperDescriptoir's are super performance sensitive so we had to optimize their serialization as much as possible 😄
...zor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/ViewComponentTagHelperDescriptorFactory.cs
Outdated
Show resolved
Hide resolved
src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultBoundAttributeDescriptorBuilder.cs
Outdated
Show resolved
Hide resolved
src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRequiredAttributeDescriptorBuilder.cs
Outdated
Show resolved
Hide resolved
9bf5f22
to
de845d0
Compare
de845d0
to
40938c6
Compare
40938c6
to
8eb5c09
Compare
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.
Assuming there's no issues in the runtime utilizing this it looks legit! Love the simplicity!
...zor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/ViewComponentTagHelperDescriptorFactory.cs
Show resolved
Hide resolved
...Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/ViewComponentTagHelperTargetExtension.cs
Outdated
Show resolved
Hide resolved
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.
Looks beautiful!! 👏
...Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/ViewComponentTagHelperTargetExtension.cs
Show resolved
Hide resolved
…otnet/aspnetcore#32979) Addresses dotnet/aspnetcore#5011. To implement support for optional parameters, we largely needed to do two things: - Update the `ViewComponentTagHelperDescriptorFactory` that is responsible for constructing `VCTHD` objects to _not_ produce the `RequiredAttributeDescriptors` for parameters that have a default value provided. - Update the generated code for VCTHs to produce an arguments object to the `InvokeCoreAsync` method that contains only the provided parameters **Updates to the VCTH descriptor factory** The VCTH descriptor contains a `RequiredAttributeDescriptor` for every parameter to the `InvokeAsync` method on the VC. When a parameter has a default value provided, we don't need to configure the parameter as a `RequiredAttribute` on the VCTH descriptor. With this change in place, instances of the VCTH that don't contain the required attribute will still match with the VCTH. For example, the following VC: ```csharp public class FooViewComponent : ViewComponent { public IViewResult InvokeAsync(string foo, string bar = "default value") { return $"{foo} : {bar}"; } } ``` Would produce a VCTH descriptor that looks like this: ``` - ViewComponentTagHelper - RequiredAttributeDescriptor(propertyName = "foo") - BoundAttributeDescriptor(propertyName = "foo") - BoundAttributeDescriptor(propertyName = "bar") ``` And will match VCTH instances like: ``` <vc:foo foo="a-foo-value"></vc:foo> <vc:foo foo="a-foo-value" bar="a-bar-value"></vc:foo> ``` But not VCTH instances that looks like: ``` <vc:foo bar="a-bar-value"></vc:foo> <vc:foo></vc:foo> ``` All this can happen with very few changes to the tag helper matching logic that already exists. **Update the generated code for VCTHs** Presently, whenever a VCTH is encountered, generated code is produced that: - Writes a class representing the view component which contains properties for each parameter provided to the `InvokeAsync` method and a `ProcessAsync` method that passes the class properties as arguments to `InvokeAsync` - Instantiates an instance of the class above and sets class properties with their associated attributes Even if the VCTH is matched to the VC successfully as a result of the changes above, the generated code will be inaccurate because null values will be passed for parameters that have default values. Instead, we want to construct an arguments object that only contains the parameters that were provided as attributes to the VCTH instance. We do this by adding a new `ProcessInvokeAsyncArgs` that evaluates if each of the parameters was provided as an attribute to the VCTH instance and if so, sets its value in the arguments object. ```csharp private Dictionary<string, object> ProcessInvokeAsyncArgs(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext __context) { Dictionary<string, object> args = new(); if (__context.AllAttributes.ContainsName("first-name")) { args[nameof(firstName)] = firstName; } return args; } ``` The net effect is that we only pass set parameters as arguments to the method. For parameters that aren't passed as arguments, the default value will be used. Commit migrated from dotnet/aspnetcore@8d169332d558
Addresses #5011.
To implement support for optional parameters, we largely needed to do two things:
ViewComponentTagHelperDescriptorFactory
that is responsible for constructingVCTHD
objects to not produce theRequiredAttributeDescriptors
for parameters that have a default value provided.InvokeCoreAsync
method that contains only the provided parametersUpdates to the VCTH descriptor factory
The VCTH descriptor contains a
RequiredAttributeDescriptor
for every parameter to theInvokeAsync
method on the VC. When a parameter has a default value provided, we don't need to configure the parameter as aRequiredAttribute
on the VCTH descriptor.With this change in place, instances of the VCTH that don't contain the required attribute will still match with the VCTH. For example, the following VC:
Would produce a VCTH descriptor that looks like this:
And will match VCTH instances like:
But not VCTH instances that looks like:
All this can happen with very few changes to the tag helper matching logic that already exists.
Update the generated code for VCTHs
Presently, whenever a VCTH is encountered, generated code is produced that:
InvokeAsync
method and aProcessAsync
method that passes the class properties as arguments toInvokeAsync
Even if the VCTH is matched to the VC successfully as a result of the changes above, the generated code will be inaccurate because null values will be passed for parameters that have default values. Instead, we want to construct an arguments object that only contains the parameters that were provided as attributes to the VCTH instance.
We do this by adding a new
ProcessInvokeAsyncArgs
that evaluates if each of the parameters was provided as an attribute to the VCTH instance and if so, sets its value in the arguments object.The net effect is that we only pass set parameters as arguments to the method. For parameters that aren't passed as arguments, the default value will be used.