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

Appearance settings

Roadmap: ASP.NET API Versioning 6.0 #808

commonsensesoftware started this conversation in Show and tell
Discussion options

The 6.0 version will be a large, evolutionary release for API Versioning. This will be the first release where Project Asp stands
on its own two feet under the .NET Foundation without any Microsoft branding. There are a bunch of new features and enhancements
coming in this release that should make the juice worth the squeeze. Two new areas that will be supporting API versioning are HTTP
clients and expanded support for advertising API policies. In addition to the new enchancements, the laundry list of open issues
will be burned down.

The first part of the roadmap will be adding a new main branch that reorganizes the structure of the repo to be more
compliant with mono repos. Hopefully, that will make it easier to view, browse, and work with various parts of the codebase. The
existing master branch will renamed to ms so it is clear where the older Microsoft-branded content lies.

High-Level Items

  • Onboard to the .NET Foundation
  • Fix open issues
  • Publish fixes for 5.x for existing packages
  • Support .NET 6.0
  • Performance improvements
  • Support Minimal APIs
  • Add Sunset Policies (RFC 8594)
    • This implicitly means adding Web Linking (RFC 8288) as well
  • Support OData 8.0
  • Support OData query options for non-OData controllers
  • Add API version-aware HTTP clients

You can expect .NET 7.0 support in a 7.0 release that is separate, but on the horizon.

Performance

Although no formal benchmarks have ever be collected for comparison, I've gone back through every square inch of the code and
revisted a number of places that see increased performance improvements. A few of the common uses include:

  • Use indexing with for over foreach where possible
  • Allocate only when necessary
    • Micro-optimizations in methods
    • Lazy-initialized fields
  • Remove regular expression usage
  • Leverage Span<T> and ReadOnlySpan<T> where possible

.NET 6.0 and beyond will see additional support for optmized features such as ISpanFormattable. The .NET Framework targets will
continue to support .NET 4.5, but will also now include targeting .NET 4.7.2 to take partial advantage of optimizations such as
Span<T> and ReadOnlySpan<T>.

Minimal APIs

API Versioning in ASP.NET Core will now be broken into two parts:

  1. Http (core)
  2. Mvc

The core HTTP library will provide the foundational routing extensions and support for Minimal APIs. Although you reference all
of ASP.NET Core as a framework, this core library will no longer have any dependencies on MVC. The MVC library will extend upon
the core HTTP library and provide of the functionality you're accustomed to today.

API Explorer support for versioned Minimal APIs is not entirely clear - yet. The API Explorer is currently part of MVC. It is
expected that API Explorer extensions will require MVC Core, if you only intend to use Minimal APIs. This might change in
the future.

Sunset Policies

API Versioning has long supported advertising which API versions are supported and deprecated via the api-supported-versions and
api-deprecated-versions respectively. A key limitation of this support is that it does not indicate when an API version will
be sunset nor what the stated policy is.

API Versioning 6.0 will introduce support for RFC 8594. This will allow an
API version to indicate when it will disappear for good via the Sunset header. This header does not necessarily apply to all
API versions, it will only apply to the API version that was requested. The sunset policy can include additional information
such as a web page or OpenAPI document. These additional links will conform to Web Linking as defined by
RFC 8288.

These capabilities are useful, not only for instrumented clients, but also for tooling. As an example, an API might
support an OPTIONS request to retrieve this information for tooling:

OPTIONS /weather?api-version=1.0 HTTP/3
Host: localhost
HTTP/3 200 OK
Allow: GET, POST, OPTIONS
Api-Supported-Versions: 1.0, 2.0, 3.0
Api-Deprecated-Versions: 0.9
Sunset: Thu, 01 Apr 2022 00:00:00 GMT
Link: <https://docs.service.com/policy.html?api-version=1.0>; rel="sunset"; title="API Policy"; type="text/html"
Link: </swagger/v1/swagger.json>; rel="openapi"; title="OpenAPI"; type="application/json"

This indicates to a client that the requested API version 1.0 will sunset on 4/1/2022. It also provides a link to a public
web page that outlines the API versioning policy as well as a link to where the OpenAPI document is located.

