.net Core 3 [JsonIgnore] не работает при запросе одного ресурса

в моем .net Core 3.0 Api атрибут [JsonIgnore] не работает как исключение. Я использую System.Text.Json

вместо старого Newtonsoft.Json

Когда я использую свой ресурс, который возвращает список объектов, например:

/api/Object/

объекты сериализуются следующим образом:

  [
  {
    "id": 1,
    "date": "2020-02-12T08:45:51.502",
    "userId": 1,
    "tags": [
      {
        "name": "string"
      }
    ]
  }
]

Но когда я запрашиваю единственный результат

/api/Object/{id}

полный объект сериализуется следующим образом:

    {
  "user": {
    "hasAccess": false,
    "id": 1,
    "userName": "***",
    "normalizedUserName": "***",
    "email": "***",
    "normalizedEmail": "***",
    "emailConfirmed": true,
    "passwordHash": "***",
    "concurrencyStamp": "***",
    "phoneNumberConfirmed": false,
    "twoFactorEnabled": false,
    "lockoutEnabled": true,
    "accessFailedCount": 0
  },
  "lazyLoader": {},
  "id": 1,
  "date": "2020-02-12T08:45:51.502",
  "userId": 1,
  "tags": [
    {
      "name": "string"
    }
  ]
}

Класс с атрибутом JsonIgnore выглядит так:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json.Serialization;
 public class Object
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public DateTime Date { get; set; }
    [ForeignKey("User")]
    public int UserId { get; set; }
    [JsonIgnore]
    public virtual User User { get; set; }
}

Это мой класс контроллера WebApi:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Models;
using Services;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

  [Route("api/object")]
    [ApiController]
    [Authorize(Roles = Roles.ACCESS_GRANTED)]
    public class ObjectController : AbstractController
    {
        private readonly ObjectService objectService;

        public ObjectController(IDataService<Object> service, ExtendedUserManager manager) : base(manager)
        {
            objectService = (ObjectService)service;
        }

        // GET: api/Object
        [HttpGet]
        public IActionResult Get()
        {
            List<Object> object = objectService.GetAll();
            return Ok(object);
        }




        // GET: api/Object/5
        [HttpGet("{id}", Name = "GetObject")]
        public IActionResult Get(int id)
        {
            Object object = objectService.Get(id);

            if (object == null)
            {
                return NotFound(string.Format("Object with Id {0} could not be found", id));
            }

            return Ok(object);
    }
}

Мой файл csproj:

    <Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
    <IsPackable>false</IsPackable>
    <SpaRoot>ClientApp\</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>

    <!-- Set this to true if you enable server-side prerendering -->
    <BuildServerSideRenderer>false</BuildServerSideRenderer>
    <RootNamespace>Project</RootNamespace>
    <Configurations>Debug;Release;</Configurations>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
    <PackageReference Include="log4net" Version="2.0.8" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="3.0.3" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.1" />
    <PackageReference Include="SendGrid" Version="9.12.6" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" />
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0" />
  </ItemGroup>

  <ItemGroup>
    <!-- Don't publish the SPA source files, but do show them in the project files list -->
    <Content Remove="$(SpaRoot)**" />
    <None Remove="$(SpaRoot)**" />
    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
  </ItemGroup>

  <Target Name="Restore">
    <MSBuild Projects="$.\open-success.sln" Targets="Restore" />
  </Target>

  <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
  </Target>

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />


    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
      <DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</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.AddCors();


            services.AddControllers();



        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseCors(builder => builder.AllowAnyHeader().AllowAnyOrigin().WithMethods("*"));


            app.UseDeveloperExceptionPage();



            app.UseHttpsRedirection();


            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });


        }
    }

Я что-то упускаю или это ошибка?

Обновление:

Я заметил другое странное поведение: когда я возвращаю только что созданный объект вместо объекта из базы данных, все работает отлично.

 [HttpGet("{id}", Name = "GetObject")]
    public IActionResult Get(int id)
    {
        // Object object= objectService.Get(id);
        Object object= new Object ();
        object.User = new User();

        if (object== null)
        {
            return NotFound(string.Format("object with Id {0} could not be found", id));
        }

        return Ok(object);
    }

person mic    schedule 12.02.2020    source источник
comment
Что произойдет, если вы измените свойство User на не виртуальное? Не могли бы вы поделиться минимальным воспроизводимым примером, который я могу запустить локально, чтобы увидеть ошибку? Это поможет выяснить, является ли это ошибкой в ​​System.Text.Json или как вы ее используете в своем API.   -  person ahsonkhan    schedule 13.02.2020
comment
Я пробовал ваш код с ASP.NET Core SDK 3.1.101, но не смог воспроизвести. Он отлично работает для меня. Вы уверены, что используете правильный JsonIgnore из правильного пространства имен? Если да, не могли бы вы показать нам способ воспроизведения?   -  person itminus    schedule 13.02.2020
comment
@mic, основная причина, вероятно, в том, что вам не хватает директивы using System.Text.Json.Serialization в верхней части файла и вы по-прежнему используете атрибут JsonIgnore из Newtonsoft.Json (если вы ссылаетесь на этот пакет), что S.T.J не соблюдает. Обе библиотеки имеют этот атрибут (с тем же именем), поэтому могут возникнуть конфликты.   -  person ahsonkhan    schedule 13.02.2020
comment
@ahsonkhan Я попытался воспроизвести ошибку в новом проекте Web-Api. Но это невозможно, код точно такой же, с той лишь разницей, что я не использую базу данных. Newtonsoft.Json не упоминается в моем веб-приложении. Только Swagger и несколько других фреймворков, которые не имеют ничего общего с Json.   -  person mic    schedule 13.02.2020
comment
@itminus Мне также не удалось воспроизвести эту ошибку в новом веб-приложении, и я совершенно беспомощен.   -  person mic    schedule 13.02.2020
comment
Я обновил пример кода в исходном вопросе   -  person mic    schedule 13.02.2020
comment
@mic похоже, что вы находитесь в аналогичной ситуации, как Как Могу ли я сделать, чтобы сериализатор JSON игнорировал свойства навигации?. [JsonIgnore] public virtual User User { get; set; }   -  person Kirk Horton    schedule 13.02.2020
comment
@KirkHorton Это решение заставило меня использовать Newtonsoft.Json. Это работает, если виртуальные свойства всегда следует игнорировать.   -  person mic    schedule 17.02.2020
comment
@mic просто немного покопался, и похоже, что это может быть то, с чем мы застряли, может быть, до .NET 5 в соответствии с этой открытой проблемой github от октября 2019 года github.com/dotnet/runtime/issues/31257   -  person Kirk Horton    schedule 17.02.2020


Ответы (5)


Тебе нужно:

using Newtonsoft.Json;

Вместо того:

using System.Text.Json.Serialization;

Пакет nuget - это Microsoft.AspNetCore.Mvc.NewtonsoftJson. Он больше не включен в ядро ​​.net.

person Tim    schedule 12.03.2020
comment
Спасибо тебе за это! - person IamIC; 17.05.2020
comment
Не используйте NewtonSoft, он возится со строками, когда они содержат даты. См. Эту ветку: github.com/JamesNK/Newtonsoft.Json/issues/862 - person Aurelien B; 10.07.2020
comment
В моем проекте мы намеренно используем System.Text.Json, а не Newtonsoft (для этого мы настраиваем построитель MVC в Startup с помощью AddJsonOptions). Между ними существует множество различий, и их нужно настраивать по-разному, так что это разумный ответ для людей, которые случайно использовали System.Text.Json. - person Qwertie; 30.03.2021

Вы можете использовать [JsonIgnore] из System.Text.Json, и он будет работать, если вы вызовете методы серлизации / десериализации вручную. Тем не мение; встроенная система разделения ядра .net, т.е. для контроллеров, httpclient.GetFromJsonAsync .. и т. д. Атрибут [JsonIgnore] на сегодняшний день не работает.

Похоже, что внутренняя сериализация не использует System.Text.Json, а атрибут JsonIgnore еще не адаптирован. В таком случае используйте атрибут [IgnoreDataMember], и внутренняя система ядра .net проигнорирует такие свойства и будет работать нормально.

person freewill    schedule 25.10.2020

Вам необходимо добавить код ниже в Startup.cs внутри метода ConfigureServices

            services.AddControllers()
                    .AddNewtonsoftJson(options =>
                    {
                        options.SerializerSettings.ContractResolver = 
                                                     new DefaultContractResolver();
                    });

и, конечно же, вам понадобится пакет Microsoft.AspNetCore.Mvc.NewtonsoftJson

person Keshab    schedule 24.09.2020

Для меня проблема вызвана lazyLoadingProxies. Пользовательская загрузка из базы данных относится не к типу User, а к типу Castle.Proxies.UserProxy, к которому не применяется атрибут JsonIgnore. Я вижу 3 решения

  1. используйте ILazyLoader вместо прокси, как описано здесь: https://www.learnentityframeworkcore.com/lazy-loading
  2. Всегда отображать ленивые загруженные свойства
  3. Не используйте ленивую загрузку вообще (лучше не использовать ее в EF Core)
person Martin2112    schedule 06.10.2020

Я только что отправил отчет об этой ошибке. Это происходит при использовании прокси с отложенной загрузкой с System.Text.Json.

Обходной путь (если вы не хотите переключиться на Newtonsoft) - вызвать сериализатор вручную и вернуть Content(). Например:

[HttpGet("{id}")]
public ActionResult Get(int id)
{
    var result = _context.Table.Find(id);
    return Content(JsonSerializer.Serialize(result), "application/json");
}

Не забудьте предоставить те же параметры сериализатора, которые вы настроили через AddJsonOptions в Startup (если есть)

person Qwertie    schedule 30.03.2021
comment
Спасибо, что нашли время написать об этом! Я буду использовать ваш обходной путь, пока ошибка не будет исправлена - person Aaron Jordan; 06.07.2021