Skip to content

Commit

Permalink
Added caching for validators
Browse files Browse the repository at this point in the history
  • Loading branch information
ajaybhargavb committed Jan 27, 2016
1 parent 71a815b commit 8c4bcf1
Show file tree
Hide file tree
Showing 25 changed files with 486 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ public interface IModelValidatorProvider
/// </summary>
/// <param name="context">The <see cref="ModelValidatorProviderContext"/>.</param>
/// <remarks>
/// Implementations should add <see cref="IModelValidator"/> instances to
/// <see cref="ModelValidatorProviderContext.Validators"/>.
/// Implementations should add the <see cref="IModelValidator"/> instances to the appropriate
/// <see cref="ValidatorItem"/> instance which should be added to
/// <see cref="ModelValidatorProviderContext.Results"/>.
/// </remarks>
void GetValidators(ModelValidatorProviderContext context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ public class ModelValidatorProviderContext
/// Creates a new <see cref="ModelValidatorProviderContext"/>.
/// </summary>
/// <param name="modelMetadata">The <see cref="ModelBinding.ModelMetadata"/>.</param>
public ModelValidatorProviderContext(ModelMetadata modelMetadata)
/// <param name="items">The list of <see cref="ValidatorItem"/>s.</param>
public ModelValidatorProviderContext(ModelMetadata modelMetadata, IList<ValidatorItem> items)
{
ModelMetadata = modelMetadata;
Results = items;
}

/// <summary>
Expand All @@ -39,11 +41,11 @@ public IReadOnlyList<object> ValidatorMetadata
}

