OData / WebApi

OData Web API: A server library built upon ODataLib and WebApi
https://docs.microsoft.com/odata
Other
854 stars 475 forks source link

$apply=aggregate returns only JSON payload #1712

Open anekrash opened 5 years ago

anekrash commented 5 years ago

$apply=aggregate returns only JSON payload in response without ODATA annotations for unbound function. There is no ODATA headers in response as well.

Request: $apply=aggregate(Id with sum as IdSum)

Response Body: [{"IdSum":6}]

Response Headers: Content-Type: application/json; charset=utf-8 Date: Fri, 14 Dec 2018 09:53:26 GMT Server: Kestrel Transfer-Encoding: chunked

Assemblies affected

Microsoft.AspNetCore.OData 7.1.0

Reproduce steps

Create unbound function like this [HttpGet] [EnableQuery] [ODataRoute(nameof(GetAddress))] public IActionResult GetAddress()

Get EDM model: var builder = new ODataConventionModelBuilder(); builder.ComplexType < Address > (); builder.Function(nameof(AddressController.GetAddress)).ReturnsCollection < Address > (); return builder.GetEdmModel();

Model: public class Address { public int Id { get; set; } }

Expected result

{"@odata.context":"http://localhost:12345/odata/$metadata#Collection(WebApiClassic.Address)","value":[{"@odata.id":null,"IdSum":6}]}

Actual result

[{"IdSum":6}]

Additional detail

anekrash commented 5 years ago

It looks like Microsoft.AspNet.OData.Formatter.Serialization.DefaultODataSerializerProvider.GetODataPayloadSerializerImpl(Type type, Func modelFunction, ODataPath path, Type errorType) can't find proper serializer for type {System.Linq.EnumerableQuery`1[Microsoft.AspNet.OData.Query.Expressions.NoGroupByAggregationWrapper]}.

I use the following code to make it works: builder.ComplexType< Microsoft.AspNet.OData.Query.Expressions.DynamicTypeWrapper >();

Now it returns: {"@odata.context":""http://localhost:12345/odata/$metadata#Collection(Microsoft.AspNet.OData.Query.Expressions.DynamicTypeWrapper)","value":[{"@odata.id":null,"IdSum":6}]}

raheph commented 5 years ago

@kosinsky Do you have any idea about this?

kosinsky commented 5 years ago

Are you deriving controller form ODataController or adding [ODataFormatting] attribute explicitly? Without ODataFormatting serialization fallbacks to basic Asp .Net

anekrash commented 5 years ago

Are you deriving controller form ODataController or adding [ODataFormatting] attribute explicitly? Without ODataFormatting serialization fallbacks to basic Asp .Net

I inherit my controller from ODataController: public class AddressController : ODataController

Code:

public class Program
{
    public static void Main(string[] args)
    {
        WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().Build().Run();
    }
}

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOData();
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc(b =>
        {
            b.Select().Expand().Filter().OrderBy().MaxTop(100).Count();
            b.MapODataServiceRoute("odata", "odata", GetEdmModel());
        });
    }

    private static IEdmModel GetEdmModel()
    {
        var builder = new ODataConventionModelBuilder();
        builder.ComplexType<Address>();
        builder.Function(nameof(AddressController.GetAddress)).ReturnsCollection<Address>();
        return builder.GetEdmModel();
    }
}

public class AddressController : ODataController
{
    [HttpGet]
    [EnableQuery(MaxOrderByNodeCount = 32)]
    [ODataRoute(nameof(GetAddress))]
    public IActionResult GetAddress()
    {
        return Ok(new List<Address>
        {
            new Address { Id =  1 },
            new Address { Id =  2 },
            new Address { Id =  3 }
        });
    }
}

public class Address
{
    public int Id { get; set; }
}
kosinsky commented 5 years ago

I did some digging. Looks like it reproduces only for .NET Core and for cases when controller returns IActionResult. It worked properly when I modified GetAddress() method to look like:

        public IQueryable<Address> GetAddress()
        {
            return new List<Address>
            {
                new Address { Id =  1 },
                new Address { Id =  2 },
                new Address { Id =  3 }
            }.AsQueryable();
        }

I'll continue investigation

anekrash commented 5 years ago

I did some digging. Looks like it reproduces only for .NET Core and for cases when controller returns IActionResult. It worked properly when I modified GetAddress() method to look like:

        public IQueryable<Address> GetAddress()
        {
            return new List<Address>
            {
                new Address { Id =  1 },
                new Address { Id =  2 },
                new Address { Id =  3 }
            }.AsQueryable();
        }

I'll continue investigation

It works with with IQueryable. Thank you.

leechow007 commented 5 years ago

https://github.com/OData/WebApi/issues/1712#issuecomment-451596881 if I need return badrequest in this function, what shall I do?

audacity76 commented 9 months ago

This is still an issue in the latest AspNetCoreOData Version

peterkovecses commented 9 months ago

I faced the same issue, this is how I solved it in my application: https://github.com/peterkovecses/ODataApplyDemo