Skip to content

Asp.Net Core's ImplicitRequiredAttributeForNonNullableReferenceTypes is known to be broken for generic types. This project implements a NullabilityModelValidator to properly validate null values for (non-)nullable reference types.

License

Notifications You must be signed in to change notification settings

jharjung/aspnetcore-implicitrequired-fix

Repository files navigation

aspnetcore-implicitrequired-fix

Asp.Net Core's ImplicitRequiredAttributeForNonNullableReferenceTypes is known to be broken for generic types. See: dotnet/aspnetcore#21501

This project implements a NullabilityModelValidator to provide proper validation of null values for (non-)nullable reference types in projects that have opted in to C# 8's nullability analysis.

Resolving nullability at the metadata provider level, as DataAnnotationsMetadataProvider attempts to do, is a dead end. The metadata is computed and cached for a runtime System.Type. Erasure of reference type nullability means that List<string?>? and List<string> are the same type at runtime, and therefore have the same metadata. Not only the "same" metadata, but the same metadata instance. Given this, it's not possible to represent reference type nullability in the validation metadata without either:

  • pushing it into the ModelMetadataIdentity, thus creating different metadata instances, but fundamentally changing how metadata is created and retrieved
    • or
  • tracking it for every possible place this type could be used

To properly resolve nullability, we need:

  • The metadata for the root model (the top-level type of the action parameter)
  • The full path from the root to the value being validated (eg, MyActionParam.MyList[3].MyProp.MyDict[12]...)

This information isn't available on ModelValidationContext, but it's easily obtainable within ValidationVisitor, which constructs the ModelValidationContext and invokes the IModelValidator. Exposing this information to an IModelValidator is a simple matter of subclassing ModelValidationContext to add the properties, subclassing ValidationVisitor to pass them in to the new validation context's constructor, and subclassing ObjectModelValidator to construct the new validation visitor.

With this information in hand, an IModelValidator can walk down the metadata tree, traversing through the Property and Element metadata, and reconstruct the nullability of generic types from the [Nullable] attributes found at their parameter/property declaration sites (and of course the [NullableContext] attributes of the declaring type, where applicable).

Please be aware that this project is a proof of concept, and while it works for my use cases, type systems are complex things with big surface areas. I definitely haven't considered every possible crazy thing you could legally do with generic types in C#, and I've likely overlooked a couple not so crazy things too.

To get going, wire everything up in ConfigureServices like this:

var mvcCore = Services.AddMvcCore();

// A DefaultObjectValidator will be registered by Services.AddMvc() or Services.AddMvcCore()
// Remove it and replace it with our own

var omv = Services.Single(d => d.ServiceType == typeof(IObjectModelValidator));
Services.Remove(omv);

Services.AddSingleton<IObjectModelValidator>(s =>
{
  var opts = s.GetRequiredService<IOptions<MvcOptions>>().Value;
  return new PathAwareObjectModelValidator(s.GetRequiredService<IModelMetadataProvider>(), opts);
});

mvcCore.AddMvcOptions(opts =>
{
  // Don't forget to disable the built-in validation, so it doesn't flag
  // false positives (valid nulls for nullable reference types) on
  // properties of generic classes and elements of generic collections
  opts.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
  
  // Inject the nullability validator
  opts.ModelMetadataDetailsProviders.Insert(0, new NullabilityValidationMetadataProvider());
  opts.ModelValidatorProviders.Insert(0, new NullabilityModelValidatorProvider());
});

About

Asp.Net Core's ImplicitRequiredAttributeForNonNullableReferenceTypes is known to be broken for generic types. This project implements a NullabilityModelValidator to properly validate null values for (non-)nullable reference types.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages