Support for the "Deprecation" Response Header (RFC 9745)
Is there an existing issue for this?
- [x] I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.
Supporting the Deprecation Response Header and the Deprecation Link Relation Type for deprecated API version, as specified in RFC 9745
Describe the solution you'd like
A configurable Deprecation Policy, similar to Sunset Policy, like this
options.Policies.Deprecation(0.9)
.Effective(2025, 3, 31)
.Link("https://developer.example.com/deprecation")
.Type("text/html");
that will produce as response headers
Deprecation: @1743379200
Link: <https://developer.example.com/deprecation>; rel="deprecation"; type="text/html"
Additional context
No response
This feature would be interesting to me too. If this is in line with the product roadmap, I could offer to start working on a PR. Since the Sunset policy is already in place, this seems like a pretty parallel implementation.
I've marked this as a feature and I do intend to make this part of the next release. I'm currently squashing some 🐛 🐞 for 8.x patch release. Adding this in 8.x would require a minor version bump so I'm inclined to differ its release to correspond with the forthcoming .NET 10 release. I'm not sure if folks are champing at the bit for this in .NET 8, but I could be convinced.
@MGessinger that is gracious of you if you're still interested. It might be the most code any 3rd party has ever contributed. 🎉
I believe there are a few high-level tasks:
- [ ] Finalize design
- [ ] Update Abstractions as necessary
- [ ] Implement for ASP.NET Core
- [ ] Implement for Web API
- [ ] Update or add features to versioned HTTP client support
Conceptually, I can see the API being something like:
// 0.9 is deprecated as of today, but completely goes away 2 months from now
options.Policies.Sunset( 0.9 )
.Deprecated( DateTimeOffset.Now, "deprecation.html" )
.Effective( DateTimeOffset.Now.AddDays( 60 ) )
.Link( "policy.html" )
.Title( "Versioning Policy" )
.Type( "text/html" );
There's overlap between sunset and deprecation. An API will be deprecated and will then sunset on an effective date. An API doesn't have to be deprecated before it is sunset. It can also be deprecated without being sunset. I think that we'd only want to emit deprecation when it's explicitly specified. Similarly, the spec says that the deprecation link can be specified without being deprecated, but is that a use case this should support?
There's already a way to build a link, but the deprecation link has a specific relevance and AFAIK it is always for text/html. Putting that all together, I think we end up with:
public interface ISunsetPolicyBuilder
{
// existing; omitted
+ /// <summary>
+ /// Indicates when the date and time an API was deprecated.
+ /// </summary>
+ /// <param name="when">The <see cref="DateTimeOffset">date and time</see> when an API
+ /// was deprecated.</param>
+ /// <param name="build">The <see cref="Action{T}">callback</see> used to build the
+ /// deprecation link, if any.</param>
+ /// <returns>The current <see cref="ISunsetPolicyBuilder">sunset policy builder</see>.</returns>
+ ISunsetPolicyBuilder Deprecated( DateTimeOffset when, Action<ILinkBuilder>? build = default );
}
public static class ISunsetPolicyBuilderExtensions
{
// existing; omitted
+ /// <summary>
+ /// Indicates when the date and time an API was deprecated.
+ /// </summary>
+ /// <param name="builder">The extended <see cref="ISunsetPolicyBuilder">sunset policy builder</see>.</param>
+ /// <param name="when">The <see cref="DateTimeOffset">date and time</see> when an API
+ /// was deprecated.</param>
+ /// <param name="link">The URL for the deprecation link.</param>
+ /// <returns>The current <see cref="ISunsetPolicyBuilder">sunset policy builder</see>.</returns>
+ ISunsetPolicyBuilder Deprecated( this ISunsetPolicyBuilder builder, DateTimeOffset when, string? link = default );
}
In terms of implementation, I think the deprecated date can be allowed to be in the future, but it would not be emitted until the server clock is that time. This would allow API authors to indicate the deprecation date and time before it happens. Once an API is marked as deprecated, then an option link can be specified. In the simplest form, it is just the URL link and everything else is built on the user's behalf. The full ILinkBuilder can be also provided. This would allow full fidelity to provide additional behavior such as language or additional things the spec didn't call out. I'm not sure if multiple links are needed - maybe.
These are my initial thoughts. This probably the first design I've done in the open. Consider this just the first iteration. None of this is locked yet. Feedback, comments, and suggestions are welcome.
Personally, I wouldn't want to add the Deprecation to the ISunsetPolicyBuilder interface, since as you said yourself, I can Sunset without deprecating and I can Deprecate without sunsetting. Instead, I would have two separate policies, one for sunset and one for deprecation. They may share most of their code internally (something like an IVersionedPolicyBuilder base interface from which they both inherit).
Also, according to the Spec, the emitted Deprecation date may be in the future, as a hint to clients that a resource will (or may) become deprecated in the future. So I would always emit the Deprecation header, when it is specified.
Ah... yeah, I guess that makes sense. I was thinking of keeping things more succinct, but I can get onboard with that. It's been so long, I kind of even forgot about the design decisions I made for IApiVersioningPolicyBuilder, which is what ApiVersioningOptions.Policies returns. Splitting things out into a distinct IDeprecatedApiVersionPolicy (or something similarly named) would make sense.
Hmm... yeah, emitting the deprecation in the future should be fine. If someone publishes nonsensical ranges between deprecation and sunset - caveat emptor.
All that being said, we're thinking the policy would be applied on its own and I guess would look like:
options.Policies.Deprecate( 0.9 )
.Effective( DateTimeOffset.Now.AddDays( 60 ) )
.Link( "deprecation.html" )
.Title( "Deprecation Policy" )
.Type( "text/html" );
This is very close to the original proposal, but the method name should be a verb. Beyond that, the API would have the same overall look and feel of the sunset policy.
The RelationType should default to deprecation when unspecified, just like the SunsetPolicy:
https://github.com/dotnet/aspnet-api-versioning/blob/bd469de0990a8c3e20bf96d511038755928b5dc5/src/Abstractions/src/Asp.Versioning.Abstractions/SunsetPolicy.cs#L77
Should the Type also default to text/html if it is unspecified? The spec indicates that the link MAY be specified, but it doesn't say whether the type MUST be text/html.
I like the proposed usage pattern. It seems clear and versatile.
Regarding the media type for the link, if the spec doesn't specify that it MUST be text/html, then it could theoretically be anything. However, I expect that in practice it will be text/html in the vast majority of cases so we should be able to use that as the default pretty safely