Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Deprecating Default api version #968

Unanswered
remcolam asked this question in Q&A
Mar 8, 2023 · 2 comments · 4 replies
Discussion options

I am trying to add versioning to an existing project and part of it is that all code should move to a new route, the old routes have been declared version x, which is deprecated, however, I am unable to get the apiversiondescription in swagger to be declared deprecated.

I assume because the object is initialized from the DefaultApiVersion, which has no deprecated support, and not from the ApiVersionAttribute on my controllers, which do have it.

Any idea how I can still get my version be seen as deprecated?

Below the relevant snippets....

services.AddApiVersioning(options =>
{
	options.AssumeDefaultVersionWhenUnspecified = true;
	options.DefaultApiVersion = new ApiVersion(0);
	options.ReportApiVersions = true;
	options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
[Obsolete]
[ApiVersion(0, Deprecated = true)]
[Route("[controller]")]
[ApiController]
public class MyControllerController : Controller
{
}

Remco

You must be logged in to vote

Replies: 2 comments 4 replies

Comment options

There's a few things going on here. You are correct that there is no way to indicate that the DefaultApiVersion itself is deprecated. It wasn't meant to be used that way, but - admittedly - that is an interesting scenario. For the scenario you describe, DefaultApiVersion would come into play if there were no other attributes or conventions applied. It looks like you have already applied attributes (which is fine), but you could apply a custom convention that would apply the default, deprecated API version to all matching controllers.

When you say Swagger, I presume you mean Swashbuckle. There is no official mechanism that connects the API Explorer with the concept of deprecation. Swashbuckle and API Versioning support these concepts, but you are required to stitch them together (unfortunately). The simplest way to do that in Swashbuckle is to use an IOperationFilter, which would look like:

public class SwaggerDefaultValues : IOperationFilter
{
 public void Apply( OpenApiOperation operation, OperationFilterContext context )
 {
 // Swashbuckle ↓ API Versioning extension method ↓
 operation.Deprecated |= context.ApiDescription.IsDeprecated();
 }
}

That isn't the only information that doesn't get picked up automatically by Swashbuckle, but it might be the only thing you need. For a full end-to-end solution, see the OpenAPI example project.

You must be logged in to vote
2 replies
Comment options

Ah, i already took the swagger defaults filter, but i didn't think to query the filter context to tweak the swagger output.

I will look into that,

Thank you

Comment options

The |= operator is your choice. You could just use an assignment (e.g. =). The thinking is that if Swashbuckle somehow explicitly marked an operation as deprecated, it should not be blindly overwritten.

Comment options

The swagger filter was already custom, so this feels like a good place. Of course i could have hard coded it in the filter already, but this feels cleaner.

Still, it might be nice to have some way of marking versions deprecated inside AddApiVersioning(), like having a delegate member in it that allows you to determine if a version is deprecated.

That also would allow you to centrally manage deprecation, instead of in each controller

You must be logged in to vote
2 replies
Comment options

There are many ways this can be achieved and it hasn't been a common ask, both of which is why there isn't an out-of-the-box solution. However, adding support for it is pretty straight forward. Under the assumption you will use attributes going forward, then all all existing controllers without any attribution will be considered to match the default version which is deprecated. This can be achieved with the following custom convention:

public sealed class DefaultDeprecatedApiVersionConvention : IControllerConvention
{
 private readonly ApiVersioningOptions options;
 public DefaultDeprecatedApiVersionConvention(ApiVersioningOptions options) =>
 this.options = option;
 public bool Apply(IControllerConventionBuilder builder, ControllerModel controller)
 {
 if (IsImplicitlyVersioned(controller))
 {
 builder.HasDeprecatedApiVersion(options.DefaultApiVersion);
 return true;
 }
 return false;
 }
 private static bool IsImplicitlyVersioned(ControllerModel controller)
 {
 var attributes = controller.Attributes;
 for (var i = 0; i < attributes.Count; i++)
 {
 switch (attributes[i])
 {
 case IApiVersionNeutral:
 return false;
 case IApiVersionProvider provider:
 // ApiVersionProviderOptions.Advertised is informational only
 // ApiVersionProviderOptions.Mapped only applies to actions
 switch (provider.Options)
 {
 case ApiVersionProviderOptions.None:
 case ApiVersionProviderOptions.Deprecated:
 if (provider.Versions.Count > 0)
 {
 return false;
 }
 break;
 default:
 break;
 }
 }
 }
 return true;
 }
}

You would then wire it up with:

services.AddApiVersioning()
 .AddMvc(options => options.Conventions.Add(new DefaultDeprecatedApiVersionConvention(options)));

You might have any other number of way to identify the original, default, deprecated controllers such as by ObsoleteAttribute, route template, namespace, base type, and so on. There is no one convention to rule them all. 😉

Comment options

Thank you for the helpful explanation... I was checking the IOperationFilter, but than one is only marking operations deprecated... I was looking to getting a deprecated message at the top of the swagger document.

I have now achieved this by not just checking the apiversiondescription in the swaggergeninfo for being deprecated, but also assuming that when a version is deprecated, it will have a sunset policy. And to be honest, this should be a temporary situation anyway, all consumers should be moving to a specific version anyway.

public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
 private readonly IApiVersionDescriptionProvider provider;
 /// <summary>
 /// Initializes a new instance of the <see cref="ConfigureSwaggerOptions"/> class.
 /// </summary>
 /// <param name="provider">The <see cref="IApiVersionDescriptionProvider">provider</see> used to generate Swagger documents.</param>
 public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider;
 /// <inheritdoc />
 public void Configure(SwaggerGenOptions options)
 {
 // add a swagger document for each discovered API version
 // note: you might choose to skip or document deprecated API versions differently
 foreach (var description in provider.ApiVersionDescriptions)
 {
 options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
 }
 }
 private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
 {
 var text = new StringBuilder("");
 var info = new OpenApiInfo()
 {
 Title = description.GroupName,
 Version = description.ApiVersion.ToString(),
 };
		if (description.SunsetPolicy != null || description.IsDeprecated)
 {
 text.AppendLine("This API version has been deprecated. Please switch to a newer version<br/>");
 }
 if (description.SunsetPolicy != null)
 {
 if (description.SunsetPolicy.Date is DateTimeOffset when)
 {
 text.Append("The API will be sunset on ")
 .Append(when.Date.ToShortDateString())
 .AppendLine(".<br/>");
 }
 if (description.SunsetPolicy.HasLinks)
 {
 text.AppendLine("<br/>");
 for (var i = 0; i < description.SunsetPolicy.Links.Count; i++)
 {
 var link = description.SunsetPolicy.Links[i];
 if (link.Type == "text/html")
 {
 text.AppendLine("<br/>");
 text.Append($"<a href='{link.LinkTarget.OriginalString}'>{(link.Title.HasValue ? link.Title : link.LinkTarget.OriginalString)}</a>");
 }
 }
 }
 }
 info.Description = text.ToString();
 return info;
 }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /