title: EFCore/ASP.NET Core Many-to-many relation: JsonSerializer ReferenceLoopHandling.Ignore not working as expected
body: <p>I have an issue with the ASP.NET Core JsonSerializer. It keeps running into an infinite reference loop even though I explicitly set the ReferenceLoopHandling property to ReferenceLoopHandling.Ignore.</p>
  
  <p>I have the details in this github issue: <a href="https://github.com/aspnet/EntityFrameworkCore/issues/14997" rel="nofollow noreferrer">https://github.com/aspnet/EntityFrameworkCore/issues/14997</a></p>
  
  <p>EDIT:</p>
  
  <p>The whole Startup configuration code:</p>
  
  <pre><code>public void ConfigureServices(IServiceCollection services)
  {
    // Deserialize specific settings into living objects
    services.Configure&lt;JwtSettings&gt;(this.Configuration.GetSection("Jwt"));
    services.Configure&lt;CookieSettings&gt;(this.Configuration.GetSection("Cookies"));
  
    // Read settings to apply locally
    DatabasesSettings dbSettings = DatabasesSettings.GetDatabasesSettings(this.Configuration);
    JwtSettings jwtSettings = this.Configuration.GetSection("Jwt").Get&lt;JwtSettings&gt;();
    CookieSettings cookieSettings = this.Configuration.GetSection("Cookies").Get&lt;CookieSettings&gt;();
  
    switch (dbSettings[DatabaseConstants.DatabaseSettingsData].Provider)
    {
      case "mysql":
        services.AddEntityFrameworkMySql()
                .AddDbContext&lt;DataContext&gt;(options =&gt; options.UseMySql(ConnectionStringBuilder.BuildConnectionString(dbSettings[DatabaseConstants.DatabaseSettingsData])));
        break;
      case "mssql":
        services.AddEntityFrameworkSqlServer()
                .AddDbContext&lt;DataContext&gt;(options =&gt; options.UseSqlServer(ConnectionStringBuilder.BuildConnectionString(dbSettings[DatabaseConstants.DatabaseSettingsData])));
        break;
    }
  
    // Identity MUST precede AUTHENTICATION in order to produce correct 401 Unauthorized instead of 404 Not found
    services.AddIdentity&lt;User, IdentityRole&gt;(options =&gt;
    {
      options.User.RequireUniqueEmail = true;
      options.Password.RequiredLength = 6;
      options.Password.RequireDigit = false;
      options.Password.RequireNonAlphanumeric = false;
      options.Password.RequireUppercase = false;
      options.Password.RequireLowercase = false;
    }).AddEntityFrameworkStores&lt;DataContext&gt;().AddDefaultTokenProviders();
  
    // JWT Bearer Token Authentication configuration secretKey contains a secret passphrase only your server knows
    Action&lt;JwtBearerOptions&gt; jwtOptions = options =&gt;
    {
      options.Events = new JwtBearerEvents
      {
        OnTokenValidated = context =&gt;
        {
          // Get the EF Context responsbile for authentication
          DataContext dbContext = context.HttpContext.RequestServices.GetRequiredService&lt;DataContext&gt;();
  
          List&lt;Claim&gt; claims = new List&lt;Claim&gt;
          {
            new Claim("AuthorizedAccess", "true"),
          };
  
          // Add claim(s) based on user -- todo
          ClaimsIdentity appIdentity = new ClaimsIdentity(claims);
          context.Principal.AddIdentity(appIdentity);
  
          return Task.CompletedTask;
        }
      };
      options.TokenValidationParameters = new TokenValidationParameters
      {
        // Do not validate lifetime. Need logout to invalidate. todo: verify if we can leave it this way
        ValidateLifetime = jwtSettings.Expiration.Enabled,
  
        // If you want to allow a certain amount of clock drift, set that here:
        ClockSkew = TimeSpan.FromMinutes(jwtSettings.Expiration.ClockDrift),
  
        // Validate the JWT Issuer (iss) claim
        ValidateIssuer = true,
        ValidIssuer = jwtSettings.Issuer,
  
        // Validate the JWT Audience (aud) claim
        ValidateAudience = true,
        ValidAudience = jwtSettings.Audience,
  
        // The signing key must match!
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key)),
  
        SaveSigninToken = true
      };
    };
    // Authentication Service
    services.AddAuthentication(
      options =&gt;
      {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
      }).AddJwtBearer(jwtOptions).AddCookie();
  
    // Authorization Service
    services.AddAuthorization(
      options =&gt;
      {
        options.AddPolicy(PolicyNames.AdministrationPolicy, policy =&gt; policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.AdministrationPolicy)));
        options.AddPolicy(PolicyNames.PurchasePolicy, policy =&gt; policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.PurchasePolicy)));
        options.AddPolicy(PolicyNames.TradePolicy, policy =&gt; policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.TradePolicy)));
        options.AddPolicy(PolicyNames.ExchangePolicy, policy =&gt; policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.ExchangePolicy)));
      });
  
    // Application Cookie
    services.ConfigureApplicationCookie(
      options =&gt;
      {
        // Avoid redirecting REST clients on 401
        options.Events = new CookieAuthenticationEvents
        {
          OnRedirectToLogin = ctx =&gt;
          {
            ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            return Task.FromResult(0);
          }
        };
  
        options.Cookie.Name = cookieSettings.ApplicationCookieName;
      });
  
    // Antiforgery
    services.AddAntiforgery(
      options =&gt;
      {
        options.Cookie.Name = cookieSettings.AntiforgeryCookieName;
        options.Cookie.Domain = cookieSettings.AntiforgeryCookieDomain;
        options.Cookie.Path = "/";
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
      });
  
    // Additional Services
    services.AddSingleton&lt;IAuthorizationHandler, AuthorizationNameHandler&gt;();
  
    // Cross Origin Policies
    services.AddCors(
      options =&gt;
      {
        options.AddPolicy("AllowAnyOrigin", builder =&gt; { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials().Build(); });
      });
  
    // Enable Response Compression
    services.AddResponseCompression();
  
    // MVC
    services.AddMvc(
      options =&gt;
      {
        options.Filters.Add(new CorsAuthorizationFilterFactory("AllowAnyOrigin"));
        options.CacheProfiles.Add(
          "Default",
          new CacheProfile()
          {
            Duration = 60
          });
        options.CacheProfiles.Add(
          "Never",
          new CacheProfile()
          {
            Location = ResponseCacheLocation.None,
            NoStore = true
          });
      }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddJsonOptions(
      options =&gt;
      {
        options.SerializerSettings.Formatting = Formatting.Indented;
        options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
      });
  
  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    if (Program.IsDebug)
    {
      app.UseDeveloperExceptionPage();
    }
    else
    {
      app.UseHsts();
    }
  
    app.UseCors("AllowAnyOrigin");
  
    // app.UseCorsMiddleware(); // Custom middleware
    app.UseHttpsRedirection();
  
    app.UseDefaultFiles();
    app.UseStaticFiles();
    app.UseAuthentication();
    app.UseResponseCompression();
  
    app.UseMvc();
  }
  </code></pre>