Skip to content

Add nullability to more MVC assemblies #31338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
5 commits merged into from
Apr 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class ModelValidationResult
/// </summary>
/// <param name="memberName">The name of the entry on which validation was performed.</param>
/// <param name="message">The validation message.</param>
public ModelValidationResult(string memberName, string message)
public ModelValidationResult(string? memberName, string? message)
{
MemberName = memberName ?? string.Empty;
Message = message ?? string.Empty;
Expand Down
2 changes: 2 additions & 0 deletions src/Mvc/Mvc.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
*REMOVED*Microsoft.AspNetCore.Mvc.Routing.AttributeRouteInfo.Name.get -> string!
*REMOVED*static Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult.Success(object! model) -> Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult!
*REMOVED*static Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult.SuccessAsync(object! model) -> System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult!>!
*REMOVED*Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationResult.ModelValidationResult(string! memberName, string! message) -> void
Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor.RouteValues.get -> System.Collections.Generic.IDictionary<string!, string?>!
Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor.BindingInfo.get -> Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo?
Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintItem.Constraint.get -> Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint?
Expand Down Expand Up @@ -101,3 +102,4 @@ virtual Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext.Result.get -> Mic
virtual Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.ActionArguments.get -> System.Collections.Generic.IDictionary<string!, object?>!
virtual Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata.BoundConstructorInvoker.get -> System.Func<object?[]!, object!>?
virtual Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata.ContainerMetadata.get -> Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata?
Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationResult.ModelValidationResult(string? memberName, string? message) -> void
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable

using System.Diagnostics;
using System.Text;
using Microsoft.AspNetCore.Http;
Expand Down
5 changes: 3 additions & 2 deletions src/Mvc/Mvc.Cors/src/CorsAuthorizationFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
Expand Down Expand Up @@ -64,15 +65,15 @@ public CorsAuthorizationFilter(
/// <summary>
/// The policy name used to fetch a <see cref="CorsPolicy"/>.
/// </summary>
public string PolicyName { get; set; }
public string? PolicyName { get; set; }

/// <inheritdoc />
// Since clients' preflight requests would not have data to authenticate requests, this
// filter must run before any other authorization filters.
public int Order => int.MinValue + 100;

/// <inheritdoc />
public async Task OnAuthorizationAsync(Filters.AuthorizationFilterContext context)
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context == null)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Mvc/Mvc.Cors/src/CorsAuthorizationFilterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
/// </summary>
internal class CorsAuthorizationFilterFactory : IFilterFactory, IOrderedFilter
{
private readonly string _policyName;
private readonly string? _policyName;

/// <summary>
/// Creates a new instance of <see cref="CorsAuthorizationFilterFactory"/>.
/// </summary>
/// <param name="policyName">Name used to fetch a CORS policy.</param>
public CorsAuthorizationFilterFactory(string policyName)
public CorsAuthorizationFilterFactory(string? policyName)
{
_policyName = policyName;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Mvc/Mvc.Cors/src/CorsLoggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
Expand All @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
{
internal static class CorsLoggerExtensions
{
private static readonly Action<ILogger, Type, Exception> _notMostEffectiveFilter;
private static readonly Action<ILogger, Type, Exception?> _notMostEffectiveFilter;

static CorsLoggerExtensions()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;aspnetcoremvc;cors</PackageTags>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down
17 changes: 17 additions & 0 deletions src/Mvc/Mvc.Cors/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
#nullable enable
*REMOVED*~Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.CorsAuthorizationFilter(Microsoft.AspNetCore.Cors.Infrastructure.ICorsService corsService, Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider policyProvider) -> void
*REMOVED*~Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.CorsAuthorizationFilter(Microsoft.AspNetCore.Cors.Infrastructure.ICorsService corsService, Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider policyProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) -> void
*REMOVED*~Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.OnAuthorizationAsync(Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext context) -> System.Threading.Tasks.Task
*REMOVED*~Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.PolicyName.get -> string
*REMOVED*~Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.PolicyName.set -> void
*REMOVED*~static Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions.AddCors(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder
*REMOVED*~static Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions.AddCors(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action<Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions> setupAction) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder
*REMOVED*~static Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions.ConfigureCors(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action<Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions> setupAction) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder
Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.CorsAuthorizationFilter(Microsoft.AspNetCore.Cors.Infrastructure.ICorsService! corsService, Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider! policyProvider) -> void
Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.CorsAuthorizationFilter(Microsoft.AspNetCore.Cors.Infrastructure.ICorsService! corsService, Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider! policyProvider, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.OnAuthorizationAsync(Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext! context) -> System.Threading.Tasks.Task!
Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.PolicyName.get -> string?
Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter.PolicyName.set -> void
static Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions.AddCors(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder! builder) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder!
static Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions.AddCors(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder! builder, System.Action<Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions!>! setupAction) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder!
static Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions.ConfigureCors(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder! builder, System.Action<Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions!>! setupAction) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder!

2 changes: 1 addition & 1 deletion src/Mvc/Mvc.DataAnnotations/src/AttributeAdapterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public abstract class AttributeAdapterBase<TAttribute> :
/// </summary>
/// <param name="attribute">The <see cref="ValidationAttribute"/> being wrapped.</param>
/// <param name="stringLocalizer">The <see cref="IStringLocalizer"/> to be used in error generation.</param>
public AttributeAdapterBase(TAttribute attribute, IStringLocalizer stringLocalizer)
public AttributeAdapterBase(TAttribute attribute, IStringLocalizer? stringLocalizer)
: base(attribute, stringLocalizer)
{
}
Expand Down
4 changes: 2 additions & 2 deletions src/Mvc/Mvc.DataAnnotations/src/CompareAttributeAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class CompareAttributeAdapter : AttributeAdapterBase<CompareAttribute>
{
private readonly string _otherProperty;

public CompareAttributeAdapter(CompareAttribute attribute, IStringLocalizer stringLocalizer)
public CompareAttributeAdapter(CompareAttribute attribute, IStringLocalizer? stringLocalizer)
: base(new CompareAttributeWrapper(attribute), stringLocalizer)
{
_otherProperty = "*." + attribute.OtherProperty;
Expand Down Expand Up @@ -54,7 +54,7 @@ public override string GetErrorMessage(ModelValidationContextBase validationCont
// populate OtherPropertyDisplayName until you call FormatErrorMessage.
private sealed class CompareAttributeWrapper : CompareAttribute
{
public ModelValidationContextBase ValidationContext { get; set; }
public ModelValidationContextBase ValidationContext { get; set; } = default!;

public CompareAttributeWrapper(CompareAttribute attribute)
: base(attribute.OtherProperty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations
internal class DataAnnotationsClientModelValidatorProvider : IClientModelValidatorProvider
{
private readonly IOptions<MvcDataAnnotationsLocalizationOptions> _options;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
private readonly IStringLocalizerFactory? _stringLocalizerFactory;
private readonly IValidationAttributeAdapterProvider _validationAttributeAdapterProvider;

/// <summary>
Expand All @@ -32,7 +32,7 @@ internal class DataAnnotationsClientModelValidatorProvider : IClientModelValidat
public DataAnnotationsClientModelValidatorProvider(
IValidationAttributeAdapterProvider validationAttributeAdapterProvider,
IOptions<MvcDataAnnotationsLocalizationOptions> options,
IStringLocalizerFactory stringLocalizerFactory)
IStringLocalizerFactory? stringLocalizerFactory)
{
if (validationAttributeAdapterProvider == null)
{
Expand All @@ -55,7 +55,7 @@ public void CreateValidators(ClientValidatorProviderContext context)
{
throw new ArgumentNullException(nameof(context));
}
IStringLocalizer stringLocalizer = null;
IStringLocalizer? stringLocalizer = null;
if (_options.Value.DataAnnotationLocalizerProvider != null && _stringLocalizerFactory != null)
{
// This will pass first non-null type (either containerType or modelType) to delegate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal static class DataAnnotationsLocalizationServices
{
public static void AddDataAnnotationsLocalizationServices(
IServiceCollection services,
Action<MvcDataAnnotationsLocalizationOptions> setupAction)
Action<MvcDataAnnotationsLocalizationOptions>? setupAction)
{
services.AddLocalization();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Localization;
Expand All @@ -34,14 +30,14 @@ internal class DataAnnotationsMetadataProvider :
private const string NullableContextAttributeFullName = "System.Runtime.CompilerServices.NullableContextAttribute";
private const string NullableContextFlagsFieldName = "Flag";

private readonly IStringLocalizerFactory _stringLocalizerFactory;
private readonly IStringLocalizerFactory? _stringLocalizerFactory;
private readonly MvcOptions _options;
private readonly MvcDataAnnotationsLocalizationOptions _localizationOptions;

public DataAnnotationsMetadataProvider(
MvcOptions options,
IOptions<MvcDataAnnotationsLocalizationOptions> localizationOptions,
IStringLocalizerFactory stringLocalizerFactory)
IStringLocalizerFactory? stringLocalizerFactory)
{
if (options == null)
{
Expand Down Expand Up @@ -120,7 +116,7 @@ public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
}

var containerType = context.Key.ContainerType ?? context.Key.ModelType;
IStringLocalizer localizer = null;
IStringLocalizer? localizer = null;
if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null)
{
localizer = _localizationOptions.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory);
Expand Down Expand Up @@ -201,20 +197,20 @@ public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
var groupedDisplayNamesAndValues = new List<KeyValuePair<EnumGroupAndName, string>>();
var namesAndValues = new Dictionary<string, string>();

IStringLocalizer enumLocalizer = null;
IStringLocalizer? enumLocalizer = null;
if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null)
{
enumLocalizer = _localizationOptions.DataAnnotationLocalizerProvider(underlyingType, _stringLocalizerFactory);
}

var enumFields = Enum.GetNames(underlyingType)
.Select(name => underlyingType.GetField(name))
.Select(name => underlyingType.GetField(name)!)
.OrderBy(field => field.GetCustomAttribute<DisplayAttribute>(inherit: false)?.GetOrder() ?? 1000);

foreach (var field in enumFields)
{
var groupName = GetDisplayGroup(field);
var value = ((Enum)field.GetValue(obj: null)).ToString("d");
var value = ((Enum)field.GetValue(obj: null)!).ToString("d");

groupedDisplayNamesAndValues.Add(new KeyValuePair<EnumGroupAndName, string>(
new EnumGroupAndName(
Expand Down Expand Up @@ -271,9 +267,9 @@ public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
}

// Order
if (displayAttribute?.GetOrder() != null)
if (displayAttribute?.GetOrder() is int order)
{
displayMetadata.Order = displayAttribute.GetOrder().Value;
displayMetadata.Order = order;
}

// Placeholder
Expand Down Expand Up @@ -374,25 +370,25 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
// The only way we could arrive here is if the ModelMetadata was constructed using the non-default provider.
// We'll cursorily examine the attributes on the property, but not the ContainerType to make a decision about it's nullability.

if (HasNullableAttribute(context.PropertyAttributes, out var propertyHasNullableAttribute))
if (HasNullableAttribute(context.PropertyAttributes!, out var propertyHasNullableAttribute))
{
addInferredRequiredAttribute = propertyHasNullableAttribute;
}
}
else
{
addInferredRequiredAttribute = IsNullableReferenceType(
property.DeclaringType,
property.DeclaringType!,
member: null,
context.PropertyAttributes);
context.PropertyAttributes!);
}
}
else if (context.Key.MetadataKind == ModelMetadataKind.Parameter)
{
addInferredRequiredAttribute = IsNullableReferenceType(
context.Key.ParameterInfo?.Member.ReflectedType,
context.Key.ParameterInfo!.Member.ReflectedType,
context.Key.ParameterInfo.Member,
context.ParameterAttributes);
context.ParameterAttributes!);
}
else
{
Expand Down Expand Up @@ -431,7 +427,7 @@ public void CreateValidationMetadata(ValidationMetadataProviderContext context)
}
}

private static string GetDisplayName(FieldInfo field, IStringLocalizer stringLocalizer)
private static string GetDisplayName(FieldInfo field, IStringLocalizer? stringLocalizer)
{
var display = field.GetCustomAttribute<DisplayAttribute>(inherit: false);
if (display != null)
Expand Down Expand Up @@ -466,7 +462,7 @@ private static string GetDisplayGroup(FieldInfo field)
return string.Empty;
}

internal static bool IsNullableReferenceType(Type containingType, MemberInfo member, IEnumerable<object> attributes)
internal static bool IsNullableReferenceType(Type? containingType, MemberInfo? member, IEnumerable<object> attributes)
{
if (HasNullableAttribute(attributes, out var result))
{
Expand Down Expand Up @@ -508,8 +504,13 @@ internal static bool HasNullableAttribute(IEnumerable<object> attributes, out bo
return true; // [Nullable] found but type is not an NNRT
}

internal static bool IsNullableBasedOnContext(Type containingType, MemberInfo member)
internal static bool IsNullableBasedOnContext(Type? containingType, MemberInfo? member)
{
if (containingType is null)
{
return false;
}

// For generic types, inspecting the nullability requirement additionally requires
// inspecting the nullability constraint on generic type parameters. This is fairly non-triviial
// so we'll just avoid calculating it. Users should still be able to apply an explicit [Required]
Expand Down
Loading