this.Created(entity) throws an InvalidOperationException if the entity used with the generic EdmEntityObject type
I guess the problem occurs if you use EdmEntityObject directly without deriving a child class. At least I am doing so.
We use this type as a generic type to map our own entities which are name-value based.
The location in the CreatedNegotiatedContentResult is being resolved internally by a ClrTypeCache which is not correct in my opinion - or a optimization could be done if the type is EdmEntityObject.
As workaround I use this.Created(location, entity) in my controller to bypass the (default) calculation of the location.
Used OData-Version: 7.0.0.20629 But according to the repo, the same error still exists.
Here is my code to calculate the location based on the current logic but without using a ClrTypeCache
using System;
using System.Net.Http;
using System.Web.Http.Routing;
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Formatter;
using Microsoft.AspNet.OData.Formatter.Serialization;
using Microsoft.OData.Edm;
namespace MyCoolNamespace
{
internal class LocationHelper
{
private static readonly Lazy<Func<ResourceContext, bool, Uri>> generateODataLinkMethod;
static LocationHelper()
{
generateODataLinkMethod = new Lazy<Func<ResourceContext, bool, Uri>>(() =>
{
var resultHelpersType = Type.GetType("Microsoft.AspNet.OData.Results.ResultHelpers, Microsoft.AspNet.OData");
var generateODataLinkMethod = resultHelpersType.GetMethod(
"GenerateODataLink",
new[] { typeof(ResourceContext), typeof(bool) }
);
return (ResourceContext resourceContext, bool isEntityId) =>
(Uri)generateODataLinkMethod.Invoke(null, new object[] { resourceContext, isEntityId });
});
}
// https://github.com/OData/WebApi/blob/7.0.0/src/Microsoft.AspNet.OData/Results/ResultHelpers.cs
// https://github.com/OData/WebApi/blob/eaeed9a2a031b58b73946a91b1c45b52229cc828/src/Microsoft.AspNet.OData.Shared/Results/ResultHelpers.cs
// https://github.com/OData/WebApi/blob/eaeed9a2a031b58b73946a91b1c45b52229cc828/src/Microsoft.AspNet.OData.Shared/EdmModelExtensions.cs
// https://github.com/OData/WebApi/blob/955ee08511485f9b5ca46a4c9d6736a7e0357e85/src/Microsoft.AspNet.OData.Shared/Formatter/ClrTypeCache.cs https://github.com/OData/WebApi/blob/eaeed9a2a031b58b73946a91b1c45b52229cc828/src/Microsoft.AspNet.OData.Shared/Formatter/EdmLibHelpers.cs
public static Uri GenerateODataLink(HttpRequestMessage request, IEdmEntityObject entity, bool isEntityId)
{
var model = request.GetModel();
var path = request.ODataProperties().Path;
var navigationSource = path.NavigationSource;
var resourceContext = new ResourceContext(
new ODataSerializerContext
{
NavigationSource = navigationSource,
Model = model,
Url = request.GetUrlHelper() ?? new UrlHelper(request),
MetadataLevel = ODataMetadataLevel.FullMetadata, // Used internally to always calculate the links.
Request = request,
Path = path
},
entity.GetEdmType().AsEntity(),
entity
);
return GenerateODataLink(resourceContext, isEntityId);
}
private static Uri GenerateODataLink(ResourceContext resourceContext, bool isEntityId)
{
return generateODataLinkMethod.Value(resourceContext, isEntityId);
}
}
}
@joergmetzler Do you have a repro you can share? How are you using the LocationHelper above in your project?
@gathogojr I made a minimal example of my solution here: https://github.com/joergmetzler/ODataIssue2422
You can use this command to trigger the post method:
wget -UseBasicParsing 'http://localhost:44377/OData/Persons' -Method POST -Body '{"@odata.context":"http://localhost:44377/OData/$metadata#Persons/$entity","FirstName":"Jörg","LastName":"Metzler"}'
It seems that the same issue exists also in AspNetCore (8.0.7).
System.InvalidOperationException: Cannot find the resource type 'Microsoft.AspNetCore.OData.Formatter.Value.EdmEntityObject' in the model.
at Microsoft.AspNetCore.OData.Results.ResultHelpers.GetEntityType(IEdmModel model, Object entity)
at Microsoft.AspNetCore.OData.Results.ResultHelpers.GenerateODataLink(HttpRequest request, Object entity, Boolean isEntityId)
at Microsoft.AspNetCore.OData.Results.CreatedODataResult1.GenerateLocationHeader(HttpRequest request) at Microsoft.AspNetCore.OData.Results.CreatedODataResult1.ExecuteResultAsync(ActionContext context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultFilters>g__Awaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
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.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.OData.Routing.ODataRouteDebugMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
Accept: / Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Length: 21 Content-Type: application/json Host: localhost:4527 User-Agent: PostmanRuntime/7.29.0 Postman-Token: 6669983a-bbd4-4ab1-ad73-e0b29b58928f
Did you manage to get any further with this?
The workaround of @joergmetzler for AspNetCore (tested with 8.0.7) looks like this: It has been only slightly changed on getting the path and on the initialization of the serializer context (not requiring a URL anymore).
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Extensions;
using Microsoft.AspNetCore.OData.Formatter;
using Microsoft.AspNetCore.OData.Formatter.Serialization;
using Microsoft.AspNetCore.OData.Formatter.Value;
using Microsoft.AspNetCore.OData.Routing;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Microsoft.OData.Edm;
namespace AspNetCoreApi.OData
{
public static class ResultHelper
{
private static readonly Lazy<Func<ResourceContext, bool, Uri>> generateODataLinkMethod;
static ResultHelper()
{
generateODataLinkMethod = new Lazy<Func<ResourceContext, bool, Uri>>(() =>
{
var resultHelpersType = Type.GetType("Microsoft.AspNetCore.OData.Results.ResultHelpers, Microsoft.AspNetCore.OData");
var method = resultHelpersType.GetMethod(
"GenerateODataLink",
new[] { typeof(ResourceContext), typeof(bool) }
);
return (ResourceContext resourceContext, bool isEntityId) =>
(Uri)method.Invoke(null, new object[] { resourceContext, isEntityId });
});
}
public static IActionResult EntityCreated(this ODataController controller, IEdmEntityObject entity)
{
var url = GenerateODataLink(controller.Request, entity);
return controller.Created(url, entity);
}
private static Uri GenerateODataLink(HttpRequest request, IEdmEntityObject entity)
{
var model = request.GetModel();
var path = request.ODataFeature().Path;
var navigationSource = path.GetNavigationSource();
var resourceContext = new ResourceContext(
new ODataSerializerContext
{
NavigationSource = navigationSource,
Model = model,
MetadataLevel = ODataMetadataLevel.Full, // Used internally to always calculate the links.
Request = request,
Path = path
},
entity.GetEdmType().AsEntity(),
entity
);
return generateODataLinkMethod.Value(resourceContext, false);
}
}
}