OData / OData/AspNetCoreOData

Odata not working with Edm model loaded from file

Open
#896 6 comments 0 reactions 2 assignees View on GitHub

@xuzhg is already working on this.

Since Apr 25, 2023.

followup investigated
Dominant language
C#
Stars
505
Forks
186
PR merge metrics
No merged PRs in 30d

Description

Assemblies affected
ASP.NET Core OData 8.x

Describe the bug
When trying to read a edm model from a file, Odata stops working.

Reproduce steps
Create two entity:

 public class User
    {
        public string Id { get; set; }
        public string Name { get; set; }
    }

    public class Book
    {
        public string Id { get; set; }
        public User Author { get; set; }
    }

Create two controllers:

public class BooksController : ODataController
   {
       [EnableQuery]
       public IActionResult Get()
       {
           
           return Ok(new[] { new Book { Id = "1" }, new Book { Id = "2" } });
       }

       [EnableQuery]
       public IActionResult Get(string key)
       {
           return Ok(new Book { Id = key });
       }
   }

and

public class UsersController : ODataController
   {
       [EnableQuery]
       public IActionResult Get()
       {

           return Ok(new[] { new User { Id = "1" }, new User { Id = "2" } });
       }

       [EnableQuery]
       public IActionResult Get(string key)
       {
           return Ok(new User { Id = key });
       }
   }

Startup class:

public class Startup
   {
       public Startup(IConfiguration configuration, IWebHostEnvironment env)
       {
           Configuration = configuration;
           this.Env = env;
       }
     
       public IConfiguration Configuration { get; }
       public IWebHostEnvironment Env { get; }

       public void ConfigureServices(IServiceCollection services)
       {
           services.AddCors();

           services
               .AddMvc(o =>
               {
                   o.EnableEndpointRouting = false;
               })
               .AddJsonOptions(options =>
               {
                   options.JsonSerializerOptions.AllowTrailingCommas = true;
                   options.JsonSerializerOptions.IgnoreNullValues = true;
                   options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
               });

           services.AddControllers()
               .AddApplicationPart(Assembly.GetExecutingAssembly())
               .AddOData((setupAction, serviceProvider) =>
               {
                   setupAction.EnableAttributeRouting = true;
                   setupAction.RouteOptions.EnableUnqualifiedOperationCall = true;
                   setupAction.RouteOptions.EnableQualifiedOperationCall = true;
                   setupAction.RouteOptions.EnableKeyAsSegment = true;
                   setupAction.RouteOptions.EnableKeyInParenthesis = true;
                   setupAction.QuerySettings.EnableCount = true;
                   setupAction.QuerySettings.EnableSelect = true;
                   setupAction.QuerySettings.EnableExpand = true;
                   setupAction.QuerySettings.EnableSkipToken = true;
                   setupAction.QuerySettings.EnableFilter = true;
                   setupAction.QuerySettings.EnableOrderBy = true;
                   setupAction.QuerySettings.MaxTop = null;
                   setupAction.AddRouteComponents("api", GetEdmModel(), serviceCollection =>
                   {
                   });
               });

           services.AddHealthChecks();

           services.AddRouting();
       }

       public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
       {
           app.UseCors(x => x
               .AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader());

           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
           }

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

           app.UseHttpsRedirection();

           app.UseHealthChecks("/health");
       }

       private IEdmModel GetEdmModel()
       {
           ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
           builder.Namespace = "MyNamespace";
           builder.EntitySet<User>("Users");
           builder.EntitySet<Book>("Books");

           var edmModel = builder.GetEdmModel();
           SaveEdmModel(edmModel);

           return edmModel;
       }

       private void SaveEdmModel(IEdmModel model)
       {
           using (var writer = XmlWriter.Create("edmModel.xml"))
           {
               model.TryWriteSchema(writer, out var errors);
           }
       }
   }

After that, both entities must be registered and we can run the project and request books or a book by key.

image
image

Everything is working.
When we first started the project, we saved the schema to a file.
Let's change the GetEdmModel() method to read from a file

private IEdmModel GetEdmModel()
        {
            using (var reader = XmlReader.Create("edmModel.xml"))
            {
                var readers = new[] { reader };
                SchemaReader.TryParse(readers, out var edmModel, out var errors);

                return edmModel;
            }
        }

Let's restart the project and try to execute the same queries.

image

We can see that odata.context is missing.

image
The same situation

And if we trying to expand author, then we will receive new error:
URI: https://localhost:44387/api/Books/2?$expand=Author

Microsoft.OData.ODataException: A type named 'OdataEdmModelBug.Models.Book' could not be resolved by the model. When a model is available, each type name must resolve to a valid type.
   at Microsoft.OData.TypeNameOracle.ResolveAndValidateTypeName(IEdmModel model, String typeName, EdmTypeKind expectedTypeKind, Nullable`1 expectStructuredType, IWriterValidator writerValidator)
   at Microsoft.OData.TypeNameOracle.ResolveAndValidateTypeFromTypeName(IEdmModel model, IEdmStructuredType expectedType, String typeName, IWriterValidator writerValidator)
   at Microsoft.OData.ODataWriterCore.GetResourceType(ODataResourceBase resource)
   at Microsoft.OData.ODataWriterCore.ValidateResourceForResourceSet(ODataResourceBase resource, ResourceBaseScope resourceScope)
   at Microsoft.OData.ODataWriterCore.<>c.<<WriteStartResourceImplementationAsync>b__194_0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.OData.ODataWriterCore.InterceptExceptionAsync[TArg0](Func`3 action, TArg0 arg0)
   at Microsoft.OData.ODataWriterCore.WriteStartResourceImplementationAsync(ODataResource resource)
   at Microsoft.OData.ODataWriterCore.WriteStartAsync(ODataResource resource)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteResourceAsync(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectAsync(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
   at Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatterHelper.WriteToStreamAsync(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, HttpRequest request, IHeaderDictionary requestHeaders, IODataSerializerProvider serializerProvider)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Warning: The response has already started, the error page middleware will not be executed.
Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer: Error: Connection ID "18302628903618874841", Request ID "40000ddb-0004-fe00-b63f-84710c7967bb": An unhandled exception was thrown by the application.

Microsoft.OData.ODataException: A type named 'OdataEdmModelBug.Models.Book' could not be resolved by the model. When a model is available, each type name must resolve to a valid type.
   at Microsoft.OData.TypeNameOracle.ResolveAndValidateTypeName(IEdmModel model, String typeName, EdmTypeKind expectedTypeKind, Nullable`1 expectStructuredType, IWriterValidator writerValidator)
   at Microsoft.OData.TypeNameOracle.ResolveAndValidateTypeFromTypeName(IEdmModel model, IEdmStructuredType expectedType, String typeName, IWriterValidator writerValidator)
   at Microsoft.OData.ODataWriterCore.GetResourceType(ODataResourceBase resource)
   at Microsoft.OData.ODataWriterCore.ValidateResourceForResourceSet(ODataResourceBase resource, ResourceBaseScope resourceScope)
   at Microsoft.OData.ODataWriterCore.<>c.<<WriteStartResourceImplementationAsync>b__194_0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.OData.ODataWriterCore.InterceptExceptionAsync[TArg0](Func`3 action, TArg0 arg0)
   at Microsoft.OData.ODataWriterCore.WriteStartResourceImplementationAsync(ODataResource resource)
   at Microsoft.OData.ODataWriterCore.WriteStartAsync(ODataResource resource)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteResourceAsync(Object graph, ODataWriter writer, ODataSerializerContext writeContext, IEdmTypeReference expectedType)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectInlineAsync(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataResourceSerializer.WriteObjectAsync(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
   at Microsoft.AspNetCore.OData.Formatter.ODataOutputFormatterHelper.WriteToStreamAsync(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, HttpRequest request, IHeaderDictionary requestHeaders, IODataSerializerProvider serializerProvider)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware.InvokeAsync(HttpContext context)
   at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()

EDM (CSDL) Model

<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="MyNamespace" xmlns="http://docs.oasis-open.org/odata/ns/edm">
	<EntityType Name="User">
		<Key>
			<PropertyRef Name="Id" />
		</Key>
		<Property Name="Id" Type="Edm.String" Nullable="false" />
		<Property Name="Name" Type="Edm.String" />
	</EntityType>
	<EntityType Name="Book">
		<Key>
			<PropertyRef Name="Id" />
		</Key>
		<Property Name="Id" Type="Edm.String" Nullable="false" />
		<NavigationProperty Name="Author" Type="MyNamespace.User" />
	</EntityType>
	<EntityContainer Name="Container">
		<EntitySet Name="Users" EntityType="MyNamespace.User" />
		<EntitySet Name="Books" EntityType="MyNamespace.Book">
			<NavigationPropertyBinding Path="Author" Target="Users" />
		</EntitySet>
	</EntityContainer>
</Schema>

Expected behavior
The Edm model is loaded and all odata functions are working as expected.

Notes
The same behaviour using CsdlReader and CsdlWriter.
$metadata endpoint works after loading through file.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.