OData / OData/AspNetCoreOData

Return null in OData operations, results in different errors

Open
#634 9 comments 0 reactions 1 assignee View on GitHub

@Nthemba is already working on this.

Since Jul 12, 2022.

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

Description

Hello, I encountered ome problems when returning null in OData operations (actions and functions). I cannot provide my actual code, so I made a sample project which result in similar errors.

Here the sample code using Microsoft.AspNetCore.OData 8.0.10 followed with some of the errors which I encountered with this sample project.

public class Startup
{
	public Startup(IConfiguration configuration)
	{
		Configuration = configuration;
	}

	public IConfiguration Configuration { get; }

	// This method gets called by the runtime. Use this method to add services to the container.
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddControllers()
			.AddOData(options =>
			{
				var builder = new ODataConventionModelBuilder();
				builder.Namespace = "MyNamespace";

				var entityType = builder.EntitySet<Article>("Articles").EntityType.Collection;
				var function = entityType.Function("GetName");
				function.Returns<string>();

				var function2 = entityType.Function("GetEntity");
				function2.ReturnsFromEntitySet<Article>("Articles");

				var action1 = entityType.Action("GetEntityAction");
				action1.ReturnsFromEntitySet<Article>("Articles");

				options.AddRouteComponents("odata", builder.GetEdmModel());                    
			});
	}

	// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		if (env.IsDevelopment())
		{
			app.UseDeveloperExceptionPage();
		}
		else
		{
			app.UseExceptionHandler("/Error");
		}

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

public class ArticlesController : ODataController
{
	[HttpGet]
	public IQueryable<Article> Get()
	{
		var articles = new List<Article> { new Article { Id = 100, Name = "First" } };
		return articles.AsQueryable();
	}

	[HttpGet]
	public string GetName() => null;

	[HttpGet]
	public Article GetEntity() => null;

	[HttpPost]
	public Article GetEntityAction(ODataActionParameters parameters) => null;
}

public class Article
{
	public int Id { get; set; }
	public string Name { get; set; }
}

Calling the GetEntity(): Article function results in the following exception and same with the GetEntityAction(): Article:

GET http://localhost:32345/odata/Articles/GetEntity()
GET http://localhost:5000/odata/Articles/GetEntityAction

System.Runtime.Serialization.SerializationException: Cannot serialize a null 'Resource'.
   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.<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)

Calling the GetName(): string function results in the following response:

http://localhost:32345/odata/Articles/GetName()

Response in IIS profile:
{
	"@odata.context": "http://localhost:32345/odata/$metadata#Edm.Null"

And no response in Kestrel profile but this exception inside the console:
Microsoft.OData.ODataException: Cannot write the value 'null' in top level property; return 204 instead.
   at Microsoft.OData.WriterValidator.ValidateNullPropertyValue(IEdmTypeReference expectedPropertyTypeReference, String propertyName, Boolean isTopLevel, IEdmModel model)
   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WriteNullPropertyAsync(ODataPropertyInfo property)
   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.WritePropertyAsync(ODataProperty property, IEdmStructuredType owningType, Boolean isTopLevel, IDuplicatePropertyNameChecker duplicatePropertyNameChecker, ODataResourceMetadataBuilder metadataBuilder)
   at Microsoft.OData.JsonLight.ODataJsonLightPropertySerializer.<>c__DisplayClass9_0.<<WriteTopLevelPropertyAsync>b__0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.OData.JsonLight.ODataJsonLightSerializer.WriteTopLevelPayloadAsync(Func`1 payloadWriterFunc)
   at Microsoft.OData.JsonLight.ODataJsonLightOutputContext.WritePropertyImplementationAsync(ODataProperty property)
   at Microsoft.OData.JsonLight.ODataJsonLightOutputContext.WritePropertyAsync(ODataProperty property)
   at Microsoft.AspNetCore.OData.Formatter.Serialization.ODataPrimitiveSerializer.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.<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)
   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.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

Note the missing end bracket? Also I would expect "value": null or like the exception said a 204. This happends when using the IISExpress profile.
Using the Kestrel profile does not return a response, which is easily reproduced with WireShark.

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.