Skip to content

Add API Version Formatting #124

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
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
6 changes: 5 additions & 1 deletion ApiVersioning.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.3
VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}"
EndProject
Expand Down Expand Up @@ -58,8 +58,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.OData.Vers
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests", "test\Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests\Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests.csproj", "{280C3B03-5EED-40E9-A826-83C9F3C6EEDC}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.ApiExplorer", "src\Common.ApiExplorer\Common.ApiExplorer.shproj", "{26A67334-F6E6-49B8-8C5A-F88F28770966}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Common.ApiExplorer\Common.ApiExplorer.projitems*{26a67334-f6e6-49b8-8c5a-f88f28770966}*SharedItemsImports = 13
test\Acceptance.Test.Shared\Acceptance.Test.Shared.projitems*{6cdfb878-2642-4f98-ae35-621bac581181}*SharedItemsImports = 13
src\Common\Common.projitems*{6d0e834b-6422-44cd-9a85-e3be9dead1be}*SharedItemsImports = 13
src\Shared\Shared.projitems*{b7897873-6757-4684-83c0-39575821ae14}*SharedItemsImports = 13
Expand Down Expand Up @@ -149,5 +152,6 @@ Global
{C8D29CB1-C541-4579-A1B8-AFD4B4F5F4A3} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
{6ED07FE1-95D3-41E9-A0F1-AEF1BBD6A474} = {4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}
{280C3B03-5EED-40E9-A826-83C9F3C6EEDC} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
{26A67334-F6E6-49B8-8C5A-F88F28770966} = {4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}
EndGlobalSection
EndGlobal
6 changes: 5 additions & 1 deletion ApiVersioningWithSamples.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.3
VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}"
EndProject
Expand Down Expand Up @@ -94,8 +94,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData.Vers
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests", "test\Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests\Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests.csproj", "{3B7E0FEF-8019-4A17-A55F-A6FA378DA856}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Common.ApiExplorer", "src\Common.ApiExplorer\Common.ApiExplorer.shproj", "{26A67334-F6E6-49B8-8C5A-F88F28770966}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Common.ApiExplorer\Common.ApiExplorer.projitems*{26a67334-f6e6-49b8-8c5a-f88f28770966}*SharedItemsImports = 13
test\Acceptance.Test.Shared\Acceptance.Test.Shared.projitems*{6cdfb878-2642-4f98-ae35-621bac581181}*SharedItemsImports = 13
src\Common\Common.projitems*{6d0e834b-6422-44cd-9a85-e3be9dead1be}*SharedItemsImports = 13
src\Shared\Shared.projitems*{b7897873-6757-4684-83c0-39575821ae14}*SharedItemsImports = 13
Expand Down Expand Up @@ -247,5 +250,6 @@ Global
{F3986F7B-AF76-43D1-A44F-303023A08CD3} = {F446ED94-368F-4F67-913B-16E82CA80DFC}
{1B255310-A2B7-437F-804F-6E1D8C940A17} = {4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}
{3B7E0FEF-8019-4A17-A55F-A6FA378DA856} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
{26A67334-F6E6-49B8-8C5A-F88F28770966} = {4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}
EndGlobalSection
EndGlobal
14 changes: 5 additions & 9 deletions samples/aspnetcore/SwaggerSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,9 @@ public Startup( IHostingEnvironment env )
/// <param name="services">The collection of services to configure the application with.</param>
public void ConfigureServices( IServiceCollection services )
{
// add the versioned api explorer, which also adds the following services:
//
// * IApiVersionDescriptionProvider
// * IApiVersionGroupNameFormatter
services.AddMvcCore().AddVersionedApiExplorer();
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
services.AddMvcCore().AddVersionedApiExplorer( o => o.GroupNameFormat = "'v'VVV" );

services.AddMvc();
services.AddApiVersioning( o => o.ReportApiVersions = true );
Expand Down Expand Up @@ -79,7 +77,8 @@ public void ConfigureServices( IServiceCollection services )
/// <param name="app">The current application builder.</param>
/// <param name="env">The current hosting environment.</param>
/// <param name="loggerFactory">The logging factory used for instrumentation.</param>
public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory )
/// <param name="provider">The API version descriptor provider used to enumerate defined API versions.</param>
public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApiVersionDescriptionProvider provider )
{
loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) );
loggerFactory.AddDebug();
Expand All @@ -89,9 +88,6 @@ public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILogger
app.UseSwaggerUI(
options =>
{
// resolve the IApiVersionDescriptionProvider service
var provider = app.ApplicationServices.GetRequiredService<IApiVersionDescriptionProvider>();

// build a swagger endpoint for each discovered API version
foreach ( var description in provider.ApiVersionDescriptions )
{
Expand Down
3 changes: 2 additions & 1 deletion samples/webapi/SwaggerODataWebApiSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public void Configuration( IAppBuilder builder )
configuration.MapVersionedODataRoutes( "odata-bypath", "api/v{apiVersion}", models, ConfigureODataServices );

// add the versioned IApiExplorer and capture the strongly-typed implementation (e.g. ODataApiExplorer vs IApiExplorer)
var apiExplorer = configuration.AddODataApiExplorer();
// note: the specified format code will format the version as "'v'major[.minor][-status]"
var apiExplorer = configuration.AddODataApiExplorer( o => o.GroupNameFormat = "'v'VVV" );

configuration.EnableSwagger(
"{apiVersion}/swagger",
Expand Down
3 changes: 2 additions & 1 deletion samples/webapi/SwaggerWebApiSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public void Configuration( IAppBuilder builder )
configuration.MapHttpAttributeRoutes( constraintResolver );

// add the versioned IApiExplorer and capture the strongly-typed implementation (e.g. VersionedApiExplorer vs IApiExplorer)
var apiExplorer = configuration.AddVersionedApiExplorer();
// note: the specified format code will format the version as "'v'major[.minor][-status]"
var apiExplorer = configuration.AddVersionedApiExplorer( o => o.GroupNameFormat = "'v'VVV" );

configuration.EnableSwagger(
"{apiVersion}/swagger",
Expand Down
25 changes: 25 additions & 0 deletions src/Common.ApiExplorer/ApiExplorerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if WEBAPI
namespace Microsoft.Web.Http.Description
#else
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
#endif
{
using System;
using Versioning;

/// <summary>
/// Represents the possible API versioning options for the API explorer.
/// </summary>
public partial class ApiExplorerOptions
{
/// <summary>
/// Gets or sets the format used to create group names from API versions.
/// </summary>
/// <value>The string format used to format an <see cref="ApiVersion">API version</see>
/// as a group name. The default value is <c>null</c>.</value>
/// <remarks>For information about API version formatting, review <see cref="ApiVersionFormatProvider"/>
/// as well as the <see cref="ApiVersion.ToString(string)"/> and <see cref="ApiVersion.ToString(string, IFormatProvider)"/>
/// methods.</remarks>
public string GroupNameFormat { get; set; }
}
}
14 changes: 14 additions & 0 deletions src/Common.ApiExplorer/Common.ApiExplorer.projitems
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>26a67334-f6e6-49b8-8c5a-f88f28770966</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Microsoft</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)ApiExplorerOptions.cs" />
</ItemGroup>
</Project>
13 changes: 13 additions & 0 deletions src/Common.ApiExplorer/Common.ApiExplorer.shproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>26a67334-f6e6-49b8-8c5a-f88f28770966</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="Common.ApiExplorer.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
152 changes: 11 additions & 141 deletions src/Common/ApiVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.AspNetCore.Mvc
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using Versioning;
using static System.DateTime;
using static System.Globalization.CultureInfo;
using static System.String;
Expand Down Expand Up @@ -304,94 +305,18 @@ public static bool TryParse( string text, out ApiVersion version )
return true;
}

void AppendGroupVersion( StringBuilder text, IFormatProvider formatProvider )
{
Contract.Requires( text != null );

if ( GroupVersion != null )
{
text.Append( GroupVersion.Value.ToString( GroupVersionFormat, formatProvider ) );
}
}

void AppendMajorAndMinorVersion( StringBuilder text, IFormatProvider formatProvider )
{
Contract.Requires( text != null );

if ( MajorVersion != null )
{
if ( text.Length > 0 )
{
text.Append( '.' );
}

text.Append( MajorVersion.Value.ToString( formatProvider ) );

if ( MinorVersion == null )
{
return;
}

text.Append( '.' );
text.Append( MinorVersion.Value.ToString( formatProvider ) );
}
else if ( MinorVersion != null )
{
text.Append( "0." );
text.Append( MinorVersion.Value.ToString( formatProvider ) );
}
}

void AppendStatus( StringBuilder text )
{
Contract.Requires( text != null );

if ( text.Length > 0 && !IsNullOrEmpty( Status ) )
{
text.Append( '-' );
text.Append( Status );
}
}

/// <summary>
/// Returns the text representation of the version using the specified format and format provider.
/// </summary>
/// <param name="format">The format to return the text representation in.</param>
/// <returns>The <see cref="String">string</see> representation of the version.</returns>
/// <remarks>The supported format codes are:
/// <para>
/// <list type="table">
/// <listheader>
/// <term>Format</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>G, g</term>
/// <description>Returns only the <see cref="P:GroupVersion">group version</see>, if present.</description>
/// </item>
/// <item>
/// <term>V, v</term>
/// <description>Returns only the <see cref="P:MajorVersion">major</see> and <see cref="P:MinorVersion">minor</see> versions, if present.</description>
/// </item>
/// <item>
/// <term>S, s</term>
/// <description>Returns full API version with the <see cref="P:Status">status</see>, if <see cref="P:Status">status</see> is present.</description>
/// </item>
/// <item>
/// <term>F, f</term>
/// <description>Returns the full API version.</description>
/// </item>
/// </list>
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">The specified <paramref name="format"/> is <c>null</c> or any empty string.</exception>
/// <seealso cref="ApiVersionFormatProvider"/></summary>
/// <param name="format">The format to return the text representation in. The value can be <c>null</c> or empty.</param>
/// <returns>The <see cref="string">string</see> representation of the version.</returns>
/// <exception cref="FormatException">The specified <paramref name="format"/> is not one of the supported format values.</exception>
public virtual string ToString( string format ) => ToString( format, InvariantCulture );

/// <summary>
/// Returns the text representation of the version.
/// </summary>
/// <returns>The <see cref="String">string</see> representation of the version.</returns>
/// <returns>The <see cref="string">string</see> representation of the version.</returns>
public override string ToString() => ToString( null, InvariantCulture );

/// <summary>
Expand Down Expand Up @@ -548,71 +473,16 @@ public virtual int CompareTo( ApiVersion other )

/// <summary>
/// Returns the text representation of the version using the specified format and format provider.
/// </summary>
/// <param name="format">The format to return the text representation in.</param>
/// <seealso cref="ApiVersionFormatProvider"/></summary>
/// <param name="format">The format to return the text representation in. The value can be <c>null</c> or empty.</param>
/// <param name="formatProvider">The <see cref="IFormatProvider">format provider</see> used to generate text.
/// This implementation should typically use an <see cref="P:CultureInfo.InvariantCulture">invariant culture</see>.</param>
/// <returns>The <see cref="String">string</see> representation of the version.</returns>
/// <remarks>The supported format codes are:
/// <para>
/// <list type="table">
/// <listheader>
/// <term>Format</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>G, g</term>
/// <description>Returns only the <see cref="P:GroupVersion">group version</see>, if present.</description>
/// </item>
/// <item>
/// <term>V, v</term>
/// <description>Returns only the <see cref="P:MajorVersion">major</see> and <see cref="P:MinorVersion">minor</see> versions, if present.</description>
/// </item>
/// <item>
/// <term>S, s</term>
/// <description>Returns full API version with the <see cref="P:Status">status</see>, if <see cref="P:Status">status</see> is present.</description>
/// </item>
/// <item>
/// <term>F, f</term>
/// <description>Returns the full API version.</description>
/// </item>
/// </list>
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">The specified <paramref name="format"/> is <c>null</c> or any empty string.</exception>
/// This implementation should typically use an <see cref="InvariantCulture">invariant culture</see>.</param>
/// <returns>The <see cref="string">string</see> representation of the version.</returns>
/// <exception cref="FormatException">The specified <paramref name="format"/> is not one of the supported format values.</exception>
public virtual string ToString( string format, IFormatProvider formatProvider )
{
// syntax := <group version>[.<major>.<minor>][-status] | [<group version>.]<major>.<minor>[-status]
var text = new StringBuilder();

switch ( format )
{
case "G":
case "g":
AppendGroupVersion( text, formatProvider );
break;
case "V":
case "v":
AppendMajorAndMinorVersion( text, formatProvider );
break;
case "S":
case "s":
AppendGroupVersion( text, formatProvider );
AppendMajorAndMinorVersion( text, formatProvider );
break;
case null:
case "F":
case "f":
AppendGroupVersion( text, formatProvider );
AppendMajorAndMinorVersion( text, formatProvider );
AppendStatus( text );
break;
default:
throw new FormatException( SR.ApiVersionInvalidFormatCode.FormatDefault( format ) );
}

return text.ToString();
var provider = ApiVersionFormatProvider.GetInstance( formatProvider );
return provider.Format( format, this, formatProvider );
}
}
}
1 change: 1 addition & 0 deletions src/Common/Common.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="$(MSBuildThisFileDirectory)ReportApiVersionsAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TypeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Versioning\AmbiguousApiVersionException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Versioning\ApiVersionFormatProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Versioning\ApiVersioningOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Versioning\ApiVersionModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Versioning\ApiVersionModelDebugView.cs" />
Expand Down
Loading