Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

$apply=aggregate returns only JSON payload #1144

Open
audacity76 opened this issue Dec 19, 2023 · 6 comments
Open

$apply=aggregate returns only JSON payload #1144

audacity76 opened this issue Dec 19, 2023 · 6 comments
Assignees
Labels
bug Something isn't working

Comments

@audacity76
Copy link

This old issue still is present in the latest AspNetCoreOData Version 8.2.3: OData/WebApi#1712

@audacity76 audacity76 added the bug Something isn't working label Dec 19, 2023
@habbes
Copy link
Contributor

habbes commented Dec 19, 2023

@audacity76 did you also observe this issue when using unbound functions?

@audacity76
Copy link
Author

audacity76 commented Dec 19, 2023

@habbes In my case it was a bound function. It happened when using the aggregate function on an endpoint

@habbes
Copy link
Contributor

habbes commented Dec 20, 2023

We've confirmed that the current output does not match what's expected and that the response should be a standard OData response. We'll investigate and work on a fix.

@sturla78
Copy link

I can confirm that is still present with AspNetCoreOData Version 8.2.5, using $apply=aggregate but also $apply=groupby

@Xriuk
Copy link

Xriuk commented Jul 4, 2024

+1, from a quick look at the code I see that there is no serializer being selected because here (I guess)

private IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollections)

the type IEnumerable<GroupByWrapper> cannot get a corresponding IEdmType, thus no serializer is being selected.

@Xriuk
Copy link

Xriuk commented Jul 4, 2024

Quick fix:

public class CustomODataSerializerProvider : ODataSerializerProvider {
	private readonly IServiceProvider _serviceProvider;

	public CustomODataSerializerProvider(IServiceProvider serviceProvider) :
		base(serviceProvider) {

		_serviceProvider = serviceProvider;
	}


	public override IODataSerializer GetODataPayloadSerializer(Type type, HttpRequest request) {
		// Handle GroupByWrapper
		var ienumerable = type.GetGenericBaseType(typeof(IEnumerable<>))?.GetGenericArguments()[0];
		while(ienumerable != null) {
			// Here you could also instantiate it directly with new GroupByODataResourceSetSerializer(...)
			if(ienumerable.FullName == "Microsoft.AspNetCore.OData.Query.Wrapper.GroupByWrapper")
				return _serviceProvider.GetRequiredService<GroupByODataResourceSetSerializer>();

			if (ienumerable.BaseType != null && ienumerable.BaseType != typeof(object))
				ienumerable = ienumerable.BaseType;
			else
				ienumerable = null;
		}

		return base.GetODataPayloadSerializer(type, request);
	}
}

public class GroupByODataResourceSetSerializer : ODataResourceSetSerializer {
	public GroupByODataResourceSetSerializer(IODataSerializerProvider serializerProvider) :
		base(serializerProvider) {}

	public override async Task WriteObjectAsync(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) {
		IEdmEntitySetBase entitySet = (writeContext.NavigationSource as IEdmEntitySetBase)!;

		// The resource type should be the original collection before GroupBy so we retrieve it from the path
		var pathType = writeContext.Path.LastSegment.EdmType.AsElementType();
		IEdmTypeReference resourceSetType = new EdmCollectionTypeReference(new EdmCollectionType(pathType is IEdmEntityType ? new EdmEntityTypeReference((IEdmEntityType)pathType, false) : new EdmComplexTypeReference((IEdmComplexType)pathType, false)));
		var resourceType = resourceSetType.AsCollection().ElementType().AsStructured();

		ODataWriter writer = await messageWriter.CreateODataResourceSetWriterAsync(entitySet, resourceType.StructuredDefinition())
			.ConfigureAwait(false);
		await WriteObjectInlineAsync(graph, resourceSetType, writer, writeContext)
			.ConfigureAwait(false);
	}
}

And then while configuring:

services.AddMvc(...)
.AddOData((options, s) => {
		...
		options.AddRouteComponents("odata", s.GetRequiredService<IEdmModel>(), odataServiceCollection => {
			...
			odataServiceCollection.AddSingleton<IODataSerializerProvider, CustomODataSerializerProvider>();
			odataServiceCollection.AddSingleton<GroupByODataResourceSetSerializer>();
		});
	})

This also returns the correct @odata.context.

xuzhg added a commit that referenced this issue Jul 12, 2024
Enable write the apply result using OData payload
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants