Coder Social home page Coder Social logo

aspnetcoreodata's Introduction

ASP.NET Core OData 8.x


Component Build Status
ASP.NET Core OData Rolling Build status
ASP.NET Core OData Nightly Build status
.NET Foundation Release Build status

1. Introduction

Be noted: Switch to use "main" as default branch. 1/6/2022

This is the official ASP.NET Core OData repository. ASP.NET Core OData is a server side library built upon ODataLib and ASP.NET Core.

Blogs:

Example:

  • ODataRoutingSample: ASP.NET Core OData sample project in this repo.

    • ~/$odata gives a static routing table page of the service

    • ~/swagger gives a swagger/openapi page

    • Append ~/$openapi to each route gives a raw openapi OData page, for example, ~/v1/$openapi

    Please go to sample folder see more samples.

Solution:

2. Basic Usage

In the ASP.NET Core Web Application project, update your Startup.cs as below:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<BookStoreContext>(opt => opt.UseInMemoryDatabase("BookLists"));
        services.AddControllers().AddOData(opt => opt.AddRouteComponents("odata", GetEdmModel()));
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // Send "~/$odata" to debug routing if enable the following middleware
        // app.UseODataRouteDebug();

        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

    private static IEdmModel GetEdmModel()
    {
        // …
    }
}

That's it.

3. Building, Testing, Debugging and Release

3.1 Building and Testing in Visual Studio

Visual Studio 2022 is required to build the source project in order to support the DateOnly and TimeOnly types, which were introduced in .NET 6.

3.2 One-click build and test script in command line

Coming soon.

3.3 Debug

The symbol package is uploaded to nuget symbol server.

It supports source link debug. Remember to check Enable Source Link support if you debug using Visual Studio.

3.4 Nightly Builds

The nightly build process will upload NuGet packages for ASP.NET Core OData to:

To connect to webapinightly feed, use this feed URL:

4. Documentation

5. Community

5.1 Contribution

Any contributions, feature requests, bugs and issues are welcome.

5.2 Reporting Security Issues

Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) [email protected]. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the Security TechCenter. You can also find these instructions in this repo's SECURITY.md.

5.3 Support

Code of Conduct

This project has adopted the .NET Foundation Contributor Covenant Code of Conduct. For more information see the Code of Conduct FAQ.

.NET Foundation

This project is supported by the .NET Foundation.

AspNetCoreOData is a Copyright of © .NET Foundation and other contributors. It is licensed under MIT License

aspnetcoreodata's People

Contributors

aarabika avatar aldrashan avatar cognvn avatar dxrdxr avatar elizabethokerio avatar flaviushouk avatar gathogojr avatar giulianob avatar habbes avatar jfshark avatar kenitoinc avatar kevinnoffc avatar kylemcmaster avatar leandro-albano avatar lisicase avatar marabooy avatar mattperdeck avatar mikepizzo avatar nthemba avatar orty avatar owenhuyn avatar ramjotsingh avatar robertmclaws avatar samtrion avatar senioroman4uk avatar uffelauesen avatar virusquartirus avatar wedgberto avatar weitzhandler avatar xuzhg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aspnetcoreodata's Issues

ODataController operations without key paramater

Hi,
I have a derived base class from ODataController using Microsoft.AspNetCore.OData 7.4.0 with the following signatures without explicit key parameters:

  public abstract class ODataControllerBase<TEntity> : ODataController
    where TEntity : class
  {
    public async Task<IActionResult> Patch(Delta<TEntity> entity)
    {
    }

    public async Task<IActionResult> Put([FromBody]TEntity entity)
    {
    }

    public async Task<IActionResult> Delete()
    {
    }

    [AcceptVerbs("POST", "PUT")]
    public async Task<IActionResult> CreateRef(string navigationProperty, [FromBody] Uri link)
    {
    }

    public async Task<IActionResult> DeleteRef(string navigationProperty)
    {
    }
  }

In 8.0.0-preview I have to specify the key parameter.

public async Task<IActionResult> Delete(int key) //or
public async Task<IActionResult> Delete(string key) //etc etc

We need a way to create a general purpose ODataController for all types of keys including compound keys. Please add back this functionality. The key(s) can be extracted from ODataPath programatically during runtime:

var keys = odataPath.Segments.OfType<KeySegment>();

Thank you

Move Nuget package to Microsoft.OData.ModelBuilder 1.0.5

Hello,
I have some issues to work with Microsoft.AspNetCore.OData 8.0.0-preview2 and EFcore 5.0.
The problem is related to System.ComponentModel.Annotations.
The dependencies are incompatible.
Microsoft.AspNetCore.OData 8.0.0-preview2 has dependency with Microsoft.OData.ModelBuilder 1.0.4 that
want to use System.ComponentModel.Annotations >4.6.0 and < 5.0.0
instead EFcore 5.0 want to use System.ComponentModel.Annotations >= 5.0.
Please change the nuget package of Microsoft.AspNetCore.OData 8.0.0 to have as minimum Microsoft.OData.ModelBuilder 1.0.5 that have the right dependency to System.ComponentModel.Annotations with version >4.6.0.

In my opinion you can change the nuspec file in the following way:
<dependency id="Microsoft.OData.ModelBuilder" version="[1.0.5, 2.0.0)" />

Thanks
Fabio

SelectExpandWrapperConverter does not work

I think there are a couple of issues with SelectExpandWrapperConverter.

  • I wanted to use it in a non-edm approach, but got stuck on a $select.
  • The SelectExpandWrapperConverter was not even used due to the fact that CanConvert would never return true.

If I make SelectExpandWrapperConverter generic with a type constrain of ISelectExpandWrapper and additionally implement a SelectExpandWrapperConverterFactory which implements JsonConverterFactory everything works as I would expect it.

An additional small issue in SelectExpandWrapperConverter.Write() is, that it is not writing the serialized json to the given writer.

Assemblies affected
AspNetCore.OData Odata8.0.0-preview3

Intercepting after filter

I've got a collection of users in our database. However, we also have groups that come from an Active Directory source.

We need to collate the filtered users with groups but the function to add groups to each user will need to enumerate the IQueriable so that can add the groups to each user.

This results in the entire user table being loaded into memory > Groups being added to those users before being filtered.

Is there a way that we can intercept/hook the result after the filter has been applied? Then we could add the groups to the filtered subset of users. Is this possible?

Alternatively is there a way that we can get access to the filters, apply them ourselves and then apply the groups?

[EnableQuery(MaxTop = 100, AllowedQueryOptions = Select | Top | Skip | Count | Expand | Filter | OrderBy | Apply)]
[ProducesResponseType(typeof(IQueryable<UserDto>), 200)]
[HttpGet]
[Produces("application/json")]
public IActionResult Get()
{
    var entities = this._profilesService.GetUsers();

    IQueryable<UserDto> projected = _mapper.ProjectTo<UserDto>(entities);
    // this code internally itterates over the users causing them to enumerate before the filter can be applied.
    projected = _profilesService.AddGroupsAndPermissions(projected).AsQueryable();

    return Ok(projected);
}

Attribute Routing doesn't works on 8.0.0 Preview

Consider following controller class:

[Route("api/[controller]")]
[ApiController]
public class StudentsController: ODataController
{
   ...
}

Attribute routing stopped working after upgrading the Microsoft.AspNetCore.OData Nuget package version to 8.0.0. Preview from version 7.5.0

Swagger is not working with 8.0.0 pre-release

In my project, swagger is throwing this error.
image

I have applied the solutions but they only work with the previous versions not with the 8.0.0 pre-release which I am using in my .net 5.0 Web API project.

