EFCore / ASP.NET Core Отношение многие ко многим: JsonSerializer ReferenceLoopHandling.Ignore не работает должным образом - PullRequest
0 голосов
/ 12 марта 2019

У меня проблема с ASP.NET Core JsonSerializer.Он продолжает работать в бесконечном цикле ссылок, хотя я явно установил для свойства ReferenceLoopHandling ReferenceLoopHandling.Ignore.

У меня есть подробности в этой проблеме github: https://github.com/aspnet/EntityFrameworkCore/issues/14997

РЕДАКТИРОВАТЬ:

Весь код конфигурации запуска:

public void ConfigureServices(IServiceCollection services)
{
  // Deserialize specific settings into living objects
  services.Configure<JwtSettings>(this.Configuration.GetSection("Jwt"));
  services.Configure<CookieSettings>(this.Configuration.GetSection("Cookies"));

  // Read settings to apply locally
  DatabasesSettings dbSettings = DatabasesSettings.GetDatabasesSettings(this.Configuration);
  JwtSettings jwtSettings = this.Configuration.GetSection("Jwt").Get<JwtSettings>();
  CookieSettings cookieSettings = this.Configuration.GetSection("Cookies").Get<CookieSettings>();

  switch (dbSettings[DatabaseConstants.DatabaseSettingsData].Provider)
  {
    case "mysql":
      services.AddEntityFrameworkMySql()
              .AddDbContext<DataContext>(options => options.UseMySql(ConnectionStringBuilder.BuildConnectionString(dbSettings[DatabaseConstants.DatabaseSettingsData])));
      break;
    case "mssql":
      services.AddEntityFrameworkSqlServer()
              .AddDbContext<DataContext>(options => 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<User, IdentityRole>(options =>
  {
    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<DataContext>().AddDefaultTokenProviders();

  // JWT Bearer Token Authentication configuration secretKey contains a secret passphrase only your server knows
  Action<JwtBearerOptions> jwtOptions = options =>
  {
    options.Events = new JwtBearerEvents
    {
      OnTokenValidated = context =>
      {
        // Get the EF Context responsbile for authentication
        DataContext dbContext = context.HttpContext.RequestServices.GetRequiredService<DataContext>();

        List<Claim> claims = new List<Claim>
        {
          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 =>
    {
      options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
      options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(jwtOptions).AddCookie();

  // Authorization Service
  services.AddAuthorization(
    options =>
    {
      options.AddPolicy(PolicyNames.AdministrationPolicy, policy => policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.AdministrationPolicy)));
      options.AddPolicy(PolicyNames.PurchasePolicy, policy => policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.PurchasePolicy)));
      options.AddPolicy(PolicyNames.TradePolicy, policy => policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.TradePolicy)));
      options.AddPolicy(PolicyNames.ExchangePolicy, policy => policy.Requirements.Add(new AuthorizationNameRequirement(PolicyNames.ExchangePolicy)));
    });

  // Application Cookie
  services.ConfigureApplicationCookie(
    options =>
    {
      // Avoid redirecting REST clients on 401
      options.Events = new CookieAuthenticationEvents
      {
        OnRedirectToLogin = ctx =>
        {
          ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
          return Task.FromResult(0);
        }
      };

      options.Cookie.Name = cookieSettings.ApplicationCookieName;
    });

  // Antiforgery
  services.AddAntiforgery(
    options =>
    {
      options.Cookie.Name = cookieSettings.AntiforgeryCookieName;
      options.Cookie.Domain = cookieSettings.AntiforgeryCookieDomain;
      options.Cookie.Path = "/";
      options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    });

  // Additional Services
  services.AddSingleton<IAuthorizationHandler, AuthorizationNameHandler>();

  // Cross Origin Policies
  services.AddCors(
    options =>
    {
      options.AddPolicy("AllowAnyOrigin", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials().Build(); });
    });

  // Enable Response Compression
  services.AddResponseCompression();

  // MVC
  services.AddMvc(
    options =>
    {
      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 =>
    {
      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();
}

1 Ответ

0 голосов
/ 13 марта 2019

Вы можете настроить Json.NET на игнорирование циклов, найденных в графе объектов. Это делается в методе ConfigureServices(...) в Startup.cs следующим образом:

public void ConfigureServices(IServiceCollection services)
{
    ...

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

    ...
}

В вашем коде замените options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Error; на следующее:

options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
...