Sunset policies do not have to have a date. The following scenarios will be supported:

  • Define a sunset policy by API name and version
  • Define a sunset policy by API name for any version
  • Define a sunset policy by API version for any API
  • A sunset policy may have a date
  • A sunset policy can have zero or more links

Supporting a sunset policy with links alone enables advertising a stated policy when you don't know when an API version might
actually be sunset, which will be common for the current version of an API.

A new, fluent API will be provided to define a sunset policy. If a sunset policy is defined, it will be emitted through the
existing IReportApiVersions service. This service is automatically utilized whenever ApiVersioningOptions.ReportApiVersions
is set to true or ReportApiVersionsAttribute is applied.

API Version-Aware HTTP Clients

For many years, it has been causally asked how to take advantage of the API versioning information in HTTP clients. While there
were a few interesting cases, it was always low on the priority list and never came to fruition. The rework of the project and
the introduction of Sunset Policies necessitate re-evaluation.

Starting in API Versioning 6.0, there will be a new client-side library that will introduce an IApiVersionWriter service
that understands how to write API versions into each HTTP request automatically. For parity, you will be able to compose
multiple writers via ApiVersionWriter.Combine, even though that seems unnecessary. A big part of what will make this
functionality possible is breaking out the core API Versioning functionality, such as ApiVersion, into a common abstractions
library. The same parsing, formatting, and so on will now be available for HTTP clients.

HTTP client handlers will also support IApiNotification, which will provide a hook to notify your client when:

  • A deprecated API version is detected
  • A new API version is detected

If you are using Microsoft.Extensions.Logging, then:

  • A warning message is logged when a deprecated API version is detected along with any Sunset Policy information
  • An informational message is logged when a new API version is detected

If you're using some other logging framework, you can replace the default IApiNotification service with whatever you want.

The HttpClient.GetApiInformationAsync extension method will be provided to request API information from an endpoint using
an OPTIONS request. This will have the same semantics as the example given above for Sunset Policies. The use cases are
limited (ex: tooling) and the out-of-the-box implementation is prescriptive.

OData

OData 8.0 has been out for a while, but it is significant departure from its previous implementation. It also includes
protocol-level changes for OData 4.01, which some people may not even realize. This will ultimately result in a rewrite of
the entire implementation. API Versioning 6.0 will provide support for OData 8.0 as well as a target for .NET 6.0.

OData provides limited support for the API Explorer and OpenAPI documentation. API Versioning has supported extensive
support for OData-specific API features, such as query options. Some people, however, are interested in using the
query capabilities of OData without ultizing the whole stack. Today, this results in a poor experience that turns into
thousands of API parameters. This release will take a deeper look at how to support query parameters in non-OData
controllers.

Breaking Changes

A lot of changes need to occur as part of the project evolution. I'm very cognizant of the pain breaking changes can
cause, thus my goal is to make one large change and put it behind us for good. For the most part, you can expect the
changes to largely be a new package identifier and different namespaces. It is entirely possible that you may update
those and find the code to be nearly identical. The mileage will vary depending on your level of customization, but
you can expect the changes to be trivial in most cases.

In no particular order, here are some of the most significant breaking changes you can expect:

Package Identifiers

Beginning in API Versioning 6.0 and beyond, the NuGet package identifiers will use the Asp.Versioning.* prefix. The
older Microsoft.* prefixes will only be supported for servicing. The expected list of package identifiers is:

  • Asp.Versioning.Abstractions
  • Asp.Versioning.WebApi
  • Asp.Versioning.WebApi.ApiExplorer
  • Asp.Versioning.WebApi.OData
  • Asp.Versioning.WebApi.OData.ApiExplorer
  • Asp.Versioning.Http
  • Asp.Versioning.Mvc
  • Asp.Versioning.Mvc.ApiExplorer
  • Asp.Versioning.OData
  • Asp.Versioning.OData.ApiExplorer
  • Asp.Versioning.Http.Client

Namespaces