OData server crashes when trying to update (PATCH) a property of type NTSTopology.Geometry.Point

I am using Microsoft.AspNetCore.OData 7.5.2 on server and OData Connected Service 0.11.1 on client.

The client code tries to update a property on an Entity with type NTSTopology.Geometry.Point, the client performs the patch call but it crashes on server with this exception

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.MissingMethodException: No parameterless constructor defined for type 'NetTopologySuite.Geometries.Point'.
   at System.RuntimeType.CreateInstanceDefaultCtorSlow(Boolean publicOnly, Boolean wrapExceptions, Boolean fillCache)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, Boolean wrapExceptions)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic, Boolean wrapExceptions)
   at System.Activator.CreateInstance(Type type)
   at Microsoft.AspNet.OData.Delta`1.Reset(Type structuralType)
   at Microsoft.AspNet.OData.Delta`1..ctor(Type structuralType, IEnumerable`1 updatableProperties, PropertyInfo dynamicDictionaryPropertyInfo)
   at Microsoft.AspNet.OData.Delta`1..ctor(Type structuralType, IEnumerable`1 updatableProperties)
   --- End of inner exception stack trace ---
   at Microsoft.AspNet.OData.Formatter.ODataInputFormatter.<>c__DisplayClass7_0.<ReadRequestBodyAsync>b__1(Exception ex)
   at Microsoft.AspNet.OData.Formatter.ODataInputFormatterHelper.ReadFromStreamAsync(Type type, Object defaultValue, IEdmModel model, Uri baseAddress, IWebApiRequestMessage internalRequest, Func`1 getODataRequestMessage, Func`2 getEdmTypeDeserializer, Func`2 getODataPayloadDeserializer, Func`1 getODataDeserializerContext, Action`1 registerForDisposeAction, Action`1 logErrorAction)
   at Microsoft.AspNet.OData.Formatter.ODataInputFormatter.ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)

The client code is this:

            try
            {
                var ctx = new SincroADRODataService.Default.Container(new Uri("http://localhost/SincroADR_Api/odata"));
                var cam = new DataServiceCollection<Camera>(ctx.Cameras.Where(c => c.Id == 41)).SingleOrDefault();
                cam.Location.Y -= 0.2;
                ctx.SaveChanges();
            }
            catch (Exception ex)
            {
                log.LogError(ex, "error");
            }

The server patch method is this:

        [HttpPatch]
        public async Task<IActionResult> Patch([FromODataUri] Int64 key, Delta<Camera> c)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);

            var entity = await ctx.Set<Camera>().FindAsync(key);
            if (entity == null)
                return NotFound();

            c.Patch(entity);
            await ctx.SaveChangesAsync();
            return Updated(entity);
        }

I have no problem whatsoever in updating other properties. Any idea on how to solve this issue apart from adding the parameterless constructor to NTSTopology.Geometry.Point ?

Not able to execute Get call on odata path like D%2F%3FK which is equivalent of D/?K

When we do get on odata path with contains these special characters in combination / with either ? or % or # eg. TestEquipment('D%2F%3FK') ,then we get an exception.

Assemblies affected
Microsoft.AspNetCore.OData 7.4.1

Reproduce steps
Do a get operation on ControllerName('D%2F%3FK')

Expected result
We should be able to execute it and with Get TestEquipment with Name 'D/?K'

Actual result
I am getting following error:
Request URI '/odata/v1/TestEquipment('D%2F%3FK')' does not contain OData path 'TestEquipment('D%2F?K')'

Additional detail
Stack Trace:
"/odata/v1/TestEquipment('D%2F%3FK')-2020-12-15T18:29:12:4653 : Request URI 'https://localhost:44399/odata/v1/TestEquipment('D%2F%3FK')' does not contain OData path 'TestEquipment('D%2F?K')'. - at Microsoft.AspNet.OData.Routing.ODataPathRouteConstraint.RemoveODataPath(String uriString, String oDataPathString)\r\n at Microsoft.AspNet.OData.Routing.ODataPathRouteConstraint.GetODataPath(String oDataPathString, String uriPathString, String queryString, Func1 requestContainerFactory)\r\n at Microsoft.AspNet.OData.Routing.ODataPathRouteConstraint.Match(HttpContext httpContext, IRouter route, String routeKey, RouteValueDictionary values, RouteDirection routeDirection)\r\n at Microsoft.AspNetCore.Routing.RouteConstraintMatcher.Match(IDictionary2 constraints, RouteValueDictionary routeValues, HttpContext httpContext, IRouter route, RouteDirection routeDirection, ILogger logger)\r\n at Microsoft.AspNetCore.Routing.RouteBase.RouteAsync(RouteContext context)\r\n at Microsoft.AspNetCore.Routing.RouteCollection.RouteAsync(RouteContext context)\r\n at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)\r\n at Avl.TfmsServices.Common.TfmsAPIService.MiddleWares.CacheMiddleWare.Invoke(HttpContext context) in D:\AVL\TFMS\TFMSNxtGen\TfmsServices\Common\TfmsAPIService\MiddleWares\CacheMiddleWare.cs:line 70\r\n at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)\r\n at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)\r\n at NSwag.AspNetCore.Middlewares.SwaggerUiIndexMiddleware.Invoke(HttpContext context)\r\n at NSwag.AspNetCore.Middlewares.RedirectToIndexMiddleware.Invoke(HttpContext context)\r\n at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)\r\n at Avl.TfmsServices.Common.TfmsAPIService.MiddleWares.ExceptionMiddleware.InvokeAsync(HttpContext httpContext) in D:\AVL\TFMS\TFMSNxtGen\TfmsServices\Common\TfmsAPIService\MiddleWares\ExceptionMiddleware.cs:line 46"

Is it possible to call AddModel at runtime (not during Startup time) in V8?

Reposting from OData/WebApi#2347. Not sure what is the most appropriate repo for this

Version 8.0 on .NET 5

Question: Is it possible to call "AddModel" at runtime (from my own service)?
The goal is to iterate over:

  • dynamically load an assembly containing the models, dbcontext and the odata controller
  • add the odata model in a separate AssemblyLoadContext
  • make it available for requests
  • use this stuff
  • on a specific request, unload the AssemblyLoadContext from the memory (including the models, EDM, dbcontext and the odata controller)

TIA

OData Attribute Route not working as expected in ASP.NET Core 5 with Microsoft.AspNetCore.OData v8.0.0-preview2

I am working on an ASP.NET Core 5 API with Entity Framework Core 5 and I am using Microsoft.AspNetCore.Odata v8.0.0-preview2 and using endpoint routing.
I have reviewed Sam Xu's blogs; Routing in ASP.NET Core OData 8.0 Preview and ASP.NET Core OData 8.0 Preview for .NET 5

I am integrating with an existing SQL Server database of which I do not control the schema so I have to work with what is provided.

I am constantly fighting with 404 Not Found for API calls in Postman that seem like they should work, but are not getting resolved. I do not know of a way to actually troubleshoot this problem short of trial and error. There must be an easier way to troubleshoot this but I am just not seeing it.

How can I view the route table so I can see the routes derived by OData from my EDM and possibly find my problem?

I had worked with Microsoft OData v7.5.2 on another project with ASP.NET Core 3.1 and with endpoint routing disabled and didn't seem to have this try of problem, but I was also working with my own database schema that I built with EntityFramework Core Migrations and Database Updates so the schema was probably more aligned that the one I am dealing with for my current project.

For example, the existing SQL database I am working with now is provided by a 3rd party vendor and their tables to not have a Primary key identified so I had to spoof it by adding a [Key] attribute to the ID property in the DBContext modelbuilder.

I don't know if that is what is actually causing my difficulty in getting the expected routes to work.

I am using attribute routes, which I understand have priority over convention routes, so I would expect that if I matched up my URL in the request with the attribute routes specified in my controller, it would work.

For example, I have an OrganizationsController, as follows;

[Authorize]
[ODataRoutePrefix("organizations")]
public class OrganizationsController : ODataController
{
    private readonly MyContext context;

    public OrganizationsController(MyContext context)
    {
        this.context = context ?? throw new ArgumentNullException(nameof(context));
    }

    [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported, PageSize = 2000)]
    public IQueryable<Organization> GetOrganizations()
    {
        return context.Organizations;
    }

    [ODataRoute("{id}")]
    [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported)]
    public SingleResult<Organization> GetOrganizationById([FromODataUri] string id)
    {
        return new SingleResult<Organization>(context.Organizations.Where(o => o.OrganizationId == id));
    }

    [ODataRoute("Locations(organizationId={organizationId}")]
    public IQueryable<OrganizationLocation> GetOrganizationLocations([FromODataUri] string organizationId)
    {
        return context.OrganizationLocations.Where(ol => ol.OrganizationId == organizationId);
    }
}

With this controller, I can get the following basic requests to find their routes successfully;

https://localhost:43300//odata-v1/organizations?$count=true&$orderby=Name
https://localhost:43300//odata-v1/organizations/'10000'

But I also have a need to retrieve all of the OrganizationLocation records that are owned by a specific Organization record. What I initially wanted to do was to use a typical REST type of URL, like

https://localhost:43300/odata-v1/organizationLocations/1000/locations

...to return all of the locations linked to the specified organizationId.

And I expected to be able to add attribute routes to my OrganizationLocations controller as shown below;

[Authorize]
[ODataRoutePrefix("organizations({id})")]
public class OrganizationLocationsController : ODataController
{
    private readonly MyContext context;

    public OrganizationLocationsController(MyContext context)
    {
        this.context = context ?? throw new ArgumentNullException(nameof(context));
    }

    [ODataRoute("locations")]
    [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Supported)]
    public IQueryable<OrganizationLocation> GetOrganizationLocations([FromODataUri] string id)
    {
        return context.OrganizationLocations.Where(ol => ol.OrganizationId == id);
    }
}

But the call to this controller always returns a 404 Not Found.

Deserializing Escaped JSON Strings

One of my OData entities contains a field that holds HTML content as a string. So when I send a JSON request body, I have to escape double quotes and other special characters. However, OData is not deserializing these escaped JSON strings correctly. I have reproduced the issue in a minimal repository here.

POST /api/model

Request:

{
    "html": "<div class=\"test\"><\/div>"
}

Response:

{
    "Html": "<div class=\\\"test\\\"></div>"
}

Packages:
.NET Core 3.1
Microsoft.AspNetCore.OData 7.5.4
Microsoft.OData.Core 7.8.1
Microsoft.OData.Edm 7.8.1

Cannot use $count with ODataRoute Attribute

Using the ODataRoute atrribute makes the $count not work.

/// Startup.cs
app.UseOData("odata", "odata", GetEdmModel());

static GetEdmModel()
{
    ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<User>("User");
    builder.EntitySet<User>("Product");
    return builder.GetEdmModel();
}
/// MyController.cs
[EnableQuery]
[ODataRoute("User")]
public IQueryable<User> GetUser() => User.GetQuery());

[EnableQuery]
[ODataRoute("Product")]
public IQueryable<Product> GetProduct() => Product.GetQuery());
http://localhost:5901/odata/User (Works)
http://localhost:5901/odata/User/$count (Not Working)

No controller action is called when the key is int16\short.

Hello,
I use the "ASP.NET Core OData 8.0.0 preview".

If you have an entity with the key of type short\int16, the following controller method is not called:

public IActionResult Get(short key)

To reproduce you can change the ODataRoutingSample with the following update:

ProductsController.cs -> public IActionResult Get(short key)
ODataOperationImportController.cs -> Id = Convert.ToInt16(index)
Product.cs -> Product class -> public short Id { get; set; }

When the key is int32 the following calls work fine but if the key is short\int16 the Get(short key) method is not called.
http://localhost:64771/products(1)

Fabio

'UseExceptionHandler' doesn't work with OData?

I was trying to setup my API to redirect exceptions to an exception handler controller by using app.UseExceptionHandler("/error"), however whenever an error happens, I get a 404 instead.

Why am I getting a 404? I can manually navigate to "myApiUrl/error" and hit the error controller just fine, but when the error happens inside an OData controller in the application, it does not reach the error controller and produces 404 instead.

Here is a sample of my startup:

        public static void Configure(
            IApplicationBuilder app,
            IEdmModelProvider edmModelProvider)
        {
            app
                .UseExceptionHandler("/error")
                .UseHttpsRedirection()
                .UseRouting()
                .UseAuthorization()
                .UseCors(builder => builder.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod())
                .UseEndpoints(endpoints => endpoints.MapOData(edmModelProvider))
                .UseSwagger()
                .UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyApi"));
        }

And here is the error controller:

    [ApiController]
    [ApiExplorerSettings(IgnoreApi = true)]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }

When I transfer this into a standard MVC application, it works just fine, so I suspect it must be something to do with OData that I'm missing or doesn't work properly.

Enable key and function parameter alias

for the key value and function parameter value, it could from query option.

for example: ~/customers/1/default.MyFunction(p1=@p)?@p=4

So far, it's not supported in this repo. please add it.

Please add an overload with an IServiceCollection accessor to AddOData()

Hi,

First and foremost, thanks for the great work, the new way of setting up OData makes much more sense!

I'd like to request adding another overload of the AddOData extension set, such that its setupAction delegate comes with an IServiceProvider argument, so that we can resolve services (e.g. IEdmModel from IoC).

Desired API:

public static IODataBuilder AddOData(this IServiceCollection services, Action<IServiceProvider, ODataOptions> setup);

Controllers with same name in different namespace

Hi, I'm trying to update our current webapi to odata, where I have controllers with the same name in different namespaces.
The underlying entity types are in different DbContexts (different Databases), the DbContexts are in separate IEdmModels with different odata prefixes.

  • route odata/erp/customers (prefix: odata/erp) should resolve to controller WebService.Erp.Controllers.CustomersController
  • route odata/crm/customers (prefix: odata/crm) should resolve to controller WebService.Crm.Controllers.CustomersController

I'm getting an AmbiguousMatchException: The request matched multiple endpoints. when calling one endpoint and a 404 reply when requesting the other, so both requests route to the same controller.

Could someone point me in the right direction or how to tackle this problem? Am I missing something here? I tried to set a custom routing convention (https://devblogs.microsoft.com/odata/routing-in-asp-net-core-8-0-preview/), but to no effect.

Btw, i noticed that Order in EntityRoutingConvention is not virtual - don't know if for a reason, but seems odd as it's virtual in the others:

NullReferenceException: Value cannot be null. (Parameter 'edmModel')

Hi,

When trying to access an OData controller action I get this exception:

System.ArgumentNullException: Value cannot be null. (Parameter 'edmModel')
    at Microsoft.AspNetCore.OData.Edm.EdmClrTypeMapExtensions.GetEdmType(IEdmModel edmModel, Type clrType)
    at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.GetModel(Type elementClrType, HttpRequest request, ActionDescriptor actionDescriptor)
    at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.GetODataQueryContext(Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
    at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.GetModelBoundPageSize(ActionExecutedContext actionExecutedContext, Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
    at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext, Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
    at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext)
    at Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
    at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
    at MyProject.Services.SubdomainTenantInitializerMiddleware.Invoke(HttpContext context, ITenantProvider tenantProvider, ITenantOrchestrationService tenantOrchestrationService) in D:\Users\Shimmy\Source\Repos\MyProject\MyProject.Api\Services\SubdomainTenantInitializerMiddleware.cs:line 35
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Here's my setup:

void ConfigureOData(IServiceCollection services)
{
  services.AddTransient<EdmModelBuilder>();
  services.AddTransient(serviceProvider =>
    serviceProvider.GetRequiredService<EdmModelBuilder>().GetEdmModel());

  services
    .AddOptions<ODataOptions>()
    .Configure<IEdmModel>((odataOptions, edmModel) =>
    {
      Debug.Assert(edmModel != null);

      odataOptions
      .SetAttributeRouting(true)
      .Select()
      .Expand()
      .Filter()
      .OrderBy()
      .SetMaxTop(128)
      .Count()
      .AddModel(edmModel);
    });

  services.AddOData();
}

image

Improve function & function parameter select strategy

In current implementation, we consider that:

if function parameters defined in Edm function shows in the action, we consider the action is the candidate.

However, we should improve it to that:

Except the known parameters, for example (CancellationToken, ODataQueryOption, ODataQueryOptions, ODataPath, etc), we should exactly match the remaining parameters with the Edm function parameters.

Real world examples for OData in ASP.NET Core applications

Hi,

I'm very interesting in OData, I think it's super powerfull and I want to use in my ASP.NET Core APIs and also Microsoft uses in many products and I'm looking for real world examples more than "Hello worlds", for example, How Microsoft builts his APIs, some guidances to be implement it correctly.

I prefer to use OData instead GraphQL, but I think is easier to find more docs and samples about GraphQL than OData.

Kind regards!

8.0.0-preview2 and EnableQueryAttribute without model ("Non-Edm Approach")

Trying to use EnableQueryAttribute without model throws an exception in 8.0.0-preview2:

System.ArgumentNullException: Value cannot be null. (Parameter 'edmModel')
at Microsoft.AspNetCore.OData.Edm.EdmClrTypeMapExtensions.GetEdmType(IEdmModel edmModel, Type clrType)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.GetModel(Type elementClrType, HttpRequest request, ActionDescriptor actionDescriptor)
at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.GetODataQueryContext(Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)

This seems to work OK with 7.5.2.

Based on the code in EnableQueryAttribute, I think the idea is that this should work:

        if (model == EdmCoreModel.Instance || model.GetEdmType(elementClrType) == null)
        {
            // user has not configured anything or has registered a model without the element type
            // let's create one just for this type and cache it in the action descriptor
            model = actionDescriptor.GetEdmModel(request, elementClrType);
        }

More documents

We recently upgraded to Net 5 preview and found out about yet another OData nuget package for .Net Core (we used to get https://github.com/OData/WebApi/tree/master/src/Microsoft.AspNetCore.OData).

There are many breaking changes. Routing above all confuses me. To make matters worse Swagger also stopped working giving this exception

Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0

while there is already method declaration attributes above all actions. I even don't know if ODataRoute attributes still make sense or we should just simply use Routes.

Everything is messy and the whole transition looks crud, nondeterministic, haphazard and unclear. All previous tips and tricks on the web that used to work are a rolling dice now. You have to simply go with trial and error to look which is which and what features are still there and you can count on.

Please, do provide more documents and samples, with the lastest changes in mind. Now! Not a year from now! Do not refer to some repository from 10 years ago of WebApi ages. Not everyone can afford the time for this many breaking changes and find their way through source codes, so please be more caring and speak to the community! Just two simple (and repetitive in content) articles on devblogs with absolutely bare minimum use case scenario cannot cover all the promised and relied features of previous versions...

using $apply and groupby to get distinct values -> Exception

Hi there,

I am trying to get the distinct records from my EF mapped db using the following URL query:
https://localhost:44372/odata/ODataProfiles?$apply=groupby((UserVisibilityKey))

The following exception is thrown, and I dont know where to start:

An unhandled exception occurred while processing the request.
InvalidCastException: Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression' to type 'System.Linq.Expressions.ConstantExpression'.
Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor.GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)

InvalidCastException: Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlConstantExpression' to type 'System.Linq.Expressions.ConstantExpression'.
Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor.GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection nodes, Func<T, T> elementVisitor)
System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection nodes, Func<T, T> elementVisitor)
System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor+ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, out RelationalCommandCache relationalCommandCache, out LambdaExpression relatedDataLoaders)
Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor(Expression query)
Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery(Expression query, bool async)
Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore(IDatabase database, Expression query, IModel model, bool async)
Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler+<>c__DisplayClass12_0.b__0()
Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery(object cacheKey, Func<Func<QueryContext, TResult>> compiler)
Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync(Expression query, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync(Expression expression, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable.GetAsyncEnumerator(CancellationToken cancellationToken)
Microsoft.AspNetCore.Mvc.Infrastructure.AsyncEnumerableReader.ReadInternal(object value)
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor.ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, object asyncEnumerable, Func<object, Task> reader)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
IdentityServer.OAuth.AuthExtension.AppBuilderExtensions+<>c+<b__2_1>d.MoveNext() in AppBuilderExtensions.cs
+
await next();
Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
NSwag.AspNetCore.Middlewares.SwaggerUiIndexMiddleware.Invoke(HttpContext context)
NSwag.AspNetCore.Middlewares.RedirectToIndexMiddleware.Invoke(HttpContext context)
NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
NSwag.AspNetCore.Middlewares.SwaggerUiIndexMiddleware.Invoke(HttpContext context)
NSwag.AspNetCore.Middlewares.RedirectToIndexMiddleware.Invoke(HttpContext context)
NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
IdentityServer.Middleware.BadRequestHandlerMiddleware.Invoke(HttpContext context, ILogger logger) in BadRequestHandlerMiddleware.cs
+
await _next.Invoke(context);
IdentityServer.Middleware.BadRequestHandlerMiddleware.Invoke(HttpContext context, ILogger logger) in BadRequestHandlerMiddleware.cs
+
throw;
Microsoft.AspNetCore.OData.Batch.ODataBatchMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

I get this error when using any property on the query, including simple string properties.

IS this something that this version (.net5) supports?

Thanks for your time

Problem using $select & $top together in EntityFramework Core Oracle

When I want to retrive specific columns (column1, column2) the generated query is like:

 SELECT :TypedProperty_1 "ModelID", N'NAME' "Name", "t"."Column1" "Value", N'NUMBER0' "Name", "t"."Column2" "Value"
FROM "SomeTable" "t"
FETCH FIRST :TypedProperty_2 ROWS ONLY

This is not allowed by Oracle
image

Once I removed the $top it works correctly
image

This is the error detail
Microsoft.EntityFrameworkCore.Database.Command: Error: 2020-10-02 16:08:22.856434 ThreadID:28 (ERROR) OracleRelationalCommand.ExecuteReaderAsync() : Oracle.ManagedDataAccess.Client.OracleException (0x80004005): ORA-00918: column ambiguously defined
at OracleInternal.ServiceObjects.OracleConnectionImpl.VerifyExecution(Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, SqlStatementType sqlStatementType, Int32 arrayBindCount, OracleException& exceptionForArrayBindDML, Boolean& hasMoreRowsInDB, Boolean bFirstIterationDone)
at OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteReader(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, OracleDataReaderImpl& rdrImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[] scnForExecution, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, Int64& internalInitialLOBFS, OracleException& exceptionForArrayBindDML, OracleConnection connection, OracleLogicalTransaction& oracleLogicalTransaction, IEnumerable`1 adrianParsedStmt, Boolean isDescribeOnly, Boolean isFromEF)
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior)
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
at Oracle.EntityFrameworkCore.Storage.Internal.OracleRelationalCommandBuilderFactory.OracleRelationalCommandBuilder.OracleRelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Database.Command: Error: Failed executing DbCommand (245ms) [Parameters=[:TypedProperty_1='?' (Size = 2000), :TypedProperty_0='?' (Size = 12), :TypedProperty_2='?' (DbType = Int32)], CommandType='Text', CommandTimeout='0']
SELECT :TypedProperty_1 "ModelID", N'NAME' "Name", "t"."NAME" "Value", N'NUMBER0' "Name", "t"."NUMBER0" "Value"
FROM "TINWSYS" "t"
WHERE "t"."NUMBER0" = :TypedProperty_0
ORDER BY "t"."NUMBER0"
FETCH FIRST :TypedProperty_2 ROWS ONLY

