diff --git a/ApiVersioning.sln b/ApiVersioning.sln
index a4b1487c..b3ec97a2 100644
--- a/ApiVersioning.sln
+++ b/ApiVersioning.sln
@@ -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
@@ -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
@@ -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
diff --git a/ApiVersioningWithSamples.sln b/ApiVersioningWithSamples.sln
index 0a895e27..c5bdbda8 100644
--- a/ApiVersioningWithSamples.sln
+++ b/ApiVersioningWithSamples.sln
@@ -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
@@ -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
@@ -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
diff --git a/samples/aspnetcore/SwaggerSample/Startup.cs b/samples/aspnetcore/SwaggerSample/Startup.cs
index 14e4a1b6..4af1af8b 100644
--- a/samples/aspnetcore/SwaggerSample/Startup.cs
+++ b/samples/aspnetcore/SwaggerSample/Startup.cs
@@ -43,11 +43,9 @@ public Startup( IHostingEnvironment env )
/// The collection of services to configure the application with.
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 );
@@ -79,7 +77,8 @@ public void ConfigureServices( IServiceCollection services )
/// The current application builder.
/// The current hosting environment.
/// The logging factory used for instrumentation.
- public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory )
+ /// The API version descriptor provider used to enumerate defined API versions.
+ public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApiVersionDescriptionProvider provider )
{
loggerFactory.AddConsole( Configuration.GetSection( "Logging" ) );
loggerFactory.AddDebug();
@@ -89,9 +88,6 @@ public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILogger
app.UseSwaggerUI(
options =>
{
- // resolve the IApiVersionDescriptionProvider service
- var provider = app.ApplicationServices.GetRequiredService();
-
// build a swagger endpoint for each discovered API version
foreach ( var description in provider.ApiVersionDescriptions )
{
diff --git a/samples/webapi/SwaggerODataWebApiSample/Startup.cs b/samples/webapi/SwaggerODataWebApiSample/Startup.cs
index 4508b235..0330fe38 100644
--- a/samples/webapi/SwaggerODataWebApiSample/Startup.cs
+++ b/samples/webapi/SwaggerODataWebApiSample/Startup.cs
@@ -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",
diff --git a/samples/webapi/SwaggerWebApiSample/Startup.cs b/samples/webapi/SwaggerWebApiSample/Startup.cs
index df39be02..6279bf20 100644
--- a/samples/webapi/SwaggerWebApiSample/Startup.cs
+++ b/samples/webapi/SwaggerWebApiSample/Startup.cs
@@ -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",
diff --git a/src/Common.ApiExplorer/ApiExplorerOptions.cs b/src/Common.ApiExplorer/ApiExplorerOptions.cs
new file mode 100644
index 00000000..e2adb35f
--- /dev/null
+++ b/src/Common.ApiExplorer/ApiExplorerOptions.cs
@@ -0,0 +1,25 @@
+#if WEBAPI
+namespace Microsoft.Web.Http.Description
+#else
+namespace Microsoft.AspNetCore.Mvc.ApiExplorer
+#endif
+{
+ using System;
+ using Versioning;
+
+ ///
+ /// Represents the possible API versioning options for the API explorer.
+ ///
+ public partial class ApiExplorerOptions
+ {
+ ///
+ /// Gets or sets the format used to create group names from API versions.
+ ///
+ /// The string format used to format an API version
+ /// as a group name. The default value is null.
+ /// For information about API version formatting, review
+ /// as well as the and
+ /// methods.
+ public string GroupNameFormat { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Common.ApiExplorer/Common.ApiExplorer.projitems b/src/Common.ApiExplorer/Common.ApiExplorer.projitems
new file mode 100644
index 00000000..138877a0
--- /dev/null
+++ b/src/Common.ApiExplorer/Common.ApiExplorer.projitems
@@ -0,0 +1,14 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ 26a67334-f6e6-49b8-8c5a-f88f28770966
+
+
+ Microsoft
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Common.ApiExplorer/Common.ApiExplorer.shproj b/src/Common.ApiExplorer/Common.ApiExplorer.shproj
new file mode 100644
index 00000000..184d9d52
--- /dev/null
+++ b/src/Common.ApiExplorer/Common.ApiExplorer.shproj
@@ -0,0 +1,13 @@
+
+
+
+ 26a67334-f6e6-49b8-8c5a-f88f28770966
+ 14.0
+
+
+
+
+
+
+
+
diff --git a/src/Common/ApiVersion.cs b/src/Common/ApiVersion.cs
index 01f3a49a..08faa486 100644
--- a/src/Common/ApiVersion.cs
+++ b/src/Common/ApiVersion.cs
@@ -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;
@@ -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 );
- }
- }
-
///
/// Returns the text representation of the version using the specified format and format provider.
- ///
- /// The format to return the text representation in.
- /// The string representation of the version.
- /// The supported format codes are:
- ///
- ///
- ///
- /// Format
- /// Description
- ///
- /// -
- /// G, g
- /// Returns only the group version, if present.
- ///
- /// -
- /// V, v
- /// Returns only the major and minor versions, if present.
- ///
- /// -
- /// S, s
- /// Returns full API version with the status, if status is present.
- ///
- /// -
- /// F, f
- /// Returns the full API version.
- ///
- ///
- ///
- ///
- /// The specified is null or any empty string.
+ ///
+ /// The format to return the text representation in. The value can be null or empty.
+ /// The string representation of the version.
/// The specified is not one of the supported format values.
public virtual string ToString( string format ) => ToString( format, InvariantCulture );
///
/// Returns the text representation of the version.
///
- /// The string representation of the version.
+ /// The string representation of the version.
public override string ToString() => ToString( null, InvariantCulture );
///
@@ -548,71 +473,16 @@ public virtual int CompareTo( ApiVersion other )
///
/// Returns the text representation of the version using the specified format and format provider.
- ///
- /// The format to return the text representation in.
+ ///
+ /// The format to return the text representation in. The value can be null or empty.
/// The format provider used to generate text.
- /// This implementation should typically use an invariant culture.
- /// The string representation of the version.
- /// The supported format codes are:
- ///
- ///
- ///
- /// Format
- /// Description
- ///
- /// -
- /// G, g
- /// Returns only the group version, if present.
- ///
- /// -
- /// V, v
- /// Returns only the major and minor versions, if present.
- ///
- /// -
- /// S, s
- /// Returns full API version with the status, if status is present.
- ///
- /// -
- /// F, f
- /// Returns the full API version.
- ///
- ///
- ///
- ///
- /// The specified is null or any empty string.
+ /// This implementation should typically use an invariant culture.
+ /// The string representation of the version.
/// The specified is not one of the supported format values.
public virtual string ToString( string format, IFormatProvider formatProvider )
{
- // syntax := [..][-status] | [.].[-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 );
}
}
}
\ No newline at end of file
diff --git a/src/Common/Common.projitems b/src/Common/Common.projitems
index 88263439..9c36b383 100644
--- a/src/Common/Common.projitems
+++ b/src/Common/Common.projitems
@@ -19,6 +19,7 @@
+
diff --git a/src/Common/Versioning/ApiVersionFormatProvider.cs b/src/Common/Versioning/ApiVersionFormatProvider.cs
new file mode 100644
index 00000000..16ef8704
--- /dev/null
+++ b/src/Common/Versioning/ApiVersionFormatProvider.cs
@@ -0,0 +1,971 @@
+#if WEBAPI
+namespace Microsoft.Web.Http.Versioning
+#else
+namespace Microsoft.AspNetCore.Mvc.Versioning
+#endif
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Reflection;
+ using System.Text;
+ using static System.Globalization.DateTimeFormatInfo;
+ using static System.String;
+
+ ///
+ /// Represents a format provider for API versions.
+ ///
+ ///
+ /// This format provider supports the following custom format strings:
+ ///
+ ///
+ /// Format specifier
+ /// Description
+ /// Examples
+ ///
+ /// -
+ /// "F"
+ /// The full, formatted API version where optional, absent components are ommitted.
+ ///
+ /// 2017-01-01 -> 2017-01-01
+ /// 2017-01-01.1 -> 2017-01-01.1
+ /// 2017-01-01.1.5-RC -> 2017-01-01.1.5-RC
+ /// 2017-01-01-Beta -> 2017-01-01-Beta
+ /// 1 -> 1
+ /// 1.5 -> 1.5
+ /// 1-Beta -> 1-Beta
+ /// 0.9-Alpha -> 0.9-Alpha
+ ///
+ ///
+ /// -
+ /// "FF"
+ /// The full, formatted API version where optional components have default values.
+ ///
+ /// 2017-01-01 -> 2017-01-01
+ /// 2017-01-01.1 -> 2017-01-01.1.0
+ /// 2017-01-01.1.5-RC -> 2017-01-01.1.5-RC
+ /// 2017-01-01-Beta -> 2017-01-01-Beta
+ /// 1 -> 1.0
+ /// 1.5 -> 1.5
+ /// 1-Beta -> 1.0-Beta
+ /// 0.9-Alpha -> 0.9-Alpha
+ ///
+ ///
+ /// -
+ /// "G"
+ /// The group version of the API group version.
+ ///
+ /// 2017-01-01 -> 2017-01-01
+ /// 2017-01-01-RC -> 2017-01-01
+ /// 2017-01-01.1.0 -> 2017-01-01
+ ///
+ ///
+ /// -
+ /// "GG"
+ /// The group version and status of the API group version.
+ ///
+ /// 2017-01-01-RC -> 2017-01-01-RC
+ /// 2017-01-01.1.0-RC -> 2017-01-01-RC
+ ///
+ ///
+ /// -
+ /// "yyyy"
+ /// The year of the API group version.
+ ///
+ /// 2017-01-01 -> 2017
+ /// 2017-01-01-RC -> 2017
+ ///
+ ///
+ /// -
+ /// "MM"
+ /// The month of the API group version.
+ ///
+ /// 2017-01-01 -> 01
+ /// 2017-01-01-RC -> 01
+ ///
+ ///
+ /// -
+ /// "dd"
+ /// The day of the API group version.
+ ///
+ /// 2017-01-01 -> 01
+ /// 2017-01-01-RC -> 01
+ ///
+ ///
+ /// -
+ /// "v"
+ /// The minor version of the API version.
+ ///
+ /// 1.5 -> 5
+ /// 1.5-Alpha -> 5
+ ///
+ ///
+ /// -
+ /// "V"
+ /// The major version of the API version.
+ ///
+ /// 1.5 -> 1
+ /// 1.5-Alpha -> 1
+ ///
+ ///
+ /// -
+ /// "VV"
+ /// The major and minor version of the API version.
+ ///
+ /// 1.5 -> 1.5
+ /// 1 -> 1.0
+ /// 1.5-Alpha -> 1.5
+ /// 1-Alpha -> 1.0
+ ///
+ ///
+ /// -
+ /// "VVV"
+ /// The major version, optional minor version, and status of the API version.
+ ///
+ /// 1 -> 1
+ /// 1.5 -> 1.5
+ /// 1-Alpha -> 1-Alpha
+ /// 1.5-Alpha -> 1.5-Alpha
+ ///
+ ///
+ /// -
+ /// "VVVV"
+ /// The major version, minor version, and status of the API version.
+ ///
+ /// 1 -> 1.0
+ /// 1.5 -> 1.5
+ /// 1-Alpha -> 1.0-Alpha
+ /// 1.5-Alpha -> 1.5-Alpha
+ ///
+ ///
+ /// -
+ /// "p"
+ /// The minor version of the API version with padded zeros. The default padding is for two digits.
+ ///
+ /// 1.5 -> 05
+ /// 1.5-Alpha -> 05
+ ///
+ ///
+ /// -
+ /// "p(n)"
+ /// The minor version of the API version with padded zeros where "n" is the total number of digits.
+ ///
+ /// p3 -> 1.5 -> 005
+ /// p3 -> 1.5-Alpha -> 005
+ ///
+ ///
+ /// -
+ /// "P"
+ /// The major version of the API version with padded zeros. The default padding is for two digits.
+ ///
+ /// 1.5 -> 01
+ /// 1.5-Alpha -> 01
+ ///
+ ///
+ /// -
+ /// "P(n)"
+ /// The major version of the API version with padded zeros where "n" is the total number of digits.
+ ///
+ /// P3 -> 1.5 -> 001
+ /// P3 -> 1.5-Alpha -> 001
+ ///
+ ///
+ /// -
+ /// "S"
+ /// The API version version status.
+ ///
+ /// 1.0-Beta -> Beta
+ ///
+ ///
+ ///
+ ///
+ public class ApiVersionFormatProvider : IFormatProvider, ICustomFormatter
+ {
+ const string GroupVersionFormat = "yyyy-MM-dd";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ApiVersionFormatProvider()
+ {
+ DateTimeFormat = CurrentInfo;
+ Calendar = CurrentInfo.Calendar;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The used by the format provider.
+ [SuppressMessage( "Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Validated by a code contract" )]
+ public ApiVersionFormatProvider( DateTimeFormatInfo dateTimeFormat ) : this( dateTimeFormat, dateTimeFormat?.Calendar ) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The used by the format provider.
+ public ApiVersionFormatProvider( Calendar calendar ) : this( CurrentInfo, calendar ) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The used by the format provider.
+ /// The used by the format provider.
+ public ApiVersionFormatProvider( DateTimeFormatInfo dateTimeFormat, Calendar calendar )
+ {
+ Arg.NotNull( dateTimeFormat, nameof( dateTimeFormat ) );
+ Arg.NotNull( calendar, nameof( calendar ) );
+
+ DateTimeFormat = dateTimeFormat;
+ Calendar = calendar;
+ }
+
+ ///
+ /// Gets the underlying date and time format information.
+ ///
+ /// A object.
+ protected DateTimeFormatInfo DateTimeFormat { get; }
+
+ ///
+ /// Gets the calendar associated with the format provider.
+ ///
+ /// A object.
+ /// The cannot be assigned to a custom calendar.
+ protected Calendar Calendar { get; }
+
+ ///
+ /// Gets the API version format provider for the current culture.
+ ///
+ /// The for the current culture.
+ public static ApiVersionFormatProvider CurrentCulture { get; } = new ApiVersionFormatProvider( CurrentInfo, CurrentInfo.Calendar );
+
+ ///
+ /// Gets the API version format provider for the invariant culture.
+ ///
+ /// The for the invariant culture.
+ public static ApiVersionFormatProvider InvariantCulture { get; } = new ApiVersionFormatProvider( InvariantInfo, InvariantInfo.Calendar );
+
+ ///
+ /// Gets an instance of an API version format provider from the given format provider.
+ ///
+ /// The format provider used to retrieve the instance.
+ /// An object.
+ public static ApiVersionFormatProvider GetInstance( IFormatProvider formatProvider )
+ {
+ Contract.Ensures( Contract.Result() != null );
+
+ if ( formatProvider is ApiVersionFormatProvider provider )
+ {
+ return provider;
+ }
+
+ if ( formatProvider == null )
+ {
+ return CurrentCulture;
+ }
+
+ if ( ( provider = formatProvider.GetFormat( typeof( ApiVersionFormatProvider ) ) as ApiVersionFormatProvider ) == null )
+ {
+ if ( formatProvider is CultureInfo culture )
+ {
+ return new ApiVersionFormatProvider( culture.DateTimeFormat, culture.Calendar );
+ }
+
+ return CurrentCulture;
+ }
+
+ return provider;
+ }
+
+ ///
+ /// Formats all parts using the default format.
+ ///
+ /// The API version to format.
+ /// The format string for the API version. This parameter can be null or empty.
+ /// The used to apply the format.
+ /// A formatted string representing the API version.
+ [SuppressMessage( "Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validated by a code contract" )]
+ protected virtual string FormatAllParts( ApiVersion apiVersion, string format, IFormatProvider formatProvider )
+ {
+ Arg.NotNull( apiVersion, nameof( apiVersion ) );
+ Arg.NotNull( formatProvider, nameof( formatProvider ) );
+ Contract.Ensures( !IsNullOrEmpty( Contract.Result() ) );
+
+ var text = new StringBuilder();
+
+ if ( apiVersion.GroupVersion != null )
+ {
+ text.Append( apiVersion.GroupVersion.Value.ToString( GroupVersionFormat, formatProvider ) );
+ }
+
+ if ( apiVersion.MajorVersion != null )
+ {
+ if ( text.Length > 0 )
+ {
+ text.Append( '.' );
+ }
+
+ text.Append( apiVersion.MajorVersion.Value.ToString( formatProvider ) );
+
+ if ( apiVersion.MinorVersion == null )
+ {
+ if ( format == "FF" )
+ {
+ text.Append( ".0" );
+ }
+ }
+ else
+ {
+ text.Append( '.' );
+ text.Append( apiVersion.MinorVersion.Value.ToString( formatProvider ) );
+ }
+ }
+ else if ( apiVersion.MinorVersion != null )
+ {
+ text.Append( "0." );
+ text.Append( apiVersion.MinorVersion.Value.ToString( formatProvider ) );
+ }
+
+ if ( text.Length > 0 && !IsNullOrEmpty( apiVersion.Status ) )
+ {
+ text.Append( '-' );
+ text.Append( apiVersion.Status );
+ }
+
+ return text.ToString();
+ }
+
+ ///
+ /// Formats the specified group version using the provided format.
+ ///
+ /// The API version to format.
+ /// The format string for the group version.
+ /// The used to apply the format.
+ /// A formatted string representing the group version.
+ [SuppressMessage( "Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validated by a code contract" )]
+ protected virtual string FormatGroupVersionPart( ApiVersion apiVersion, string format, IFormatProvider formatProvider )
+ {
+ Arg.NotNull( apiVersion, nameof( apiVersion ) );
+ Arg.NotNullOrEmpty( format, nameof( format ) );
+ Arg.NotNull( formatProvider, nameof( formatProvider ) );
+ Contract.Ensures( !IsNullOrEmpty( Contract.Result() ) );
+
+ if ( apiVersion.GroupVersion == null )
+ {
+ return Empty;
+ }
+
+ var groupVersion = apiVersion.GroupVersion.Value;
+
+ switch ( format[0] )
+ {
+ case 'G':
+ // G, GG
+ var text = new StringBuilder( groupVersion.ToString( GroupVersionFormat, formatProvider ) );
+
+ // GG
+ if ( format.Length == 2 )
+ {
+ AppendStatus( text, apiVersion.Status );
+ }
+
+ return text.ToString();
+ case 'M':
+ var month = Calendar.GetMonth( groupVersion );
+
+ switch ( format.Length )
+ {
+ case 1: // M
+ return month.ToString( formatProvider );
+ case 2: // MM
+ return month.ToString( "00", formatProvider );
+ case 3: // MMM
+ return DateTimeFormat.GetAbbreviatedMonthName( month );
+ }
+
+ // MMMM*
+ return DateTimeFormat.GetMonthName( month );
+ case 'd':
+ switch ( format.Length )
+ {
+ case 1: // d
+ return Calendar.GetDayOfMonth( groupVersion ).ToString( formatProvider );
+ case 2: // dd
+ return Calendar.GetDayOfMonth( groupVersion ).ToString( "00", formatProvider );
+ case 3: // ddd
+ return DateTimeFormat.GetAbbreviatedDayName( Calendar.GetDayOfWeek( groupVersion ) );
+ }
+
+ // dddd*
+ return DateTimeFormat.GetDayName( Calendar.GetDayOfWeek( groupVersion ) );
+ case 'y':
+ var year = Calendar.GetYear( groupVersion );
+
+ switch ( format.Length )
+ {
+ case 1: // y
+ return ( year % 100 ).ToString( formatProvider );
+ case 2: // yy
+ return ( year % 100 ).ToString( "00", formatProvider );
+ case 3: // yyy
+ return year.ToString( "000", formatProvider );
+ }
+
+ // yyyy*
+ return year.ToString( formatProvider );
+ }
+
+ return groupVersion.ToString( format, formatProvider );
+ }
+
+ ///
+ /// Formats the specified version using the provided format.
+ ///
+ /// The API version to format.
+ /// The format string for the version.
+ /// The used to apply the format.
+ /// A formatted string representing the version.
+ [SuppressMessage( "Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validated by a code contract" )]
+ protected virtual string FormatVersionPart( ApiVersion apiVersion, string format, IFormatProvider formatProvider )
+ {
+ Arg.NotNull( apiVersion, nameof( apiVersion ) );
+ Arg.NotNullOrEmpty( format, nameof( format ) );
+ Arg.NotNull( formatProvider, nameof( formatProvider ) );
+ Contract.Ensures( !IsNullOrEmpty( Contract.Result() ) );
+
+ switch ( format[0] )
+ {
+ case 'V':
+ case 'v':
+ return FormatVersionWithoutPadding( apiVersion, format, formatProvider );
+ case 'P':
+ case 'p':
+ return FormatVersionWithPadding( apiVersion, format, formatProvider );
+ }
+
+ return Empty;
+ }
+
+ ///
+ /// Formats the specified status part using the provided format.
+ ///
+ /// The API version to format.
+ /// The format string for the status.
+ /// The used to apply the format.
+ /// A formatted string representing the status.
+ [SuppressMessage( "Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validated by a code contract" )]
+ protected virtual string FormatStatusPart( ApiVersion apiVersion, string format, IFormatProvider formatProvider )
+ {
+ Arg.NotNull( apiVersion, nameof( apiVersion ) );
+ Arg.NotNullOrEmpty( format, nameof( format ) );
+ Arg.NotNull( formatProvider, nameof( formatProvider ) );
+ Contract.Ensures( !IsNullOrEmpty( Contract.Result() ) );
+
+ return apiVersion.Status ?? Empty;
+ }
+
+ ///
+ /// Returns the formatter for the requested type.
+ ///
+ /// The type of requested formatter.
+ /// A , , or null depending on the requested format type.
+ public virtual object GetFormat( Type formatType )
+ {
+ if ( typeof( ICustomFormatter ).Equals( formatType ) )
+ {
+ return this;
+ }
+
+ if ( GetType().GetTypeInfo().IsAssignableFrom( formatType.GetTypeInfo() ) )
+ {
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Formats the provided argument with the specified format and provider.
+ ///
+ /// The format string to apply to the argument.
+ /// The argument to format.
+ /// The used to format the argument.
+ /// A string represeting the formatted argument.
+ public virtual string Format( string format, object arg, IFormatProvider formatProvider )
+ {
+ if ( !( arg is ApiVersion value ) )
+ {
+ return GetDefaultFormat( format, arg, formatProvider );
+ }
+
+ formatProvider = formatProvider == null || ReferenceEquals( this, formatProvider ) ? CultureInfo.CurrentCulture : formatProvider;
+
+ if ( IsNullOrEmpty( format ) )
+ {
+ return FormatAllParts( value, null, formatProvider );
+ }
+
+ var tokens = FormatTokenizer.Tokenize( format );
+ var text = new StringBuilder();
+
+ foreach ( var token in tokens )
+ {
+ if ( token.IsInvalid )
+ {
+ throw new FormatException( SR.InvalidFormatString );
+ }
+
+ text.Append( token.IsLiteral ? token.Format : GetCustomFormat( value, token.Format, formatProvider ) );
+ }
+
+ return text.ToString();
+ }
+
+ static string GetDefaultFormat( string format, object arg, IFormatProvider formatProvider )
+ {
+ if ( arg == null )
+ {
+ return format ?? Empty;
+ }
+
+ if ( !IsNullOrEmpty( format ) )
+ {
+ if ( arg is IFormattable formattable )
+ {
+ return formattable.ToString( format, formatProvider );
+ }
+ }
+
+ return arg.ToString();
+ }
+
+ string GetCustomFormat( ApiVersion value, string format, IFormatProvider formatProvider )
+ {
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( formatProvider != null );
+ Contract.Ensures( Contract.Result() != null );
+
+ switch ( format[0] )
+ {
+ case 'F':
+ return FormatAllParts( value, format, formatProvider );
+ case 'G':
+ case 'M':
+ case 'd':
+ case 'y':
+ return FormatGroupVersionPart( value, format, formatProvider );
+ case 'P':
+ case 'V':
+ case 'p':
+ case 'v':
+ return FormatVersionPart( value, format, formatProvider );
+ case 'S':
+ return FormatStatusPart( value, format, formatProvider );
+ }
+
+ return Empty;
+ }
+
+ static string FormatVersionWithoutPadding( ApiVersion apiVersion, string format, IFormatProvider formatProvider )
+ {
+ Contract.Requires( apiVersion != null );
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( formatProvider != null );
+ Contract.Ensures( !IsNullOrEmpty( Contract.Result() ) );
+
+ if ( format.Length == 1 && format[0] == 'v' )
+ {
+ return apiVersion.MinorVersion == null ? Empty : apiVersion.MinorVersion.Value.ToString( formatProvider );
+ }
+
+ if ( apiVersion.MajorVersion == null || format[0] != 'V' )
+ {
+ return Empty;
+ }
+
+ // V*
+ var text = new StringBuilder( apiVersion.MajorVersion.Value.ToString( formatProvider ) );
+
+ if ( format.Length == 1 )
+ {
+ return text.ToString();
+ }
+
+ var minor = apiVersion.MinorVersion ?? 0;
+
+ switch ( format.Length )
+ {
+ case 2: // VV
+ text.Append( '.' );
+ text.Append( minor.ToString( formatProvider ) );
+ break;
+ case 3: // VVV
+ if ( minor > 0 )
+ {
+ text.Append( '.' );
+ text.Append( minor.ToString( formatProvider ) );
+ }
+ AppendStatus( text, apiVersion.Status );
+ break;
+ case 4: // VVVV
+ text.Append( '.' );
+ text.Append( minor.ToString( formatProvider ) );
+ AppendStatus( text, apiVersion.Status );
+ break;
+ }
+
+ return text.ToString();
+ }
+
+ static string FormatVersionWithPadding( ApiVersion apiVersion, string format, IFormatProvider formatProvider )
+ {
+ Contract.Requires( apiVersion != null );
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( formatProvider != null );
+ Contract.Ensures( !IsNullOrEmpty( Contract.Result() ) );
+
+ SplitFormatSpecifierWithNumber( format, formatProvider, out var specifier, out var count );
+
+ const string TwoDigits = "D2";
+ const string LeadingZeros = "'D'0";
+
+ // p, p(n)
+ if ( specifier == "p" )
+ {
+ format = count.ToString( LeadingZeros, InvariantCulture );
+ return apiVersion.MinorVersion == null ? Empty : apiVersion.MinorVersion.Value.ToString( format, formatProvider );
+ }
+
+ if ( apiVersion.MajorVersion == null || format[0] != 'P' )
+ {
+ return Empty;
+ }
+
+ // P, P(n)
+ if ( specifier == "P" )
+ {
+ format = count.ToString( LeadingZeros, InvariantCulture );
+ return apiVersion.MajorVersion.Value.ToString( format, formatProvider );
+ }
+
+ var text = new StringBuilder( apiVersion.MajorVersion.Value.ToString( TwoDigits, formatProvider ) );
+ var minor = apiVersion.MinorVersion ?? 0;
+
+ switch ( format.Length )
+ {
+ case 2: // PP
+ text.Append( '.' );
+ text.Append( minor.ToString( TwoDigits, formatProvider ) );
+ break;
+ case 3: // PPP
+ if ( minor > 0 )
+ {
+ text.Append( '.' );
+ text.Append( minor.ToString( TwoDigits, formatProvider ) );
+ }
+ AppendStatus( text, apiVersion.Status );
+ break;
+ case 4: // PPPP
+ text.Append( '.' );
+ text.Append( minor.ToString( TwoDigits, formatProvider ) );
+ AppendStatus( text, apiVersion.Status );
+ break;
+ }
+
+ return text.ToString();
+ }
+
+ static void SplitFormatSpecifierWithNumber( string format, IFormatProvider formatProvider, out string specifier, out int count )
+ {
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( formatProvider != null );
+ Contract.Ensures( !IsNullOrEmpty( Contract.ValueAtReturn( out specifier ) ) );
+ Contract.Ensures( Contract.ValueAtReturn( out count ) >= 0 );
+
+ count = 2;
+
+ if ( format.Length == 1 )
+ {
+ specifier = format;
+ return;
+ }
+
+ var start = 0;
+ var end = 0;
+
+ for ( ; end < format.Length; end++ )
+ {
+ var ch = format[end];
+
+ if ( ch != 'P' && ch != 'p' )
+ {
+ break;
+ }
+ }
+
+ specifier = format.Substring( start, end );
+ start = end;
+
+ for ( ; end < format.Length; end++ )
+ {
+ if ( !char.IsNumber( format[end] ) )
+ {
+ break;
+ }
+ }
+
+ if ( end > start )
+ {
+ count = int.Parse( format.Substring( start, end - start ), formatProvider );
+ }
+ }
+
+ static void AppendStatus( StringBuilder text, string status )
+ {
+ Contract.Requires( text != null );
+
+ if ( !IsNullOrEmpty( status ) )
+ {
+ text.Append( '-' );
+ text.Append( status );
+ }
+ }
+
+ [DebuggerDisplay( "Token = {Token,nq}, Invalid = {IsInvalid,nq}, Literal = {IsLiteral,nq}" )]
+ sealed class FormatToken
+ {
+ internal readonly string Format;
+ internal readonly bool IsLiteral;
+ internal readonly bool IsInvalid;
+
+ internal FormatToken( string format ) : this( format, false, false ) { }
+
+ internal FormatToken( string format, bool literal ) : this( format, literal, false ) { }
+
+ internal FormatToken( string format, bool literal, bool invalid )
+ {
+ Contract.Requires( format != null );
+ Format = format;
+ IsLiteral = literal;
+ IsInvalid = invalid;
+ }
+ }
+
+ static class FormatTokenizer
+ {
+ static bool IsLiteralDelimiter( char ch ) => ch == '\'' || ch == '\"';
+
+ static bool IsFormatSpecifier( char ch )
+ {
+ switch ( ch )
+ {
+ case 'F':
+ case 'G':
+ case 'M':
+ case 'P':
+ case 'S':
+ case 'V':
+ case 'd':
+ case 'p':
+ case 'v':
+ case 'y':
+ return true;
+ }
+
+ return false;
+ }
+
+ static bool IsEscapeSequence( string sequence )
+ {
+ Contract.Requires( sequence != null );
+ Contract.Requires( sequence.Length == 2 );
+
+ switch ( sequence )
+ {
+ case @"\'":
+ case @"\\":
+ case @"\F":
+ case @"\G":
+ case @"\M":
+ case @"\P":
+ case @"\S":
+ case @"\V":
+ case @"\d":
+ case @"\p":
+ case @"\v":
+ case @"\y":
+ return true;
+ }
+
+ return false;
+ }
+
+ static bool IsSingleCustomFormatSpecifier( string sequence )
+ {
+ Contract.Requires( sequence != null );
+ Contract.Requires( sequence.Length == 2 );
+
+ switch ( sequence )
+ {
+ case "%F":
+ case "%G":
+ case "%M":
+ case "%P":
+ case "%S":
+ case "%V":
+ case "%d":
+ case "%v":
+ case "%p":
+ case "%y":
+ return true;
+ }
+
+ return false;
+ }
+
+ static void EnsureCurrentLiteralSequenceTerminated( ICollection tokens, StringBuilder token )
+ {
+ Contract.Requires( tokens != null );
+ Contract.Requires( token != null );
+
+ if ( token.Length > 0 )
+ {
+ tokens.Add( new FormatToken( token.ToString(), true ) );
+ token.Length = 0;
+ }
+ }
+
+ static void ConsumeLiteral( ICollection tokens, StringBuilder token, string format, char ch, ref int i )
+ {
+ Contract.Requires( tokens != null );
+ Contract.Requires( token != null );
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( i >= 0 );
+
+ EnsureCurrentLiteralSequenceTerminated( tokens, token );
+
+ var delimiter = ch;
+ var current = '\0';
+
+ while ( ( ++i < format.Length ) && ( ( current = format[i] ) != delimiter ) )
+ {
+ token.Append( current );
+ }
+
+ tokens.Add( new FormatToken( token.ToString(), literal: true, invalid: current != delimiter ) );
+ token.Length = 0;
+ }
+
+ static void ConsumeEscapeSequence( ICollection tokens, StringBuilder token, string format, ref int i )
+ {
+ Contract.Requires( tokens != null );
+ Contract.Requires( token != null );
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( i >= 0 );
+
+ EnsureCurrentLiteralSequenceTerminated( tokens, token );
+ tokens.Add( new FormatToken( format.Substring( ++i, 1 ), literal: true ) );
+ token.Length = 0;
+ }
+
+ static void ConsumeSingleCustomFormat( ICollection tokens, StringBuilder token, string format, ref int i )
+ {
+ Contract.Requires( tokens != null );
+ Contract.Requires( token != null );
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( i >= 0 );
+
+ EnsureCurrentLiteralSequenceTerminated( tokens, token );
+
+ var start = ++i;
+ var end = start + 1;
+
+ for ( ; end < format.Length; end++ )
+ {
+ if ( !char.IsNumber( format[end] ) )
+ {
+ break;
+ }
+ }
+
+ tokens.Add( new FormatToken( format.Substring( start, end - start ) ) );
+ token.Length = 0;
+ }
+
+ static void ConsumeCustomFormat( ICollection tokens, StringBuilder token, string format, char ch, ref int i )
+ {
+ Contract.Requires( tokens != null );
+ Contract.Requires( token != null );
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Requires( i >= 0 );
+
+ EnsureCurrentLiteralSequenceTerminated( tokens, token );
+ token.Append( ch );
+
+ var last = ch;
+
+ while ( ( ++i < format.Length ) && ( ( ch = format[i] ) == last ) )
+ {
+ token.Append( ch );
+ }
+
+ for ( var j = i; j < format.Length; j++, i++ )
+ {
+ if ( char.IsNumber( ch = format[i] ) )
+ {
+ token.Append( ch );
+ }
+
+ break;
+ }
+
+ tokens.Add( new FormatToken( token.ToString() ) );
+ token.Length = 0;
+
+ if ( i != format.Length )
+ {
+ --i;
+ }
+ }
+
+ internal static IEnumerable Tokenize( string format )
+ {
+ Contract.Requires( !IsNullOrEmpty( format ) );
+ Contract.Ensures( Contract.Result>() != null );
+
+ var tokens = new List();
+ var token = new StringBuilder();
+
+ for ( var i = 0; i < format.Length; i++ )
+ {
+ var ch = format[i];
+
+ if ( IsLiteralDelimiter( ch ) )
+ {
+ ConsumeLiteral( tokens, token, format, ch, ref i );
+ }
+ else if ( ( ch == '\\' ) && ( i < format.Length - 1 ) && IsEscapeSequence( format.Substring( i, 2 ) ) )
+ {
+ ConsumeEscapeSequence( tokens, token, format, ref i );
+ }
+ else if ( ( ch == '%' ) && ( i < format.Length - 1 ) && IsSingleCustomFormatSpecifier( format.Substring( i, 2 ) ) )
+ {
+ ConsumeSingleCustomFormat( tokens, token, format, ref i );
+ }
+ else if ( IsFormatSpecifier( ch ) )
+ {
+ ConsumeCustomFormat( tokens, token, format, ch, ref i );
+ }
+ else
+ {
+ token.Append( ch );
+ }
+ }
+
+ return tokens;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Description/ODataApiExplorer.cs b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Description/ODataApiExplorer.cs
index c7e13cb2..adf47de8 100644
--- a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Description/ODataApiExplorer.cs
+++ b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Description/ODataApiExplorer.cs
@@ -1,6 +1,5 @@
namespace Microsoft.Web.Http.Description
{
- using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData.Edm;
using Microsoft.Web.Http.Routing;
using System;
@@ -34,18 +33,23 @@ public class ODataApiExplorer : VersionedApiExplorer
/// Initializes a new instance of the class.
///
/// The current HTTP configuration.
- public ODataApiExplorer( HttpConfiguration configuration ) : base( configuration ) { }
+ public ODataApiExplorer( HttpConfiguration configuration ) : this( configuration, new ODataApiExplorerOptions( configuration ) ) { }
///
- /// Gets or sets a value indicating whether the API explorer settings are honored.
+ /// Initializes a new instance of the class.
+ ///
+ /// The current HTTP configuration.
+ /// The associated API explorer options.
+ public ODataApiExplorer( HttpConfiguration configuration, ODataApiExplorerOptions options ) : base( configuration, options )
+ {
+ Options = options;
+ }
+
+ ///
+ /// Gets the options associated with the API explorer.
///
- /// True if the is ignored; otherwise, false.
- /// The default value is false.
- /// Most OData services inherit from the , which excludes the controller
- /// from the API explorer by setting
- /// to true. By setting this property to false, these settings are ignored instead of reapplying
- /// with a value of false to all OData controllers.
- public bool UseApiExplorerSettings { get; set; }
+ /// The API explorer options.
+ new protected virtual ODataApiExplorerOptions Options { get; }
///
/// Determines whether the action should be considered.
@@ -66,7 +70,7 @@ protected override bool ShouldExploreAction( string actionRouteParameterValue, H
return base.ShouldExploreAction( actionRouteParameterValue, actionDescriptor, route, apiVersion );
}
- if ( UseApiExplorerSettings )
+ if ( Options.UseApiExplorerSettings )
{
var setting = actionDescriptor.GetCustomAttributes().FirstOrDefault();
@@ -112,7 +116,7 @@ protected override bool ShouldExploreController( string controllerRouteParameter
return base.ShouldExploreController( controllerRouteParameterValue, controllerDescriptor, route, apiVersion );
}
- if ( UseApiExplorerSettings )
+ if ( Options.UseApiExplorerSettings )
{
var setting = controllerDescriptor.GetCustomAttributes().FirstOrDefault();
diff --git a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Description/ODataApiExplorerOptions.cs b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Description/ODataApiExplorerOptions.cs
new file mode 100644
index 00000000..9d30ae72
--- /dev/null
+++ b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/Description/ODataApiExplorerOptions.cs
@@ -0,0 +1,30 @@
+namespace Microsoft.Web.Http.Description
+{
+ using System;
+ using System.Web.Http;
+ using System.Web.Http.Description;
+ using System.Web.OData;
+
+ ///
+ /// Represents the possible API versioning options for an OData API explorer.
+ ///
+ public class ODataApiExplorerOptions : ApiExplorerOptions
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current configuration associated with the options.
+ public ODataApiExplorerOptions( HttpConfiguration configuration ) : base( configuration ) { }
+
+ ///
+ /// Gets or sets a value indicating whether the API explorer settings are honored.
+ ///
+ /// True if the is ignored; otherwise, false.
+ /// The default value is false.
+ /// Most OData services inherit from the , which excludes the controller
+ /// from the API explorer by setting
+ /// to true. By setting this property to false, these settings are ignored instead of reapplying
+ /// with a value of false to all OData controllers.
+ public bool UseApiExplorerSettings { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs
index fd146960..3eab5e4c 100644
--- a/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs
+++ b/src/Microsoft.AspNet.OData.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs
@@ -23,22 +23,26 @@ public static class HttpConfigurationExtensions
/// This method always replaces the with a new instance of . This method also
/// configures the to not use , which enables exploring all OData
/// controllers without additional configuration.
- public static ODataApiExplorer AddODataApiExplorer( this HttpConfiguration configuration ) => configuration.AddODataApiExplorer( useApiExplorerSettings: false );
+ public static ODataApiExplorer AddODataApiExplorer( this HttpConfiguration configuration ) => configuration.AddODataApiExplorer( _ => { } );
///
/// Adds or replaces the configured API explorer with an implementation that supports OData and API versioning.
///
/// The configuration used to add the API explorer.
- /// Indicates whether the OData API explorer will use the
- /// when present.
+ /// An action used to configure the provided options.
/// The newly registered versioned API explorer.
/// This method always replaces the with a new instance of .
- public static ODataApiExplorer AddODataApiExplorer( this HttpConfiguration configuration, bool useApiExplorerSettings )
+ public static ODataApiExplorer AddODataApiExplorer( this HttpConfiguration configuration, Action setupAction )
{
Arg.NotNull( configuration, nameof( configuration ) );
+ Arg.NotNull( setupAction, nameof( setupAction ) );
Contract.Ensures( Contract.Result() != null );
- var apiExplorer = new ODataApiExplorer( configuration ) { UseApiExplorerSettings = useApiExplorerSettings };
+ var options = new ODataApiExplorerOptions( configuration );
+
+ setupAction( options );
+
+ var apiExplorer = new ODataApiExplorer( configuration, options );
configuration.Services.Replace( typeof( IApiExplorer ), apiExplorer );
return apiExplorer;
}
diff --git a/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/ApiExplorerOptions.cs b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/ApiExplorerOptions.cs
new file mode 100644
index 00000000..bbdf4478
--- /dev/null
+++ b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/ApiExplorerOptions.cs
@@ -0,0 +1,29 @@
+namespace Microsoft.Web.Http.Description
+{
+ using System;
+ using System.Web.Http;
+
+ ///
+ /// Provides additional implementation specific to ASP.NET Web API.
+ ///
+ public partial class ApiExplorerOptions
+ {
+ readonly HttpConfiguration configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current configuration associated with the options.
+ public ApiExplorerOptions( HttpConfiguration configuration )
+ {
+ Arg.NotNull( configuration, nameof( configuration ) );
+ this.configuration = configuration;
+ }
+
+ ///
+ /// Gets or sets the default API version applied to services that do not have explicit versions.
+ ///
+ /// The default API version.
+ public ApiVersion DefaultApiVersion => configuration.GetApiVersioningOptions().DefaultApiVersion;
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiExplorer.cs b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiExplorer.cs
index df6edf39..eb902952 100644
--- a/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiExplorer.cs
+++ b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiExplorer.cs
@@ -1,6 +1,5 @@
namespace Microsoft.Web.Http.Description
{
- using Microsoft.Web.Http.Versioning;
using Routing;
using System;
using System.Collections;
@@ -38,11 +37,20 @@ public class VersionedApiExplorer : IApiExplorer
/// Initializes a new instance of the class.
///
/// The current HTTP configuration.
- public VersionedApiExplorer( HttpConfiguration configuration )
+ public VersionedApiExplorer( HttpConfiguration configuration ) : this( configuration, new ApiExplorerOptions( configuration ) ) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current HTTP configuration.
+ /// The associated API explorer options.
+ public VersionedApiExplorer( HttpConfiguration configuration, ApiExplorerOptions options )
{
Arg.NotNull( configuration, nameof( configuration ) );
+ Arg.NotNull( options, nameof( options ) );
Configuration = configuration;
+ Options = options;
apiDescriptions = new Lazy( InitializeApiDescriptions );
}
@@ -53,10 +61,10 @@ public VersionedApiExplorer( HttpConfiguration configuration )
protected HttpConfiguration Configuration { get; }
///
- /// Gets the current API versioning options associated with the API explorer.
+ /// Gets the options associated with the API explorer.
///
- /// The current API versioning options.
- protected virtual ApiVersioningOptions Options => Configuration.GetApiVersioningOptions();
+ /// The API explorer options.
+ protected virtual ApiExplorerOptions Options { get; }
///
/// Gets the comparer used to compare API descriptions.
@@ -460,7 +468,7 @@ IEnumerable FlattenApiVersions()
var assembliesResolver = services.GetAssembliesResolver();
var typeResolver = services.GetHttpControllerTypeResolver();
var controllerTypes = typeResolver.GetControllerTypes( assembliesResolver );
- var options = Options;
+ var options = Configuration.GetApiVersioningOptions();
var declared = new HashSet();
var supported = new HashSet();
var deprecated = new HashSet();
diff --git a/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.csproj b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.csproj
index 44e34d7e..d374f844 100644
--- a/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.csproj
+++ b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Microsoft.AspNet.WebApi.Versioning.ApiExplorer.csproj
@@ -26,6 +26,7 @@
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs
index 5eff7b83..1d6f63c8 100644
--- a/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs
+++ b/src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/System.Web.Http/HttpConfigurationExtensions.cs
@@ -16,13 +16,29 @@ public static class HttpConfigurationExtensions
/// The configuration used to add the API explorer.
/// The newly registered versioned API explorer.
/// This method always replaces the with a new instance of .
- public static VersionedApiExplorer AddVersionedApiExplorer( this HttpConfiguration configuration )
+ public static VersionedApiExplorer AddVersionedApiExplorer( this HttpConfiguration configuration ) => configuration.AddVersionedApiExplorer( _ => { } );
+
+ ///
+ /// Adds or replaces the configured API explorer with an implementation that supports API versioning.
+ ///
+ /// The configuration used to add the API explorer.
+ /// An action used to configure the provided options.
+ /// The newly registered versioned API explorer.
+ /// This method always replaces the with a new instance of .
+ public static VersionedApiExplorer AddVersionedApiExplorer( this HttpConfiguration configuration, Action setupAction )
{
Arg.NotNull( configuration, nameof( configuration ) );
+ Arg.NotNull( setupAction, nameof( setupAction ) );
Contract.Ensures( Contract.Result() != null );
- var apiExplorer = new VersionedApiExplorer( configuration );
+ var options = new ApiExplorerOptions( configuration );
+
+ setupAction( options );
+
+ var apiExplorer = new VersionedApiExplorer( configuration, options );
+
configuration.Services.Replace( typeof( IApiExplorer ), apiExplorer );
+
return apiExplorer;
}
}
diff --git a/src/Microsoft.AspNet.WebApi.Versioning/SR.Designer.cs b/src/Microsoft.AspNet.WebApi.Versioning/SR.Designer.cs
index b83859d5..81bff3f2 100644
--- a/src/Microsoft.AspNet.WebApi.Versioning/SR.Designer.cs
+++ b/src/Microsoft.AspNet.WebApi.Versioning/SR.Designer.cs
@@ -204,6 +204,15 @@ internal static string InvalidActionMethodExpression {
}
}
+ ///
+ /// Looks up a localized string similar to Input string was not in a correct format..
+ ///
+ internal static string InvalidFormatString {
+ get {
+ return ResourceManager.GetString("InvalidFormatString", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The following API versions were requested: {0}. At most, only a single API version may be specified. Please update the intended API version and retry the request..
///
diff --git a/src/Microsoft.AspNet.WebApi.Versioning/SR.resx b/src/Microsoft.AspNet.WebApi.Versioning/SR.resx
index eec4585b..a144c24b 100644
--- a/src/Microsoft.AspNet.WebApi.Versioning/SR.resx
+++ b/src/Microsoft.AspNet.WebApi.Versioning/SR.resx
@@ -165,6 +165,9 @@
The expression '{0}' must refer to a controller action method.
+
+ Input string was not in a correct format.
+
The following API versions were requested: {0}. At most, only a single API version may be specified. Please update the intended API version and retry the request.
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiExplorerOptions.cs b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiExplorerOptions.cs
new file mode 100644
index 00000000..ab903129
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/ApiExplorerOptions.cs
@@ -0,0 +1,26 @@
+namespace Microsoft.AspNetCore.Mvc.ApiExplorer
+{
+ using System;
+
+ ///
+ /// Provides additional implementation specific to ASP.NET Core.
+ ///
+ public partial class ApiExplorerOptions
+ {
+ ApiVersion defaultApiVersion = ApiVersion.Default;
+
+ ///
+ /// Gets or sets the default API version applied to services that do not have explicit versions.
+ ///
+ /// The default API version. The default value is .
+ public ApiVersion DefaultApiVersion
+ {
+ get => defaultApiVersion;
+ set
+ {
+ Arg.NotNull( value, nameof( value ) );
+ defaultApiVersion = value;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/DefaultApiVersionDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/DefaultApiVersionDescriptionProvider.cs
index f3f123c0..2af0c0f6 100644
--- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/DefaultApiVersionDescriptionProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/DefaultApiVersionDescriptionProvider.cs
@@ -9,6 +9,7 @@
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
+ using static System.Globalization.CultureInfo;
///
/// Represents the default implementation of an object that discovers and describes the API version information within an application.
@@ -17,39 +18,27 @@
public class DefaultApiVersionDescriptionProvider : IApiVersionDescriptionProvider
{
readonly Lazy> apiVersionDescriptions;
- readonly IOptions options;
+ readonly IOptions options;
///
/// Initializes a new instance of the class.
///
/// The provider used to enumerate the actions within an application.
- /// The formatter used to get group names for API versions.
- /// The container of configured API versioning options.
- public DefaultApiVersionDescriptionProvider(
- IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
- IApiVersionGroupNameFormatter groupNameFormatter,
- IOptions apiVersioningOptions )
+ /// The container of configured API explorer options.
+ public DefaultApiVersionDescriptionProvider( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, IOptions apiExplorerOptions )
{
Arg.NotNull( actionDescriptorCollectionProvider, nameof( actionDescriptorCollectionProvider ) );
- Arg.NotNull( groupNameFormatter, nameof( groupNameFormatter ) );
- Arg.NotNull( apiVersioningOptions, nameof( apiVersioningOptions ) );
+ Arg.NotNull( apiExplorerOptions, nameof( apiExplorerOptions ) );
apiVersionDescriptions = new Lazy>( () => EnumerateApiVersions( actionDescriptorCollectionProvider ) );
- GroupNameFormatter = groupNameFormatter;
- options = apiVersioningOptions;
+ options = apiExplorerOptions;
}
///
- /// Gets the group name formatter associated with the provider.
+ /// Gets the options associated with the API explorer.
///
- /// The group name formatter used to format group names.
- protected IApiVersionGroupNameFormatter GroupNameFormatter { get; }
-
- ///
- /// Gets the current API versioning options associated with the API explorer.
- ///
- /// The current API versioning options.
- protected ApiVersioningOptions Options => options.Value;
+ /// The current API explorer options.
+ protected ApiExplorerOptions Options => options.Value;
///
/// Gets a read-only list of discovered API version descriptions.
@@ -153,7 +142,7 @@ void AppendDescriptions( ICollection descriptions, IEnume
foreach ( var version in versions )
{
- var groupName = GroupNameFormatter.GetGroupName( version );
+ var groupName = version.ToString( Options.GroupNameFormat, CurrentCulture );
descriptions.Add( new ApiVersionDescription( version, groupName, deprecated ) );
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/DefaultApiVersionGroupNameFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/DefaultApiVersionGroupNameFormatter.cs
deleted file mode 100644
index 11758d25..00000000
--- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/DefaultApiVersionGroupNameFormatter.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-namespace Microsoft.AspNetCore.Mvc.ApiExplorer
-{
- using System.Text;
- using static System.Globalization.CultureInfo;
- using static System.String;
-
- ///
- /// Represents the default implementation used to format group names for API versions.
- ///
- public sealed class DefaultApiVersionGroupNameFormatter : IApiVersionGroupNameFormatter
- {
- ///
- /// Returns the group name for the specified API version.
- ///
- /// The API version to retrieve a group name for.
- /// The group name for the specified API version.
- public string GetGroupName( ApiVersion apiVersion )
- {
- Arg.NotNull( apiVersion, nameof( apiVersion ) );
-
- var format = new StringBuilder();
- var formatProvider = InvariantCulture;
-
- if ( apiVersion.GroupVersion == null )
- {
- format.Append( 'v' );
- format.Append( apiVersion.MajorVersion ?? 0 );
-
- if ( apiVersion.MinorVersion != null && apiVersion.MinorVersion.Value > 0 )
- {
- format.Append( '.' );
- format.Append( apiVersion.MinorVersion );
- }
- }
- else
- {
- format.Append( apiVersion.GroupVersion.Value.ToString( "yyyy-MM-dd", formatProvider ) );
-
- if ( apiVersion.MajorVersion == null )
- {
- if ( apiVersion.MinorVersion != null )
- {
- format.Append( "-0." );
- format.Append( apiVersion.MinorVersion.Value );
- }
- }
- else
- {
- format.Append( '-' );
- format.Append( apiVersion.MajorVersion.Value );
-
- if ( apiVersion.MinorVersion != null && apiVersion.MinorVersion.Value > 0 )
- {
- format.Append( '.' );
- format.Append( apiVersion.MinorVersion.Value );
- }
- }
- }
-
- if ( !IsNullOrEmpty( apiVersion.Status ) )
- {
- format.Append( '-' );
- format.Append( apiVersion.Status );
- }
-
- return format.ToString();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/IApiVersionGroupNameFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/IApiVersionGroupNameFormatter.cs
deleted file mode 100644
index a857a786..00000000
--- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/IApiVersionGroupNameFormatter.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Microsoft.AspNetCore.Mvc.ApiExplorer
-{
- using System;
-
- ///
- /// Defines the behavior of a formatter for API version group names.
- ///
- public interface IApiVersionGroupNameFormatter
- {
- ///
- /// Returns the group name for the specified API version.
- ///
- /// The API version to retrieve a group name for.
- /// The group name for the specified API version.
- string GetGroupName( ApiVersion apiVersion );
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj
index c69b2902..b1f160bc 100644
--- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj
+++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.csproj
@@ -22,6 +22,7 @@
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.Extensions.DependencyInjection/IMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.Extensions.DependencyInjection/IMvcCoreBuilderExtensions.cs
index 8202eecb..8785a45b 100644
--- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.Extensions.DependencyInjection/IMvcCoreBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/Microsoft.Extensions.DependencyInjection/IMvcCoreBuilderExtensions.cs
@@ -2,7 +2,10 @@
{
using Extensions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
+ using Microsoft.AspNetCore.Mvc.Versioning;
+ using Microsoft.Extensions.Options;
using System;
+ using System.Diagnostics.Contracts;
using static ServiceDescriptor;
///
@@ -16,16 +19,44 @@ public static class IServiceCollectionExtensions
///
/// The core MVC builder available in the application
/// The original instance.
- public static IMvcCoreBuilder AddVersionedApiExplorer( this IMvcCoreBuilder builder )
+ public static IMvcCoreBuilder AddVersionedApiExplorer( this IMvcCoreBuilder builder ) => builder.AddVersionedApiExplorer( _ => { } );
+
+ ///
+ /// Adds an API explorer that is API version aware.
+ ///
+ /// The core MVC builder available in the application
+ /// An action used to configure the provided options.
+ /// The original instance.
+ public static IMvcCoreBuilder AddVersionedApiExplorer( this IMvcCoreBuilder builder, Action setupAction )
{
Arg.NotNull( builder, nameof( builder ) );
+ Arg.NotNull( setupAction, nameof( setupAction ) );
- builder.Services.TryAddSingleton();
+ builder.Services.Add( Singleton( serviceProvider => NewOptions( serviceProvider, setupAction ) ) );
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddEnumerable( Transient() );
return builder;
}
+
+ static IOptions NewOptions( IServiceProvider serviceProvider, Action setupAction )
+ {
+ Contract.Requires( serviceProvider != null );
+ Contract.Requires( setupAction != null );
+ Contract.Ensures( Contract.Result>() != null );
+
+ var versioningOptions = serviceProvider.GetService>();
+ var options = new ApiExplorerOptions();
+
+ if ( versioningOptions != null )
+ {
+ options.DefaultApiVersion = versioningOptions.Value.DefaultApiVersion;
+ }
+
+ setupAction( options );
+
+ return new OptionsWrapper( options );
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/VersionedApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/VersionedApiDescriptionProvider.cs
index bed57d88..28479b21 100644
--- a/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/VersionedApiDescriptionProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer/VersionedApiDescriptionProvider.cs
@@ -12,6 +12,7 @@
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
+ using static System.Globalization.CultureInfo;
using static System.Linq.Enumerable;
///
@@ -21,34 +22,22 @@
[CLSCompliant( false )]
public class VersionedApiDescriptionProvider : IApiDescriptionProvider
{
- readonly IOptions options;
+ readonly IOptions options;
///
/// Initializes a new instance of class.
///
- /// The formatter used to get group names for API versions.
/// The provider used to retrieve model metadata.
- /// The container of configured API versioning options.
- public VersionedApiDescriptionProvider(
- IApiVersionGroupNameFormatter groupNameFormatter,
- IModelMetadataProvider metadadataProvider,
- IOptions apiVersioningOptions )
+ /// The container of configured API explorer options.
+ public VersionedApiDescriptionProvider( IModelMetadataProvider metadadataProvider, IOptions options )
{
- Arg.NotNull( groupNameFormatter, nameof( groupNameFormatter ) );
Arg.NotNull( metadadataProvider, nameof( metadadataProvider ) );
- Arg.NotNull( apiVersioningOptions, nameof( apiVersioningOptions ) );
+ Arg.NotNull( options, nameof( options ) );
- GroupNameFormatter = groupNameFormatter;
MetadadataProvider = metadadataProvider;
- options = apiVersioningOptions;
+ this.options = options;
}
- ///
- /// Gets the group name formatter associated with the API description provider.
- ///
- /// The group name formatter used to format group names.
- protected IApiVersionGroupNameFormatter GroupNameFormatter { get; }
-
///
/// Gets the model metadata provider associated with the API description provider.
///
@@ -56,10 +45,10 @@ public VersionedApiDescriptionProvider(
protected IModelMetadataProvider MetadadataProvider { get; }
///
- /// Gets the current API versioning options associated with the API explorer.
+ /// Gets the options associated with the API explorer.
///
- /// The current API versioning options.
- protected ApiVersioningOptions Options => options.Value;
+ /// The current API explorer options.
+ protected ApiExplorerOptions Options => options.Value;
///
/// Gets the order prescendence of the current API description provider.
@@ -117,7 +106,7 @@ public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context )
foreach ( var version in FlattenApiVersions( results ) )
{
- var groupName = GroupNameFormatter.GetGroupName( version );
+ var groupName = version.ToString( Options.GroupNameFormat, CurrentCulture );
foreach ( var result in results )
{
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/SR.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Versioning/SR.Designer.cs
index 09bcf1ff..65c860eb 100644
--- a/src/Microsoft.AspNetCore.Mvc.Versioning/SR.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Versioning/SR.Designer.cs
@@ -133,6 +133,15 @@ internal static string InvalidActionMethodExpression {
}
}
+ ///
+ /// Looks up a localized string similar to Input string was not in a correct format..
+ ///
+ internal static string InvalidFormatString {
+ get {
+ return ResourceManager.GetString("InvalidFormatString", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The following API versions were requested: {0}. At most, only a single API version may be specified. Please update the intended API version and retry the request..
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Versioning/SR.resx b/src/Microsoft.AspNetCore.Mvc.Versioning/SR.resx
index d3a54aa7..2b1d9f0a 100644
--- a/src/Microsoft.AspNetCore.Mvc.Versioning/SR.resx
+++ b/src/Microsoft.AspNetCore.Mvc.Versioning/SR.resx
@@ -142,6 +142,9 @@
The expression '{0}' must refer to a controller action method.
+
+ Input string was not in a correct format.
+
The following API versions were requested: {0}. At most, only a single API version may be specified. Please update the intended API version and retry the request.
diff --git a/test/Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs b/test/Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
index 70e678c9..1c726c0a 100644
--- a/test/Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
+++ b/test/Microsoft.AspNet.OData.Versioning.ApiExplorer.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
@@ -1,6 +1,7 @@
namespace System.Web.Http
{
using FluentAssertions;
+ using Microsoft.Web.Http.Description;
using System;
using Xunit;
@@ -11,12 +12,13 @@ public void add_odata_api_explorer_should_use_default_settings()
{
// arrange
var configuration = new HttpConfiguration();
+ var options = default( ODataApiExplorerOptions );
// act
- var apiExplorer = configuration.AddODataApiExplorer();
+ configuration.AddODataApiExplorer( o => options = o );
// assert
- apiExplorer.UseApiExplorerSettings.Should().BeFalse();
+ options.UseApiExplorerSettings.Should().BeFalse();
}
[Fact]
@@ -24,12 +26,13 @@ public void add_odata_api_explorer_should_use_api_explorer_settings_when_enabled
{
// arrange
var configuration = new HttpConfiguration();
+ var options = default( ODataApiExplorerOptions );
// act
- var apiExplorer = configuration.AddODataApiExplorer( useApiExplorerSettings: true );
+ configuration.AddODataApiExplorer( o => { o.UseApiExplorerSettings = true; options = o; } );
// assert
- apiExplorer.UseApiExplorerSettings.Should().BeTrue();
+ options.UseApiExplorerSettings.Should().BeTrue();
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.WebApi.Versioning.Tests/ApiVersionTest.cs b/test/Microsoft.AspNet.WebApi.Versioning.Tests/ApiVersionTest.cs
index 1aa5e61e..ba2bdaa9 100644
--- a/test/Microsoft.AspNet.WebApi.Versioning.Tests/ApiVersionTest.cs
+++ b/test/Microsoft.AspNet.WebApi.Versioning.Tests/ApiVersionTest.cs
@@ -306,30 +306,20 @@ public void to_string_should_return_expected_string( string text )
}
[Theory]
- [InlineData( "F", "2013-08-06", "2013-08-06" )]
- [InlineData( "F", "2013-08-06-Alpha", "2013-08-06-Alpha" )]
- [InlineData( "F", "1.1", "1.1" )]
- [InlineData( "F", "1.1-Alpha", "1.1-Alpha" )]
- [InlineData( "F", "2013-08-06.1.1", "2013-08-06.1.1" )]
- [InlineData( "F", "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha" )]
+ [InlineData( null, "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha" )]
+ [InlineData( "", "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha" )]
[InlineData( "G", "2013-08-06", "2013-08-06" )]
- [InlineData( "G", "2013-08-06-Alpha", "2013-08-06" )]
+ [InlineData( "GG", "2013-08-06-Alpha", "2013-08-06-Alpha" )]
[InlineData( "G", "1.1", "" )]
[InlineData( "G", "1.1-Alpha", "" )]
[InlineData( "G", "2013-08-06.1.1", "2013-08-06" )]
- [InlineData( "G", "2013-08-06.1.1-Alpha", "2013-08-06" )]
+ [InlineData( "GG", "2013-08-06.1.1-Alpha", "2013-08-06-Alpha" )]
[InlineData( "V", "2013-08-06", "" )]
- [InlineData( "V", "2013-08-06-Alpha", "" )]
- [InlineData( "V", "1.1", "1.1" )]
- [InlineData( "V", "1.1-Alpha", "1.1" )]
- [InlineData( "V", "2013-08-06.1.1", "1.1" )]
- [InlineData( "V", "2013-08-06.1.1-Alpha", "1.1" )]
- [InlineData( "S", "2013-08-06", "2013-08-06" )]
- [InlineData( "S", "2013-08-06-Alpha", "2013-08-06" )]
- [InlineData( "S", "1.1", "1.1" )]
- [InlineData( "S", "1.1-Alpha", "1.1" )]
- [InlineData( "S", "2013-08-06.1.1", "2013-08-06.1.1" )]
- [InlineData( "S", "2013-08-06.1.1-Alpha", "2013-08-06.1.1" )]
+ [InlineData( "VVVV", "2013-08-06-Alpha", "" )]
+ [InlineData( "VV", "1.1", "1.1" )]
+ [InlineData( "VVVV", "1.1-Alpha", "1.1-Alpha" )]
+ [InlineData( "VV", "2013-08-06.1.1", "1.1" )]
+ [InlineData( "VVVV", "2013-08-06.1.1-Alpha", "1.1-Alpha" )]
public void to_string_with_format_should_return_expected_string( string format, string text, string formattedString )
{
// arrange
@@ -342,34 +332,6 @@ public void to_string_with_format_should_return_expected_string( string format,
@string.Should().Be( formattedString );
}
- [Fact]
- public void to_string_should_throw_format_exception_when_format_code_is_invalid()
- {
- // arrange
- var apiVersion = ApiVersion.Default;
- Action toString = () => apiVersion.ToString( "x" );
-
- // act
-
-
- // assert
- toString.ShouldThrow();
- }
-
- [Fact]
- public void to_string_with_format_provider_should_throw_format_exception_when_format_code_is_invalid()
- {
- // arrange
- var apiVersion = ApiVersion.Default;
- Action toString = () => apiVersion.ToString( "x", CurrentCulture );
-
- // act
-
-
- // assert
- toString.ShouldThrow();
- }
-
[Theory]
[InlineData( "2013-08-06" )]
[InlineData( "2013-08-06-Alpha" )]
diff --git a/test/Microsoft.AspNet.WebApi.Versioning.Tests/Versioning/ApiVersionFormatProviderTest.cs b/test/Microsoft.AspNet.WebApi.Versioning.Tests/Versioning/ApiVersionFormatProviderTest.cs
new file mode 100644
index 00000000..5861e10d
--- /dev/null
+++ b/test/Microsoft.AspNet.WebApi.Versioning.Tests/Versioning/ApiVersionFormatProviderTest.cs
@@ -0,0 +1,413 @@
+namespace Microsoft.Web.Http.Versioning
+{
+ using FluentAssertions;
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using Xunit;
+ using static System.Globalization.CultureInfo;
+ using static System.String;
+
+ public class ApiVersionFormatProviderTest
+ {
+ [Fact]
+ public void get_format_should_return_null_for_unsupported_format_type()
+ {
+ // arrange
+ var formatType = typeof( object );
+ var provider = new ApiVersionFormatProvider();
+
+ // act
+ var format = provider.GetFormat( formatType );
+
+ // assert
+ format.Should().BeNull();
+ }
+
+ [Fact]
+ public void get_format_should_return_expected_format_provider()
+ {
+ // arrange
+ var formatType = typeof( ICustomFormatter );
+ var provider = new ApiVersionFormatProvider();
+
+ // act
+ var format = provider.GetFormat( formatType );
+
+ // assert
+ format.Should().BeSameAs( provider );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_allow_null_or_empty_format_string( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = new ApiVersion( 1, 0 );
+ var expected = new[] { apiVersion.ToString(), apiVersion.ToString() };
+
+ // act
+ var actual = new[] { provider.Format( null, apiVersion, CurrentCulture ), provider.Format( Empty, apiVersion, CurrentCulture ) };
+
+ // assert
+ actual.Should().Equal( expected );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_full_formatted_string_without_optional_components( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = ApiVersion.Parse( "2017-05-01.1-Beta" );
+
+ // act
+ var format = provider.Format( "F", apiVersion, CurrentCulture );
+
+ // assert
+ format.Should().Be( "2017-05-01.1-Beta" );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_full_formatted_string_with_optional_components( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = ApiVersion.Parse( "2017-05-01.1-Beta" );
+
+ // act
+ var format = provider.Format( "FF", apiVersion, CurrentCulture );
+
+ // assert
+ format.Should().Be( "2017-05-01.1.0-Beta" );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_original_string_format_when_argument_cannot_be_formatted( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var value = new object();
+ var expected = new string[] { "d", value.ToString() };
+
+ // act
+ var actual = new[] { provider.Format( "d", null, CurrentCulture ), provider.Format( "d", value, CurrentCulture ) };
+
+ // assert
+ actual.Should().Equal( expected );
+ }
+
+ [Theory]
+ [MemberData( nameof( MalformedLiteralStringsData ) )]
+ public void format_should_not_allow_malformed_literal_string( ApiVersionFormatProvider provider, string malformedFormat )
+ {
+ // arrange
+ var apiVersion = new ApiVersion( new DateTime( 2017, 5, 1 ) );
+
+ // act
+ Action format = () => provider.Format( malformedFormat, apiVersion, null );
+
+ // assert
+ format.ShouldThrow();
+ }
+
+ [Theory]
+ [MemberData( nameof( GroupVersionFormatData ) )]
+ public void format_should_return_formatted_group_version_string( ApiVersionFormatProvider provider, string format )
+ {
+ // arrange
+ var groupVersion = new DateTime( 2017, 5, 1 );
+ var apiVersion = new ApiVersion( groupVersion );
+ var expected = groupVersion.ToString( format, CurrentCulture );
+
+ // act
+ var actual = provider.Format( format, apiVersion, CurrentCulture );
+
+ // assert
+ actual.Should().Be( expected );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_formatted_minor_version_string( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = new ApiVersion( 2, 5 );
+
+ // act
+ var minorVersion = provider.Format( "v", apiVersion, CurrentCulture );
+
+ // assert
+ minorVersion.Should().Be( "5" );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_formatted_major_version_string( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = new ApiVersion( 2, 5 );
+
+ // act
+ var majorVersion = provider.Format( "V", apiVersion, CurrentCulture );
+
+ // assert
+ majorVersion.Should().Be( "2" );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_formatted_major_and_minor_version_string( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = new ApiVersion( 2, 0 );
+
+ // act
+ var minorVersion = provider.Format( "VV", apiVersion, CurrentCulture );
+
+ // assert
+ minorVersion.Should().Be( "2.0" );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_formatted_short_version_string( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = new ApiVersion( 2, 0 );
+
+ // act
+ var minorVersion = provider.Format( "VVV", apiVersion, CurrentCulture );
+
+ // assert
+ minorVersion.Should().Be( "2" );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_formatted_long_version_string( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = ApiVersion.Parse( "1-RC" );
+
+ // act
+ var minorVersion = provider.Format( "VVVV", apiVersion, CurrentCulture );
+
+ // assert
+ minorVersion.Should().Be( "1.0-RC" );
+ }
+
+ [Theory]
+ [MemberData( nameof( FormatProvidersData ) )]
+ public void format_should_return_formatted_status_string( ApiVersionFormatProvider provider )
+ {
+ // arrange
+ var apiVersion = new ApiVersion( 2, 5, "Beta" );
+
+ // act
+ var status = provider.Format( "S", apiVersion, CurrentCulture );
+
+ // assert
+ status.Should().Be( "Beta" );
+ }
+
+ [Theory]
+ [MemberData( nameof( PaddedMinorVersionFormatData ) )]
+ public void format_should_return_formatted_minor_version_with_padding_string( ApiVersionFormatProvider provider, string format )
+ {
+ // arrange
+ var numberFormat = format.Replace( "p", "D" );
+ var apiVersion = new ApiVersion( 2, 5 );
+
+ if ( numberFormat == "D" )
+ {
+ numberFormat += "2";
+ }
+
+ // act
+ var minorVersion = provider.Format( format, apiVersion, CurrentCulture );
+
+ // assert
+ minorVersion.Should().Be( apiVersion.MinorVersion.Value.ToString( numberFormat, CurrentCulture ) );
+ }
+
+ [Theory]
+ [MemberData( nameof( PaddedMajorVersionFormatData ) )]
+ public void format_should_return_formatted_major_version_with_padding_string( ApiVersionFormatProvider provider, string format )
+ {
+ // arrange
+ var numberFormat = format.Replace( "P", "D" );
+ var apiVersion = new ApiVersion( 2, 5 );
+
+ if ( numberFormat == "D" )
+ {
+ numberFormat += "2";
+ }
+
+ // act
+ var majorVersion = provider.Format( format, apiVersion, CurrentCulture );
+
+ // assert
+ majorVersion.Should().Be( apiVersion.MajorVersion.Value.ToString( numberFormat, CurrentCulture ) );
+ }
+
+ [Theory]
+ [MemberData( nameof( CustomFormatData ) )]
+ public void format_should_return_custom_format_string( Func format, string expected )
+ {
+ // arrange
+ var groupVersion = new DateTime( 2017, 5, 1 );
+ var apiVersion = new ApiVersion( groupVersion, 1, 0, "Beta" );
+
+ // act
+ var actual = format( apiVersion );
+
+ // assert
+ actual.Should().Be( expected );
+ }
+
+ [Theory]
+ [MemberData( nameof( MultipleFormatParameterData ) )]
+ public void format_should_return_formatted_string_with_multiple_parameters( ApiVersionFormatProvider provider, string format, object secondArgument, string expected )
+ {
+ // arrange
+ var groupVersion = new DateTime( 2017, 5, 1 );
+ var apiVersion = new ApiVersion( groupVersion, 1, 0, "Beta" );
+ var args = new object[] { apiVersion, secondArgument };
+
+ // act
+ var status = Format( provider, format, args );
+
+ // assert
+ status.Should().Be( expected );
+ }
+
+ [Fact]
+ public void format_should_return_formatted_string_with_escape_sequence()
+ {
+ // arrange
+ var groupVersion = new DateTime( 2017, 5, 1 );
+ var apiVersion = new ApiVersion( groupVersion, 1, 0, "Beta" );
+ var provider = new ApiVersionFormatProvider();
+
+ // act
+ var result = provider.Format( "VV '('\\'yy')'", apiVersion, CurrentCulture );
+
+ // assert
+ result.Should().Be( "1.0 ('17)" );
+ }
+
+ public static IEnumerable