As the project is no longer part of Microsoft, all namespaces will become Asp.Versioning.*. I thought long and hard about
this, but it doesn't make sense to keep using Microsoft.* when things don't line up. Furthermore, what namespace should
all new code live under? Continuing to use the Microsoft namespace seemed wrong. An interesting benefit, however, is
that using Api.Versioning.* allows for more consistency across the ASP.NET Web API and Core implementations. The existing
differences in library namespaces for shared code often led to a lot of ugly compiler directives. For ease of use,
extension methods will continue to live in the namespace they correspond to.

API Version

The format and default implementation will not change, but parsing will be broken apart. People have often asked about using
a custom ApiVersion. This has always been possible, but it was not easy to do. A big reason why it was not easy is
because the type was coupled with parsing. The original idea was to have data type parsing analogous to int.Parse, but
it ultimately didn't pan out that way.

A new IApiVersionParser service will be introduced to support this capability. For .NET targets that support it, parsing
will also support accepting ReadOnlySpan<char>. ApiVersion.Parse and ApiVersion.TryParse will be removed, but are
replaced by ApiVersionParser.Default, which will provide a default implementation that is parallel to today.

Some addition features include relaxed rules for ApiVersion.Status and constructing API versions from numbers (e.g. double)
as opposed to magic strings. For example, new ApiVersion(1.0) or new ApiVersion(2.1, "Beta").

The one breaking change to ApiVersion I believe will be a welcomed one. ApiVersion.GroupVersion in .NET 6.0 and beyond
now will be represented as DateOnly. DateOnly accurrately represents how a group version or date version was always
meant to be, but couldn't without introducing its own type due to the design of DateTime. The .NET Standard and .NET
Framework representions will continue to use DateTime.

API Version Reader

IApiVersionReader.Read will now return IReadOnlyList<string> instead of string?. There are a few reasons for this change.
First, the Null Mistake is removed as an empty list is completely acceptable. Second, it was entirely possible for a particular
reader implementation to return more than one value. Consider that ?api-version=1.0&api-version=2.0 would return both 1.0 and
2.0. In today's implementation that would instead throw an exception that would have to be handled. This becomes problematic
for the server to correctly report the response to the client. This really isn't exceptional, it's just an invalid client
request. This change will make it easier to collect and report that behavior. Finally, ApiVersionReader.Combine enabled
combining different types of readers through composition. Readers for different parts of a request are even more likely to
return different values. Refactoring to return a list makes it very simple to return all of the raw API versions provided
without any exceptions and regardless of where they were read from.

API Version Reporting

IReportApiVersions.Report will now accept the entire HTTP response as opposed to just the headers. Accepting only the
headers was an over-normalization that wasn't really necessary. Additional information was also necessary to support
Sunset Policies. The Report overload that accepts Lazy<ApiVersionModel> will be removed as it's no longer used
or necessary.

API Version Model Extensions

Most of the extension methods related to retrieving an ApiVersionModel have been supplanted by the new extension method
GetApiVersionMetadata(). The GetApiVersionModel() extension method, for example, was a shortcut for
GetApiVersionModel(ApiVersionMapping.Explicit). A new type - ApiVersionMetadata - has been introduced that unifies the
metadata implementation across ASP.NET frameworks. In most cases, this information was retrieved from
HttpActionDescriptor in ASP.NET Web API and ActionDescriptor in ASP.NET Core. In ASP.NET Core, this information is
now stored in the ActionDescriptor.EndpointMetadata and Endpoint.Metadata collections (depending on context).

This is the old to new mapping:

  • GetApiVersionModel(ApiVersionMapping) → GetApiVersionMetadata()
  • GetApiVersionModel() → ApiVersionMetadata.Map(ApiVersionMapping.Explicit)
  • MappingTo(ApiVersion) → ApiVersionMetadata.MappingTo(ApiVersion)
  • IsMappedTo(ApiVersion) → ApiVersionMetadata.IsMappedTo(ApiVersion)

Error Response Provider

