Skip to content

Migration

Chris Martinez edited this page Nov 6, 2023 · 4 revisions

This topic serves as the guide for migrating from version <= 5.x.x to version >= 6.0.0. The majority of this information has been outlined in previous discussions.

If you'd like more information on the background context, you can read the Hello Project "Asp" announcement.

For the most part, you can expect the required changes to be a new package identifier and different namespaces. It is entirely possible that you may update those and find the rest of the code to be identical. The mileage will vary depending on your level of customization, but you can expect the changes to be trivial in most cases.

Package Identifiers

The original Microsoft.* packages are now deprecated and will only undergo servicing:

Platform Package Version TFM
ASP.NET Web API Microsoft.AspNet.WebApi.Versioning <= 5.x.x net45
ASP.NET Web API Microsoft.AspNet.WebApi.Versioning.ApiExplorer <= 5.x.x net45
ASP.NET Web API Microsoft.AspNet.OData.Versioning <= 5.x.x net45
ASP.NET Web API Microsoft.AspNet.OData.Versioning.ApiExplorer <= 5.x.x net45
ASP.NET Core Microsoft.AspNetCore.Mvc.Versioning <= 5.x.x netcoreapp3.1, net5.0
ASP.NET Core Microsoft.AspNetCore.Mvc.ApiExplorer <= 5.x.x netcoreapp3.1, net5.0
ASP.NET Core Microsoft.AspNetCore.OData <= 5.x.x netcoreapp3.1, net5.0
ASP.NET Core Microsoft.AspNetCore.OData.ApiExplorer <= 5.x.x netcoreapp3.1, net5.0

All new features and platform support will use the Asp.Versioning.* prefix:

Platform Package Version TFM
All Asp.Versioning.Abstractions 6.0.0+ net6.0+, netstandard1.0, netstandard2.0
ASP.NET Web API Asp.Versioning.WebApi 6.0.0+ net45, net472
ASP.NET Web API Asp.Versioning.WebApi.ApiExplorer 6.0.0+ net45, net472
ASP.NET Web API Asp.Versioning.WebApi.OData 6.0.0+ net45, net472
ASP.NET Web API Asp.Versioning.WebApi.OData.ApiExplorer 6.0.0+ net45, net472
ASP.NET Core Asp.Versioning.Http1 6.0.0+ net6.0+
ASP.NET Core Asp.Versioning.Mvc2 6.0.0+ net6.0+
ASP.NET Core Asp.Versioning.Mvc.ApiExplorer3 6.0.0+ net6.0+
ASP.NET Core Asp.Versioning.OData 6.0.0+ net6.0+
ASP.NET Core Asp.Versioning.OData.ApiExplorer 6.0.0+ net6.0+
All Asp.Versioning.Http.Client 6.0.0+ net6.0+, netstandard1.1, netstandard2.0

[1] Base library that supports Minimal APIs
[2] MVC Core with controller support
[3] Supports exploration of Minimal APIs and controllers

Namespaces

As the project is no longer part of Microsoft, all namespaces have become Asp.Versioning.*. It didn'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 conditional 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 has not changed, but parsing has been broken apart. The new IApiVersionParser service has been introduced to support this capability. ApiVersion.Parse and ApiVersion.TryParse have been removed, but are replaced by ApiVersionParser.Default, which will provide a default implementation.

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

API Version Reader

IApiVersionReader.Read now returns 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 previous versions, the implementation would instead throw AmbiguousApiVersionException that would have to be handled. That behavior becomes problematic for the server to correctly report the response to the client. Reading multiple API version values in and of itself isn't exceptional, it's just an invalid client request. ApiVersionReader.Combine also enables 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 Behaviors

In versions >= 2.1.0 && < 6.0.0, the ApiVersioningOptions provided the property UseApiBehavior. This setting was a bridge to the API Behaviors feature introduced in ASP.NET Core 2.1. In earlier versions of ASP.NET Core, there was not a clear way to disambiguate between a UI and API controller. Adding API Behaviors via [ApiController] to a controller or assembly provided a way to solve that problem. API Versioning subsequently added two new services that align to it:

  • IApiControllerFilter - filters out non-API controllers
  • IApiControllerSpecification - determines whether a controller is for an API

