Expose `OutputCachePolicyBuilder.AddPolicy(IOutputCachePolicy)` as `public`
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
## Background and Motivation
I want to create a parameterized custom policy to dynamically generate tags based on route values so that I can evict only a subset of the cached contents later on on one of my APIs.
To allow me to tag cache entries based on route values, I need to pass the name of the route parameter to my custom policy, as well as the tag prefix to use. Both values are primitive `string`s.
However, due to the way `OutputCachePolicyBuilder` works today, it only allows one to pass a custom policy object by specifying a `Type`:
```csharp
public OutputCachePolicyBuilder AddPolicy(Type)
```
Or by specifing the type via generics and letting the container resolve the policy automatically:
```csharp
public OutputCachePolicyBuilder AddPolicy()
where T : IOutputCachePolicy
```
This makes it extremely convoluted to pass a parameterized policy like this:
```csharp
public sealed class TagPerRouteOutputCachePolicy : IOutputCachePolicy
{
private readonly string tagPrefix;
private readonly string routeValueName;
public TagPerRouteOutputCachePolicy(string tagPrefix, string routeValueName)
{
ArgumentNullException.ThrowIfNull(tagPrefix);
ArgumentNullException.ThrowIfNull(routeValueName);
this.tagPrefix = tagPrefix;
this.routeValueName = routeValueName;
}
...
```
The only way I see to allow for my use case would be to somehow pre-register the type in the container with a lambda factory and hardcoded values, but that's not possible since the values I'm passing are coming from the policy setup logic which happens in a different place.
I'm now forced to completely change the design of my object to use some sort of fake mutable container object so I can pass in values from the outside using the container in a convoluted manner.
I'd rather simply pass the instance directly using the currently `internal` overload:
```csharp
internal OutputCachePolicyBuilder AddPolicy(IOutputCachePolicy)
```
## Proposed API
```diff
public sealed class OutputCachePolicyBuilder
{
- internal OutputCachePolicyBuilder AddPolicy(IOutputCachePolicy)
+ public OutputCachePolicyBuilder AddPolicy(IOutputCachePolicy)
}
```
## Usage Examples
```csharp
public static class OutputCachePolicyBuilderExtensions
{
public static OutputCachePolicyBuilder TagByRouteValue(
this OutputCachePolicyBuilder builder,
string tagPrefix,
string routeValueName)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(tagPrefix);
ArgumentNullException.ThrowIfNull(routeValueName);
return builder.AddPolicy(new TagPerRouteOutputCachePolicy(tagPrefix, routeValueName));
}
}
```
This API is also used extensively for all existing extensions and policy operations as the way policy builder works relies on composing several smaller implementations of `IOutputCachePolicy`.
## Alternative Designs
I don't see any alternative design that allows me to add a parameterized policy from the builder perspective. I'd have to forcefully introduce hard-to-use mutable fake "parameter container" objects in DI, then pass those in as my parameters in the policy. Then, before I call `AddPolicy()`, I'd have to resolve those fake container objects and set the values in them so that I can later fetch the values from the DI-injected instance.
Something like this which is absolutely terrible design:
```csharp
public interface IParameterPosition;
public sealed class FirstParameterPosition : IParameterPosition;
public sealed class SecondParameterPosition : IParameterPosition;
public sealed class TagPerRouteOutputCachePolicy : IOutputCachePolicy
{
private readonly string tagPrefix;
private readonly string routeValueName;
public TagPerRouteOutputCachePolicy(
ParameterContainer tagPrefix,
ParameterContainerrouteValueName)
{
this.tagPrefix = tagPrefix.Value;
this.routeValueName = routeValueName.Value;
}
```
And even then, this would only work if I have a single instance of this class. It is completely unmanageable.
## Risks
I see zero risk in exposing the widely used `internal` method as it relies on public-facing interfaces already.
Contributor guide
Research direction
Start at OutputCachePolicyBuilder and inspect the existing AddPolicy overloads, IOutputCachePolicy usage, and extensions that compose policies. Review the API review process linked in the issue; done means the instance overload is publicly exposed without changing the proposed signature or existing policy behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100