The IErrorResponseProvider service has been the hook to provide custom error responses. Problem Details
(RFC 7807) had just been ratified when this project started and wasn't
part of ASP.NET. ASP.NET Core eventually added first class support for Problem Details and IErrorResponseProvider
had an adapter implementation for alignment. Now that Problem Details are the de factor method for error reporting,
it no longer makes sense to keep IErrorResponseProvider around, so it has been removed.

ASP.NET Web API does not provide such a capability so the implemetation from ASP.NET Core has been backported. In addition,
the ASP.NET Core ProblemDetailsFactory is an abstract class that is within MVC Core. To maintain separation from MVC,
the IProblemDetailsFactory interface will be added that can be used in Minimal APIs and an automatic adapter will be
provided for MVC so that developers do not need two implementations if/when they customize things. The ASP.NET team
is tracking work to make the use of ProblemDetails consistent so things might merge in the .NET 7.0 release.

ProblemDetails.Type is supposed to be a URI. For backward compatibility, the existing error codes will be emitted as
the Code extension. The detailed error message in ASP.NET Web API will be emitted as the Error extension.

Routing Behaviors

The first big change is that legacy, convention-based routing with IActionSelector will be dropped. Limitations in
the original ASP.NET Core routing design caused a number of issues and inconsistencies, which were resolved when
Endpoint Routing was introduced. The only real reason to keep it around was due to OData until it finally supported
Endpoint Routing as well. Certain response scenarios such as 405 or 415 were difficult to support. Given the
maturity of Endpoint Routing, I don't think it's worth the effort to continue supporting IActionSelector in the
context of API Versioning. If anyone really needs it, the code still exists in the repo history for people to use in
their own solutions or it can be supported through a community extension.

The changes to routing implementation are more of an enhancement, but it will result in some behavioral changes that
might be unexpected. ASP.NET Core, in particular, requires these changes to address other open issues. The routing logic
will be updated to properly return a response for 404, 405, and 415. 415 will now be properly reported when
versioning by media type without requiring custom error reporting as is the case today.

Due to the way routing works in ASP.NET Core, it is no longer possible to always report 400 when an API version
could be matched, but doesn't. In most cases, what would have been 400 will now return 404, 405, or 415. In
some of these cases it is also not possible to add ProblemDetails. Where possible, 400 and 404 will return
ProblemDetails, but it is not always guaranteed. Improvements to ASP.NET Core in .NET 7.0 might alleviate that.

ASP.NET Web API will have parity with these behaviors, even though the routing system is completely different. API Versioning
has more control over the routing system in Web API so for the most part the only thing that will change is the status code.

What happens when an API version could match, but doesn't has always been a bit of a gray area. The general consensus seems
to be that developers don't care because it's a client error or they expect it to be 404. These revised rules will now
return 404 more likely than not.

Improved documentation is planned as well, which will include diagrams of the routing logic.

A few other breaking changes are fairly minor. The existing UseApiVersioning() middleware will be removed. It never did
anything except setup the IApiVersioningFeature in the current request. API Versioning doesn't have any real middleware
so there is no sense it keeping it around. It also removes an unnecessary level of indirection out of the request pipeline.

Configuration

Support for Minimal APIs and OData in ASP.NET Core required some changes to how services are configured in an application.
A new IApiVersioningBuilder interface has been added which all API Versioning related extensions will hang off of. This will
also help address extension method naming conflicts and scenarios where you might forget to register another set of required
services. If you referenced and enabled everything supported by API Versioning, then your configuration might look like:

services.AddApiVersioning()     // Core API Versioning services with support for Minimal APIs
        .AddMvc()               // API version-aware extensions for MVC Core
        .AddApiExplorer()       // API version-aware API Explorer extensions
        .AddOData()             // API versioning extensions for OData
        .AddODataApiExplorer(); // API version-aware API Explorer extensions for OData

The setup and extension methods in ASP.NET Web API are unchanged

In Closing

