# ASP.NET Core Considerations

ASP.NET Core reads nullable annotations at runtime to drive model validation and serialization behavior. Enabling NRTs in an ASP.NET Core project can change request validation outcomes, not just compiler warnings:

- **MVC model validation treats non-nullable properties as `[Required]`**: When NRTs are enabled, ASP.NET Core MVC and Web API implicitly add `[Required(AllowEmptyStrings = true)]` to every non-nullable reference type property in DTOs and view models. A `string Name` property that previously accepted null from JSON or form posts will now return a 400 Bad Request. Review all model classes when enabling NRTs. To disable this behavior during gradual migration, set `SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true` in `AddControllers` options.
- **Minimal API parameter optionality changes with NRTs**: When NRTs are enabled, minimal API parameter binding uses nullable annotations to determine whether a parameter is required or optional. A `string name` parameter that was previously treated as optional (accepting null) becomes required and returns a 400 Bad Request if missing. To preserve the previous behavior, explicitly mark the parameter as nullable (`string? name`). Review all minimal API endpoint parameters when enabling NRTs. See [optional parameters](https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis/parameter-binding#optional-parameters).
- **Enable `JsonSerializerOptions.RespectNullableAnnotations = true` (.NET 9+)**: For .NET 9+ projects, always enable `RespectNullableAnnotations` (along with `RespectRequiredConstructorParameters`) to align runtime serialization behavior with your NRT annotations. Without this, `System.Text.Json` silently assigns `null` to non-nullable properties, undermining compile-time null safety. When enabled, the serializer throws `JsonException` when a non-nullable property receives an explicit `null` during deserialization, or emits `null` for a non-nullable property during serialization. Be aware this enforcement has hard limitations rooted in how NRTs are represented in IL. It does **not** cover:
  - Collection element types (`List<string>` and `List<string?>` are indistinguishable via reflection)
  - Dictionary value types (`Dictionary<string, string>` vs `Dictionary<string, string?>`)
  - Top-level types passed directly to `Deserialize<T>`
  - Generic type parameter nullability

  For these gaps, use manual validation or custom converters. Do not rely on `RespectNullableAnnotations` alone for complete null safety in your JSON layer.
- **Use `#nullable disable`, not `#nullable disable warnings` on model files**: Just as with EF Core, `#nullable disable warnings` only suppresses compiler diagnostics — the annotations remain active and MVC still reads them via reflection to infer `[Required]`. Use `#nullable disable` to fully opt out for files not yet migrated.
- **Razor Pages `[BindProperty]` properties**: Properties decorated with `[BindProperty]` (e.g., `public InputModel Input { get; set; }`) are populated by model binding during POST requests — similar to how EF Core initializes `DbSet` properties. Initialize with `= default!` or suppress CS8618 with a pragma. After a `ModelState.IsValid` check succeeds, sub-properties with `[Required]` can be accessed with the null-forgiving operator (`!`), since validation guarantees they are non-null.
- **Collection properties in ViewModels and DTOs**: Prefer non-nullable with an empty initializer (`= new List<T>()`) over nullable. An empty collection means "no items"; null means "unknown/not loaded." This avoids forcing every consumer to null-check before iterating and matches the EF Core convention for collection navigations.
- **Avoid `?.` followed by `!`**: The pattern `obj?.Property!` is contradictory — `?.` handles the null case by producing null, then `!` immediately asserts the result is non-null. Use either `obj!.Property` (assert non-null, then access) or `obj?.Property` (conditionally access and handle null downstream). The `?.` + `!` combination often appears in Razor Page code-behind when accessing `[BindProperty]` model sub-properties; prefer `obj!.Property` after validation confirms the model is bound.
