diff --git a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs index 3c502d7d4b..c27af66ed3 100644 --- a/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs +++ b/src/Http/WebUtilities/ref/Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs @@ -54,6 +54,8 @@ namespace Microsoft.AspNetCore.WebUtilities public override long Position { get { throw null; } set { } } public string TempFileName { get { throw null; } } protected override void Dispose(bool disposing) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public override System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] diff --git a/src/Http/WebUtilities/src/FileBufferingReadStream.cs b/src/Http/WebUtilities/src/FileBufferingReadStream.cs index 3aa1fef6b9..ddbeeb81e6 100644 --- a/src/Http/WebUtilities/src/FileBufferingReadStream.cs +++ b/src/Http/WebUtilities/src/FileBufferingReadStream.cs @@ -366,6 +366,20 @@ namespace Microsoft.AspNetCore.WebUtilities } } + public async override ValueTask DisposeAsync() + { + if (!_disposed) + { + _disposed = true; + if (_rentedBuffer != null) + { + _bytePool.Return(_rentedBuffer); + } + + await _buffer.DisposeAsync(); + } + } + private void ThrowIfDisposed() { if (_disposed) diff --git a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs index be72592cfa..10029a5c44 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/DefaultApiDescriptionProvider.cs @@ -569,9 +569,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer newContainerName = GetName(containerName, bindingContext); } - for (var i = 0; i < modelMetadata.Properties.Count; i++) + var metadataProperties = modelMetadata.Properties; + var metadataPropertiesCount = metadataProperties.Count; + for (var i = 0; i < metadataPropertiesCount; i++) { - var propertyMetadata = modelMetadata.Properties[i]; + var propertyMetadata = metadataProperties[i]; var key = new PropertyKey(propertyMetadata, source); var bindingInfo = BindingInfo.GetBindingInfo(Enumerable.Empty(), propertyMetadata); diff --git a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs index 1ac7314281..4b13a1b709 100644 --- a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs +++ b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonInputFormatter.cs @@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { if (inputStream is TranscodingReadStream transcoding) { - transcoding.Dispose(); + await transcoding.DisposeAsync(); } } diff --git a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs index efb4cc1cb9..fc5eba60ec 100644 --- a/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs +++ b/src/Mvc/Mvc.Core/src/Formatters/SystemTextJsonOutputFormatter.cs @@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { if (writeStream is TranscodingWriteStream transcoding) { - transcoding.Dispose(); + await transcoding.DisposeAsync(); } } } diff --git a/src/Mvc/Mvc.Cors/src/CorsApplicationModelProvider.cs b/src/Mvc/Mvc.Cors/src/CorsApplicationModelProvider.cs index 415e358ea0..37e286fa03 100644 --- a/src/Mvc/Mvc.Cors/src/CorsApplicationModelProvider.cs +++ b/src/Mvc/Mvc.Cors/src/CorsApplicationModelProvider.cs @@ -97,15 +97,22 @@ namespace Microsoft.AspNetCore.Mvc.Cors private static void ConfigureCorsActionConstraint(ActionModel actionModel) { - for (var i = 0; i < actionModel.Selectors.Count; i++) - { - var selectorModel = actionModel.Selectors[i]; + var selectors = actionModel.Selectors; + // Read interface .Count once rather than per iteration + var selectorsCount = selectors.Count; - for (var j = 0; j < selectorModel.ActionConstraints.Count; j++) + for (var i = 0; i < selectorsCount; i++) + { + var selectorModel = selectors[i]; + + var actionConstraints = selectorModel.ActionConstraints; + // Read interface .Count once rather than per iteration + var actionConstraintsCount = actionConstraints.Count; + for (var j = 0; j < actionConstraintsCount; j++) { - if (selectorModel.ActionConstraints[j] is HttpMethodActionConstraint httpConstraint) + if (actionConstraints[j] is HttpMethodActionConstraint httpConstraint) { - selectorModel.ActionConstraints[j] = new CorsHttpMethodActionConstraint(httpConstraint); + actionConstraints[j] = new CorsHttpMethodActionConstraint(httpConstraint); } } } @@ -131,11 +138,14 @@ namespace Microsoft.AspNetCore.Mvc.Cors foreach (var selector in action.Selectors) { - for (var i = 0; i < selector.EndpointMetadata.Count; i++) + var metadata = selector.EndpointMetadata; + // Read interface .Count once rather than per iteration + var metadataCount = metadata.Count; + for (var i = 0; i < metadataCount; i++) { - if (selector.EndpointMetadata[i] is HttpMethodMetadata httpMethodMetadata) + if (metadata[i] is HttpMethodMetadata httpMethodMetadata) { - selector.EndpointMetadata[i] = new HttpMethodMetadata(httpMethodMetadata.HttpMethods, acceptCorsPreflight: true); + metadata[i] = new HttpMethodMetadata(httpMethodMetadata.HttpMethods, acceptCorsPreflight: true); } } } diff --git a/src/Mvc/Mvc.Cors/src/CorsHttpMethodActionConstraint.cs b/src/Mvc/Mvc.Cors/src/CorsHttpMethodActionConstraint.cs index 2128b2479c..cab2f0c676 100644 --- a/src/Mvc/Mvc.Cors/src/CorsHttpMethodActionConstraint.cs +++ b/src/Mvc/Mvc.Cors/src/CorsHttpMethodActionConstraint.cs @@ -39,7 +39,9 @@ namespace Microsoft.AspNetCore.Mvc.Cors request.Headers.TryGetValue(AccessControlRequestMethod, out var accessControlRequestMethod) && !StringValues.IsNullOrEmpty(accessControlRequestMethod)) { - for (var i = 0; i < methods.Count; i++) + // Read interface .Count once rather than per iteration + var methodsCount = methods.Count; + for (var i = 0; i < methodsCount; i++) { var supportedMethod = methods[i]; if (string.Equals(supportedMethod, accessControlRequestMethod, StringComparison.OrdinalIgnoreCase)) diff --git a/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsClientModelValidatorProvider.cs b/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsClientModelValidatorProvider.cs index 946db2b755..7e5b77756a 100644 --- a/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsClientModelValidatorProvider.cs +++ b/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsClientModelValidatorProvider.cs @@ -67,9 +67,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations var hasRequiredAttribute = false; - for (var i = 0; i < context.Results.Count; i++) + var results = context.Results; + // Read interface .Count once rather than per iteration + var resultsCount = results.Count; + for (var i = 0; i < resultsCount; i++) { - var validatorItem = context.Results[i]; + var validatorItem = results[i]; if (validatorItem.Validator != null) { // Check if a required attribute is already cached. diff --git a/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsMetadataProvider.cs b/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsMetadataProvider.cs index 1299d6f778..97058965b4 100644 --- a/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsMetadataProvider.cs +++ b/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsMetadataProvider.cs @@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations } else if (displayFormatAttribute != null && !displayFormatAttribute.HtmlEncode) { - displayMetadata.DataTypeName = DataType.Html.ToString(); + displayMetadata.DataTypeName = nameof(DataType.Html); } var containerType = context.Key.ContainerType ?? context.Key.ModelType; @@ -322,11 +322,14 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations throw new ArgumentNullException(nameof(context)); } - var attributes = new List(context.Attributes.Count); - - for (var i = 0; i < context.Attributes.Count; i++) + // Read interface .Count once rather than per iteration + var contextAttributes = context.Attributes; + var contextAttributesCount = contextAttributes.Count; + var attributes = new List(contextAttributesCount); + + for (var i = 0; i < contextAttributesCount; i++) { - var attribute = context.Attributes[i]; + var attribute = contextAttributes[i]; if (attribute is ValidationProviderAttribute validationProviderAttribute) { attributes.AddRange(validationProviderAttribute.GetValidationAttributes()); diff --git a/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsModelValidatorProvider.cs b/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsModelValidatorProvider.cs index a0cd1c4d68..eedb31a781 100644 --- a/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsModelValidatorProvider.cs +++ b/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsModelValidatorProvider.cs @@ -59,9 +59,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations _stringLocalizerFactory); } - for (var i = 0; i < context.Results.Count; i++) + var results = context.Results; + // Read interface .Count once rather than per iteration + var resultsCount = results.Count; + for (var i = 0; i < resultsCount; i++) { - var validatorItem = context.Results[i]; + var validatorItem = results[i]; if (validatorItem.Validator != null) { continue; @@ -106,7 +109,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations return true; } - for (var i = 0; i < validatorMetadata.Count; i++) + // Read interface .Count once rather than per iteration + var validatorMetadataCount = validatorMetadata.Count; + for (var i = 0; i < validatorMetadataCount; i++) { if (validatorMetadata[i] is ValidationAttribute) { diff --git a/src/Mvc/Mvc.DataAnnotations/src/DefaultClientModelValidatorProvider.cs b/src/Mvc/Mvc.DataAnnotations/src/DefaultClientModelValidatorProvider.cs index 54a533a417..60c8197ce1 100644 --- a/src/Mvc/Mvc.DataAnnotations/src/DefaultClientModelValidatorProvider.cs +++ b/src/Mvc/Mvc.DataAnnotations/src/DefaultClientModelValidatorProvider.cs @@ -24,9 +24,11 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations } // Perf: Avoid allocations - for (var i = 0; i < context.Results.Count; i++) + var results = context.Results; + var resultsCount = results.Count; + for (var i = 0; i < resultsCount; i++) { - var validatorItem = context.Results[i]; + var validatorItem = results[i]; // Don't overwrite anything that was done by a previous provider. if (validatorItem.Validator != null) { diff --git a/src/Mvc/Mvc.DataAnnotations/src/NumericClientModelValidatorProvider.cs b/src/Mvc/Mvc.DataAnnotations/src/NumericClientModelValidatorProvider.cs index 20c1cf086a..4c44014e8e 100644 --- a/src/Mvc/Mvc.DataAnnotations/src/NumericClientModelValidatorProvider.cs +++ b/src/Mvc/Mvc.DataAnnotations/src/NumericClientModelValidatorProvider.cs @@ -27,9 +27,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations typeToValidate == typeof(double) || typeToValidate == typeof(decimal)) { - for (var i = 0; i < context.Results.Count; i++) + var results = context.Results; + // Read interface .Count once rather than per iteration + var resultsCount = results.Count; + for (var i = 0; i < resultsCount; i++) { - var validator = context.Results[i].Validator; + var validator = results[i].Validator; if (validator != null && validator is NumericClientModelValidator) { // A validator is already present. No need to add one. @@ -37,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations } } - context.Results.Add(new ClientValidatorItem + results.Add(new ClientValidatorItem { Validator = new NumericClientModelValidator(), IsReusable = true diff --git a/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerInputFormatter.cs b/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerInputFormatter.cs index 69f73e688a..362c8db997 100644 --- a/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerInputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Xml/src/XmlDataContractSerializerInputFormatter.cs @@ -124,10 +124,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters // XmlDataContractSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. var memoryThreshold = DefaultMemoryThreshold; - if (request.ContentLength.HasValue && request.ContentLength.Value > 0 && request.ContentLength.Value < memoryThreshold) + var contentLength = request.ContentLength.GetValueOrDefault(); + if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. - memoryThreshold = (int)request.ContentLength.Value; + memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); @@ -163,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { if (readStream is FileBufferingReadStream fileBufferingReadStream) { - fileBufferingReadStream.Dispose(); + await fileBufferingReadStream.DisposeAsync(); } } } diff --git a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs index 6bc52920b1..4debb6fe69 100644 --- a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs @@ -104,10 +104,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters // XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. var memoryThreshold = DefaultMemoryThreshold; - if (request.ContentLength.HasValue && request.ContentLength.Value > 0 && request.ContentLength.Value < memoryThreshold) + var contentLength = request.ContentLength.GetValueOrDefault(); + if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. - memoryThreshold = (int)request.ContentLength.Value; + memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); @@ -156,7 +157,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { if (readStream is FileBufferingReadStream fileBufferingReadStream) { - fileBufferingReadStream.Dispose(); + await fileBufferingReadStream.DisposeAsync(); } } } diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/BsonTempDataSerializer.cs b/src/Mvc/Mvc.NewtonsoftJson/src/BsonTempDataSerializer.cs index a9d892f222..ae72a7c3a6 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/BsonTempDataSerializer.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/BsonTempDataSerializer.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson private static readonly ConcurrentDictionary> _dictionaryConverters = new ConcurrentDictionary>(); - private static readonly Dictionary _tokenTypeLookup = new Dictionary + private static readonly Dictionary _tokenTypeLookup = new Dictionary(8) { { JTokenType.String, typeof(string) }, { JTokenType.Integer, typeof(int) }, diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs index 8275dbc542..31a96631fc 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/NewtonsoftJsonInputFormatter.cs @@ -134,10 +134,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. var memoryThreshold = DefaultMemoryThreshold; - if (request.ContentLength.HasValue && request.ContentLength.Value > 0 && request.ContentLength.Value < memoryThreshold) + var contentLength = request.ContentLength.GetValueOrDefault(); + if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. - memoryThreshold = (int)request.ContentLength.Value; + memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); @@ -171,7 +172,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters if (readStream is FileBufferingReadStream fileBufferingReadStream) { - fileBufferingReadStream.Dispose(); + await fileBufferingReadStream.DisposeAsync(); } } }