The changes to response error codes and ProblemDetails for errors are the most concerning in my opinion. These affect server
responses over the wire that clients might depend on. Honestly, that seems unlikely because these responses are all errors
that indicate the client doesn't know how to call the server API. Nevertheless, it's a breaking change that isn't easily
addressed. When it comes to swapping the implementation, how do you version the versioning behavior? This would only affect
existing services that want to upgrade. New services will simply have the new behavior. Perhaps this is a non-issue. Services
that must maintain the current behaviors simple do not upgrade to newer versions of the libraries until the old APIs
are sunset.

What's your take? I'd love to hear more from the community. What else do you want to see happen? Are there features
that I've missed. Are there changes you think are unacceptable? Do you want different names? How would you like to
engage on these topics? Discussions, Discord, Slack, online meeting, an Ask Me Anything (AMA)? I doubt there
will be a universal consensus, but I definitely want feedback and input from the community. Provide your thoughts
below.

I don't have an official release date scheduled as there are still some unknowns, but the in-flight work is now available in
the new main branch. As soon as all of the .NET Foundation tasks are complete, a new build server is configured,
and code signing is setup, I'll begin queuing up preview releases.

Chris (@commonsensesoftware)

The API Versioning Team

You must be logged in to vote

Replies: 1 comment · 2 replies

Comment options

The new requirement that error responses must be in the problem details format seems like a very strange decision, and unfortunately block us from updating. Very few existing web services are going to change their error formats, so I can't fathom the logic behind this.

You must be logged in to vote
2 replies
@commonsensesoftware
Comment options

There is no requirement to change your error responses, but there are defaults.

In the beginning, which is 8+ years ago now, there was no concept of a standard error response. The closest semblance of that came from the Microsoft REST API Guidelines, which itself assumes the format of the OData error response (without calling it out in so many words).

Problem Details were ratified circa 2016 and ASP.NET Core eventually elected to adopt that format as the de facto default standard format for error responses. As an extension to the platform, it does not make sense to provide a default format that is counter to the rest of the platform. That makes adoption confusing and inconsistent. I suspect most would agree that incongruence between error responses is inconvenient for the server and clients.

When Problem Details first entered the scene, there was an IErrorResponseProvider that provided an adapter. Software rarely stays the same forever and breaking changes will occur at some point. There is never a perfect time for that to happen. Given the major rewrite requirement and major version change, 6.0 seemed like the best possible time make a change and remove legacy abstractions that were simply no longer required. Another long-standing issue was that it was not possible to opt out of IErrorResponseProvider, whereas with the latest Problem Details support to you can (technically, you must opt in a la AddProblemDetails()).

Even since the time of the 6.0 release it has been possible to customize Problem Details to retain the old Error Object format; it just wasn't provided out-of-the-box. Starting in 7.1, there is support for Error Object Backward Compatibility. If you always want all API Versioning errors to retain the Error Object format, it's easy to enable. If you want to mix and match between API versions, the process is a bit more involved. It's definitely possible to do, but I would prefer the level of effort required to set it up be simpler. It's something I'll be evaluating in the upcoming releases.

There are a lot things that did not go as smoothly in this transition as I would have liked; some with good reason. I am cognizant of most of these issues, but not all of them are within my control. I know library consumers to do not have a stomach for continuous, volatile changes. I knew if I was making all of these changes in 6.0, they couldn't be dramatically changed again and they haven't in the 2+ years since. Getting the message out regarding the changes was really difficult and slow. Many of the limitations or gaps in this announcement have since been addressed. If there is something else missing, I'm always listening to feedback. If there is a way to fill a gap without regression, I almost certainly will oblige. In the specific case of errors responses, there is a careful balance between new and old library consumers. New consumers should expect consistent error responses when they enable AddProblemDetails, but existing users still need an escape hatch for how things have worked in the past, which is why backward compatibility was added. If there something else missing, I'm happy to help guide you through your migration.

@commonsensesoftware
Comment options

@mattzink 8.1.0 has been published and it's now easier than ever to emit Problem Details in the old Error Object format (see the documentation). The default behavior only covers API Versioning related problems. The process can be extended to cover additional problem types or configured to not emit problem details for errors that occur outside of API Versioning.

Release NuGet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.