skip to Main Content

I’m working on a .NET 7 api and I’m having a problem regarding Id field of the DTO object.

This is my UserDTO (used to return users in get as well as to post/put a user):

public class UserDTO
{
  public string Id { get; set; }
  public string Email { get; set; }
  public string Password { get; set; }
  public string? FirstName { get; set; }
  public string? LastName { get; set; }
  public bool Status { get; set; } = true;
  public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>();

  public bool ShouldSerializeId()
  {
    return false;
  }
}

UsersController Post:

[HttpPost]
[Route("users")]
public async Task<IActionResult> CreateUserAsync(UserDTO u)
{
    ...
}

Get:

[HttpGet("user/{userId}")]
public async Task<IActionResult> GetUserByIdAsync(string userId)
{
    var u = await _usersApplication.GetByIdAsync(userId);
    var user = _mapper.Map<UserDTO>(u);
    return Ok(user);
}

My problem is leaving the class like that, this next is the json presented by swagger as a Post request:

{
  "id": "string",
  "email": "string",
  "password": "string",
  "firstName": "string",
  "lastName": "string",
  "status": true,
  "roles": [
    {
      "roleName": "string"
    }
  ]
}

And, of course, I don’t want "Id" to be present in the request as it is, obviously, auto-generated.

If I use the [JsonIgnore] attribute it works, but then the "Id" won’t be presented in Get, where it is obviously needed.

What I need is a conditional serializer/deserializer.

I tried the "ShouldSerializeId" approach, but as I’ve read it does not work anymore in .Net 7, as this version of .Net comes with a different Json serializer that does not support it.

Any help to do a conditional serialization?

4

Answers


  1. If you don’t want to show Id property at all , the only way is to separate your DTO into two

    public class UserDTO:UserDtoBase
    {
      public string Id { get; set; }
    }
    
    public class UserDTOBase
    {
       public string Email { get; set; }
      public string Password { get; set; }
      public string? FirstName { get; set; }
      public string? LastName { get; set; }
      public bool Status { get; set; } = true;
      public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>();
    
    }
    

    and use without Id for post

    Using JsonIgnore or the custom serializer is the same as just assign Id = null inside of the POST action since new Dto will be created, just Id will not be assigned and will be null by default.

    Login or Signup to reply.
  2. Simple answer – use different DTOs to create and return data:

    public class CreateUserDTO
    {
      public string Email { get; set; }
      public string Password { get; set; }
      public string? FirstName { get; set; }
      public string? LastName { get; set; }
      public bool Status { get; set; } = true;
      public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>();
    }
    

    Optionally you can share the base set of properties between UserDTO and CreateUserDTO via inheritance.

    For serialization part (if you really need, though in this particular case it is an overkill) you can leverage contract customization support added in .NET 7 to System.Text.Json:

    class TestSe
    {
        public int Id { get; set; }
    
        public string Data { get; set; }
    
        [System.Text.Json.Serialization.JsonIgnore]
        public bool ShouldSerializeId { get; set; } = true;
    }
    
    void SetShouldSerialize(JsonTypeInfo info)
    {
        if (info.Type == typeof(TestSe))
        {
            // NB: this uses JSON property name, not a class member name
            var jsonPropertyInfo = info.Properties.Single(propertyInfo => propertyInfo.Name == "Id");
            jsonPropertyInfo.ShouldSerialize = static (obj, val) => ((TestSe)obj).ShouldSerializeId;
        }
    }
    

    And usage:

    var testSe = new TestSe
    {
        Data = "data"
    };
    var opts = new JsonSerializerOptions
    {
        TypeInfoResolver = new DefaultJsonTypeInfoResolver
        {
            Modifiers =
            {
                SetShouldSerialize
            }
        }
    };
    
    Console.WriteLine(JsonSerializer.Serialize(testSe, opts)); // {"Id":0,"Data":"data"}
    testSe.ShouldSerializeId = false;
    Console.WriteLine(JsonSerializer.Serialize(testSe, opts)); // {"Data":"data"}
    
    Login or Signup to reply.
  3. Separate your object-structure into two objects- one model you’ll save in your database (The returned object might be a DTO as well if you want to add data eg. "Time requested) and one for creating new Users.

    public class User //This is the one you'll have in your database
    {
      [Key]
      public string Id { get; set; }
      public string Email { get; set; }
      public string Password { get; set; }
      public string? FirstName { get; set; }
      public string? LastName { get; set; }
      public bool Status { get; set; } = true;
      public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>();
    }
    
    public class NewUserRequest //This is the one you'll send to your endpoint
    {
      public string Email { get; set; }
      public string Password { get; set; }
      public string? FirstName { get; set; }
      public string? LastName { get; set; }
      public bool Status { get; set; } = true;
      public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>();
    }
    
    Login or Signup to reply.
  4. Have you tried using the [JsonIgnore] decorator and Newtonsoft.NET JSONConverter?

    public class UserDTO
    {
      [JsonIgnore]
      public string Id { get; set; }
      public string Email { get; set; }
      public string Password { get; set; }
      public string? FirstName { get; set; }
      public string? LastName { get; set; }
      public bool Status { get; set; } = true;
      public List<RoleDTO> Roles { get; set; } = new List<RoleDTO>();
    
      public bool ShouldSerializeId()
      {
        return false;
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search