24

I'm having some issues which I'm guessing are related to self-referencing using .NET Core Web API and Entity Framework Core. My Web API starting choking when I added .Includes for some navigation properties.

I found what appears to be a solution in the older Web API but I don't know how to implement the same thing for .NET Core Web API (I'm still in the early learning stages).

The older solution was sticking this in the Application_Start() of the Global.asax:

 GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;

I suspect this is handled in the StartUp's ConfigureService() method but I don't know much beyond there.

Or is there a more appropriate way to handle this issue?

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
Sailing Judo
  • 11,083
  • 20
  • 66
  • 97
  • 1
    Generally speaking, I use API objects that don't have the circular loops (not the straight EF objects) – BradleyDotNET Mar 01 '17 at 01:40
  • @BradleyDotNET i agree, map your DB to wire objects that are API caller focused. The DB over a wire is an anti-pattern for software development (ironically it's a goto solution for so many people "doing REST") – Chris Marisic Mar 01 '17 at 01:44
  • 4
    It's seems to violate the DRY principle since my wire models would look identical to my models which are being populated by EF. – Sailing Judo Mar 01 '17 at 01:46
  • @SailingJudo The real question is should they? Generally API objects can be much flatter than those required for a relational database – BradleyDotNET Mar 01 '17 at 19:47

4 Answers4

43

Okay... I finally found some reference material on this. The solution is:

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
}

I got this from here

Sailing Judo
  • 11,083
  • 20
  • 66
  • 97
  • Thank you so much... this was driving me crazy. I had the same exact issue. I feel like that should be handled by default when converting to json. – Bojo Aug 12 '18 at 03:58
  • 2
    This has changed in .NetCore 3. See [this](https://stackoverflow.com/questions/55666826/where-did-imvcbuilder-addjsonoptions-go-in-net-core-3-0) awnser. Maybe you can add it to your anwer. – Understatic Oct 16 '19 at 09:14
20

If you are using ASP.NET Core 3.0, and experience that problem please install the NuGET package: Microsoft.AspNetCore.Mvc.NewtonsoftJson 3.0.0.

To replace the new System.Text.Json which does not yet have the Reference Loop Handling do this in the Startup.cs, make sure that in the ConfigureServices, is included:

If using the latest .Net Core 3.0 way:

services.AddControllers().AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});

or the old way:

services.AddMvc(option => option.EnableEndpointRouting = false)
       .AddNewtonsoftJson(options => 
                 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore)
       .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
Eduard Braun
  • 370
  • 3
  • 5
  • 1
    how I can do that with System.Text.JSON – Moaz Salem Nov 28 '19 at 03:21
  • as I mentioned it is not supported yet in Text.JSON – Eduard Braun Nov 28 '19 at 11:15
  • If anybody figures out how to do this chaining with .AddControllersWithViews() lmk, I am not able to find a way. – beggarboy Oct 22 '20 at 15:41
  • I kept getting errors on `SerializerSettings`, so I kept looking for ways to replace those options, but it turns out I was using the old(?) `AddJsonOptions` without realizing I needed to replace it with `AddNewtonsoftJson`. Once I did that, I was able to use `SerializerSettings` again and keep old functionality when upgrading an old project. Thank you for this! I spent hours trying to figure this out. – computercarguy Jan 13 '21 at 00:11
7

ReferenceLoopHandling.Ignore “hides” the problem, not solves it. What you really need to be doing is building layers. Create domain objects to sit on top of your entities and wrap them in some sort of service/business layer. Look up the repository pattern and apply that if it helps. You’ll need to map between your entities and domain objects, and this gives you the chance to fit in some sort of mapper (automapper) and validation layer..

If your domain objects and entities are exactly the same, then you need to think more about what your doing.

For example: Do your entities have soft deletes? (IsDeleted) flag? If so, this doesn’t necessarily need to go back to the client over the web, so that’s a perfect example of where they would be different.

Either way, the answer is not to override it in JSON, it’s to change your architecture..

Robert Perry
  • 1,906
  • 1
  • 14
  • 14
  • Just curious, but what's the justification for this? Where's the problem with using the domain entities instead of recreating them as DTOs (which is what I assume you mean by "entities")? Sometimes it's just easier (even with the repository pattern which I use), especially if you have a lot of domain entities and if those entities are in the schema you already want to consume on the client. I want to know what "I'm" doing wrong here. – Zach Feb 03 '20 at 12:43
  • Domain Objects typically have business logic in them.. It could be composing a property like “Display Name” from “FirstName” and “Surname” for example.. A DTO will want to be as “clean” as possible.. Partly for serialisation, but also because it makes the whole transaction cleaner since the caller will only want to see what fields they need to manipulate to pass a valid object back and forth across the wire. Depending on your circumstances it could be purely semantics - but there is definite distinction between a domain object and a DTO – Robert Perry Feb 03 '20 at 13:41
1

If you have a Minimal API this will be useful:

using System.Text.Json.Serialization;

builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(opt =>
{
    opt.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
giorgi02
  • 683
  • 6
  • 13