The default filter is an aggregation over all specifications. The default specifications look for API Behaviors and OData routing.

In the 2.1.x timeframe, this was a behavioral breaking change. To facilitate a smoother transition, the UseApiBehavior option was introduced with a value of false, which maintained the existing behavior. Starting in 3.0, the value defaulted to true, which only considers controllers with API Behaviors applied. Starting in 6.0, the property has been completely removed as it is no longer necessary.

IApiControllerFilter and any of the IApiControllerSpecification services can be modified through dependency injection. To align with the legacy behavior of UseApiBehavior = false, you can use the NoControllerFilter implementation:

builder.Services.AddTransient<IApiControllerFilter, NoControllerFilter>();
builder.Services.AddApiVersioning().AddMvc();

API Version Reporting

IReportApiVersions.Report now accepts 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> has been removed as it's no longer used or necessary.

API Version Model Extensions

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 platforms. 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).

The following is the mapping between the old and new extension methods:

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

Error Responses

The IErrorResponseProvider service had been the hook to provide custom error responses. Problem Details (RFC 7807) had only just been ratified when this project started and they were not part of ASP.NET yet. ASP.NET Core eventually added first-class support for Problem Details and IErrorResponseProvider had an adapter implementation for alignment in previous versions. Now that Problem Details are the de factor method for error reporting, it no longer makes sense to retain IErrorResponseProvider and it has been removed.

The error responses bodies provided by IErrorResponseProvider complied with the Microsoft REST Guidelines error response format, which is itself the error response format used by the OData protocol (see OData JSON Format §21.1). If you need to retain that format, the Error Response Backward Compatibility topic discusses how to enable it.

ProblemDetails.Type could logically be used to model the established error Code; however, the value is supposed to be a URI. For backward compatibility, the existing error codes will be emitted as the Code extension in Problem Details. The Error Responses topic provides details for each well-known problem that may be returned in responses.

Routing Behaviors

The legacy, convention-based routing with IActionSelector has been 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; especially 405 or 415 responses. The primary reason it continued to be supported was waiting for OData to support Endpoint Routing, which it does as of 8.0.

The routing logic has been updated to properly return a response for 404, 405, 406, and 415. Due to necessary API Versioning fixes and 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 some of these cases it is also not possible to add ProblemDetails; especially prior to .NET 7 because ASP.NET Core did not provide a hook for it.

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 default rule will continue to return 400 when versioning by query string or header, but that can now be changed via ApiVersioningOptions.UnsupportedApiVersionStatusCode. Versioning by URL segment will always return 404. Versioning by media type will always return 406 or 415.

The UseApiVersioning() middleware in ASP.NET Core has been removed. It never did anything except setup the IApiVersioningFeature in the current request, which doesn't require middleware.

Configuration

Applies to ASP.NET Core only. The setup and extension methods in ASP.NET Web API are unchanged.

Support for Minimal APIs and OData in ASP.NET Core required some changes to how services are configured in an application. The new IApiVersioningBuilder interface provides a way to hang all API Versioning related extensions off of. This approach also helps 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:

var builder = WebApplication.CreateBuilder( args );
var services = builder.Services;

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

Changes

  • As noted above, ApiVersioningOptions.UseApiBehaviors has been removed
  • ApiVersioningOptions.Conventions has been moved to MvcApiVersioningOptions.Conventions as API Versioning no longer requires MVC Core
    • To configure conventions, use .AddMvc(options => options.Conventions = ?) via the IApiVersioningBuilder extension method
  • ApiVersioningOptions.ControllerNameConvention has been removed as an explicit option, but can be changed via dependency injection
    • To configure a different naming convention, use builder.Services.AddSingleton<IControllerNameConvention, OriginalControllerNameConvention>()
Clone this wiki locally