6

Am using Blazor (Hosted) and looking to preserve referencing when sending results back to client. The sample below doesn't really need reference preservation but is my test scenario for a more complex structure that does.

  • Class "Staff" is defined in the Shared project.
  • WebAPI method returns an IEnumerable

That payload looks like this:

[
  {
    "id":"a583baf9-8990-484f-9dc6-e8ea822f49c6",
    "name":"Neil",
    "themeName":"Blue Gray"
  },
  {
    "id":"a7a8e753-c7af-4b29-9242-7b2f5bdac830",
    "name":"Caroline",
    "themeName":"Yellow"
  }
]

Using

var result = await response.Content.ReadFromJsonAsync<IEnumerable<Staff>>();

I am able to get my Staff objects in the client.

Moving on to reference preservation:

I updated StartUp.cs on server to include:

services.AddControllersWithViews()
    .AddJsonOptions(o => 
        o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve
    );

Result was that the return payload now looks like this:

{
  "$id":"1",
  "$values":
     [
       {
         "$id":"2",
         "id":"a583baf9-8990-484f-9dc6-e8ea822f49c6",
         "name":"Neil",
         "themeName":"Blue Gray"
       },
       {
         "$id":"3",
         "id":"a7a8e753-c7af-4b29-9242-7b2f5bdac830",
         "name":"Caroline",
         "themeName":"Yellow"
       }
     ]
}

Seems correct.

This caused JSON deserialisation exception at line:

var result = await response.Content.ReadFromJsonAsync<IEnumerable<Staff>>();

So, I thought I might need to include reference handling options when deserializing on the client as well. So, changed to:

JsonSerializerOptions options = new JsonSerializerOptions();
options.ReferenceHandler = ReferenceHandler.Preserve;
var result = await response.Content.ReadFromJsonAsync<IEnumerable<Staff>>(options);

I got no errors, but my Enumerable included:

The 2 Staff objects (but will all properties nulled). A 3rd null object in the Enumerable.

Could anyone guide me on what I'm doing wrong?

Neil W
  • 7,670
  • 3
  • 28
  • 41

2 Answers2

8

I have found a solution. This is what appeared to be happening:

The default configuration for Json serialization on WebAPI appears to be camel case. However, even though this was the case, I had not had any problem serializing shared classes (that use capitalisation) and deserializing on the client, even though the JSON itself was using camel case.

This started to fail when I added ReferenceHandler.Preserve to my JsonSerializerOptions.

Updating my Json Options as follows, solved the problem:

services.AddControlersWithViews()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
        options.JsonSerializerOptions.PropertyNamingPolicy = null // prevent camel case
    }

Alternative approach is to use MvcOptions. I don't claim to know which is prefereable, but both the above and the below seem to give the same outcome.

services.AddControllersWithViews(options =>
{
    options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();
    options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(
        new JsonSerializerOptions(JsonSerializerDefaults.Web)
        {
            ReferenceHandler = ReferenaceHandler.Preserve,
            PropertyNamingPolicy = null    // prevent camel casing of Json
        }));
});

Then on client, when receiving response from WebAPI:

HttpResponseMessage response = await Http.GetAsync(myapiroute);
IEnumerable<Staff> staff = response.Content.ReadFromJsonAsync<IEnumerable<Staff>>(
    new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.Preserve });

And now reference handling appears to cross the boundary from WebAPI to Blazor Client.

Neil W
  • 7,670
  • 3
  • 28
  • 41
  • for .NET Core 6 newbies (like me) the code goes into the Web API project, Startup.cs file, ConfigureServices method. For me services.AddControllers was already in there. I added Neil's options stuff and it fixed my problem. I was getting "possible object cycle" in my controller when saving the object to the database. – pdschuller Jun 22 '22 at 18:44
0

In my case I only had one controller that was causing this issue. If I changed the serialization options app-wide in my program.cs, it caused the rest of my app not to work. So I reasolve it on my troublesome controller by changing this (this is a simplified example):

return Json(new { status = true });

...to...

var serialized = System.Text.Json.JsonSerializer.Serialize(new { status = true });
return Ok(serialized);
Post Impatica
  • 14,999
  • 9
  • 67
  • 78