odata / aspnetcoreodata Goto Github PK
View Code? Open in Web Editor NEWASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
License: Other
ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
License: Other
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 ...
AspNetCore.OData Odata8.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.
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
for example:
If customer entity type has a "Edm.Int32" key named "ID", so, when we construct the route template for entity access,
we should add the constraint for the key template.
~/odata/Customers/{key:int}
The same for the function parameter template.
for the route constraint, please refer to: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.1#route-constraint-reference
CreateRef and DeleteRef ODataController methods with navigation property parameter are not working in the preview version. Please add support for them too. It should be yet another convention beside CreateRefTo(EntitySet).
Thank you
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.
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.
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)
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"
I think there are a couple of issues with SelectExpandWrapperConverter
.
$select
.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
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.
Reported here: OData/ModelBuilder#12.
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)
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..
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, Func
2 createQueryOptionFunction)\r\n at Microsoft.AspNet.OData.EnableQueryAttribute.OnActionExecuted(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, IWebApiRequestMessage request, Func2 modelFunction, Func
2 createQueryOptionFunction, Action1 createResponseAction, Action
3 createErrorAction)"
}`
How to call $select on paramterized endpoint. I must be missing some point. please help me get through this.
We can remove CompatibilityOptions.DisableCaseInsensitiveRequestPropertyBinding in the next breaking change release.
Added in this PR OData/WebApi#2311
*Which assemblies and versions are known to be affected e.g. OData WebApi lib 7.7.3
The simplest set of steps to reproduce the issue. If possible, reference a commit that demonstrates the issue.
What would happen if there wasn't a bug.
What is actually happening.
Optional, details of the root cause if known. Delete this section if you have no additional details to add.
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).
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
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
Once I removed the $top it works correctly
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
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
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
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
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.
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
Please add an option to enable camelcase serialization
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);
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!
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();
}
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:
TIA
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
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]
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:
[EnableQuery]
[HttpGet("{key}")]
public ActionResult<SingleResult<User>> Get(Guid key)
{
var currentUser = dbContext.Users().Where(u => u.Id == key);
return Ok(SingleResult.Create(currentUser));
}
[EnableQuery]
[HttpGet("current")]
public IActionResult Current()
{
var currentUser = dbContext.Users().Where(u => u.Id == _currentUser.Id).Single();
return Ok(currentUser);
}
var currentUser = dbContext.Users().Where(u => u.Id == _currentUser.Id);
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?
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 ?
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 Route
s.
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...
So far, we have the https://github.com/OData/AspNetCoreOData/blob/master/src/Microsoft.AspNetCore.OData/Formatter/ODataInputFormatter.cs#L123-L127 setting to allow synchronous call.
We should get rid of this setting and replace all writing call using asynchronous version.
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);
}
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.
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)
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
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)Temperature
class, which accepts strings of the form 15°C
or 50°F
Distance
class, like 5m
or 10ft
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?
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.
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
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.
odata/erp/customers
(prefix: odata/erp) should resolve to controller WebService.Erp.Controllers.CustomersControllerodata/crm/customers
(prefix: odata/crm) should resolve to controller WebService.Crm.Controllers.CustomersControllerI'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:
I am trying to add OData to a .net5 web API but once added the endpoints starts throwing ArgumentNullException
OData WebApi lib 8.0.0-preview3
services.AddOData();
The endpoint should work with OData or at least with simple query parameters.
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)
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.
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);
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.