Skip to content

Commit fa81478

Browse files
support IAsyncenumerable (#988)
1 parent b36f3d3 commit fa81478

12 files changed

+595
-72
lines changed

src/Microsoft.AspNetCore.OData/Common/TypeHelper.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ Type collectionInterface
240240
.Union(new[] { clrType })
241241
.FirstOrDefault(
242242
t => t.IsGenericType
243-
&& t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
243+
&& (t.GetGenericTypeDefinition() == typeof(IEnumerable<>)
244+
|| t.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>)));
244245

245246
if (collectionInterface != null)
246247
{

src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,20 @@ private IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollectio
222222
}
223223

224224
Type enumerableOfT = ExtractGenericInterface(clrType, typeof(IEnumerable<>));
225-
if (enumerableOfT != null)
225+
Type asyncEnumerableOfT = ExtractGenericInterface(clrType, typeof(IAsyncEnumerable<>));
226+
227+
if (enumerableOfT != null || asyncEnumerableOfT != null)
226228
{
227-
Type elementClrType = enumerableOfT.GetGenericArguments()[0];
229+
Type elementClrType = null;
230+
231+
if (enumerableOfT != null)
232+
{
233+
elementClrType = enumerableOfT.GetGenericArguments()[0];
234+
}
235+
else
236+
{
237+
elementClrType = asyncEnumerableOfT.GetGenericArguments()[0];
238+
}
228239

229240
// IEnumerable<SelectExpandWrapper<T>> is a collection of T.
230241
if (elementClrType.IsSelectExpandWrapper(out entityType))

src/Microsoft.AspNetCore.OData/Formatter/ResourceSetContext.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//------------------------------------------------------------------------------
77

88
using System.Collections;
9+
using System.Collections.Generic;
910
using Microsoft.AspNetCore.Http;
1011
using Microsoft.AspNetCore.OData.Extensions;
1112
using Microsoft.AspNetCore.OData.Formatter.Serialization;
@@ -55,11 +56,27 @@ internal static ResourceSetContext Create(ODataSerializerContext writeContext, I
5556
{
5657
Request = writeContext.Request,
5758
EntitySetBase = writeContext.NavigationSource as IEdmEntitySetBase,
58-
// Url = writeContext.Url,
5959
ResourceSetInstance = resourceSetInstance
6060
};
6161

6262
return resourceSetContext;
6363
}
64+
65+
/// <summary>
66+
/// Create a <see cref="ResourceSetContext"/> from an <see cref="ODataSerializerContext"/> and <see cref="IAsyncEnumerable{T}"/>.
67+
/// </summary>
68+
/// <param name="writeContext">The serializer context.</param>
69+
/// <param name="resourceSetInstance">The instance representing the resourceSet being written.</param>
70+
/// <returns>A new <see cref="ResourceSetContext"/>.</returns>
71+
/// <remarks>This signature uses types that are AspNetCore-specific.</remarks>
72+
internal static ResourceSetContext Create(ODataSerializerContext writeContext, IAsyncEnumerable<object> resourceSetInstance)
73+
{
74+
return new ResourceSetContext
75+
{
76+
Request = writeContext.Request,
77+
EntitySetBase = writeContext.NavigationSource as IEdmEntitySetBase,
78+
ResourceSetInstance = resourceSetInstance
79+
};
80+
}
6481
}
6582
}

src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataResourceSetSerializer.cs

+238-68
Large diffs are not rendered by default.

src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml

+27
Original file line numberDiff line numberDiff line change
@@ -4309,6 +4309,15 @@
43094309
<returns>A new <see cref="T:Microsoft.AspNetCore.OData.Formatter.ResourceSetContext"/>.</returns>
43104310
<remarks>This signature uses types that are AspNetCore-specific.</remarks>
43114311
</member>
4312+
<member name="M:Microsoft.AspNetCore.OData.Formatter.ResourceSetContext.Create(Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext,System.Collections.Generic.IAsyncEnumerable{System.Object})">
4313+
<summary>
4314+
Create a <see cref="T:Microsoft.AspNetCore.OData.Formatter.ResourceSetContext"/> from an <see cref="T:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext"/> and <see cref="T:System.Collections.Generic.IAsyncEnumerable`1"/>.
4315+
</summary>
4316+
<param name="writeContext">The serializer context.</param>
4317+
<param name="resourceSetInstance">The instance representing the resourceSet being written.</param>
4318+
<returns>A new <see cref="T:Microsoft.AspNetCore.OData.Formatter.ResourceSetContext"/>.</returns>
4319+
<remarks>This signature uses types that are AspNetCore-specific.</remarks>
4320+
</member>
43124321
<member name="T:Microsoft.AspNetCore.OData.Formatter.Serialization.IODataEdmTypeSerializer">
43134322
<summary>
43144323
Represents an <see cref="T:Microsoft.AspNetCore.OData.Formatter.Serialization.IODataSerializer"/> that serializes instances of objects backed by an <see cref="T:Microsoft.OData.Edm.IEdmType"/>.
@@ -4914,6 +4923,15 @@
49144923
<param name="writeContext">The serializer context.</param>
49154924
<returns>The created <see cref="T:Microsoft.OData.ODataResourceSet"/> object.</returns>
49164925
</member>
4926+
<member name="M:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.CreateResourceSet(System.Collections.Generic.IAsyncEnumerable{System.Object},Microsoft.OData.Edm.IEdmCollectionTypeReference,Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext)">
4927+
<summary>
4928+
Create the <see cref="T:Microsoft.OData.ODataResourceSet"/> to be written for the given resourceSet instance.
4929+
</summary>
4930+
<param name="resourceSetInstance">The instance representing the resourceSet being written.</param>
4931+
<param name="resourceSetType">The EDM type of the resourceSet being written.</param>
4932+
<param name="writeContext">The serializer context.</param>
4933+
<returns>The created <see cref="T:Microsoft.OData.ODataResourceSet"/> object.</returns>
4934+
</member>
49174935
<member name="M:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.GetNextLinkGenerator(Microsoft.OData.ODataResourceSetBase,System.Collections.IEnumerable,Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext)">
49184936
<summary>
49194937
Creates a function that takes in an object and generates nextlink uri.
@@ -4923,6 +4941,15 @@
49234941
<param name="writeContext">The serializer context.</param>
49244942
<returns>The function that generates the NextLink from an object.</returns>
49254943
</member>
4944+
<member name="M:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.GetNextLinkGenerator(Microsoft.OData.ODataResourceSetBase,System.Collections.Generic.IAsyncEnumerable{System.Object},Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext)">
4945+
<summary>
4946+
Creates a function that takes in an object and generates a nextlink uri.
4947+
</summary>
4948+
<param name="resourceSet">The resource set describing a collection of structured objects.</param>
4949+
<param name="resourceSetInstance">The instance representing the resourceSet being written.</param>
4950+
<param name="writeContext">The serializer context.</param>
4951+
<returns>The function that generates the NextLink from an object.</returns>
4952+
</member>
49264953
<member name="M:Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.CreateODataOperation(Microsoft.OData.Edm.IEdmOperation,Microsoft.AspNetCore.OData.Formatter.ResourceSetContext,Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext)">
49274954
<summary>
49284955
Creates an <see cref="T:Microsoft.OData.ODataOperation" /> to be written for the given operation and the resourceSet instance.

