question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

No way to create a resource derived from Identifiable<string> without sending Id from client side

See original GitHub issue

DESCRIPTION

Cannot make a client POST request to create a resource derived from Identifiable<string>

  • Controller validates the model saying The Id field is required.

For many reasons, we might want to assign the Id from the server-side. In the 4.x version, I assign an Id at OnWritingAsync. However, with the 5.x version, we validate the ModelState before reaching the OnWritingAsync function

STEPS TO REPRODUCE

public sealed class RgbColor : Identifiable<string>
{
    [Attr]
    public string DisplayName { get; set; } = null!;

    [HasOne]
    public WorkItemGroup? Group { get; set; }
}
options.AllowClientGeneratedIds = false;
options.ValidateModelState = true;

POST request with the request body

{
   "data":{
      "type":"rgbColor",
      "attributes":{
         "DisplayName":"Blue"
      }
   }
}

EXPECTED BEHAVIOR

  • Allow assigning Id from server-side
  • Don’t throw the Modal Validation with Id

ACTUAL BEHAVIOR

  • The controller always validates the model and throws The Id field is required. exception

VERSIONS USED

  • JsonApiDotNetCore version: 5.0.1
  • ASP.NET Core version: 6.0
  • Entity Framework Core version: 6.0
  • Database provider: InMemory

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
bkoelmancommented, May 14, 2022

I’ve taken a deep dive and found the root cause.

Using a minimal repro project, I was unable to get the same validation error. So I started comparing the compiler-generated nullability attributes in the DLL using ILDASM and found interesting differences. The JsonApiDotNetCore.Annotations assembly contained [NullableContextAttribute] usages that weren’t in the DLL from my simplified repro project:

public abstract class Identifiable<TId>
{
    public virtual TId Id { get; set; } = default!;
}

public class Customer : Identifiable<string>
{
    public string Name { get; set; } = null!;

    [Range(1, 150)]
    public int Age { get; set; }
}

As described here, this is because the StringId and LocalId properties are missing in my simplified version of Identifiable:

The C# compiler doesn’t just use NullableAttribute to mark which values are nullable, it also saves up on the assembly size by compacting several NullableAttributes into a single NullableContextAttribute.

The exact algorithm is described in the nullable-metadata.md file but it’s along the lines of "if a class has more nullable members than non-nullable members, annotate only the non-nullable members with NullableAttribute and mark the class itself as nullable using NullableContextAttribute.

There’s a bug in ASP.NET 6: it duplicates compiler logic to determine nullability, but takes some shortcuts and gets it wrong. This was recently fixed by replacing the custom logic with relying on the NullabilityInfoContext reflection API in the runtime. I’ve created a bug report here.

But it doesn’t stop there. Turns out that NullabilityInfoContext in .NET 6 does not work correctly for our case (see the end of my bug report). There are several recent fixes to that, see https://github.com/dotnet/runtime/issues?q=is%3Aclosed+NullabilityInfoContext+label%3Aarea-System.Reflection.

So I tried with .NET 7 preview 4, where everything works as expected. The solution I recommend to you: use option 3 from my earlier comment. When updating to .NET 7, you can remove the Id property override.

1reaction
bkoelmancommented, May 12, 2022

Thanks for the extra details. One difference between v4 and v5 is that v5 is compiled with nullable reference types enabled. This changes how ASP.NET ModelState interprets the required-ness of Identifiable.Id.

Yesterday I started digging into this some more, but unfortunately my time is limited. I’ll see if I can continue my investigation during the weekend. Depending on my observations, I’ll reopen this issue.

Read more comments on GitHub >

github_iconTop Results From Across the Web

rest - Can I PUT without an ID?
If a resource can be identified without an ID, i.e. there only ... No. PUT means "create or update", and should come with...
Read more >
ModelState validation changes when adding a new ...
No way to create a resource derived from Identifiable<string> without sending Id from client side json-api-dotnet/JsonApiDotNetCore#1153.
Read more >
To include a resource ID in the payload or to derive from URI
1. The reason for us questioning the placement of the ID in the payload was due to Backbone.js passing the ID in a...
Read more >
What security issues could occur when generating ids on ...
The only way to ensure that a client side id does not conflict with existing ids is to check for this on the...
Read more >
Adobe Sign for Microsoft PowerApps and Power Automate
Adobe Acrobat Sign can be integrated with Microsoft® Power Automate and the PowerApps environment to provide you with a smooth digital ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found