Skip to content

Commit db37e29

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Added initial support for OData API explorer
1 parent 61fd62f commit db37e29

29 files changed

+1792
-2
lines changed

ApiVersioning.sln

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26228.9
4+
VisualStudioVersion = 15.0.26403.3
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}"
77
EndProject
@@ -54,6 +54,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Ve
5454
EndProject
5555
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests", "test\Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests\Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.Tests.csproj", "{C8D29CB1-C541-4579-A1B8-AFD4B4F5F4A3}"
5656
EndProject
57+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNet.OData.Versioning.ApiExplorer", "src\Microsoft.AspNet.OData.Versioning.ApiExplorer\Microsoft.AspNet.OData.Versioning.ApiExplorer.csproj", "{6ED07FE1-95D3-41E9-A0F1-AEF1BBD6A474}"
58+
EndProject
59+
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}"
60+
EndProject
5761
Global
5862
GlobalSection(SharedMSBuildProjectFiles) = preSolution
5963
test\Acceptance.Test.Shared\Acceptance.Test.Shared.projitems*{6cdfb878-2642-4f98-ae35-621bac581181}*SharedItemsImports = 13
@@ -114,6 +118,14 @@ Global
114118
{C8D29CB1-C541-4579-A1B8-AFD4B4F5F4A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
115119
{C8D29CB1-C541-4579-A1B8-AFD4B4F5F4A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
116120
{C8D29CB1-C541-4579-A1B8-AFD4B4F5F4A3}.Release|Any CPU.Build.0 = Release|Any CPU
121+
{6ED07FE1-95D3-41E9-A0F1-AEF1BBD6A474}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
122+
{6ED07FE1-95D3-41E9-A0F1-AEF1BBD6A474}.Debug|Any CPU.Build.0 = Debug|Any CPU
123+
{6ED07FE1-95D3-41E9-A0F1-AEF1BBD6A474}.Release|Any CPU.ActiveCfg = Release|Any CPU
124+
{6ED07FE1-95D3-41E9-A0F1-AEF1BBD6A474}.Release|Any CPU.Build.0 = Release|Any CPU
125+
{280C3B03-5EED-40E9-A826-83C9F3C6EEDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
126+
{280C3B03-5EED-40E9-A826-83C9F3C6EEDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
127+
{280C3B03-5EED-40E9-A826-83C9F3C6EEDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
128+
{280C3B03-5EED-40E9-A826-83C9F3C6EEDC}.Release|Any CPU.Build.0 = Release|Any CPU
117129
EndGlobalSection
118130
GlobalSection(SolutionProperties) = preSolution
119131
HideSolutionNode = FALSE
@@ -135,5 +147,7 @@ Global
135147
{AAFE5030-DF0A-4156-9AF5-1AAA53CA1FF9} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
136148
{FE19225D-6564-4FCF-BA1C-0ED5CA336C44} = {4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}
137149
{C8D29CB1-C541-4579-A1B8-AFD4B4F5F4A3} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
150+
{6ED07FE1-95D3-41E9-A0F1-AEF1BBD6A474} = {4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}
151+
{280C3B03-5EED-40E9-A826-83C9F3C6EEDC} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
138152
EndGlobalSection
139153
EndGlobal

ApiVersioningWithSamples.sln

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26228.10
4+
VisualStudioVersion = 15.0.26403.3
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}"
77
EndProject
@@ -88,6 +88,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwaggerSample", "samples\as
8888
EndProject
8989
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwaggerWebApiSample", "samples\webapi\SwaggerWebApiSample\SwaggerWebApiSample.csproj", "{6BEDE228-4BE9-499E-B1E6-93B6B0AC62DA}"
9090
EndProject
91+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwaggerODataWebApiSample", "samples\webapi\SwaggerODataWebApiSample\SwaggerODataWebApiSample.csproj", "{F3986F7B-AF76-43D1-A44F-303023A08CD3}"
92+
EndProject
93+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.OData.Versioning.ApiExplorer", "src\Microsoft.AspNet.OData.Versioning.ApiExplorer\Microsoft.AspNet.OData.Versioning.ApiExplorer.csproj", "{1B255310-A2B7-437F-804F-6E1D8C940A17}"
94+
EndProject
95+
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}"
96+
EndProject
9197
Global
9298
GlobalSection(SharedMSBuildProjectFiles) = preSolution
9399
test\Acceptance.Test.Shared\Acceptance.Test.Shared.projitems*{6cdfb878-2642-4f98-ae35-621bac581181}*SharedItemsImports = 13
@@ -192,6 +198,18 @@ Global
192198
{6BEDE228-4BE9-499E-B1E6-93B6B0AC62DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
193199
{6BEDE228-4BE9-499E-B1E6-93B6B0AC62DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
194200
{6BEDE228-4BE9-499E-B1E6-93B6B0AC62DA}.Release|Any CPU.Build.0 = Release|Any CPU
201+
{F3986F7B-AF76-43D1-A44F-303023A08CD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
202+
{F3986F7B-AF76-43D1-A44F-303023A08CD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
203+
{F3986F7B-AF76-43D1-A44F-303023A08CD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
204+
{F3986F7B-AF76-43D1-A44F-303023A08CD3}.Release|Any CPU.Build.0 = Release|Any CPU
205+
{1B255310-A2B7-437F-804F-6E1D8C940A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
206+
{1B255310-A2B7-437F-804F-6E1D8C940A17}.Debug|Any CPU.Build.0 = Debug|Any CPU
207+
{1B255310-A2B7-437F-804F-6E1D8C940A17}.Release|Any CPU.ActiveCfg = Release|Any CPU
208+
{1B255310-A2B7-437F-804F-6E1D8C940A17}.Release|Any CPU.Build.0 = Release|Any CPU
209+
{3B7E0FEF-8019-4A17-A55F-A6FA378DA856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
210+
{3B7E0FEF-8019-4A17-A55F-A6FA378DA856}.Debug|Any CPU.Build.0 = Debug|Any CPU
211+
{3B7E0FEF-8019-4A17-A55F-A6FA378DA856}.Release|Any CPU.ActiveCfg = Release|Any CPU
212+
{3B7E0FEF-8019-4A17-A55F-A6FA378DA856}.Release|Any CPU.Build.0 = Release|Any CPU
195213
EndGlobalSection
196214
GlobalSection(SolutionProperties) = preSolution
197215
HideSolutionNode = FALSE
@@ -226,5 +244,8 @@ Global
226244
{15461DBB-95AD-4CA7-AF41-E70F54860FE3} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
227245
{D95BC932-50F7-4014-970E-0C6E8400BE25} = {900DD210-8500-4D89-A05D-C9526935A719}
228246
{6BEDE228-4BE9-499E-B1E6-93B6B0AC62DA} = {F446ED94-368F-4F67-913B-16E82CA80DFC}
247+
{F3986F7B-AF76-43D1-A44F-303023A08CD3} = {F446ED94-368F-4F67-913B-16E82CA80DFC}
248+
{1B255310-A2B7-437F-804F-6E1D8C940A17} = {4D5F5F21-0CB7-4B4E-A42F-732BD4AFD0FF}
249+
{3B7E0FEF-8019-4A17-A55F-A6FA378DA856} = {0987757E-4D09-4523-B9C9-65B1E8832AA1}
229250
EndGlobalSection
230251
EndGlobal
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace Microsoft.Examples
2+
{
3+
using Microsoft.OData.UriParser;
4+
using System;
5+
6+
/// <summary>
7+
/// Represents a case-insensitive URI resolver.
8+
/// </summary>
9+
/// <remarks>
10+
/// <list type="bullet">
11+
/// <item>HACK: required due to bug in ODL</item>
12+
/// <item>REF: https://github.com/OData/odata.net/issues/695</item>
13+
/// </list></remarks>
14+
public sealed class CaseInsensitiveODataUriResolver : UnqualifiedODataUriResolver
15+
{
16+
/// <summary>
17+
/// Gets or sets whether the URI resolver is case-sensitive.
18+
/// </summary>
19+
/// <value>True if the URI resolver is case-sensitive; otherwise, false.</value>
20+
/// <remarks>This property will always return <c>false</c>.</remarks>
21+
public override bool EnableCaseInsensitive { get { return true; } set { } }
22+
}
23+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace Microsoft.Examples.Configuration
2+
{
3+
using Microsoft.Web.Http;
4+
using Microsoft.Web.OData.Builder;
5+
using Models;
6+
using System.Web.OData.Builder;
7+
8+
/// <summary>
9+
/// Represents the model configuration for <see cref="Order">orders</see>.
10+
/// </summary>
11+
public class OrderModelConfiguration : IModelConfiguration
12+
{
13+
/// <summary>
14+
/// Applies model configurations using the provided builder for the specified API version.
15+
/// </summary>
16+
/// <param name="builder">The <see cref="ODataModelBuilder">builder</see> used to apply configurations.</param>
17+
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the <paramref name="builder"/>.</param>
18+
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion )
19+
{
20+
var order = builder.EntitySet<Order>( "Orders" ).EntityType;
21+
22+
order.HasKey( o => o.Id );
23+
24+
if ( apiVersion < ApiVersions.V2 )
25+
{
26+
order.Ignore( o => o.EffectiveDate );
27+
}
28+
}
29+
}
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace Microsoft.Examples.Configuration
2+
{
3+
using Microsoft.Web.Http;
4+
using Microsoft.Web.OData.Builder;
5+
using Models;
6+
using System.Web.OData.Builder;
7+
8+
/// <summary>
9+
/// Represents the model configuration for <see cref="Person">people</see>.
10+
/// </summary>
11+
public class PersonModelConfiguration : IModelConfiguration
12+
{
13+
/// <summary>
14+
/// Applies model configurations using the provided builder for the specified API version.
15+
/// </summary>
16+
/// <param name="builder">The <see cref="ODataModelBuilder">builder</see> used to apply configurations.</param>
17+
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the <paramref name="builder"/>.</param>
18+
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion )
19+
{
20+
var person = builder.EntitySet<Person>( "People" ).EntityType;
21+
22+
person.HasKey( p => p.Id );
23+
24+
if ( apiVersion < ApiVersions.V3 )
25+
{
26+
person.Ignore( p => p.Phone );
27+
}
28+
29+
if ( apiVersion < ApiVersions.V2 )
30+
{
31+
person.Ignore( p => p.Email );
32+
}
33+
}
34+
}
35+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
namespace Microsoft.Examples
2+
{
3+
using Microsoft.Web.Http.Description;
4+
using Swashbuckle.Swagger;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Web.Http.Description;
8+
9+
/// <summary>
10+
/// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
11+
/// </summary>
12+
public class ImplicitApiVersionParameter : IOperationFilter
13+
{
14+
/// <summary>
15+
/// Applies the filter to the specified operation using the given context.
16+
/// </summary>
17+
/// <param name="operation">The operation to apply the filter to.</param>
18+
/// <param name="schemaRegistry">The API schema registry.</param>
19+
/// <param name="apiDescription">The API description being filtered.</param>
20+
public void Apply( Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription )
21+
{
22+
var description = apiDescription as VersionedApiDescription;
23+
24+
// if the api explorer did not capture an API version for this operation
25+
// then the action must be API version-neutral; there's nothing to add
26+
if ( description?.ApiVersion == null )
27+
{
28+
return;
29+
}
30+
31+
var parameters = operation.parameters;
32+
33+
if ( parameters == null )
34+
{
35+
operation.parameters = parameters = new List<Parameter>();
36+
}
37+
38+
// note: in most applications, service authors will choose a single, consistent
39+
// approach to how API versioning is applied. this sample uses a:
40+
//
41+
// 1. query string parameter method with the name "api-version"
42+
// 2. url path segement with the route parameter name "api-version"
43+
//
44+
// unless you allow multiple API versioning methods in your application,
45+
// your implementation should be even simpler.
46+
47+
// consider the url path segment parameter first
48+
// note: in OData, the ApiVersionRouteConstraint always has the name "apiVersion" which cannot be changed
49+
var parameter = parameters.SingleOrDefault( p => p.name == "apiVersion" || p.name == "api-version" );
50+
51+
if ( parameter == null )
52+
{
53+
// the only other method in this sample is by query string
54+
parameter = new Parameter()
55+
{
56+
name = "api-version",
57+
required = true,
58+
@in = "query",
59+
type = "string"
60+
};
61+
62+
parameters.Add( parameter );
63+
}
64+
65+
// update the default value with the current API version so that
66+
// the route can be invoked in the "Try It!" feature
67+
parameter.@default = description.ApiVersion.ToString();
68+
parameter.description = "The requested API version";
69+
}
70+
}
71+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Microsoft.Examples.Models
2+
{
3+
using Microsoft.Web.Http;
4+
using System;
5+
6+
static class ApiVersions
7+
{
8+
internal static readonly ApiVersion V1 = new ApiVersion( 1, 0 );
9+
internal static readonly ApiVersion V2 = new ApiVersion( 2, 0 );
10+
internal static readonly ApiVersion V3 = new ApiVersion( 3, 0 );
11+
}
12+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace Microsoft.Examples.Models
2+
{
3+
using System;
4+
using System.ComponentModel.DataAnnotations;
5+
6+
/// <summary>
7+
/// Represents an order.
8+
/// </summary>
9+
public class Order
10+
{
11+
/// <summary>
12+
/// Gets or sets the unique identifier for the order.
13+
/// </summary>
14+
/// <value>The order's unique identifier.</value>
15+
public int Id { get; set; }
16+
17+
/// <summary>
18+
/// Gets or sets the date and time when the order was created.
19+
/// </summary>
20+
/// <value>The order's creation date.</value>
21+
public DateTimeOffset CreatedDate { get; set; } = DateTimeOffset.Now;
22+
23+
/// <summary>
24+
/// Gets or sets the date and time when the order becomes effective.
25+
/// </summary>
26+
/// <value>The order's effective date.</value>
27+
public DateTimeOffset EffectiveDate { get; set; } = DateTimeOffset.Now;
28+
29+
/// <summary>
30+
/// Gets or sets the name of the ordering customer.
31+
/// </summary>
32+
/// <value>The name of the customer that placed the order.</value>
33+
[Required]
34+
public string Customer { get; set; }
35+
}
36+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
namespace Microsoft.Examples.Models
2+
{
3+
using System;
4+
using System.ComponentModel.DataAnnotations;
5+
6+
/// <summary>
7+
/// Represents a person.
8+
/// </summary>
9+
public class Person
10+
{
11+
/// <summary>
12+
/// Gets or sets the unique identifier for a person.
13+
/// </summary>
14+
/// <value>The person's unique identifier.</value>
15+
public int Id { get; set; }
16+
17+
/// <summary>
18+
/// Gets or sets the first name of a person.
19+
/// </summary>
20+
/// <value>The person's first name.</value>
21+
[Required]
22+
[StringLength( 25 )]
23+
public string FirstName { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the last name of a person.
27+
/// </summary>
28+
/// <value>The person's last name.</value>
29+
[Required]
30+
[StringLength( 25 )]
31+
public string LastName { get; set; }
32+
33+
/// <summary>
34+
/// Gets or sets the email address for a person.
35+
/// </summary>
36+
/// <value>The person's email address.</value>
37+
public string Email { get; set; }
38+
39+
/// <summary>
40+
/// Gets or sets the telephone number for a person.
41+
/// </summary>
42+
/// <value>The person's telephone number.</value>
43+
public string Phone { get; set; }
44+
}
45+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyTitle( "SwaggerODataWebApiSample" )]
9+
[assembly: AssemblyDescription( "" )]
10+
[assembly: AssemblyConfiguration( "" )]
11+
[assembly: AssemblyCompany( "" )]
12+
[assembly: AssemblyProduct( "SwaggerODataWebApiSample" )]
13+
[assembly: AssemblyCopyright( "Copyright © 2017" )]
14+
[assembly: AssemblyTrademark( "" )]
15+
[assembly: AssemblyCulture( "" )]
16+
17+
// Setting ComVisible to false makes the types in this assembly not visible
18+
// to COM components. If you need to access a type in this assembly from
19+
// COM, set the ComVisible attribute to true on that type.
20+
[assembly: ComVisible( false )]
21+
22+
// The following GUID is for the ID of the typelib if this project is exposed to COM
23+
[assembly: Guid( "f3986f7b-af76-43d1-a44f-303023a08cd3" )]
24+
25+
// Version information for an assembly consists of the following four values:
26+
//
27+
// Major Version
28+
// Minor Version
29+
// Build Number
30+
// Revision
31+
//
32+
// You can specify all the values or you can default the Revision and Build Numbers
33+
// by using the '*' as shown below:
34+
[assembly: AssemblyVersion( "1.0.0.0" )]
35+
[assembly: AssemblyFileVersion( "1.0.0.0" )]

0 commit comments

Comments
 (0)