12

In my .net Core 3.0 API the [JsonIgnore] Attribute is not working as excepted. Im using System.Text.Json

Instead of the old Newtonsoft.Json

When I'm using my resource that returns a list of Objects, for example:

/api/Object/

The objects are serialized like this:

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

But when I request a single result

/api/Object/{id}

The full object gets serialized like this:

    {
  "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"
    }
  ]
}

The class with the JsonIgnore Attribute looks like this:

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; }
}

This is my WebApi Controller Class:

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);
    }
}

My csproj File:

    <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>

And my Startup:

 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?}");
            });
        }
    }

Am I missing something or is this a Bug?

Update:

I noticed an other strange behavior, when I return a newly created Object instead of the object from the database, everything works perfectly fine.

 [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);
    }
fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
mic
  • 133
  • 1
  • 1
  • 6
  • What happens if you change the `User` property to be non-virtual? Can you please share a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) that I can run locally to see the error? That will help in figuring out whether its a bug in `System.Text.Json` or how you are using it in your api. – ahsonkhan Feb 13 '20 at 00:00
  • I tried your code with ASP.NET Core SDK 3.1.101, but failed to reproduce. It works fine for me. Are you sure you're using the correct JsonIgnore from the right namespace? If it is, could you please show us a way to reproduce? – itminus Feb 13 '20 at 02:16
  • 1
    @mic, the root cause is probably that you are missing a `using System.Text.Json.Serialization` directive at the top of your file and still using the `JsonIgnore` attribute from `Newtonsoft.Json` (if you reference that package), which `S.T.J` doesn't honor. Both libraries have this attribute (with the same name) which is why things might conflict. – ahsonkhan Feb 13 '20 at 05:14
  • @ahsonkhan I tried to reproduce the Error in a new Web-Api Project. But Its not possible, the Code is exactly the same, the only difference is that I don't use a database. Newtonsoft.Json is not referenced in my webapp. Only Swagger and a few other Frameworks that have nothing todo with Json. – mic Feb 13 '20 at 13:17
  • @itminus I also Failed reproducing that error in a new webapp and im completely helpless. – mic Feb 13 '20 at 13:19
  • I updated the example code in the initial question – mic Feb 13 '20 at 13:30
  • @mic it looks like you are in a similar situation as [How can I do JSON serializer ignore navigation properties?](https://stackoverflow.com/questions/26162902/how-can-i-do-json-serializer-ignore-navigation-properties/). `[JsonIgnore]` `public virtual User User { get; set; }` – Kirk Horton Feb 13 '20 at 15:27
  • @KirkHorton This solution forced me to use Newtonsoft.Json. It works, as long as virtual properties should always be ignored. – mic Feb 17 '20 at 09:01
  • @mic just did a bit more digging and it looks like that might be what we're stuck with until maybe .NET 5 according to this open github issue from october 2019 https://github.com/dotnet/runtime/issues/31257 – Kirk Horton Feb 17 '20 at 14:51

7 Answers7

31

You need:

using Newtonsoft.Json;

Instead of:

using System.Text.Json.Serialization;

The nuget package is Microsoft.AspNetCore.Mvc.NewtonsoftJson. It's no longer included in .net core.

Tim
  • 857
  • 6
  • 13
  • Thank you for this! – IamIC May 17 '20 at 18:13
  • Don't use NewtonSoft it screws around with strings when they contain dates. See this thread: https://github.com/JamesNK/Newtonsoft.Json/issues/862 – Aurelien B Jul 10 '20 at 18:23
  • In my project we quite intentionally use System.Text.Json and not Newtonsoft (to this end, we configure the MVC builder in Startup with `AddJsonOptions`). There are numerous differences between them and they need to be configured in different ways, so this is only a reasonable answer for people who used System.Text.Json accidentally. – Qwertie Mar 30 '21 at 17:26
23

You can use [JsonIgnore] from System.Text.Json, and it will work if you call serlization/deserialization methods manually. However; built-in .net core seralization system i.e. for controllers, httpclient.GetFromJsonAsync .. etc. [JsonIgnore] attribute is not working as of today.

Looks like internal serialization dont use System.Text.Json and the JsonIgnore attribute has not yet been adapted. In such a case use [IgnoreDataMember] attribute and .net core internal system will ignore such properties and it will work well.

freewill
  • 1,111
  • 1
  • 10
  • 23
3

I just filed a bug report about this. It occurs when using Lazy-Loading Proxies with System.Text.Json.

The workaround (unless you want to switch to Newtonsoft) is to invoke the serializer manually and return Content(). For example:

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

Don't forget to provide the same serializer options you configured via AddJsonOptions in Startup (if any)

Qwertie
  • 16,354
  • 20
  • 105
  • 148
1

You need to add below code in Startup.cs inside method ConfigureServices

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

and of course, you will need package Microsoft.AspNetCore.Mvc.NewtonsoftJson

Keshab
  • 226
  • 2
  • 3
  • 14
1

For me the issue is caused by lazyLoadingProxies. User load from db is not of type User but of type Castle.Proxies.UserProxy, where no JsonIgnore attribute is applied. I see 3 solutions

  1. use ILazyLoader instead of proxies as described here: https://www.learnentityframeworkcore.com/lazy-loading
  2. Always map lazy loaded properties
  3. Do not use lazy loading at all (it is best practice not to use it in EF Core)
Martin2112
  • 31
  • 3
0

I am using .NetCore 3.1 and System.Text.Json.Serialization v6.0.0.0

[JsonIgnore] is working fine for me

Satish Kumar sonker
  • 1,250
  • 8
  • 15
0

For me I found if you are return the object rather than an explicit JsonResult, it will ignore the JsonIngore attribute and you will get an exception. If you change the return type to JsonResult, it will respect the attribute.

Throws exception -

[HttpGet]
public async Task<PurchaseOrder> Order(string orderNumber)

Respects [JsonIgnore] attribute -

[HttpGet]
public async Task<JsonResult> Order(string orderNumber)
David Lindon
  • 305
  • 2
  • 8