AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

Derived Class Does Not Show odata.type

Open EvanHennisAtMicrosoft opened this issue 2 years ago • 3 comments

Short summary (3-5 sentences) describing the issue. I have an entity that has a property, and that property is a complex type. When I return a derived class from the abstract complex type the @odata.type is not returned. This causes issues when I am trying to use graph.microsoft.com because graph can't determine it is the derived class.

Assemblies affected

Which assemblies and versions are known to be affected e.g. OData .Net lib 7.x .NET 7 Microsoft.AspNetCore.OData v8.2.0 Microsoft.OData.Core v 7.16

Reproduce steps

Use repo https://github.com/OData/odata.net/files/13048351/WebApplication3.zip (from OData/odata.net#2774)

Run it and send request: http://localhost:5197/odata/IdentityGovernance/PermissionsAnalytics/Findings/%7Bkey%7D/WebApplication3.Models.ExternallyFinding/AccountsWithAccess

Expected result

{
    "@odata.context": "http://localhost:5197/odata/$metadata#identityGovernance/PermissionsAnalytics/Findings('key')/WebApplication3.Models.ExternallyFinding/AccountsWithAccess(WebApplication3.Models.EnumeratedAccountsWithAccess/Accounts())",
    "@odata.type": "#WebApplication3.Models.EnumeratedAccountsWithAccess",
    "Accounts": [
        {
            "@odata.type": "#WebApplication3.Models.AwsAuthorizationSystem",
            "AuthorizationSystemId": "123",
            "AuthorizationSystemName": null,
            "AuthorizationSystemType": null,
            "AwsTitle": null
        },
        {
            "@odata.type": "#WebApplication3.Models.AwsAuthorizationSystem",
            "AuthorizationSystemId": "456",
            "AuthorizationSystemName": null,
            "AuthorizationSystemType": null,
            "AwsTitle": null
        }
    ]
}

Actual result

{
    "@odata.context": "http://localhost:5197/odata/$metadata#identityGovernance/PermissionsAnalytics/Findings('key')/WebApplication3.Models.ExternallyFinding/AccountsWithAccess(WebApplication3.Models.EnumeratedAccountsWithAccess/Accounts())",
    "Accounts": [
        {
            "@odata.type": "#WebApplication3.Models.AwsAuthorizationSystem",
            "AuthorizationSystemId": "123",
            "AuthorizationSystemName": null,
            "AuthorizationSystemType": null,
            "AwsTitle": null
        },
        {
            "@odata.type": "#WebApplication3.Models.AwsAuthorizationSystem",
            "AuthorizationSystemId": "456",
            "AuthorizationSystemName": null,
            "AuthorizationSystemType": null,
            "AwsTitle": null
        }
    ]
}

Additional detail

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

EvanHennisAtMicrosoft avatar Oct 20 '23 18:10 EvanHennisAtMicrosoft

@EvanHennisAtMicrosoft

That's by design from the code at: https://github.com/OData/AspNetCoreOData/blob/main/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataResourceSerializer.cs#L1756

        internal static bool ShouldAddTypeNameAnnotationForComplex(ODataMetadataLevel metadataLevel)
        {
            switch (metadataLevel)
            {
                // For complex types, the default behavior matches the requirements for minimal metadata mode, so no
                // annotation is necessary.
                case ODataMetadataLevel.Minimal:
                    return false;
                // In other cases, this class must control the type name serialization behavior.
                case ODataMetadataLevel.Full:
                case ODataMetadataLevel.None:
                default: // All values already specified; just keeping the compiler happy.
                    return true;
            }
        }

Actually, I don't think the first comment for ODataMetadataLevel.Minimal makes sense. @mikepizzo can you share your thoughts?

as a workaround, @EvanHennisAtMicrosoft you can create a class derive from ODataResourceSerializer, override CreateResource, call var resource = base.CreateResource(...); first, then specify the TypeAnnotation for the resource as resource.TypeAnnotation = new ODataTypeAnnotation(typeName);

Let me know the result. Thanks.

xuzhg avatar Oct 23 '23 18:10 xuzhg

As Sam's code shows, this issue is in webapi, not in ODL, so I'm going to move it there.

If WebAPI just correctly sets the typename we should get the expected behavior. I'm not sure why we're trying to override it in WebAPI. The above code in WebAPI looks complicated and wrong.

Code in ODL in minimal metadata to determine if type annotation should be written:

        internal override string GetResourceTypeNameForWriting(string expectedTypeName, ODataResourceBase resource, bool isUndeclared = false)
        {
            Debug.Assert(resource != null, "resource != null");

            if (resource.TypeAnnotation != null)
            {
                return resource.TypeAnnotation.TypeName;
            }

            // We only write entity type names in Json Light if it's more derived (different) from the expected type name.
            string resourceTypeName = resource.TypeName;
            if (expectedTypeName != resourceTypeName || isUndeclared)
            {
                return resourceTypeName;
            }

            return null;
        }

mikepizzo avatar Oct 24 '23 16:10 mikepizzo

@xuzhg Adding in the code that you suggested allowed this to work.

EvanHennisAtMicrosoft avatar Oct 24 '23 18:10 EvanHennisAtMicrosoft