Skip to content
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

Merged
merged 7 commits into from
May 29, 2021

Conversation

captainsafia
Copy link
Member

@captainsafia captainsafia commented May 24, 2021

Addresses #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:

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.

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.

@ghost ghost added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label May 24, 2021
@captainsafia captainsafia force-pushed the safia/mvc-optional-params branch 2 times, most recently from fe90dfc to 9792f12 Compare May 25, 2021 17:51
@captainsafia captainsafia marked this pull request as ready for review May 25, 2021 20:02
@captainsafia captainsafia requested a review from Pilchie as a code owner May 25, 2021 20:02
@captainsafia captainsafia requested review from NTaylorMullen and pranavkm and removed request for Pilchie May 25, 2021 20:02
Copy link
Contributor

@NTaylorMullen NTaylorMullen left a comment

Choose a reason for hiding this comment

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

@captainsafia captainsafia force-pushed the safia/mvc-optional-params branch 2 times, most recently from 9bf5f22 to de845d0 Compare May 27, 2021 02:43
@captainsafia captainsafia force-pushed the safia/mvc-optional-params branch from de845d0 to 40938c6 Compare May 27, 2021 02:47
@captainsafia captainsafia force-pushed the safia/mvc-optional-params branch from 40938c6 to 8eb5c09 Compare May 27, 2021 02:49
Copy link
Contributor

@NTaylorMullen NTaylorMullen left a 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!

Copy link
Contributor

@NTaylorMullen NTaylorMullen left a comment

Choose a reason for hiding this comment

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

Looks beautiful!! 👏

@captainsafia captainsafia merged commit 8d16933 into main May 29, 2021
@captainsafia captainsafia deleted the safia/mvc-optional-params branch May 29, 2021 14:54
@ghost ghost added this to the 6.0-preview6 milestone May 29, 2021
dougbu pushed a commit to dougbu/razor-compiler that referenced this pull request Nov 17, 2021
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants