Skip to content

Commit 63b9de6

Browse files
author
Chris Martinez
committed
Complete support for exploring all OData routes. Resolves #494.
1 parent 18103ed commit 63b9de6

File tree

19 files changed

+149
-109
lines changed

19 files changed

+149
-109
lines changed

samples/aspnetcore/SwaggerODataSample/V3/ProductsController.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.Linq;
11-
using System.Text;
1211
using static Microsoft.AspNetCore.Http.StatusCodes;
1312

1413
/// <summary>

samples/aspnetcore/SwaggerODataSample/V3/SuppliersController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ public IActionResult Put( [FromODataUri] int key, [FromBody] Supplier update )
147147
[ProducesResponseType( Status404NotFound )]
148148
public IActionResult CreateRefToProducts( [FromODataUri] int key, [FromODataUri] string navigationProperty, [FromBody] Uri link ) => NoContent();
149149

150+
// TODO: OData doesn't seem to currently support this action in ASP.NET Core, but it works in Web API
151+
150152
/// <summary>
151153
/// Unlinks a product from a supplier.
152154
/// </summary>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Microsoft.Examples.Configuration
2+
{
3+
using Microsoft.AspNet.OData.Builder;
4+
using Microsoft.Examples.Models;
5+
using Microsoft.Web.Http;
6+
7+
/// <summary>
8+
/// Represents the model configuration for products.
9+
/// </summary>
10+
public class ProductConfiguration : IModelConfiguration
11+
{
12+
/// <summary>
13+
/// Applies model configurations using the provided builder for the specified API version.
14+
/// </summary>
15+
/// <param name="builder">The <see cref="ODataModelBuilder">builder</see> used to apply configurations.</param>
16+
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the <paramref name="builder"/>.</param>
17+
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion )
18+
{
19+
if ( apiVersion < ApiVersions.V3 )
20+
{
21+
return;
22+
}
23+
24+
var product = builder.EntitySet<Product>( "Products" ).EntityType.HasKey( p => p.Id );
25+
}
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Microsoft.Examples.Configuration
2+
{
3+
using Microsoft.AspNet.OData.Builder;
4+
using Microsoft.Examples.Models;
5+
using Microsoft.Web.Http;
6+
7+
/// <summary>
8+
/// Represents the model configuration for suppliers.
9+
/// </summary>
10+
public class SupplierConfiguration : IModelConfiguration
11+
{
12+
/// <summary>
13+
/// Applies model configurations using the provided builder for the specified API version.
14+
/// </summary>
15+
/// <param name="builder">The <see cref="ODataModelBuilder">builder</see> used to apply configurations.</param>
16+
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the <paramref name="builder"/>.</param>
17+
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion )
18+
{
19+
if ( apiVersion < ApiVersions.V3 )
20+
{
21+
return;
22+
}
23+
24+
var supplier = builder.EntitySet<Supplier>( "Suppliers" ).EntityType.HasKey( p => p.Id );
25+
}
26+
}
27+
}

samples/webapi/SwaggerODataWebApiSample/Startup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public void Configuration( IAppBuilder builder )
4747
new AllConfigurations(),
4848
new PersonModelConfiguration(),
4949
new OrderModelConfiguration(),
50+
new ProductConfiguration(),
51+
new SupplierConfiguration(),
5052
}
5153
};
5254
var models = modelBuilder.GetEdmModels();

samples/webapi/SwaggerODataWebApiSample/V3/ProductsController.cs