/// <summary>
/// Gets the list of <see cref="IModelValidator"/> instances. <see cref="IModelValidatorProvider"/> instances
/// should add validators to this list when
/// Gets the list of <see cref="ValidatorItem"/> instances. <see cref="IModelValidatorProvider"/> instances
/// should add the appropriate <see cref="ValidatorItem.Validator"/> properties when
/// <see cref="IModelValidatorProvider.GetValidators(ModelValidatorProviderContext)"/>
/// is called.
/// </summary>
public IList<IModelValidator> Validators { get; } = new List<IModelValidator>();
public IList<ValidatorItem> Results { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.

namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{
/// <summary>
/// Used to associate validators with <see cref="ValidatorMetadata"/> instances
/// as part of <see cref="ModelValidatorProviderContext"/>. An <see cref="IModelValidator"/> should
/// inspect <see cref="ModelValidatorProviderContext.Results"/> and set <see cref="Validator"/> and
/// <see cref="IsReusable"/> as appropriate.
/// </summary>
public class ValidatorItem
{
/// <summary>
/// Creates a new <see cref="ValidatorItem"/>.
/// </summary>
public ValidatorItem()
{
}

/// <summary>
/// Creates a new <see cref="ValidatorItem"/>.
/// </summary>
/// <param name="validatorMetadata">The <see cref="ValidatorMetadata"/>.</param>
public ValidatorItem(object validatorMetadata)
{
ValidatorMetadata = validatorMetadata;
}

/// <summary>
/// Gets the metadata associated with the <see cref="Validator"/>.
/// </summary>
public object ValidatorMetadata { get; }

/// <summary>
/// Gets or sets the <see cref="IModelValidator"/>.
/// </summary>
public IModelValidator Validator { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not <see cref="Validator"/> can be reused across requests.
/// </summary>
public bool IsReusable { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ internal static void AddMvcCoreServices(IServiceCollection services)
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
}));
services.TryAddSingleton<IObjectModelValidator, DefaultObjectValidator>();
services.TryAddSingleton<ValidatorCache>();

//
// Random Infrastructure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,13 @@ private IReadOnlyList<IActionConstraint> ExtractActionConstraints(List<ActionCon
}

var actionConstraints = new IActionConstraint[count];
for (int i = 0, j = 0; i < items.Count; i++)
var actionConstraintIndex = 0;
for (int i = 0; i < items.Count; i++)
{
var actionConstraint = items[i].Constraint;
if (actionConstraint != null)
{
actionConstraints[j++] = actionConstraint;
actionConstraints[actionConstraintIndex++] = actionConstraint;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ public class DefaultModelValidatorProvider : IModelValidatorProvider
public void GetValidators(ModelValidatorProviderContext context)
{
//Perf: Avoid allocations here
for (var i = 0; i < context.ValidatorMetadata.Count; i++)
for (var i = 0; i < context.Results.Count; i++)
{
var validator = context.ValidatorMetadata[i] as IModelValidator;
var validatorItem = context.Results[i];

// Don't overwrite anything that was done by a previous provider.
if (validatorItem.Validator != null)
{
continue;
}

var validator = validatorItem.ValidatorMetadata as IModelValidator;
if (validator != null)
{
context.Validators.Add(validator);
validatorItem.Validator = validator;
validatorItem.IsReusable = true;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public class DefaultObjectValidator : IObjectModelValidator
{
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly ValidatorCache _validatorCache;

/// <summary>
/// Initializes a new instance of <see cref="DefaultObjectValidator"/>.
/// </summary>
/// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
public DefaultObjectValidator(
IModelMetadataProvider modelMetadataProvider)
IModelMetadataProvider modelMetadataProvider,
ValidatorCache validatorCache)
{
if (modelMetadataProvider == null)
{
throw new ArgumentNullException(nameof(modelMetadataProvider));
}

if (validatorCache == null)
{
throw new ArgumentNullException(nameof(validatorCache));
}

_modelMetadataProvider = modelMetadataProvider;
_validatorCache = validatorCache;
}

/// <inheritdoc />
Expand All @@ -50,6 +58,7 @@ public void Validate(
var visitor = new ValidationVisitor(
actionContext,
validatorProvider,
_validatorCache,
_modelMetadataProvider,
validationState);

Expand Down
5 changes: 3 additions & 2 deletions src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,13 @@ private IFilterMetadata[] ExtractFilters(List<FilterItem> items)
else
{
var filters = new IFilterMetadata[count];
for (int i = 0, j = 0; i < items.Count; i++)
var filterIndex = 0;
for (int i = 0; i < items.Count; i++)
{
var filter = items[i].Filter;
if (filter != null)
{
filters[j++] = filter;
filters[filterIndex++] = filter;
}
}

Expand Down
145 changes: 145 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ValidatorCache
{
private readonly IReadOnlyList<IModelValidator> EmptyArray = new IModelValidator[0];

private readonly ConcurrentDictionary<ModelMetadata, CacheEntry> _cacheEntries = new ConcurrentDictionary<ModelMetadata, CacheEntry>();

public IReadOnlyList<IModelValidator> GetValidators(ModelMetadata metadata, IModelValidatorProvider validatorProvider)
{
CacheEntry entry;
if (_cacheEntries.TryGetValue(metadata, out entry))
{
return GetValidatorsFromEntry(entry, metadata, validatorProvider);
}

var items = new List<ValidatorItem>(metadata.ValidatorMetadata.Count);
for (var i = 0; i < metadata.ValidatorMetadata.Count; i++)
{
items.Add(new ValidatorItem(metadata.ValidatorMetadata[i]));
}

ExecuteProvider(validatorProvider, metadata, items);

var validators = ExtractValidators(items);

var allValidatorsCached = true;
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
if (!item.IsReusable)
{
item.Validator = null;
allValidatorsCached = false;
}
}

if (allValidatorsCached)
{
entry = new CacheEntry(validators);
}
else
{
entry = new CacheEntry(items);
}

_cacheEntries.TryAdd(metadata, entry);

return validators;
}

private IReadOnlyList<IModelValidator> GetValidatorsFromEntry(CacheEntry entry, ModelMetadata metadata, IModelValidatorProvider validationProvider)
{
Debug.Assert(entry.Validators != null || entry.Items != null);

if (entry.Validators != null)
{
return entry.Validators;
}

var items = new List<ValidatorItem>(entry.Items.Count);
for (var i = 0; i < entry.Items.Count; i++)
{
var item = entry.Items[i];
if (item.IsReusable)
{
items.Add(item);
}
else
{
items.Add(new ValidatorItem(item.ValidatorMetadata));
}
}

ExecuteProvider(validationProvider, metadata, items);

return ExtractValidators(items);
}

private void ExecuteProvider(IModelValidatorProvider validatorProvider, ModelMetadata metadata, List<ValidatorItem> items)
{
var context = new ModelValidatorProviderContext(metadata, items);
validatorProvider.GetValidators(context);
}

private IReadOnlyList<IModelValidator> ExtractValidators(List<ValidatorItem> items)
{
var count = 0;
for (var i = 0; i < items.Count; i++)
{
if (items[i].Validator != null)
{
count++;
}
}

if (count == 0)
{
return EmptyArray;
}

var validators = new IModelValidator[count];

var validatorIndex = 0;
for (int i = 0; i < items.Count; i++)
{
var validator = items[i].Validator;
if (validator != null)
{
validators[validatorIndex++] = validator;
}
}

return validators;
}

private struct CacheEntry
{
public CacheEntry(IReadOnlyList<IModelValidator> validators)
{
Validators = validators;
Items = null;
}

public CacheEntry(List<ValidatorItem> items)
{
Items = items;
Validators = null;
}

public IReadOnlyList<IModelValidator> Validators { get; }

public List<ValidatorItem> Items { get; }
}
}
}
Loading

0 comments on commit 8c4bcf1

Please sign in to comment.