Upgrading to 8.0.0-preview2 broke some dependencies

TypeLoadException: Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Unknown location

TypeLoadException: Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Unknown location

TypeLoadException: Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Unknown location

TypeLoadException: Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Unknown location

ReflectionTypeLoadException: Unable to load one or more of the requested types.
Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
Could not load type 'Microsoft.AspNet.OData.Query.HandleNullPropagationOption' from assembly 'Microsoft.AspNetCore.OData, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.


System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
System.Reflection.RuntimeAssembly.get_DefinedTypes()
Microsoft.AspNetCore.Mvc.ApplicationParts.AssemblyPart.get_Types()
Microsoft.AspNetCore.Mvc.Controllers.ControllerFeatureProvider.PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager.PopulateFeature<TFeature>(TFeature feature)
Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerActionDescriptorProvider.GetControllerTypes()
Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerActionDescriptorProvider.GetDescriptors()
Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerActionDescriptorProvider.OnProvidersExecuting(ActionDescriptorProviderContext context)
Microsoft.AspNetCore.Mvc.Infrastructure.DefaultActionDescriptorCollectionProvider.UpdateCollection()
Microsoft.AspNetCore.Mvc.Infrastructure.DefaultActionDescriptorCollectionProvider.Initialize()
Microsoft.AspNetCore.Mvc.Infrastructure.DefaultActionDescriptorCollectionProvider.GetChangeToken()
Microsoft.AspNetCore.Mvc.Routing.ActionEndpointDataSourceBase+<>c__DisplayClass11_0.<Subscribe>b__0()
Microsoft.Extensions.Primitives.ChangeToken.OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
Microsoft.AspNetCore.Mvc.Routing.ActionEndpointDataSourceBase.Subscribe()
Microsoft.AspNetCore.Mvc.Routing.ControllerActionEndpointDataSource..ctor(ControllerActionEndpointDataSourceIdProvider dataSourceIdProvider, IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory, OrderedEndpointsSequenceProvider orderSequence)
Microsoft.AspNetCore.Builder.ControllerEndpointRouteBuilderExtensions.GetOrCreateDataSource(IEndpointRouteBuilder endpoints)
Microsoft.AspNetCore.Builder.ControllerEndpointRouteBuilderExtensions.MapControllers(IEndpointRouteBuilder endpoints)
IME.CustomerManagement.Api.OData.Broker.Startup+<>c.<Configure>b__9_0(IEndpointRouteBuilder endpoints) in Startup.cs
Microsoft.AspNetCore.Builder.EndpointRoutingApplicationBuilderExtensions.UseEndpoints(IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
IME.CustomerManagement.Api.OData.Broker.Startup.Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) in Startup.cs
System.RuntimeMethodHandle.InvokeMethod(object target, object[] arguments, Signature sig, bool constructor, bool wrapExceptions)
System.Reflection.RuntimeMethodInfo.Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(object instance, IApplicationBuilder builder)
Microsoft.AspNetCore.Hosting.ConfigureBuilder+<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
Microsoft.AspNetCore.Hosting.GenericWebHostBuilder+<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter+<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
Microsoft.AspNetCore.Server.IIS.Core.IISServerSetupFilter+<>c__DisplayClass2_0.<Configure>b__0(IApplicationBuilder app)
Microsoft.AspNetCore.Server.IIS.Core.IISServerSetupFilter+<>c__DisplayClass2_0.<Configure>b__0(IApplicationBuilder app)
Microsoft.AspNetCore.HostFilteringStartupFilter+<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)

Remove CompatibilityOption DisableCaseInsensitiveRequestPropertyBinding

We can remove CompatibilityOptions.DisableCaseInsensitiveRequestPropertyBinding in the next breaking change release.
Added in this PR OData/WebApi#2311

Assemblies affected

*Which assemblies and versions are known to be affected e.g. OData WebApi lib 7.7.3

Reproduce steps

The simplest set of steps to reproduce the issue. If possible, reference a commit that demonstrates the issue.

Expected result

What would happen if there wasn't a bug.

Actual result

What is actually happening.

Additional detail

Optional, details of the root cause if known. Delete this section if you have no additional details to add.

AddODataQueryFilter not available in ASP.NET Core OData 8.0 (Preview)

Could we get a timeline for incorporation of AddODataQueryFilter, please?

I know that it is a preview, but I have migrated a complex customer application to Net 5.0 and the only thing missing to run it is AddODataQueryFilter ...

Assemblies affected

AspNetCore.OData Odata8.0.0-preview2

Dynamic property is null with endpoint routing

I was following this post to build a service with dynamic property support. The example in github works fine for me. However, when I try to enable endpoint routing by adding [Route("odata/test")] to the post method. The dynamic property is always null. Is this a known issue? Any comments are highly appreciated.

[EnableQuery] attribute fails on .NET5 web api