Lines changed: 28 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
namespace Microsoft.Examples.V3
22
{
3+
using Microsoft.AspNet.OData;
34
using Microsoft.AspNet.OData.Extensions;
4-
using Microsoft.AspNet.OData.Simulators.Models;
5-
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Examples.Models;
6+
using Microsoft.OData.UriParser;
7+
using Microsoft.Web.Http;
68
using System;
79
using System.Collections.Generic;
810
using System.Linq;
9-
using System.Text;
10-
using static Microsoft.AspNetCore.Http.StatusCodes;
11+
using System.Web.Http;
12+
using System.Web.Http.Description;
13+
using static System.Net.HttpStatusCode;
1114

1215
/// <summary>
1316
/// Represents a RESTful service of products.
@@ -23,8 +26,7 @@ public class ProductsController : ODataController
2326
/// <returns>All available products.</returns>
2427
/// <response code="200">Products successfully retrieved.</response>
2528
[EnableQuery]
26-
[Produces( "application/json" )]
27-
[ProducesResponseType( typeof( ODataValue<IEnumerable<Product>> ), Status200OK )]
29+
[ResponseType( typeof( ODataValue<IEnumerable<Product>> ) )]
2830
public IQueryable<Product> Get() => products;
2931

3032
/// <summary>
@@ -35,24 +37,19 @@ public class ProductsController : ODataController
3537
/// <response code="200">The product was successfully retrieved.</response>
3638
/// <response code="404">The product does not exist.</response>
3739
[EnableQuery]
38-
[Produces( "application/json" )]
39-
[ProducesResponseType( typeof( Product ), Status200OK )]
40-
[ProducesResponseType( Status404NotFound )]
40+
[ResponseType( typeof( Product ) )]
4141
public SingleResult<Product> Get( [FromODataUri] int key ) => SingleResult.Create( products.Where( p => p.Id == key ) );
4242

4343
/// <summary>
4444
/// Creates a new product.
4545
/// </summary>
46-
/// <param name="order">The product to create.</param>
46+
/// <param name="product">The product to create.</param>
4747
/// <returns>The created product.</returns>
4848
/// <response code="201">The product was successfully created.</response>
4949
/// <response code="204">The product was successfully created.</response>
5050
/// <response code="400">The product is invalid.</response>
51-
[Produces( "application/json" )]
52-
[ProducesResponseType( typeof( Product ), Status201Created )]
53-
[ProducesResponseType( Status204NoContent )]
54-
[ProducesResponseType( Status400BadRequest )]
55-
public IActionResult Post( [FromBody] Product product )
51+
[ResponseType( typeof( Product ) )]
52+
public IHttpActionResult Post( [FromBody] Product product )
5653
{
5754
if ( !ModelState.IsValid )
5855
{
@@ -74,12 +71,8 @@ public IActionResult Post( [FromBody] Product product )
7471
/// <response code="204">The product was successfully updated.</response>
7572
/// <response code="400">The product is invalid.</response>
7673
/// <response code="404">The product does not exist.</response>
77-
[Produces( "application/json" )]
78-
[ProducesResponseType( typeof( Product ), Status200OK )]
79-
[ProducesResponseType( Status204NoContent )]
80-
[ProducesResponseType( Status400BadRequest )]
81-
[ProducesResponseType( Status404NotFound )]
82-
public IActionResult Patch( [FromODataUri] int key, Delta<Product> delta )
74+
[ResponseType( typeof( Product ) )]
75+
public IHttpActionResult Patch( [FromODataUri] int key, Delta<Product> delta )
8376
{
8477
if ( !ModelState.IsValid )
8578
{
@@ -103,12 +96,8 @@ public IActionResult Patch( [FromODataUri] int key, Delta<Product> delta )
10396
/// <response code="204">The product was successfully updated.</response>
10497
/// <response code="400">The product is invalid.</response>
10598
/// <response code="404">The product does not exist.</response>
106-
[Produces( "application/json" )]
107-
[ProducesResponseType( typeof( Product ), Status200OK )]
108-
[ProducesResponseType( Status204NoContent )]
109-
[ProducesResponseType( Status400BadRequest )]
110-
[ProducesResponseType( Status404NotFound )]
111-
public IActionResult Put( [FromODataUri] int key, [FromBody] Product update )
99+
[ResponseType( typeof( Product ) )]
100+
public IHttpActionResult Put( [FromODataUri] int key, [FromBody] Product update )
112101
{
113102
if ( !ModelState.IsValid )
114103
{
@@ -124,9 +113,7 @@ public IActionResult Put( [FromODataUri] int key, [FromBody] Product update )
124113
/// <param name="key">The product to delete.</param>
125114
/// <returns>None</returns>
126115
/// <response code="204">The product was successfully deleted.</response>
127-
[ProducesResponseType( Status204NoContent )]
128-
[ProducesResponseType( Status404NotFound )]
129-
public IActionResult Delete( [FromODataUri] int key ) => NoContent();
116+
public IHttpActionResult Delete( [FromODataUri] int key ) => StatusCode( NoContent );
130117

131118
/// <summary>
132119
/// Gets the supplier associated with the product.
@@ -135,9 +122,7 @@ public IActionResult Put( [FromODataUri] int key, [FromBody] Product update )
135122
/// <returns>The supplier</returns>
136123
/// <returns>The requested supplier.</returns>
137124
[EnableQuery]
138-
[Produces( "application/json" )]
139-
[ProducesResponseType( typeof( Supplier ), Status200OK )]
140-
[ProducesResponseType( Status404NotFound )]
125+
[ResponseType( typeof( Supplier ) )]
141126
public SingleResult<Supplier> GetSupplier( [FromODataUri] int key ) => SingleResult.Create( products.Where( p => p.Id == key ).Select( p => p.Supplier ) );
142127

143128
/// <summary>
@@ -146,29 +131,18 @@ public IActionResult Put( [FromODataUri] int key, [FromBody] Product update )
146131
/// <param name="key">The product identifier.</param>
147132
/// <param name="navigationProperty">The supplier to link.</param>
148133
/// <returns>The supplier link.</returns>
149-
[Produces( "application/json" )]
150-
[ProducesResponseType( typeof( ODataId ), Status200OK )]
151-
[ProducesResponseType( Status404NotFound )]
152-
public IActionResult GetRefToSupplier( [FromODataUri] int key, string navigationProperty )
134+
[ResponseType( typeof( ODataId ) )]
135+
public IHttpActionResult GetRefToSupplier( [FromODataUri] int key, string navigationProperty )
153136
{
154-
var routeName = Request.ODataFeature().RouteName;
155-
var builder = new StringBuilder( Request.Scheme );
137+
var segments = Request.ODataProperties().Path.Segments.ToArray();
138+
var entitySet = ( (EntitySetSegment) segments[0] ).EntitySet;
139+
var property = entitySet.NavigationPropertyBindings.Single( p => p.Path.Path == navigationProperty ).NavigationProperty;
156140

157-
builder.Append( Uri.SchemeDelimiter );
158-
builder.Append( Request.Host.HasValue ? Request.Host.Value : Request.Host.ToString() );
141+
segments[segments.Length - 1] = new NavigationPropertySegment( property, entitySet );
159142

160-
if ( !string.IsNullOrEmpty( routeName ) )
161-
{
162-
builder.Append( '/' );
163-
builder.Append( routeName );
164-
}
165-
166-
builder.Append( "/Products/" );
167-
builder.Append( key );
168-
builder.Append( '/' );
169-
builder.Append( navigationProperty );
143+
var relatedKey = new Uri( Url.CreateODataLink( segments ) );
170144

171-
return Ok( new Uri( builder.ToString() ) );
145+
return Ok( relatedKey );
172146
}
173147

174148
/// <summary>
@@ -179,19 +153,15 @@ public IActionResult GetRefToSupplier( [FromODataUri] int key, string navigation
179153
/// <param name="link">The supplier identifier.</param>
180154
/// <returns>None</returns>
181155
[HttpPut]
182-
[ProducesResponseType( Status204NoContent )]
183-
[ProducesResponseType( Status404NotFound )]
184-
public IActionResult CreateRefToSupplier( [FromODataUri] int key, string navigationProperty, [FromBody] Uri link ) => NoContent();
156+
public IHttpActionResult CreateRefToSupplier( [FromODataUri] int key, string navigationProperty, [FromBody] Uri link ) => StatusCode( NoContent );
185157

186158
/// <summary>
187159
/// Unlinks a supplier from a product.
188160
/// </summary>
189161
/// <param name="key">The product identifier.</param>
190162
/// <param name="navigationProperty">The supplier to unlink.</param>
191163
/// <returns>None</returns>
192-
[ProducesResponseType( Status204NoContent )]
193-
[ProducesResponseType( Status404NotFound )]
194-
public IActionResult DeleteRefToSupplier( [FromODataUri] int key, string navigationProperty ) => NoContent();
164+
public IHttpActionResult DeleteRefToSupplier( [FromODataUri] int key, string navigationProperty ) => StatusCode( NoContent );
195165

196166
static Product NewProduct( int id ) =>
197167
new Product()

samples/webapi/SwaggerODataWebApiSample/V3/SuppliersController.cs

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
namespace Microsoft.Examples.V3
22
{
33
using Microsoft.AspNet.OData;
4-
using Microsoft.AspNet.OData.Simulators.Models;
5-
using Microsoft.AspNetCore.Mvc;
64
using Microsoft.Examples.Models;
75
using Microsoft.Web.Http;
86
using System;
97
using System.Collections.Generic;
108
using System.Linq;
11-
using static Microsoft.AspNetCore.Http.StatusCodes;
9+
using System.Web.Http;
10+
using System.Web.Http.Description;
11+
using static System.Net.HttpStatusCode;
1212

1313
/// <summary>
1414
/// Represents a RESTful service of suppliers.
@@ -24,8 +24,7 @@ public class SuppliersController : ODataController
2424
/// <returns>All available suppliers.</returns>
2525
/// <response code="200">Products successfully retrieved.</response>
2626
[EnableQuery]
27-
[Produces( "application/json" )]
28-
[ProducesResponseType( typeof( ODataValue<IEnumerable<Supplier>> ), Status200OK )]
27+
[ResponseType( typeof( ODataValue<IEnumerable<Supplier>> ) )]
2928
public IQueryable<Supplier> Get() => suppliers;
3029

3130
/// <summary>
@@ -36,24 +35,19 @@ public class SuppliersController : ODataController
3635
/// <response code="200">The supplier was successfully retrieved.</response>
3736
/// <response code="404">The supplier does not exist.</response>
3837
[EnableQuery]
39-
[Produces( "application/json" )]
40-
[ProducesResponseType( typeof( Supplier ), Status200OK )]
41-
[ProducesResponseType( Status404NotFound )]
38+
[ResponseType( typeof( Supplier ) )]
4239
public SingleResult<Supplier> Get( [FromODataUri] int key ) => SingleResult.Create( suppliers.Where( p => p.Id == key ) );
4340

4441
/// <summary>
4542
/// Creates a new supplier.
4643
/// </summary>
47-
/// <param name="order">The supplier to create.</param>
44+
/// <param name="supplier">The supplier to create.</param>
4845
/// <returns>The created supplier.</returns>
4946
/// <response code="201">The supplier was successfully created.</response>
5047
/// <response code="204">The supplier was successfully created.</response>
5148
/// <response code="400">The supplier is invalid.</response>
52-
[Produces( "application/json" )]
53-
[ProducesResponseType( typeof( Supplier ), Status201Created )]
54-
[ProducesResponseType( Status204NoContent )]
55-
[ProducesResponseType( Status400BadRequest )]
56-
public IActionResult Post( [FromBody]Supplier supplier )
49+
[ResponseType( typeof( Supplier ) )]
50+
public IHttpActionResult Post( [FromBody] Supplier supplier )
5751
{
5852
if ( !ModelState.IsValid )
5953
{
@@ -75,12 +69,8 @@ public IActionResult Post( [FromBody]Supplier supplier )
7569
/// <response code="204">The supplier was successfully updated.</response>
7670
/// <response code="400">The supplier is invalid.</response>
7771
/// <response code="404">The supplier does not exist.</response>
78-
[Produces( "application/json" )]
79-
[ProducesResponseType( typeof( Supplier ), Status200OK )]
80-
[ProducesResponseType( Status204NoContent )]
81-
[ProducesResponseType( Status400BadRequest )]
82-
[ProducesResponseType( Status404NotFound )]
83-
public IActionResult Patch( [FromODataUri] int key, Delta<Supplier> delta )
72+
[ResponseType( typeof( Supplier ) )]
73+
public IHttpActionResult Patch( [FromODataUri] int key, Delta<Supplier> delta )
8474
{
8575
if ( !ModelState.IsValid )
8676
{
@@ -104,12 +94,8 @@ public IActionResult Patch( [FromODataUri] int key, Delta<Supplier> delta )
10494
/// <response code="204">The supplier was successfully updated.</response>
10595
/// <response code="400">The supplier is invalid.</response>
10696
/// <response code="404">The supplier does not exist.</response>
107-
[Produces( "application/json" )]
108-
[ProducesResponseType( typeof( Supplier ), Status200OK )]
109-
[ProducesResponseType( Status204NoContent )]
110-
[ProducesResponseType( Status400BadRequest )]
111-
[ProducesResponseType( Status404NotFound )]
112-
public IActionResult Put( [FromODataUri] int key, [FromBody]Supplier update )
97+
[ResponseType( typeof( Supplier ) )]
98+
public IHttpActionResult Put( [FromODataUri] int key, [FromBody] Supplier update )
11399
{
114100
if ( !ModelState.IsValid )
115101
{
@@ -125,9 +111,7 @@ public IActionResult Put( [FromODataUri] int key, [FromBody]Supplier update )
125111
/// <param name="key">The supplier to delete.</param>
126112
/// <returns>None</returns>
127113
/// <response code="204">The supplier was successfully deleted.</response>
128-
[ProducesResponseType( Status204NoContent )]
129-
[ProducesResponseType( Status404NotFound )]
130-
public IActionResult Delete( [FromODataUri] int key ) => NoContent();
114+
public IHttpActionResult Delete( [FromODataUri] int key ) => StatusCode( NoContent );
131115

132116
/// <summary>
133117
/// Gets the products associated with the supplier.
@@ -145,9 +129,7 @@ public IActionResult Put( [FromODataUri] int key, [FromBody]Supplier update )
145129
/// <param name="link">The product identifier.</param>
146130
/// <returns>None</returns>
147131
[HttpPost]
148-
[ProducesResponseType( Status204NoContent )]
149-
[ProducesResponseType( Status404NotFound )]
150-
public IActionResult CreateRefToProducts( [FromODataUri] int key, string navigationProperty, [FromBody] Uri link ) => NoContent();
132+
public IHttpActionResult CreateRefToProducts( [FromODataUri] int key, string navigationProperty, [FromBody] Uri link ) => StatusCode( NoContent );
151133

152134
/// <summary>
153135
/// Unlinks a product from a supplier.
@@ -156,16 +138,14 @@ public IActionResult Put( [FromODataUri] int key, [FromBody]Supplier update )
156138
/// <param name="relatedKey">The related product identifier.</param>
157139
/// <param name="navigationProperty">The product to unlink.</param>
158140
/// <returns>None</returns>
159-
[ProducesResponseType( Status204NoContent )]
160-
[ProducesResponseType( Status404NotFound )]
161-
public IActionResult DeleteRefToProducts( [FromODataUri] int key, [FromODataUri] string relatedKey, string navigationProperty ) => NoContent();
141+
public IHttpActionResult DeleteRefToProducts( [FromODataUri] int key, [FromODataUri] string relatedKey, string navigationProperty ) => StatusCode( NoContent );
162142

163143
private static Supplier NewSupplier( int id ) =>
164144
new Supplier()
165145
{
166146
Id = id,
167147
Name = "Supplier " + id.ToString(),
168-
Products =
148+
Products = new List<Product>()
169149
{
170150
new Product()
171151
{

src/Common.OData.ApiExplorer/AspNet.OData/Routing/ODataRouteBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ static bool IsKey( IReadOnlyList<IEdmStructuralProperty> keys, ApiParameterDescr
671671
}
672672
}
673673

674-
return parameter.Name.StartsWith( ODataRouteConstants.Key, OrdinalIgnoreCase );
674+
return parameter.Name.StartsWith( Key, OrdinalIgnoreCase );
675675
}
676676

677677
static bool IsFunctionParameter( IEdmOperation operation, ApiParameterDescription parameter )

0 commit comments

Comments
 (0)