diff --git a/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs b/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs
index 83beb1bc1..5ed748969 100644
--- a/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs
+++ b/src/Microsoft.AspNetCore.OData/Extensions/HttpRequestExtensions.cs
@@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.OData.Abstracts;
using Microsoft.AspNetCore.OData.Common;
+using Microsoft.AspNetCore.OData.Formatter;
using Microsoft.AspNetCore.OData.Formatter.Deserialization;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.Extensions.DependencyInjection;
@@ -71,6 +72,44 @@ public static ODataOptions ODataOptions(this HttpRequest request)
return request.HttpContext.ODataOptions();
}
+ ///
+ /// Returns the from the request.
+ ///
+ /// The instance to access.
+ /// The from the request.
+ public static OmitValuesKind GetOmitValuesKind(this HttpRequest request)
+ {
+ if (request == null)
+ {
+ throw Error.ArgumentNull(nameof(request));
+ }
+
+ // The 'Prefer' header from client has the top priority
+ string preferHeader = RequestPreferenceHelpers.GetRequestPreferHeader(request.Headers);
+ if (preferHeader != null)
+ {
+ // use case insensitive string comparison
+ if (preferHeader.Contains("omit-values=nulls", StringComparison.OrdinalIgnoreCase))
+ {
+ return OmitValuesKind.Nulls;
+ }
+
+ if (preferHeader.Contains("omit-values=defaults", StringComparison.OrdinalIgnoreCase))
+ {
+ return OmitValuesKind.Defaults;
+ }
+ }
+
+ // If there's no setting from client, we move to use the configuration on the OData options.
+ ODataOptions options = request.ODataOptions();
+ if (options == null)
+ {
+ return OmitValuesKind.Unknown;
+ }
+
+ return options.OmitValuesKind;
+ }
+
///
/// Gets the from the request container.
///
diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs
index d9255bfed..9ca27dae5 100644
--- a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs
+++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatter.cs
@@ -209,6 +209,9 @@ public override void WriteResponseHeaders(OutputFormatterWriteContext context)
// Add version header.
response.Headers["OData-Version"] = ODataUtils.ODataVersionToString(request.GetODataVersion());
+
+ // Set the prefer-applied header on response
+ ResponsePreferenceHelpers.SetResponsePreferAppliedHeader(response);
}
///
diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs
index 57d28ab60..787071ff7 100644
--- a/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs
+++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataOutputFormatterHelper.cs
@@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
@@ -24,7 +23,6 @@
using Microsoft.Net.Http.Headers;
using Microsoft.OData;
using Microsoft.OData.Edm;
-using Microsoft.OData.ModelBuilder;
using Microsoft.OData.UriParser;
namespace Microsoft.AspNetCore.OData.Formatter
@@ -142,6 +140,7 @@ internal static async Task WriteToStreamAsync(
writeContext.MetadataLevel = metadataLevel;
writeContext.QueryOptions = queryOptions;
writeContext.SetComputedProperties(queryOptions?.Compute?.ComputeClause);
+ writeContext.OmitValuesKind = request.GetOmitValuesKind();
//Set the SelectExpandClause on the context if it was explicitly specified.
if (selectExpandDifferentFromQueryOptions != null)
diff --git a/src/Microsoft.AspNetCore.OData/Formatter/OmitValuesKind.cs b/src/Microsoft.AspNetCore.OData/Formatter/OmitValuesKind.cs
new file mode 100644
index 000000000..af92407c3
--- /dev/null
+++ b/src/Microsoft.AspNetCore.OData/Formatter/OmitValuesKind.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.AspNetCore.OData.Formatter
+{
+ ///
+ /// Omit-Values kind
+ ///
+ public enum OmitValuesKind
+ {
+ ///
+ /// Not set, unknown
+ ///
+ Unknown,
+
+ ///
+ /// If nulls is specified, then the service MAY omit properties containing null values from the response,
+ /// in which case it MUST specify the Preference-Applied response header with omit-values=nulls.
+ ///
+ Nulls,
+
+ ///
+ /// If defaults is specified, then the service MAY omit properties containing default values from the response, including nulls for properties that have no other defined default value.
+ /// Nulls MUST be included for properties that have a non-null default value defined.
+ /// If the service omits default values it MUST specify the Preference-Applied response header with omit-values=defaults.
+ ///
+ Defaults
+ }
+}
diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ResponsePreferenceHelpers.cs b/src/Microsoft.AspNetCore.OData/Formatter/ResponsePreferenceHelpers.cs
new file mode 100644
index 000000000..e05f8d5a8
--- /dev/null
+++ b/src/Microsoft.AspNetCore.OData/Formatter/ResponsePreferenceHelpers.cs
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.OData.Extensions;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.OData.Formatter
+{
+ internal static class ResponsePreferenceHelpers
+ {
+ public const string PreferAppliedHeaderName = "Preference-Applied";
+
+ public static void SetResponsePreferAppliedHeader(HttpResponse response)
+ {
+ if (response == null)
+ {
+ throw Error.ArgumentNull(nameof(response));
+ }
+
+ HttpRequest request = response.HttpContext.Request;
+
+ OmitValuesKind omitValuesKind = request.GetOmitValuesKind();
+ if (omitValuesKind == OmitValuesKind.Unknown)
+ {
+ return;
+ }
+
+ string prefer_applied = null;
+ if (response.Headers.TryGetValue(PreferAppliedHeaderName, out StringValues values))
+ {
+ // If there are many "Preference-Applied" headers, pick up the first one.
+ prefer_applied = values.FirstOrDefault();
+ }
+
+ string omitValuesHead = omitValuesKind == OmitValuesKind.Nulls ?
+ "omit-values=nulls" :
+ "omit-values=defaults";
+
+ if (prefer_applied == null)
+ {
+ response.Headers[PreferAppliedHeaderName] = omitValuesHead;
+ }
+ else
+ {
+ response.Headers[PreferAppliedHeaderName] = $"{prefer_applied},{omitValuesHead}";
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataResourceSerializer.cs b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataResourceSerializer.cs
index 297e152e4..c8e4ca79a 100644
--- a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataResourceSerializer.cs
+++ b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataResourceSerializer.cs
@@ -749,16 +749,16 @@ private async Task WriteDynamicComplexPropertiesAsync(ResourceContext resourceCo
if (edmTypeReference.IsStructured() ||
(edmTypeReference.IsCollection() && edmTypeReference.AsCollection().ElementType().IsStructured()))
{
- ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo
- {
- IsCollection = edmTypeReference.IsCollection(),
- Name = dynamicComplexProperty.Key,
- };
+ ODataNestedResourceInfo nestedResourceInfo
+ = CreateDynamicComplexNestedResourceInfo(dynamicComplexProperty.Key, dynamicComplexProperty.Value, edmTypeReference, resourceContext);
- await writer.WriteStartAsync(nestedResourceInfo).ConfigureAwait(false);
- await WriteDynamicComplexPropertyAsync(dynamicComplexProperty.Value, edmTypeReference, resourceContext, writer)
- .ConfigureAwait(false);
- await writer.WriteEndAsync().ConfigureAwait(false);
+ if (nestedResourceInfo != null)
+ {
+ await writer.WriteStartAsync(nestedResourceInfo).ConfigureAwait(false);
+ await WriteDynamicComplexPropertyAsync(dynamicComplexProperty.Value, edmTypeReference, resourceContext, writer)
+ .ConfigureAwait(false);
+ await writer.WriteEndAsync().ConfigureAwait(false);
+ }
}
}
}
@@ -834,16 +834,14 @@ private async Task WriteComplexPropertiesAsync(SelectExpandNode selectExpandNode
{
IEdmStructuralProperty complexProperty = selectedComplex.Key;
- ODataNestedResourceInfo nestedResourceInfo = new ODataNestedResourceInfo
+ ODataNestedResourceInfo nestedResourceInfo = CreateComplexNestedResourceInfo(complexProperty, selectedComplex.Value, resourceContext);
+ if (nestedResourceInfo != null)
{
- IsCollection = complexProperty.Type.IsCollection(),
- Name = complexProperty.Name
- };
-
- await writer.WriteStartAsync(nestedResourceInfo).ConfigureAwait(false);
- await WriteComplexAndExpandedNavigationPropertyAsync(complexProperty, selectedComplex.Value, resourceContext, writer)
- .ConfigureAwait(false);
- await writer.WriteEndAsync().ConfigureAwait(false);
+ await writer.WriteStartAsync(nestedResourceInfo).ConfigureAwait(false);
+ await WriteComplexAndExpandedNavigationPropertyAsync(complexProperty, selectedComplex.Value, resourceContext, writer)
+ .ConfigureAwait(false);
+ await writer.WriteEndAsync().ConfigureAwait(false);
+ }
}
}
@@ -960,6 +958,66 @@ private IEnumerable CreateNavigationLinks(
}
}
+ ///
+ /// Creates the to be written while writing this dynamic complex property.
+ ///
+ /// The dynamic property name.
+ /// The dynamic property value.
+ /// The edm type reference.
+ /// The context for the complex instance being written.
+ /// The nested resource info to be written. Returns 'null' will omit this serialization.
+ /// It enables customer to get more controll by overriding this method.
+ public virtual ODataNestedResourceInfo CreateDynamicComplexNestedResourceInfo(string propertyName, object propertyValue, IEdmTypeReference edmType, ResourceContext resourceContext)
+ {
+ if (edmType == null)
+ {
+ throw Error.ArgumentNull(nameof(edmType));
+ }
+
+ return new ODataNestedResourceInfo
+ {
+ IsCollection = edmType.IsCollection(),
+ Name = propertyName,
+ };
+ }
+
+ ///
+ /// Creates the to be written while writing this complex property.
+ ///
+ /// The complex property for which the nested resource info is being created.
+ /// The corresponding sub select item belongs to this complex property.
+ /// The context for the complex instance being written.
+ /// The nested resource info to be written. Returns 'null' will omit this complex serialization.
+ /// It enables customer to get more controll by overriding this method.
+ public virtual ODataNestedResourceInfo CreateComplexNestedResourceInfo(IEdmStructuralProperty complexProperty, PathSelectItem pathSelectItem, ResourceContext resourceContext)
+ {
+ if (complexProperty == null)
+ {
+ throw Error.ArgumentNull(nameof(complexProperty));
+ }
+
+ if (resourceContext == null)
+ {
+ throw Error.ArgumentNull(nameof(resourceContext));
+ }
+
+
+ if (resourceContext.SerializerContext.OmitValuesKind == OmitValuesKind.Nulls)
+ {
+ object propertyValue = resourceContext.GetPropertyValue(complexProperty.Name);
+ if (propertyValue == null || propertyValue is NullEdmComplexObject)
+ {
+ return null;
+ }
+ }
+
+ return new ODataNestedResourceInfo
+ {
+ IsCollection = complexProperty.Type.IsCollection(),
+ Name = complexProperty.Name
+ };
+ }
+
///
/// Creates the to be written while writing this entity.
///
@@ -978,6 +1036,15 @@ public virtual ODataNestedResourceInfo CreateNavigationLink(IEdmNavigationProper
throw Error.ArgumentNull(nameof(resourceContext));
}
+ if (resourceContext.SerializerContext.OmitValuesKind == OmitValuesKind.Nulls)
+ {
+ object propertyValue = resourceContext.GetPropertyValue(navigationProperty.Name);
+ if (propertyValue == null)
+ {
+ return null;
+ }
+ }
+
ODataSerializerContext writeContext = resourceContext.SerializerContext;
IEdmNavigationSource navigationSource = writeContext.NavigationSource;
ODataNestedResourceInfo navigationLink = null;
@@ -1160,6 +1227,21 @@ public virtual ODataProperty CreateStructuralProperty(IEdmStructuralProperty str
object propertyValue = resourceContext.GetPropertyValue(structuralProperty.Name);
+ if (resourceContext.SerializerContext.OmitValuesKind == OmitValuesKind.Nulls && propertyValue == null)
+ {
+ return null; // omit null value
+ }
+
+ if (resourceContext.SerializerContext.OmitValuesKind == OmitValuesKind.Defaults &&
+ structuralProperty.DefaultValueString != null &&
+ structuralProperty.Type.IsString() && propertyValue != null) // let's focus on the "Edm.String" property only, NOW!!
+ {
+ if (propertyValue.ToString() == structuralProperty.DefaultValueString)
+ {
+ return null; // omit default value
+ }
+ }
+
IEdmTypeReference propertyType = structuralProperty.Type;
if (propertyValue != null)
{
diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs
index 725a1d525..d132be239 100644
--- a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs
+++ b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs
@@ -78,6 +78,7 @@ internal ODataSerializerContext(ResourceContext resource, IEdmProperty edmProper
Items = context.Items;
ExpandReference = context.ExpandReference;
TimeZone = context.TimeZone;
+ OmitValuesKind = context.OmitValuesKind;
QueryContext = queryContext;
@@ -180,6 +181,11 @@ internal ODataSerializerContext(ResourceContext resource, IEdmProperty edmProper
///
public TimeZoneInfo TimeZone { get; set; }
+ ///
+ /// Gets or sets the .
+ ///
+ public OmitValuesKind OmitValuesKind { get; set; }
+
///
/// Gets or sets the .
///
diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
index dc0971029..ce01039e7 100644
--- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
+++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
@@ -1094,6 +1094,20 @@
The type to test.
True if the type is a DateTime; false otherwise.
+
+
+ Determine if a type is a .
+
+ The type to test.
+ True if the type is a DateOnly; false otherwise.
+
+
+
+ Determine if a type is a .
+
+ The type to test.
+ True if the type is a TimeOnly; false otherwise.
+
Determine if a type is a TimeSpan.
@@ -2894,6 +2908,13 @@
The instance to extend.
The instance from the DI container.
+
+
+ Returns the from the request.
+
+ The instance to access.
+ The from the request.
+
Gets the from the request container.
@@ -4076,6 +4097,29 @@
Gets the OData action of this parameters.
+
+
+ Omit-Values kind
+
+
+
+
+ Not set, unknown
+
+
+
+
+ If nulls is specified, then the service MAY omit properties containing null values from the response,
+ in which case it MUST specify the Preference-Applied response header with omit-values=nulls.
+
+
+
+
+ If defaults is specified, then the service MAY omit properties containing default values from the response, including nulls for properties that have no other defined default value.
+ Nulls MUST be included for properties that have a non-null default value defined.
+ If the service omits default values it MUST specify the Preference-Applied response header with omit-values=defaults.
+
+
Contains context information about the resource currently being serialized.
@@ -4606,6 +4650,27 @@
Write the navigation link for the select navigation properties.
+
+
+ Creates the to be written while writing this dynamic complex property.
+
+ The dynamic property name.
+ The dynamic property value.
+ The edm type reference.
+ The context for the complex instance being written.
+ The nested resource info to be written. Returns 'null' will omit this serialization.
+ It enables customer to get more controll by overriding this method.
+
+
+
+ Creates the to be written while writing this complex property.
+
+ The complex property for which the nested resource info is being created.
+ The corresponding sub select item belongs to this complex property.
+ The context for the complex instance being written.
+ The nested resource info to be written. Returns 'null' will omit this complex serialization.
+ It enables customer to get more controll by overriding this method.
+
Creates the to be written while writing this entity.
@@ -4802,6 +4867,11 @@
Gets or sets the .
+
+
+ Gets or sets the .
+
+
Gets or sets the .
@@ -6093,6 +6163,11 @@
Gets or sets a TimeZoneInfo for the serialization and deserialization.
+
+
+ Gets or sets a omit values kind for the property value serialization.
+
+
Gets the routing conventions.
@@ -14382,19 +14457,3 @@
-ummary>
- The value segment.
-
-
-
- Gets the value segment.
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Microsoft.AspNetCore.OData/ODataOptions.cs b/src/Microsoft.AspNetCore.OData/ODataOptions.cs
index 500265fa5..794603544 100644
--- a/src/Microsoft.AspNetCore.OData/ODataOptions.cs
+++ b/src/Microsoft.AspNetCore.OData/ODataOptions.cs
@@ -11,6 +11,7 @@
using System.Diagnostics.Contracts;
using Microsoft.AspNetCore.OData.Abstracts;
using Microsoft.AspNetCore.OData.Batch;
+using Microsoft.AspNetCore.OData.Formatter;
using Microsoft.AspNetCore.OData.Routing;
using Microsoft.AspNetCore.OData.Routing.Conventions;
using Microsoft.Extensions.DependencyInjection;
@@ -52,6 +53,11 @@ public class ODataOptions
///
public TimeZoneInfo TimeZone { get; set; } = TimeZoneInfo.Local;
+ ///
+ /// Gets or sets a omit values kind for the property value serialization.
+ ///
+ public OmitValuesKind OmitValuesKind { get; set; } = OmitValuesKind.Unknown;
+
///
/// Gets the routing conventions.
///
diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
index 3ee9900ec..b23d011c8 100644
--- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
@@ -373,6 +373,10 @@ Microsoft.AspNetCore.OData.Formatter.ODataParameterValue.Value.get -> object
Microsoft.AspNetCore.OData.Formatter.ODataUntypedActionParameters
Microsoft.AspNetCore.OData.Formatter.ODataUntypedActionParameters.Action.get -> Microsoft.OData.Edm.IEdmAction
Microsoft.AspNetCore.OData.Formatter.ODataUntypedActionParameters.ODataUntypedActionParameters(Microsoft.OData.Edm.IEdmAction action) -> void
+Microsoft.AspNetCore.OData.Formatter.OmitValuesKind
+Microsoft.AspNetCore.OData.Formatter.OmitValuesKind.Defaults = 2 -> Microsoft.AspNetCore.OData.Formatter.OmitValuesKind
+Microsoft.AspNetCore.OData.Formatter.OmitValuesKind.Nulls = 1 -> Microsoft.AspNetCore.OData.Formatter.OmitValuesKind
+Microsoft.AspNetCore.OData.Formatter.OmitValuesKind.Unknown = 0 -> Microsoft.AspNetCore.OData.Formatter.OmitValuesKind
Microsoft.AspNetCore.OData.Formatter.ResourceContext
Microsoft.AspNetCore.OData.Formatter.ResourceContext.DynamicComplexProperties.get -> System.Collections.Generic.IDictionary
Microsoft.AspNetCore.OData.Formatter.ResourceContext.DynamicComplexProperties.set -> void
@@ -460,6 +464,8 @@ Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.Naviga
Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.NavigationSource.set -> void
Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.ODataSerializerContext() -> void
Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.ODataSerializerContext(Microsoft.AspNetCore.OData.Formatter.ResourceContext resource, Microsoft.OData.UriParser.SelectExpandClause selectExpandClause, Microsoft.OData.Edm.IEdmProperty edmProperty) -> void
+Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.OmitValuesKind.get -> Microsoft.AspNetCore.OData.Formatter.OmitValuesKind
+Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.OmitValuesKind.set -> void
Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.Path.get -> Microsoft.OData.UriParser.ODataPath
Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.Path.set -> void
Microsoft.AspNetCore.OData.Formatter.Serialization.ODataSerializerContext.QueryOptions.get -> Microsoft.AspNetCore.OData.Query.ODataQueryOptions
@@ -666,6 +672,8 @@ Microsoft.AspNetCore.OData.ODataOptions.Expand() -> Microsoft.AspNetCore.OData.O
Microsoft.AspNetCore.OData.ODataOptions.Filter() -> Microsoft.AspNetCore.OData.ODataOptions
Microsoft.AspNetCore.OData.ODataOptions.GetRouteServices(string routePrefix) -> System.IServiceProvider
Microsoft.AspNetCore.OData.ODataOptions.ODataOptions() -> void
+Microsoft.AspNetCore.OData.ODataOptions.OmitValuesKind.get -> Microsoft.AspNetCore.OData.Formatter.OmitValuesKind
+Microsoft.AspNetCore.OData.ODataOptions.OmitValuesKind.set -> void
Microsoft.AspNetCore.OData.ODataOptions.OrderBy() -> Microsoft.AspNetCore.OData.ODataOptions
Microsoft.AspNetCore.OData.ODataOptions.QuerySettings.get -> Microsoft.OData.ModelBuilder.Config.DefaultQuerySettings
Microsoft.AspNetCore.OData.ODataOptions.RouteComponents.get -> System.Collections.Generic.IDictionary
@@ -1610,6 +1618,7 @@ static Microsoft.AspNetCore.OData.Extensions.HttpRequestExtensions.GetETagHandle
static Microsoft.AspNetCore.OData.Extensions.HttpRequestExtensions.GetModel(this Microsoft.AspNetCore.Http.HttpRequest request) -> Microsoft.OData.Edm.IEdmModel
static Microsoft.AspNetCore.OData.Extensions.HttpRequestExtensions.GetNextPageLink(this Microsoft.AspNetCore.Http.HttpRequest request, int pageSize, object instance, System.Func