Skip to content

Commit fd7ae2a

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Adds the baseline support to explore OData query options
1 parent 9bdde9d commit fd7ae2a

File tree

94 files changed

+7641
-221
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+7641
-221
lines changed

samples/aspnetcore/SwaggerODataSample/Configuration/PersonModelConfiguration.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ public class PersonModelConfiguration : IModelConfiguration
1717
/// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the <paramref name="builder"/>.</param>
1818
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion )
1919
{
20-
var person = builder.EntitySet<Person>( "People" ).EntityType.HasKey( p => p.Id );
20+
var person = builder.EntitySet<Person>( "People" ).EntityType;
21+
22+
person.HasKey( p => p.Id );
23+
person.Select().OrderBy( "firstName", "lastName" );
2124

2225
if ( apiVersion < ApiVersions.V3 )
2326
{
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
namespace Microsoft.Examples
2+
{
3+
using System;
4+
using System.Collections;
5+
6+
/// <summary>
7+
/// Provides extension methods for the <see cref="IEnumerable"/> interface.
8+
/// </summary>
9+
public static class EnumerableExtensions
10+
{
11+
/// <summary>
12+
/// Returns the first element from the specified sequence.
13+
/// </summary>
14+
/// <param name="enumerable">The <see cref="IEnumerable">sequence</see> to take an element from.</param>
15+
/// <returns>The first element in the sequence or <c>null</c>.</returns>
16+
public static object FirstOrDefault( this IEnumerable enumerable )
17+
{
18+
var iterator = enumerable.GetEnumerator();
19+
20+
try
21+
{
22+
if ( iterator.MoveNext() )
23+
{
24+
return iterator.Current;
25+
}
26+
}
27+
finally
28+
{
29+
( iterator as IDisposable )?.Dispose();
30+
}
31+
32+
return default( object );
33+
}
34+
35+
/// <summary>
36+
/// Returns a single element from the specified sequence.
37+
/// </summary>
38+
/// <param name="enumerable">The <see cref="IEnumerable">sequence</see> to take an element from.</param>
39+
/// <returns>The single element in the sequence or <c>null</c>.</returns>
40+
public static object SingleOrDefault( this IEnumerable enumerable )
41+
{
42+
var iterator = enumerable.GetEnumerator();
43+
var result = default( object );
44+
45+
try
46+
{
47+
if ( iterator.MoveNext() )
48+
{
49+
result = iterator.Current;
50+
51+
if ( iterator.MoveNext() )
52+
{
53+
throw new InvalidOperationException( "The sequence contains more than one element." );
54+
}
55+
}
56+
}
57+
finally
58+
{
59+
( iterator as IDisposable )?.Dispose();
60+
}
61+
62+
return result;
63+
}
64+
}
65+
}

samples/aspnetcore/SwaggerODataSample/Models/Order.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
namespace Microsoft.Examples.Models
22
{
3+
using Microsoft.AspNet.OData.Query;
34
using System;
45
using System.ComponentModel.DataAnnotations;
56

67
/// <summary>
78
/// Represents an order.
89
/// </summary>
10+
[Select]
11+
[Select( "effectiveDate", SelectType = SelectExpandType.Disabled )]
912
public class Order
1013
{
1114
/// <summary>

samples/aspnetcore/SwaggerODataSample/Startup.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
{
33
using Microsoft.AspNet.OData.Builder;
44
using Microsoft.AspNet.OData.Extensions;
5+
using Microsoft.AspNet.OData.Query;
56
using Microsoft.AspNetCore.Builder;
67
using Microsoft.AspNetCore.Hosting;
78
using Microsoft.AspNetCore.Mvc.ApiExplorer;
9+
using Microsoft.Examples.Models;
810
using Microsoft.Extensions.DependencyInjection;
911
using Microsoft.Extensions.PlatformAbstractions;
1012
using Swashbuckle.AspNetCore.Swagger;
1113
using System.IO;
1214
using System.Reflection;
15+
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
1316

1417
/// <summary>
1518
/// Represents the startup process for the application.
@@ -35,6 +38,13 @@ public void ConfigureServices( IServiceCollection services )
3538
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
3639
// can also be used to control the format of the API version in route templates
3740
options.SubstituteApiVersionInUrl = true;
41+
42+
// configure query options (which cannot otherwise be configured by OData conventions)
43+
options.QueryOptions.Controller<V2.PeopleController>()
44+
.Action( c => c.Get( default( ODataQueryOptions<Person> ) ) ).Allow( Skip | Count ).AllowTop( 100 );
45+
46+
options.QueryOptions.Controller<V3.PeopleController>()
47+
.Action( c => c.Get( default( ODataQueryOptions<Person> ) ) ).Allow( Skip | Count ).AllowTop( 100 );
3848
} );
3949
services.AddSwaggerGen(
4050
options =>

samples/aspnetcore/SwaggerODataSample/V1/OrdersController.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using Microsoft.AspNet.OData.Routing;
55
using Microsoft.AspNetCore.Mvc;
66
using Microsoft.Examples.Models;
7+
using System.Linq;
8+
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
79
using static Microsoft.AspNetCore.Http.StatusCodes;
810

911
/// <summary>
@@ -21,11 +23,12 @@ public class OrdersController : ODataController
2123
/// <returns>The requested order.</returns>
2224
/// <response code="200">The order was successfully retrieved.</response>
2325
/// <response code="404">The order does not exist.</response>
26+
[ODataRoute( "({key})" )]
2427
[Produces( "application/json" )]
2528
[ProducesResponseType( typeof( Order ), Status200OK )]
2629
[ProducesResponseType( Status404NotFound )]
27-
[ODataRoute( "({key})" )]
28-
public IActionResult Get( int key ) => Ok( new Order() { Id = key, Customer = "John Doe" } );
30+
[EnableQuery( AllowedQueryOptions = Select )]
31+
public SingleResult<Order> Get( int key ) => SingleResult.Create( new[] { new Order() { Id = key, Customer = "John Doe" } }.AsQueryable() );
2932

3033
/// <summary>
3134
/// Places a new order.
@@ -34,10 +37,10 @@ public class OrdersController : ODataController
3437
/// <returns>The created order.</returns>
3538
/// <response code="201">The order was successfully placed.</response>
3639
/// <response code="400">The order is invalid.</response>
40+
[ODataRoute]
3741
[MapToApiVersion( "1.0" )]
3842
[ProducesResponseType( typeof( Order ), Status201Created )]
3943
[ProducesResponseType( Status400BadRequest )]
40-
[ODataRoute]
4144
public IActionResult Post( [FromBody] Order order )
4245
{
4346
if ( !ModelState.IsValid )
@@ -57,11 +60,12 @@ public IActionResult Post( [FromBody] Order order )
5760
/// <response code="200">The order was successfully retrieved.</response>
5861
/// <response code="404">The no orders exist.</response>
5962
[HttpGet]
63+
[ODataRoute( "MostExpensive" )]
6064
[MapToApiVersion( "1.0" )]
6165
[Produces( "application/json" )]
6266
[ProducesResponseType( typeof( Order ), Status200OK )]
6367
[ProducesResponseType( Status404NotFound )]
64-
[ODataRoute( "MostExpensive" )]
65-
public IActionResult MostExpensive() => Ok( new Order() { Id = 42, Customer = "Bill Mei" } );
68+
[EnableQuery( AllowedQueryOptions = Select )]
69+
public SingleResult<Order> MostExpensive() => SingleResult.Create( new[] { new Order() { Id = 42, Customer = "Bill Mei" } }.AsQueryable() );
6670
}
6771
}
Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
namespace Microsoft.Examples.V1
22
{
33
using Microsoft.AspNet.OData;
4+
using Microsoft.AspNet.OData.Query;
45
using Microsoft.AspNetCore.Mvc;
56
using Microsoft.Examples.Models;
7+
using System.Linq;
68
using static Microsoft.AspNetCore.Http.StatusCodes;
79

810
/// <summary>
@@ -16,19 +18,34 @@ public class PeopleController : ODataController
1618
/// Gets a single person.
1719
/// </summary>
1820
/// <param name="key">The requested person identifier.</param>
21+
/// <param name="options">The current OData query options.</param>
1922
/// <returns>The requested person.</returns>
2023
/// <response code="200">The person was successfully retrieved.</response>
2124
/// <response code="404">The person does not exist.</response>
2225
[HttpGet]
2326
[Produces( "application/json" )]
2427
[ProducesResponseType( typeof( Person ), Status200OK )]
2528
[ProducesResponseType( Status404NotFound )]
26-
public IActionResult Get( int key ) =>
27-
Ok( new Person()
29+
public IActionResult Get( int key, ODataQueryOptions<Person> options )
30+
{
31+
var people = new[]
2832
{
29-
Id = key,
30-
FirstName = "John",
31-
LastName = "Doe",
32-
} );
33+
new Person()
34+
{
35+
Id = key,
36+
FirstName = "John",
37+
LastName = "Doe",
38+
}
39+
};
40+
41+
var person = options.ApplyTo( people.AsQueryable() ).SingleOrDefault();
42+
43+
if ( person == null )
44+
{
45+
return NotFound();
46+
}
47+
48+
return Ok( person );
49+
}
3350
}
3451
}

samples/aspnetcore/SwaggerODataSample/V2/OrdersController.cs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using Microsoft.Examples.Models;
77
using System;
88
using System.Collections.Generic;
9+
using System.Linq;
10+
using static Microsoft.AspNet.OData.Query.AllowedQueryOptions;
911
using static Microsoft.AspNetCore.Http.StatusCodes;
1012

1113
/// <summary>
@@ -20,19 +22,20 @@ public class OrdersController : ODataController
2022
/// </summary>
2123
/// <returns>All available orders.</returns>
2224
/// <response code="200">The successfully retrieved orders.</response>
25+
[ODataRoute]
2326
[Produces( "application/json" )]
2427
[ProducesResponseType( typeof( ODataValue<IEnumerable<Order>> ), Status200OK )]
25-
[ODataRoute]
26-
public IActionResult Get()
28+
[EnableQuery( MaxTop = 100, AllowedQueryOptions = Select | Top | Skip | Count )]
29+
public IQueryable<Order> Get()
2730
{
2831
var orders = new[]
2932
{
3033
new Order(){ Id = 1, Customer = "John Doe" },
31-
new Order(){ Id = 2, Customer = "Bob Smith" },
32-
new Order(){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTime.UtcNow.AddDays( 7d ) },
34+
new Order(){ Id = 2, Customer = "John Doe" },
35+
new Order(){ Id = 3, Customer = "Jane Doe", EffectiveDate = DateTime.UtcNow.AddDays( 7d ) }
3336
};
3437

35-
return Ok( orders );
38+
return orders.AsQueryable();
3639
}
3740

3841
/// <summary>
@@ -42,11 +45,12 @@ public IActionResult Get()
4245
/// <returns>The requested order.</returns>
4346
/// <response code="200">The order was successfully retrieved.</response>
4447
/// <response code="404">The order does not exist.</response>
48+
[ODataRoute( "({key})" )]
4549
[Produces( "application/json" )]
4650
[ProducesResponseType( typeof( Order ), Status200OK )]
4751
[ProducesResponseType( Status404NotFound )]
48-
[ODataRoute( "({key})" )]
49-
public IActionResult Get( int key ) => Ok( new Order() { Id = key, Customer = "John Doe" } );
52+
[EnableQuery( AllowedQueryOptions = Select )]
53+
public SingleResult<Order> Get( int key ) => SingleResult.Create( new[] { new Order() { Id = key, Customer = "John Doe" } }.AsQueryable() );
5054

5155
/// <summary>
5256
/// Places a new order.
@@ -55,9 +59,9 @@ public IActionResult Get()
5559
/// <returns>The created order.</returns>
5660
/// <response code="201">The order was successfully placed.</response>
5761
/// <response code="400">The order is invalid.</response>
62+
[ODataRoute]
5863
[ProducesResponseType( typeof( Order ), Status201Created )]
5964
[ProducesResponseType( Status400BadRequest )]
60-
[ODataRoute]
6165
public IActionResult Post( [FromBody] Order order )
6266
{
6367
if ( !ModelState.IsValid )
@@ -79,10 +83,10 @@ public IActionResult Post( [FromBody] Order order )
7983
/// <response code="204">The order was successfully updated.</response>
8084
/// <response code="400">The order is invalid.</response>
8185
/// <response code="404">The order does not exist.</response>
86+
[ODataRoute( "({key})" )]
8287
[ProducesResponseType( typeof( Order ), Status204NoContent )]
8388
[ProducesResponseType( Status400BadRequest )]
8489
[ProducesResponseType( Status404NotFound )]
85-
[ODataRoute( "({key})" )]
8690
public IActionResult Patch( int key, Delta<Order> delta )
8791
{
8892
if ( !ModelState.IsValid )
@@ -104,11 +108,12 @@ public IActionResult Patch( int key, Delta<Order> delta )
104108
/// <response code="200">The order was successfully retrieved.</response>
105109
/// <response code="404">The no orders exist.</response>
106110
[HttpGet]
111+
[ODataRoute( nameof( MostExpensive ) )]
107112
[Produces( "application/json" )]
108113
[ProducesResponseType( typeof( Order ), Status200OK )]
109114
[ProducesResponseType( Status404NotFound )]
110-
[ODataRoute( nameof( MostExpensive ) )]
111-
public IActionResult MostExpensive() => Ok( new Order() { Id = 42, Customer = "Bill Mei" } );
115+
[EnableQuery( AllowedQueryOptions = Select )]
116+
public SingleResult<Order> MostExpensive() => SingleResult.Create( new[] { new Order() { Id = 42, Customer = "Bill Mei" } }.AsQueryable() );
112117

113118
/// <summary>
114119
/// Rates an order.
@@ -120,10 +125,10 @@ public IActionResult Patch( int key, Delta<Order> delta )
120125
/// <response code="400">The parameters are invalid.</response>
121126
/// <response code="404">The order does not exist.</response>
122127
[HttpPost]
128+
[ODataRoute( "({key})/Rate" )]
123129
[ProducesResponseType( Status200OK )]
124130
[ProducesResponseType( Status400BadRequest )]
125131
[ProducesResponseType( Status404NotFound )]
126-
[ODataRoute( "({key})/Rate" )]
127132
public IActionResult Rate( int key, ODataActionParameters parameters )
128133
{
129134
if ( !ModelState.IsValid )

0 commit comments

Comments
 (0)