src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1842,6 +1842,7 @@ virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializ
18421842
virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.CreateUntypedPropertyValue(Microsoft.OData.Edm.IEdmStructuralProperty structuralProperty, Microsoft.AspNetCore.OData.Formatter.ResourceContext resourceContext, out Microsoft.OData.Edm.IEdmTypeReference actualType) -> object
18431843
virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteDeltaObjectInlineAsync(object graph, Microsoft.OData.Edm.IEdmTypeReference expectedType, Microsoft.OData.ODataWriter writer, Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext writeContext) -> System.Threading.Tasks.Task
18441844
virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.CreateODataOperation(Microsoft.OData.Edm.IEdmOperation operation, Microsoft.AspNetCore.OData.Formatter.ResourceSetContext resourceSetContext, Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext writeContext) -> Microsoft.OData.ODataOperation
1845+
virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.CreateResourceSet(System.Collections.Generic.IAsyncEnumerable<object> resourceSetInstance, Microsoft.OData.Edm.IEdmCollectionTypeReference resourceSetType, Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext writeContext) -> Microsoft.OData.ODataResourceSet
18451846
virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.CreateResourceSet(System.Collections.IEnumerable resourceSetInstance, Microsoft.OData.Edm.IEdmCollectionTypeReference resourceSetType, Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext writeContext) -> Microsoft.OData.ODataResourceSet
18461847
virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteEnumItemAsync(object enumValue, Microsoft.OData.Edm.IEdmTypeReference enumType, Microsoft.OData.Edm.IEdmTypeReference parentSetType, Microsoft.OData.ODataWriter writer, Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext writeContext) -> System.Threading.Tasks.Task
18471848
virtual Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSetSerializer.WritePrimitiveItemAsync(object primitiveValue, Microsoft.OData.Edm.IEdmTypeReference primitiveType, Microsoft.OData.Edm.IEdmTypeReference parentSetType, Microsoft.OData.ODataWriter writer, Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext writeContext) -> System.Threading.Tasks.Task
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//-----------------------------------------------------------------------------
2+
// <copyright file="IAsyncEnumerableController.cs" company=".NET Foundation">
3+
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
4+
// See License.txt in the project root for license information.
5+
// </copyright>
6+
//------------------------------------------------------------------------------
7+
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Threading.Tasks;
11+
using Microsoft.AspNetCore.Mvc;
12+
using Microsoft.AspNetCore.OData.Query;
13+
using Microsoft.AspNetCore.OData.Routing.Controllers;
14+
15+
namespace Microsoft.AspNetCore.OData.E2E.Tests.IAsyncEnumerableTests
16+
{
17+
public class CustomersController : ODataController
18+
{
19+
private readonly IAsyncEnumerableContext _context;
20+
21+
public CustomersController(IAsyncEnumerableContext context)
22+
{
23+
context.Database.EnsureCreated();
24+
_context = context;
25+
26+
if (!_context.Customers.Any())
27+
{
28+
Generate();
29+
}
30+
}
31+
32+
[EnableQuery]
33+
[HttpGet("v1/Customers")]
34+
public IAsyncEnumerable<Customer> CustomersData()
35+
{
36+
IAsyncEnumerable<Customer> customers = CreateCollectionAsync<Customer>();
37+
38+
return customers;
39+
}
40+
41+
[EnableQuery]
42+
[HttpGet("odata/Customers")]
43+
public IAsyncEnumerable<Customer> Get()
44+
{
45+
return _context.Customers.AsAsyncEnumerable();
46+
}
47+
48+
public async IAsyncEnumerable<Customer> CreateCollectionAsync<T>()
49+
{
50+
await Task.Delay(5);
51+
// Yield the items one by one asynchronously
52+
yield return new Customer
53+
{
54+
Id = 1,
55+
Name = "Customer1",
56+
Orders = new List<Order> {
57+
new Order {
58+
Name = "Order1",
59+
Price = 25
60+
},
61+
new Order {
62+
Name = "Order2",
63+
Price = 75
64+
}
65+
},
66+
Address = new Address
67+
{
68+
Name = "City1",
69+
Street = "Street1"
70+
}
71+
};
72+
73+
await Task.Delay(5);
74+
75+
yield return new Customer
76+
{
77+
Id = 2,
78+
Name = "Customer2",
79+
Orders = new List<Order> {
80+
new Order {
81+
Name = "Order1",
82+
Price = 35
83+
},
84+
new Order {
85+
Name = "Order2",
86+
Price = 65
87+
}
88+
},
89+
Address = new Address
90+
{
91+
Name = "City2",
92+
Street = "Street2"
93+
}
94+
};
95+
}
96+
97+
public void Generate()
98+
{
99+
for (int i = 1; i <= 3; i++)
100+
{
101+
var customer = new Customer
102+
{
103+
Name = "Customer" + (i + 1) % 2,
104+
Orders =
105+
new List<Order> {
106+
new Order {
107+
Name = "Order" + 2*i,
108+
Price = i * 25
109+
},
110+
new Order {
111+
Name = "Order" + 2*i+1,
112+
Price = i * 75
113+
}
114+
},
115+
Address = new Address
116+
{
117+
Name = "City" + i % 2,
118+
Street = "Street" + i % 2,
119+
}
120+
};
121+
122+
_context.Customers.Add(customer);
123+
}
124+
125+
_context.SaveChanges();
126+
}
127+
}
128+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//-----------------------------------------------------------------------------
2+
// <copyright file="IAsyncEnumerableDataModel.cs" company=".NET Foundation">
3+
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
4+
// See License.txt in the project root for license information.
5+
// </copyright>
6+
//------------------------------------------------------------------------------
7+
8+
using System.Collections.Generic;
9+
using Microsoft.EntityFrameworkCore;
10+
11+
namespace Microsoft.AspNetCore.OData.E2E.Tests.IAsyncEnumerableTests
12+
{
13+
public class IAsyncEnumerableContext : DbContext
14+
{
15+
public IAsyncEnumerableContext(DbContextOptions<IAsyncEnumerableContext> options)
16+
: base(options)
17+
{
18+
}
19+
20+
public DbSet<Customer> Customers { get; set; }
21+
22+
protected override void OnModelCreating(ModelBuilder modelBuilder)
23+
{
24+
modelBuilder.Entity<Customer>().OwnsOne(c => c.Address).WithOwner();
25+
}
26+
}
27+
28+
public class Customer
29+
{
30+
public int Id { get; set; }
31+
32+
public string Name { get; set; }
33+
34+
public Address Address { get; set; }
35+
36+
public IList<Order> Orders { get; set; }
37+
}
38+
39+
public class Order
40+
{
41+
public int Id { get; set; }
42+
43+
public string Name { get; set; }
44+
45+
public int Price { get; set; }
46+
}
47+
48+
public class Address
49+
{
50+
public string Name { get; set; }
51+
52+
public string Street { get; set; }
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//-----------------------------------------------------------------------------
2+
// <copyright file="IAsyncEnumerableEdmModel.cs" company=".NET Foundation">
3+
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
4+
// See License.txt in the project root for license information.
5+
// </copyright>
6+
//------------------------------------------------------------------------------
7+
8+
using Microsoft.OData.Edm;
9+
using Microsoft.OData.ModelBuilder;
10+
11+
namespace Microsoft.AspNetCore.OData.E2E.Tests.IAsyncEnumerableTests
12+
{
13+
public class IAsyncEnumerableEdmModel
14+
{
15+
public static IEdmModel GetEdmModel()
16+
{
17+
var builder = new ODataConventionModelBuilder();
18+
builder.EntitySet<Customer>("Customers");
19+
builder.EntitySet<Order>("Orders");
20+
IEdmModel model = builder.GetEdmModel();
21+
return model;
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)