I am trying to add OData to a .net5 web API but once added the endpoints starts throwing ArgumentNullException

Assemblies affected

OData WebApi lib 8.0.0-preview3

Reproduce steps

  • Create a new .net 5 web API project
  • Add Microsoft.AspNetCore.OData (8.0.0-preview3) from Nuget
  • In Startup.cs add services.AddOData();
  • In any controller method add [EnableQuery] attribute

Expected result

The endpoint should work with OData or at least with simple query parameters.

Actual result

It throws the following stack trace when a query parameter is added to the URL.

The endpoint works normally if there are no query parameters. Say the URL is /weather-forecast then /weather-forecast?q=123456 will fail with the following stack trace.

System.ArgumentNullException: Value cannot be null. (Parameter 'key')
   at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
   at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)
   at Microsoft.AspNetCore.OData.ODataOptions.GetODataServiceProvider(String prefix)
   at Microsoft.AspNetCore.OData.Extensions.HttpRequestExtensions.GetSubServiceProvider(HttpRequest request)
   at Microsoft.AspNetCore.OData.Query.ODataQueryOptions..ctor(ODataQueryContext context, HttpRequest request)
   at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.CreateAndValidateQueryOptions(HttpRequest request, ODataQueryContext queryContext)
   at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
   at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext, Object responseValue, IQueryable singleResultCollection, ControllerActionDescriptor actionDescriptor, HttpRequest request)
   at Microsoft.AspNetCore.OData.Query.EnableQueryAttribute.OnActionExecuted(ActionExecutedContext actionExecutedContext)
   at Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at SharedLib.Auth.ExtractJwtMiddleware.Invoke(HttpContext context) in /Users/nilotpal/code/preventure/live/stratos/Src/SharedLib/Auth/ExtractJwtMiddleware.cs:line 40
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Microsoft.OData.ODataException when returning a SingleResult from a function

Here is an issue that seems to occur in the current version 8.0.0-preview (and also in the current master branch). It worked correctly in version 7.5.1.

I have a controller method that returns an single entity IQueryable<User>, because I would like to use it with expand:

[EnableQuery]
[HttpGet("current")]
public ActionResult<SingleResult<User>> Current()
{
    var currentUser = dbContext.Users().Where(u => u.Id == _currentUser.Id);
    return Ok(SingleResult.Create(currentUser));
}

It's defined in the EdmModel like this:

builder.EntityType<User>().Collection
    .Function("Current")
    .Returns<User>();

This throws an ODataException during serialization:

When writing a JSON response, a user model must be specified and the entity set and entity type must be passed to the ODataMessageWriter.CreateODataResourceWriter method or the ODataResourceSerializationInfo must be set on the ODataResource or ODataResourceSet that is being written.

Stacktrace:
   at Microsoft.OData.ODataResourceTypeContext.ValidateAndReturn[T](T value)
   at Microsoft.OData.ODataResourceTypeContext.get_NavigationSourceName()
   at Microsoft.OData.ODataContextUrlInfo.Create(ODataResourceTypeContext typeContext, ODataVersion version, Boolean isSingle, ODataUri odataUri)
   at Microsoft.OData.JsonLight.ODataJsonLightResourceSerializer.<>c__DisplayClass11_0.<WriteResourceContextUri>b__0()
   at Microsoft.OData.JsonLight.ODataJsonLightSerializer.WriteContextUriProperty(ODataPayloadKind payloadKind, Func`1 contextUrlInfoGen, ODataContextUrlInfo parentContextUrlInfo, String propertyName)
   at Microsoft.OData.JsonLight.ODataJsonLightResourceSerializer.WriteResourceContextUri(ODataResourceTypeContext typeContext, ODataContextUrlInfo parentContextUrlInfo)
   at Microsoft.OData.JsonLight.ODataJsonLightWriter.StartResource(ODataResource resource)
   at Microsoft.OData.ODataWriterCore.<>c__DisplayClass121_0.<WriteStartResourceImplementation>b__0()
   at Microsoft.OData.ODataWriterCore.InterceptException(Action action)
   at Microsoft.OData.ODataWriterCore.WriteStartResourceImplementation(ODataResource resource)
   at Microsoft.OData.ODataWriterCore.WriteStart(ODataResource resource)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteResource(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType) in C:\Users\..\AspNetCoreOData\src\Microsoft.AspNetCore.OData\Formatter\Serialization\ODataResourceSerializer.cs:line 362
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext) in C:\Users\..\AspNetCoreOData\src\Microsoft.AspNetCore.OData\Formatter\Serialization\ODataResourceSerializer.cs:line 85
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) in C:\Users\..\AspNetCoreOData\src\Microsoft.AspNetCore.OData\Formatter\Serialization\ODataResourceSerializer.cs:line 62
   at Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatterHelper.WriteToStream(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, HttpRequest request, IHeaderDictionary requestHeaders, ODataSerializerProvider serializerProvider) in C:\Users\..\AspNetCoreOData\src\Microsoft.AspNetCore.OData\Formatter\ODataOutputFormatterHelper.cs:line 153
   at Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) in C:\Users\..\AspNetCoreOData\src\Microsoft.AspNetCore.OData\Formatter\ODataOutputFormatter.cs:line 230

Notes:

  1. It works fine if I return the same result from a normal controller method without a special definition in the EdmModel, so SingleResult itself is not the problem:
[EnableQuery]
[HttpGet("{key}")]
public ActionResult<SingleResult<User>> Get(Guid key)
{
    var currentUser = dbContext.Users().Where(u => u.Id == key);
    return Ok(SingleResult.Create(currentUser));
}
  1. The exception also occurs when I rewrite the method like this:
[EnableQuery]
[HttpGet("current")]
public IActionResult Current()
{
    var currentUser = dbContext.Users().Where(u => u.Id == _currentUser.Id).Single();
    return Ok(currentUser);
}
  1. However it suddenly works if I remove the Single() call from the query:
    var currentUser = dbContext.Users().Where(u => u.Id == _currentUser.Id);
  1. Defining the function like this also does not work. This was the previous definition that worked in 7.5.1:
builder.EntityType<User>().Collection
    .Function("Current")
    .ReturnsFromEntitySet<User>("Users");

So the combination of SingleResult with the function definition seems to be the reason for this Exception. Is this a bug or am I doing something wrong?

[Question] Is it possible to add support for a custom value type?

Suppose I have a custom type that abstracts some concept in a higher level of abstraction than a primitive type does (string, int, etc). I could think of a few such types:

  • DateTime, DateTimeOffset: natively supported (Edm.DateTimeOffset for ex)
  • TimeSpan: natively supported as Edm.Duration
  • Uri: not natively supported in OData (gets mapped as a custom complex type)
  • a Temperature class, which accepts strings of the form 15°C or 50°F
  • a Distance class, like 5m or 10ft
  • so on and so forth

Additionally, some types had support added later, like Spatial types.

What does one need to do to make these custom types available as if they were native constructs in an OData API?

  • How to make them serialize/deserialize into their corresponding string/number representations?
  • How to make them appear as a custom EDM type in the API's metadata?
  • How to allow them to be used in queries and filters?

I have not found any documentation around this whatsoever, so I'm asking the owners to see if there is any extension point and how they work.

Microsoft.OData.ODataException-The query specified in the URI is not valid. Could not find a property named 'tag' on type

I am using oData version - 7.5.0 and .net core version -3.1

I have a get endpoint request - endpt{id} which retrieves the table row based on the passed integer id. So by default all the columns are returned from the table.

I want to return only a subset of total columns of table. So, I am trying to use oData $select I query as

{baseurl}/endpoint/3?$select=tag
where Tag is one of the column name.

In my startup.cs file, Confiure method , registered oData this way..
image

My Get Endpoint..

[HttpGet]
   [EnableQuery]
   [ODataRoute("endpoint({id})")]
   [Route("endpoints", Name = "endpoints", Order = 0)]
   [Route("endpoint/{id}", Name = "endpoint", Order = 1)]      
   [ProducesResponseType(typeof(APIResponseModel<List<EndPoints>>), StatusCodes.Status200OK)]      
   public APIResponseViewModel<List<EndPoints>> GetEndPoints([FromODataUri] int id)
   {
       List<EndPoints> ListOfEndPoints;
       try
       {
           if (id.Equals(0))
           {
               ListOfEndPoints = db.EndPoints.ToList();
           }
           else
           {
               ListOfEndPoints = db.EndPoints.Where(x => x.ID.Equals(id)).ToList();                    
           } 

         this.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK;

           return new APIResponseModel<List<EndPoints>>
           {
               StatusCode = (int)HttpStatusCode.OK,
               Message = "Success",
               Result = ListOfEndPoints,
               Count = ListOfEndPoints.Count
           };              
       }
       catch (Exception e)
       {
          //catch exp
       }
}

Breakpoint hits, but in _id_ I get only the ID that I passed - 3. select tag doesn't comes up, and from the implemented logic,
it returns complete row matching that id with all the columns.
But at postman response, I recieve an error from oDataUriParser..
`{
"Message": "The query specified in the URI is not valid. Could not find a property named 'tag' on type ''.",

"ExceptionMessage": "Could not find a property named 'tag' on type ''.",

"ExceptionType": "Microsoft.OData.ODataException",

"StackTrace": " at Microsoft.OData.UriParser.SelectPathSegmentTokenBinder.ConvertNonTypeTokenToSegment(PathSegmentToken tokenIn, IEdmModel model, IEdmStructuredType edmType, ODataUriResolver resolver, BindingState state)\r\n at Microsoft.OData.UriParser.SelectExpandBinder.ProcessSelectTokenPath(PathSegmentToken tokenIn)\r\n at Microsoft.OData.UriParser.SelectExpandBinder.GenerateSelectItem(SelectTermToken tokenIn)\r\n at Microsoft.OData.UriParser.SelectExpandBinder.Bind(ExpandToken expandToken, SelectToken selectToken)\r\n at Microsoft.OData.UriParser.SelectExpandSemanticBinder.Bind(ODataPathInfo odataPathInfo, ExpandToken expandToken, SelectToken selectToken, ODataUriParserConfiguration configuration, BindingState state)\r\n at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseSelectAndExpandImplementation(String select, String expand, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)\r\n at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseSelectAndExpand()\r\n at Microsoft.AspNet.OData.Query.SelectExpandQueryOption.get_SelectExpandClause()\r\n at Microsoft.AspNet.OData.Query.Validators.SelectExpandQueryValidator.Validate(SelectExpandQueryOption selectExpandQueryOption, ODataValidationSettings validationSettings)\r\n at Microsoft.AspNet.OData.Query.SelectExpandQueryOption.Validate(ODataValidationSettings validationSettings)\r\n at Microsoft.AspNet.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n at Microsoft.AspNet.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.ValidateQuery(HttpRequest request, ODataQueryOptions queryOptions)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.CreateAndValidateQueryOptions(HttpRequest request, ODataQueryContext queryContext)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.<>c__DisplayClass1_0.b__1(ODataQueryContext queryContext)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, Func2 modelFunction, IWebApiRequestMessage request, Func2 createQueryOptionFunction)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.OnActionExecuted(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, IWebApiRequestMessage request, Func2 modelFunction, Func2 createQueryOptionFunction, Action1 createResponseAction, Action3 createErrorAction)"
}`
How to call $select on paramterized endpoint. I must be missing some point. please help me get through this.

OData function works with one parameter but not two or more

I am working on a greenfield ASP.NET 5 (net5) API and we are using Microsoft OData v8.00-preview3. I have created a function with one parameter that is working correctly. However, when I add a second parameter to that same function, I get a 404 not found when I run the API and try to hit the endpoint function in Postman.

Here is my Edm model builder. Note: With the 2nd parameter commented out, This function works properly;

   public static IEdmModel GetEdmModelV1()
   {
        var builder = new ODataConventionModelBuilder();

        var organizationModel = builder.EntitySet<Organization>("Organizations").EntityType.HasKey(o => o.OrganizationId);

        var productPrice = organizationModel.Collection
            .Function("GetPrice")
            .Returns<ProductPrice>();
        productPrice.Parameter<string>("organizationId").Required();
        //productPrice.Parameter<string>("partId").Required();

        return builder.GetEdmModel();
    }

Here is my controller. Note: The Get and Get() method work just fine. And the GetPrice method with just one parameter gets hit as expected and stops at the marked debug breakpoint. However, if I uncomment the 2nd parameter in the edm model builder class and replace the ODataRoute and method signature of the GetPrice method with the lines that are currently commented out below, I get a 404 Not Found error and the GetPrice method marked breakpoint is never hit.

    [Authorize]
    [ODataModel("odata-v1")]
    public class OrganizationsController : ControllerBase
    {
        private readonly CdContext context;

        public OrganizationsController(CdContext context)
        {
            this.context = context ?? throw new ArgumentNullException(nameof(context));
        }

        [HttpGet]
        [EnableQuery]
        public IEnumerable<Organization> Get()
        {
            return context.Organizations;
        }

        [EnableQuery]
        public IActionResult Get(string key)
        {
            context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
            var entity = context.Organizations.SingleOrDefault(o => o.OrganizationId == key);
            if (entity == null)
            {
                return NotFound($"No organization was found with OrganizationId = {key}");
            }

            return Ok(entity);
        }

        [HttpGet]
        //[ODataRoute("ProductPrices(organizationId={organizationId}, partId={partId})")]
        [ODataRoute("GetPrice(organizationId={organizationId}")]
        //public ProductPrice ProductPrice([FromODataUri] string organizationId, [FromODataUri] string partId)
        public IActionResult GetPrice([FromODataUri] string organizationId)
        {
            ...   // <-- Breakpoint placed on this line
            ...
            ...
        }
    }

Here is the URL in Postman for the single parameter function that works and the two parameter function that get the 404 Not Found.

https://{{url}}/odata-v1/organizations/GetPrice(OrganizationId='10001')  <-- Working single parameter request URL

https://{{url}}/odata-v1/organizations/GetProductPrice(organizationId='10001', partId='100000-01')  <-- This does not work and gets a 404 Not Found response

I should mention that the second parameter, PartId, is not a field in the Organization Model. I just need that value for the processing within the controller method

Is this a bug or am I missing something.

Updating ODataSamples for creating dynamically EDM models

There is a great sample which shows how to dynamically create an EDM model and bind it into OData pipeline for an older version. Can this sample (and the others) be updated and included in this project? It will help me and alot of other people to understand the new version.

Routing question - how opinionated is it?

Hi,

My team and I have an API working on netcore3.1 using the v7 OData library. We are experimenting with .NET 5 and the v8 preview and are trying to build the following structure;

Lets say we have User and Account entities. Account can contain many users.

The URLs should look like the following;

/accounts
Get all accounts

/accounts/{id}
Get one account

/users
Get all users

/users/{id}
Get one user

/accounts/{accountId}/users
Get all users in an account

I've gotten as far as creating a UsersController and AccountsController with a matching EDM model (whose entities are actually DTOs). This gives me /users and /accounts seemingly working, how would I go about implementing the other endpoints? Normally I'd just use [HttpGet("some_route")] but it looks like the OData stuff has a sort of alternate routing system using its own attribute [ODataRoute] which looks to be only partially documented, and the relationship between it and [HttpGet] etc. is quite unclear. I've tried using this but it seems to do nothing, I just get 404 responses.

I realise that our URL structure might not be exactly to the OData spec (e.g. accounts/{id} vs. accounts({id})) but I can't seem to get either working, and surely the point of having the attribute based routes is so we can choose our own?

In the v7 library we simply ignored all the EDM stuff (we aren't necessarily interested in having OData-based actions etc.) and just threw ODataQueryOptions<T> parameters on all the methods we needed OData query support for, using ApplyTo and Automapper to do the work, but this doesn't seem to work properly in v8, unless I'm misunderstanding something.

Any help would be much appreciated.

'UpdatedODataResult' and 'CreatedODataResult' are not convertible to 'ActionResult<T>'

Both OData-specific action result types UpdatedODataResult and CreatedODataResult do not currently support being converted into a ActionResult<T> value. This is because neither type inherit from the abstract ActionResult type, which is what allows the automatic conversion to take place.

From my understanding, this gap in functionality occurred when the core MVC team introduced ActionResult<T> and the ActionResult base class. At that time, these OData-specific types already existed and only implemented IActionResult.

Allowing the OData results to also be convertible to ActionResult<T> will result in a more consistent and friendly framework (imagine migrating normal controllers to OData for example, and the "principle of least astonishment" violation) as well as provide better readability by exposing the type on the return explicitly (be it for the developers, or tools like Swagger).

Nested $expand writes wrong SQL when PageSize is present on [EnableQuery]

I have Customer and ImageData entities and a link table CustomerImage that models a many-to-may relationship. Therefore I can use an $expand inside an $expand to get the images for a customer. For example:

/odata/customer?$top=3&$expand=CustomerImages($expand=ImageData)

This works when the controller action has the attribute [EnableQuery] but not [EnableQuery(PageSize = 100)]. It appears that different SQL is produced which doesn't even parse in the latter case.

EF model:

    public class DbContextCustomers : DbContext
    {
        public DbContextCustomers(DbContextOptions<DbContextCustomers> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // Configuration.
            modelBuilder.Entity<CustomerImage>().HasKey(z => new { z.CustomerID, z.ImageDataID });
            modelBuilder.Entity<CustomerImage>().HasOne(z => z.ImageData);

            modelBuilder.Entity<Customer>().HasMany<CustomerImage>(z => z.CustomerImages);
        }

        public DbSet<Customer> Customer { get; set; }
        public DbSet<ImageData> ImageData { get; set; }

    }

    public class Customer
    {
        [Key]
        public virtual int CustomerID { get; set; }
        public virtual int Number { get; set; }
        public virtual string Surname { get; set; }
        public virtual IEnumerable<CustomerImage> CustomerImages { get; set; }
    }

    public class ImageData
    {
        [Key]
        public virtual int ImageDataID { get; set; }
        public virtual DateTime ImageDateTime { get; set; }
        public virtual int PropertyID { get; set; }
        public virtual int? UserID { get; set; }

        // Will come out base64 encoded.
        [Column("ImageData")]// CS0542: member names cannot be the same as their enclosing type.
        public virtual byte[] Image { get; set; }
    }

    public class CustomerImage
    {
        [Key]
        public virtual int CustomerID { get; set; }

        [Key]
        public virtual int ImageDataID { get; set; }

        public virtual ImageData ImageData { get; set; }
    }

Controller:

    [Authorize(Roles = Role.RightToBeForgottenCreate)]
    [Route("odata/[controller]")]
    public class CustomerController : ControllerBase
    {
        private readonly ILogger<CustomerController> _logger;
        private readonly DbContextCustomers _DbContextCustomers;

        public CustomerController(
            ILogger<CustomerController> logger,
             DbContextCustomers dbContextCustomers
           )
        {
            _logger = logger;
            _DbContextCustomers = dbContextCustomers;
        }

        [EnableQuery] // Adding (PageSize = 100) makes expanding ImageData break.
        [HttpGet]
        public IQueryable<Customer> Get()
        {
            return _DbContextCustomers.Customer;
        }
    }

The invald query is:

SELECT [t0].[Number], [t0].[Surname], [t0].[CustomerID], [t2].[ModelID], [t2].[CustomerID], [t2].[ImageDataID], [t2].[ImageType], [t2].[UseInstanceForProperties], [t2].[Name], [t2].[ModelID0], [t2].[ImageDataID0], [t2].[ImageData], [t2].[ImageDateTime], [t2].[PropertyID], [t2].[UserID], [t2].[IsNull]
FROM (
    SELECT TOP(@__TypedProperty_6) [t].[Number], [t].[Surname], [t].[CustomerID]
    FROM (
        SELECT TOP(@__TypedProperty_5) [c].[CustomerID], [c].[Number], [c].[Surname]
        FROM [Customer] AS [c]
        ORDER BY [c].[CustomerID]
    ) AS [t]
    ORDER BY [t].[CustomerID]
) AS [t0]
OUTER APPLY (
    SELECT @__TypedProperty_3 AS [ModelID], [t].[CustomerID], [t].[ImageDataID], [t].[ImageType], CAST(1 AS bit) AS [UseInstanceForProperties], N'ImageData' AS [Name], @__TypedProperty_4 AS [ModelID0], [i].[ImageDataID] AS [ImageDataID0], [i].[ImageData], [i].[ImageDateTime], [i].[PropertyID], [i].[UserID], CAST(0 AS bit) AS [IsNull]
    FROM (
        SELECT TOP(@__TypedProperty_2) [c0].[CustomerID], [c0].[ImageDataID], [c0].[ImageType]
        FROM [CustomerImage] AS [c0]
        WHERE ([t0].[CustomerID] = [c0].[CustomerID]) AND ([c0].[ImageType] = @__TypedProperty_1)
        ORDER BY [c0].[CustomerID], [c0].[ImageDataID]
    ) AS [t1]
    INNER JOIN [ImageData] AS [i] ON [t].[ImageDataID] = [i].[ImageDataID]
) AS [t2]
ORDER BY [t0].[CustomerID], [t2].[CustomerID], [t2].[ImageDataID], [t2].[ImageDataID0]

Intermittent error on serialization in aspnetcore odata 7.5.4

Getting intermittent serialization errors in 7.5.4 under load, worked without issues in 7.5.2

We use a class IgnoreDefaultEntityPropertiesSerializer : ODataResourceSerializer that on CreateStructuralProperty returns null if
the value of the created ODataproperty is of primitive type and has a default value.
Prop is created by calling base.CreateStructuralProperty(structuralProperty, resourceContext);

The error happens when multiple (16) concurrent calls are made from a webpage, typically 10-20% of the time one of the calls will fail with the error:
System.InvalidOperationException: The EDM instance of type '[xxx Nullable=True]' is missing the property 'yyy'..

--
System.InvalidOperationException: The EDM instance of type '[xxx Nullable=True]' is missing the property 'yyy'.
at Microsoft.AspNet.OData.ResourceContext.GetPropertyValue(String propertyName)
at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSerializer.CreateStructuralProperty(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)
at [ournamespace].Server.Configuration.Serialization.IgnoreDefaultEntityPropertiesSerializer.CreateStructuralProperty(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)
at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSerializer.CreateStructuralPropertyBag(SelectExpandNode selectExpandNode, ResourceContext resourceContext)

Parallel requests cause responses to get mixed

When 2 requests are performed at the same time, there is a possibility that one's response will contain 2 concatenated JSON strings

{"@odata.context":"http://localhost:64149/odata/$metadata#Books(Author())","value":[]}{"@odata.context":"http://localhost:64149/odata/$metadata#Books(Author())","value":[]}

and the other will crash with System.ObjectDisposedException: Cannot write to the response body, the response has completed. Object name: 'HttpResponseStream' or System.InvalidOperationException: Reading is already in progress..

I've created a minimal repo you can clone and try to reproduce the issue. It uses efcore with in memory database but the same happens with SQL Server. I've also added a readme for the few steps required to reproduce the issue.

Tried the same with an Api Controller and had no issues.
NET 5, Microsoft.AspNetCore.OData 8.0.0-preview3, Visual Studio 2019 16.8.3

select result serialization broken (upgrading from 7.5 to 8)

It is now serializing as:

{
    "instance": null,
    "container": {
        "name": "column",
        "value": "value",
        "next0": {
            "name": "column2",
            "value": "value2",
            "autoSelected": true
        },
        "autoSelected": false
        },
        "modelID": "62488a16-0c3a-485f-9a66-bd74bb192560",
        "untypedInstance": null,
        "instanceType": null,
        "useInstanceForProperties": false
}

when previously it was:

{
    "column": "value"
}

Looks like this is because you know use an STJ converter instead of Newtonsoft. This seems like quite a large breaking change, is there any way to configure the behaviour? Newtonsoft is still a very popular solution

Question: How to provide data to OData without e.g. EF Core as provider?

Hi,

Thanks for a fantastic solution with OData. I am pondering the scenario where I want to have an in-memory store of data, build a scheme (using Edm I guess) and make it possible for clients to pull data from the store when querying. The extension to this would be to delegate to services, pulling data and let OData aggregate client models depending on the query.

The sample I see in this repo goes straight via EF Core. Is there a sample available with in-memory store, described by some scheme used by OData?

Thanks,
Kind Regards,
Jan Johansson

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.