Skip to content

6.3.1 Fixes #925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<VersionPrefix>6.3.0</VersionPrefix>
<VersionPrefix>6.3.1</VersionPrefix>
<AssemblyVersion>6.3.0.0</AssemblyVersion>
<TargetFrameworks>net6.0;netcoreapp3.1</TargetFrameworks>
<RootNamespace>Asp.Versioning</RootNamespace>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<VersionPrefix>6.3.0</VersionPrefix>
<VersionPrefix>6.3.1</VersionPrefix>
<AssemblyVersion>6.3.0.0</AssemblyVersion>
<TargetFrameworks>net6.0;netcoreapp3.1</TargetFrameworks>
<RootNamespace>Asp.Versioning</RootNamespace>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Asp.Versioning.OData;
using Microsoft.AspNetCore.OData.Routing.Template;
using Microsoft.OData.Edm;
using Microsoft.OData.UriParser;
using System.Runtime.CompilerServices;

/// <summary>
/// Represents a versioned <see cref="IODataTemplateTranslator">OData template translator</see>.
Expand All @@ -30,9 +31,7 @@ public sealed class VersionedODataTemplateTranslator : IODataTemplateTranslator

if ( apiVersion == null )
{
var metadata = context.Endpoint.Metadata.GetMetadata<ApiVersionMetadata>();

if ( metadata == null || !metadata.IsApiVersionNeutral )
if ( !IsVersionNeutral( context ) )
{
return default;
}
Expand All @@ -42,7 +41,13 @@ public sealed class VersionedODataTemplateTranslator : IODataTemplateTranslator
var model = context.Model;
var otherApiVersion = model.GetAnnotationValue<ApiVersionAnnotation>( model )?.ApiVersion;

if ( !apiVersion.Equals( otherApiVersion ) )
// HACK: a version-neutral endpoint can fail to match here because odata tries to match the
// first endpoint metadata when there could be multiple. such an endpoint is expected to be
// the same in all versions so allow it to flow through. revisit if/when odata fixes this.
//
// REF: https://github.com/OData/AspNetCoreOData/issues/753
// REF: https://github.com/OData/AspNetCoreOData/blob/main/src/Microsoft.AspNetCore.OData/Routing/ODataRoutingMatcherPolicy.cs#L86
if ( !apiVersion.Equals( otherApiVersion ) && !IsVersionNeutral( context ) )
{
return default;
}
Expand All @@ -58,4 +63,9 @@ public sealed class VersionedODataTemplateTranslator : IODataTemplateTranslator

return new( context.Segments );
}

[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static bool IsVersionNeutral( ODataTemplateTranslateContext context ) =>
context.Endpoint.Metadata.GetMetadata<ApiVersionMetadata>() is ApiVersionMetadata metadata
&& metadata.IsApiVersionNeutral;
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@

Added workaround for [OData #753](https://github.com/OData/AspNetCoreOData/issues/753)
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.

namespace Asp.Versioning.ApiExplorer;

using System.Collections;

/// <summary>
/// Represents a collection of collated API version metadata.
/// </summary>
public class ApiVersionMetadataCollationCollection : IList<ApiVersionMetadata>, IReadOnlyList<ApiVersionMetadata>
{
private readonly List<ApiVersionMetadata> items;
private readonly List<string?> groups;

/// <summary>
/// Initializes a new instance of the <see cref="ApiVersionMetadataCollationCollection"/> class.
/// </summary>
public ApiVersionMetadataCollationCollection()
{
items = new();
groups = new();
}

/// <summary>
/// Initializes a new instance of the <see cref="ApiVersionMetadataCollationCollection"/> class.
/// </summary>
/// <param name="capacity">The initial capacity of the collection.</param>
public ApiVersionMetadataCollationCollection( int capacity )
{
items = new( capacity );
groups = new( capacity );
}

/// <summary>
/// Gets the item in the list at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the item to retrieve.</param>
/// <returns>The item at the specified index.</returns>
public ApiVersionMetadata this[int index] => items[index];

ApiVersionMetadata IList<ApiVersionMetadata>.this[int index]
{
get => items[index];
set => throw new NotSupportedException();
}

/// <inheritdoc />
public int Count => items.Count;

#pragma warning disable CA1033 // Interface methods should be callable by child types
bool ICollection<ApiVersionMetadata>.IsReadOnly => ( (ICollection<ApiVersionMetadata>) items ).IsReadOnly;
#pragma warning restore CA1033 // Interface methods should be callable by child types

/// <inheritdoc />
public void Add( ApiVersionMetadata item ) => Insert( Count, item, default );

/// <summary>
/// Adds an item to the collection.
/// </summary>
/// <param name="item">The item to add.</param>
/// <param name="groupName">The associated group name, if any.</param>
public void Add( ApiVersionMetadata item, string? groupName ) => Insert( Count, item, groupName );

/// <inheritdoc />
public void Clear()
{
items.Clear();
groups.Clear();
}

/// <inheritdoc />
public bool Contains( ApiVersionMetadata item ) => item != null && items.Contains( item );

/// <inheritdoc />
public void CopyTo( ApiVersionMetadata[] array, int arrayIndex ) => items.CopyTo( array, arrayIndex );

/// <inheritdoc />
public IEnumerator<ApiVersionMetadata> GetEnumerator() => items.GetEnumerator();

/// <inheritdoc />
public int IndexOf( ApiVersionMetadata item ) => item == null ? -1 : items.IndexOf( item );

/// <inheritdoc />
public void Insert( int index, ApiVersionMetadata item ) => Insert( index, item, default );

/// <summary>
/// Inserts an item into the collection.
/// </summary>
/// <param name="index">The zero-based index where insertion takes place.</param>
/// <param name="item">The item to insert.</param>
/// <param name="groupName">The associated group name, if any.</param>
public void Insert( int index, ApiVersionMetadata item, string? groupName )
{
items.Insert( index, item ?? throw new ArgumentNullException( nameof( item ) ) );
groups.Insert( index, groupName );
}

/// <inheritdoc />
public bool Remove( ApiVersionMetadata item )
{
if ( item == null )
{
return false;
}

var index = items.IndexOf( item );

if ( index < 0 )
{
return false;
}

RemoveAt( index );
return true;
}

/// <inheritdoc />
public void RemoveAt( int index )
{
items.RemoveAt( index );
groups.RemoveAt( index );
}

IEnumerator IEnumerable.GetEnumerator() => ( (IEnumerable) items ).GetEnumerator();

/// <summary>
/// Gets the group name for the item at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the item to get the group name for.</param>
/// <returns>The associated group name or <c>null</c>.</returns>
/// <remarks>If the specified <paramref name="index"/> is out of range, <c>null</c>
/// is returned.</remarks>
public string? GroupName( int index ) =>
index < 0 || index >= groups.Count ? default : groups[index];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.

namespace Asp.Versioning.ApiExplorer;

/// <summary>
/// Represents the context used during API version metadata collation.
/// </summary>
public class ApiVersionMetadataCollationContext
{
/// <summary>
/// Gets the read-only list of collation results.
/// </summary>
/// <value>The <see cref="IReadOnlyList{T}">read-only list</see> of collation results.</value>
public ApiVersionMetadataCollationCollection Results { get; } = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.

namespace Asp.Versioning.ApiExplorer;

using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Primitives;

/// <summary>
/// Represents the API version metadata collection provider for endpoints.
/// </summary>
[CLSCompliant( false )]
public sealed class EndpointApiVersionMetadataCollationProvider : IApiVersionMetadataCollationProvider
{
private readonly EndpointDataSource endpointDataSource;
private int version;

/// <summary>
/// Initializes a new instance of the <see cref="EndpointApiVersionMetadataCollationProvider"/> class.
/// </summary>
/// <param name="endpointDataSource">The underlying <see cref="endpointDataSource">endpoint data source</see>.</param>
public EndpointApiVersionMetadataCollationProvider( EndpointDataSource endpointDataSource )
{
this.endpointDataSource = endpointDataSource ?? throw new ArgumentNullException( nameof( endpointDataSource ) );
ChangeToken.OnChange( endpointDataSource.GetChangeToken, () => ++version );
}

/// <inheritdoc />
public int Version => version;

/// <inheritdoc />
public void Execute( ApiVersionMetadataCollationContext context )
{
if ( context == null )
{
throw new ArgumentNullException( nameof( context ) );
}

var endpoints = endpointDataSource.Endpoints;

for ( var i = 0; i < endpoints.Count; i++ )
{
var endpoint = endpoints[i];

if ( endpoint.Metadata.GetMetadata<ApiVersionMetadata>() is not ApiVersionMetadata item )
{
continue;
}

#if NETCOREAPP3_1
// this code path doesn't appear to exist for netcoreapp3.1
// REF: https://github.com/dotnet/aspnetcore/blob/release/3.1/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs#L74
context.Results.Add( item );
#else
var groupName = endpoint.Metadata.OfType<IEndpointGroupNameMetadata>().LastOrDefault()?.EndpointGroupName;
context.Results.Add( item, groupName );
#endif
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.

namespace Asp.Versioning.ApiExplorer;

/// <summary>
/// Defines the behavior of an API version metadata collation provider.
/// </summary>
public interface IApiVersionMetadataCollationProvider
{
/// <summary>
/// Gets version of the underlying provider results.
/// </summary>
/// <value>The version of the provider results. This can be used to detect changes.</value>
int Version { get; }

/// <summary>
/// Executes the provider using the given context.
/// </summary>
/// <param name="context">The collation context.</param>
void Execute( ApiVersionMetadataCollationContext context );
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<VersionPrefix>6.3.0</VersionPrefix>
<VersionPrefix>6.3.1</VersionPrefix>
<AssemblyVersion>6.3.0.0</AssemblyVersion>
<TargetFrameworks>net6.0;netcoreapp3.1</TargetFrameworks>
<RootNamespace>Asp.Versioning</RootNamespace>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
namespace Microsoft.Extensions.DependencyInjection;

using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
#if !NETCOREAPP3_1
using Asp.Versioning.Builder;
#endif
using Asp.Versioning.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor;
Expand Down Expand Up @@ -61,7 +63,15 @@ private static void AddApiVersioningServices( IServiceCollection services )
services.TryAddSingleton<IReportApiVersions, DefaultApiVersionReporter>();
services.TryAddSingleton<ISunsetPolicyManager, SunsetPolicyManager>();
services.TryAddEnumerable( Transient<IPostConfigureOptions<RouteOptions>, ApiVersioningRouteOptionsSetup>() );
services.TryAddEnumerable( Singleton<MatcherPolicy, ApiVersionMatcherPolicy>() );
//// UNDONE: explicit constructor choice to avoid breaking change; revert in next major release
services.TryAddEnumerable( Singleton<MatcherPolicy, ApiVersionMatcherPolicy>(
sp => new ApiVersionMatcherPolicy(
sp.GetRequiredService<IApiVersionParser>(),
sp.GetServices<IApiVersionMetadataCollationProvider>(),
sp.GetRequiredService<IOptions<ApiVersioningOptions>>(),
sp.GetRequiredService<ILogger<ApiVersionMatcherPolicy>>() ) ) );

services.TryAddEnumerable( Singleton<IApiVersionMetadataCollationProvider, EndpointApiVersionMetadataCollationProvider>() );
services.Replace( WithLinkGeneratorDecorator( services ) );
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@

[Fixed #922](https://github.com/dotnet/aspnet-api-versioning/discussions/922)
Loading