25
25
using Microsoft . Extensions . Options ;
26
26
using Microsoft . OData ;
27
27
using Microsoft . OData . Edm ;
28
- using Microsoft . OData . ModelBuilder . Capabilities . V1 ;
29
28
using Microsoft . OData . UriParser ;
30
29
31
30
namespace Microsoft . AspNetCore . OData . Formatter . Serialization
@@ -97,14 +96,30 @@ public override async Task WriteObjectInlineAsync(object graph, IEdmTypeReferenc
97
96
throw new SerializationException ( Error . Format ( SRResources . CannotSerializerNull , ResourceSet ) ) ;
98
97
}
99
98
100
- IEnumerable enumerable = graph as IEnumerable ; // Data to serialize
101
- if ( enumerable == null )
99
+ if ( writeContext . Type != null && writeContext . Type . IsGenericType && writeContext . Type ? . GetGenericTypeDefinition ( ) == typeof ( IAsyncEnumerable < > ) )
102
100
{
103
- throw new SerializationException (
104
- Error . Format ( SRResources . CannotWriteType , GetType ( ) . Name , graph . GetType ( ) . FullName ) ) ;
101
+ IAsyncEnumerable < object > asyncEnumerable = graph as IAsyncEnumerable < object > ; // Data to serialize
102
+
103
+ if ( asyncEnumerable == null )
104
+ {
105
+ throw new SerializationException (
106
+ Error . Format ( SRResources . CannotWriteType , GetType ( ) . Name , graph . GetType ( ) . FullName ) ) ;
107
+ }
108
+
109
+ await WriteResourceSetAsync ( asyncEnumerable , expectedType , writer , writeContext ) . ConfigureAwait ( false ) ;
105
110
}
111
+ else
112
+ {
113
+ IEnumerable enumerable = graph as IEnumerable ; // Data to serialize
106
114
107
- await WriteResourceSetAsync ( enumerable , expectedType , writer , writeContext ) . ConfigureAwait ( false ) ;
115
+ if ( enumerable == null )
116
+ {
117
+ throw new SerializationException (
118
+ Error . Format ( SRResources . CannotWriteType , GetType ( ) . Name , graph . GetType ( ) . FullName ) ) ;
119
+ }
120
+
121
+ await WriteResourceSetAsync ( enumerable , expectedType , writer , writeContext ) . ConfigureAwait ( false ) ;
122
+ }
108
123
}
109
124
110
125
private async Task WriteResourceSetAsync ( IEnumerable enumerable , IEdmTypeReference resourceSetType , ODataWriter writer ,
@@ -150,28 +165,74 @@ private async Task WriteResourceSetAsync(IEnumerable enumerable, IEdmTypeReferen
150
165
resourceSet . NextPageLink = null ;
151
166
await writer . WriteStartAsync ( resourceSet ) . ConfigureAwait ( false ) ;
152
167
object lastResource = null ;
168
+
153
169
foreach ( object item in enumerable )
154
170
{
155
171
lastResource = item ;
156
- if ( item == null || item is NullEdmComplexObject )
157
- {
158
- if ( elementType . IsEntity ( ) )
159
- {
160
- throw new SerializationException ( SRResources . NullElementInCollection ) ;
161
- }
162
172
163
- // for null complex element, it can be serialized as "null" in the collection.
164
- await writer . WriteStartAsync ( resource : null ) . ConfigureAwait ( false ) ;
165
- await writer . WriteEndAsync ( ) . ConfigureAwait ( false ) ;
166
- }
167
- else if ( isUntypedCollection )
168
- {
169
- await WriteUntypedResourceSetItemAsync ( item , resourceSetType , writer , writeContext ) . ConfigureAwait ( false ) ;
170
- }
171
- else
173
+ await WriteResourceSetAsync ( item , elementType , isUntypedCollection , resourceSetType , writer , resourceSerializer , writeContext ) ;
174
+ }
175
+
176
+ // Subtle and surprising behavior: If the NextPageLink property is set before calling WriteStart(resourceSet),
177
+ // the next page link will be written early in a manner not compatible with odata.streaming=true. Instead, if
178
+ // the next page link is not set when calling WriteStart(resourceSet) but is instead set later on that resourceSet
179
+ // object before calling WriteEnd(), the next page link will be written at the end, as required for
180
+ // odata.streaming=true support.
181
+
182
+ resourceSet . NextPageLink = nextLinkGenerator ( lastResource ) ;
183
+
184
+ await writer . WriteEndAsync ( ) . ConfigureAwait ( false ) ;
185
+ }
186
+
187
+ private async Task WriteResourceSetAsync ( IAsyncEnumerable < object > asyncEnumerable , IEdmTypeReference resourceSetType , ODataWriter writer ,
188
+ ODataSerializerContext writeContext )
189
+ {
190
+ Contract . Assert ( writer != null ) ;
191
+ Contract . Assert ( writeContext != null ) ;
192
+ Contract . Assert ( asyncEnumerable != null ) ;
193
+ Contract . Assert ( resourceSetType != null ) ;
194
+
195
+ IEdmStructuredTypeReference elementType = GetResourceType ( resourceSetType ) ;
196
+ ODataResourceSet resourceSet = CreateResourceSet ( asyncEnumerable , resourceSetType . AsCollection ( ) , writeContext ) ;
197
+
198
+ Func < object , Uri > nextLinkGenerator = GetNextLinkGenerator ( resourceSet , null , writeContext ) ;
199
+
200
+ if ( resourceSet == null )
201
+ {
202
+ throw new SerializationException ( Error . Format ( SRResources . CannotSerializerNull , ResourceSet ) ) ;
203
+ }
204
+
205
+ IEdmEntitySetBase entitySet = writeContext . NavigationSource as IEdmEntitySetBase ;
206
+ if ( entitySet == null )
207
+ {
208
+ resourceSet . SetSerializationInfo ( new ODataResourceSerializationInfo
172
209
{
173
- await resourceSerializer . WriteObjectInlineAsync ( item , elementType , writer , writeContext ) . ConfigureAwait ( false ) ;
174
- }
210
+ IsFromCollection = true ,
211
+ NavigationSourceEntityTypeName = elementType . FullName ( ) ,
212
+ NavigationSourceKind = EdmNavigationSourceKind . UnknownEntitySet ,
213
+ NavigationSourceName = null
214
+ } ) ;
215
+ }
216
+
217
+ IODataEdmTypeSerializer resourceSerializer = SerializerProvider . GetEdmTypeSerializer ( elementType ) ;
218
+ if ( resourceSerializer == null )
219
+ {
220
+ throw new SerializationException (
221
+ Error . Format ( SRResources . TypeCannotBeSerialized , elementType . FullName ( ) ) ) ;
222
+ }
223
+
224
+ bool isUntypedCollection = resourceSetType . IsCollectionUntyped ( ) ;
225
+
226
+ // set the nextpagelink to null to support JSON odata.streaming.
227
+ resourceSet . NextPageLink = null ;
228
+ await writer . WriteStartAsync ( resourceSet ) . ConfigureAwait ( false ) ;
229
+ object lastResource = null ;
230
+
231
+ await foreach ( object item in asyncEnumerable )
232
+ {
233
+ lastResource = item ;
234
+
235
+ await WriteResourceSetAsync ( item , elementType , isUntypedCollection , resourceSetType , writer , resourceSerializer , writeContext ) ;
175
236
}
176
237
177
238
// Subtle and surprising behavior: If the NextPageLink property is set before calling WriteStart(resourceSet),
@@ -185,6 +246,36 @@ private async Task WriteResourceSetAsync(IEnumerable enumerable, IEdmTypeReferen
185
246
await writer . WriteEndAsync ( ) . ConfigureAwait ( false ) ;
186
247
}
187
248
249
+ private async Task WriteResourceSetAsync (
250
+ object item ,
251
+ IEdmStructuredTypeReference elementType ,
252
+ bool isUntypedCollection ,
253
+ IEdmTypeReference resourceSetType ,
254
+ ODataWriter writer ,
255
+ IODataEdmTypeSerializer resourceSerializer ,
256
+ ODataSerializerContext writeContext )
257
+ {
258
+ if ( item == null || item is NullEdmComplexObject )
259
+ {
260
+ if ( elementType . IsEntity ( ) )
261
+ {
262
+ throw new SerializationException ( SRResources . NullElementInCollection ) ;
263
+ }
264
+
265
+ // for null complex element, it can be serialized as "null" in the collection.
266
+ await writer . WriteStartAsync ( resource : null ) . ConfigureAwait ( false ) ;
267
+ await writer . WriteEndAsync ( ) . ConfigureAwait ( false ) ;
268
+ }
269
+ else if ( isUntypedCollection )
270
+ {
271
+ await WriteUntypedResourceSetItemAsync ( item , resourceSetType , writer , writeContext ) . ConfigureAwait ( false ) ;
272
+ }
273
+ else
274
+ {
275
+ await resourceSerializer . WriteObjectInlineAsync ( item , elementType , writer , writeContext ) . ConfigureAwait ( false ) ;
276
+ }
277
+ }
278
+
188
279
private async Task WriteUntypedResourceSetItemAsync ( object item , IEdmTypeReference parentSetType , ODataWriter writer , ODataSerializerContext writeContext )
189
280
{
190
281
Contract . Assert ( item != null ) ; // "item == null" is handled.
@@ -403,6 +494,81 @@ public virtual ODataResourceSet CreateResourceSet(IEnumerable resourceSetInstanc
403
494
return resourceSet ;
404
495
}
405
496
497
+ /// <summary>
498
+ /// Create the <see cref="ODataResourceSet"/> to be written for the given resourceSet instance.
499
+ /// </summary>
500
+ /// <param name="resourceSetInstance">The instance representing the resourceSet being written.</param>
501
+ /// <param name="resourceSetType">The EDM type of the resourceSet being written.</param>
502
+ /// <param name="writeContext">The serializer context.</param>
503
+ /// <returns>The created <see cref="ODataResourceSet"/> object.</returns>
504
+ public virtual ODataResourceSet CreateResourceSet ( IAsyncEnumerable < object > resourceSetInstance , IEdmCollectionTypeReference resourceSetType ,
505
+ ODataSerializerContext writeContext )
506
+ {
507
+ if ( writeContext == null )
508
+ {
509
+ throw Error . ArgumentNull ( nameof ( writeContext ) ) ;
510
+ }
511
+
512
+ ODataResourceSet resourceSet = new ODataResourceSet
513
+ {
514
+ TypeName = resourceSetType . FullName ( )
515
+ } ;
516
+
517
+ IEdmStructuredTypeReference structuredType = GetResourceType ( resourceSetType ) . AsStructured ( ) ;
518
+ if ( writeContext . NavigationSource != null && structuredType . IsEntity ( ) )
519
+ {
520
+ ResourceSetContext resourceSetContext = ResourceSetContext . Create ( writeContext , resourceSetInstance ) ;
521
+ IEdmEntityType entityType = structuredType . AsEntity ( ) . EntityDefinition ( ) ;
522
+ var operations = writeContext . Model . GetAvailableOperationsBoundToCollection ( entityType ) ;
523
+ var odataOperations = CreateODataOperations ( operations , resourceSetContext , writeContext ) ;
524
+ foreach ( var odataOperation in odataOperations )
525
+ {
526
+ ODataAction action = odataOperation as ODataAction ;
527
+ if ( action != null )
528
+ {
529
+ resourceSet . AddAction ( action ) ;
530
+ }
531
+ else
532
+ {
533
+ resourceSet . AddFunction ( ( ODataFunction ) odataOperation ) ;
534
+ }
535
+ }
536
+ }
537
+
538
+ if ( writeContext . ExpandedResource == null )
539
+ {
540
+ // If we have more OData format specific information apply it now, only if we are the root feed.
541
+ PageResult odataResourceSetAnnotations = resourceSetInstance as PageResult ;
542
+ if ( odataResourceSetAnnotations != null )
543
+ {
544
+ resourceSet . Count = odataResourceSetAnnotations . Count ;
545
+ resourceSet . NextPageLink = odataResourceSetAnnotations . NextPageLink ;
546
+ }
547
+ else if ( writeContext . Request != null )
548
+ {
549
+ IODataFeature odataFeature = writeContext . Request . ODataFeature ( ) ;
550
+ resourceSet . NextPageLink = odataFeature . NextLink ;
551
+ resourceSet . DeltaLink = odataFeature . DeltaLink ;
552
+
553
+ long ? countValue = odataFeature . TotalCount ;
554
+ if ( countValue . HasValue )
555
+ {
556
+ resourceSet . Count = countValue . Value ;
557
+ }
558
+ }
559
+ }
560
+ else
561
+ {
562
+ ICountOptionCollection countOptionCollection = resourceSetInstance as ICountOptionCollection ;
563
+ if ( countOptionCollection != null && countOptionCollection . TotalCount != null )
564
+ {
565
+ resourceSet . Count = countOptionCollection . TotalCount ;
566
+ }
567
+ }
568
+
569
+ return resourceSet ;
570
+ }
571
+
406
572
/// <summary>
407
573
/// Creates a function that takes in an object and generates nextlink uri.
408
574
/// </summary>
0 commit comments