| File: ActionConstraints\ActionConstraintCache.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc.ActionConstraints; internal sealed class ActionConstraintCache { private readonly IActionDescriptorCollectionProvider _collectionProvider; private readonly IActionConstraintProvider[] _actionConstraintProviders; private volatile InnerCache? _currentCache; public ActionConstraintCache( IActionDescriptorCollectionProvider collectionProvider, IEnumerable<IActionConstraintProvider> actionConstraintProviders) { _collectionProvider = collectionProvider; _actionConstraintProviders = actionConstraintProviders.OrderBy(item => item.Order).ToArray(); } internal InnerCache CurrentCache { get { var current = _currentCache; var actionDescriptors = _collectionProvider.ActionDescriptors; if (current == null || current.Version != actionDescriptors.Version) { current = new InnerCache(actionDescriptors); _currentCache = current; } return current; } } public IReadOnlyList<IActionConstraint>? GetActionConstraints(HttpContext httpContext, ActionDescriptor action) { var cache = CurrentCache; if (cache.Entries.TryGetValue(action, out var entry)) { return GetActionConstraintsFromEntry(entry, httpContext, action); } if (action.ActionConstraints == null || action.ActionConstraints.Count == 0) { return null; } var items = new List<ActionConstraintItem>(action.ActionConstraints.Count); for (var i = 0; i < action.ActionConstraints.Count; i++) { items.Add(new ActionConstraintItem(action.ActionConstraints[i])); } ExecuteProviders(httpContext, action, items); var actionConstraints = ExtractActionConstraints(items); var allActionConstraintsCached = true; for (var i = 0; i < items.Count; i++) { var item = items[i]; if (!item.IsReusable) { item.Constraint = null; allActionConstraintsCached = false; } } if (allActionConstraintsCached) { entry = new CacheEntry(actionConstraints!); } else { entry = new CacheEntry(items); } cache.Entries.TryAdd(action, entry); return actionConstraints; } private IReadOnlyList<IActionConstraint>? GetActionConstraintsFromEntry(CacheEntry entry, HttpContext httpContext, ActionDescriptor action) { Debug.Assert(entry.ActionConstraints != null || entry.Items != null); if (entry.ActionConstraints != null) { return entry.ActionConstraints; } var items = new List<ActionConstraintItem>(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 ActionConstraintItem(item.Metadata)); } } ExecuteProviders(httpContext, action, items); return ExtractActionConstraints(items); } private void ExecuteProviders(HttpContext httpContext, ActionDescriptor action, List<ActionConstraintItem> items) { var context = new ActionConstraintProviderContext(httpContext, action, items); for (var i = 0; i < _actionConstraintProviders.Length; i++) { _actionConstraintProviders[i].OnProvidersExecuting(context); } for (var i = _actionConstraintProviders.Length - 1; i >= 0; i--) { _actionConstraintProviders[i].OnProvidersExecuted(context); } } private static IReadOnlyList<IActionConstraint>? ExtractActionConstraints(List<ActionConstraintItem> items) { var count = 0; for (var i = 0; i < items.Count; i++) { if (items[i].Constraint != null) { count++; } } if (count == 0) { return null; } var actionConstraints = new IActionConstraint[count]; var actionConstraintIndex = 0; for (var i = 0; i < items.Count; i++) { var actionConstraint = items[i].Constraint; if (actionConstraint != null) { actionConstraints[actionConstraintIndex++] = actionConstraint; } } return actionConstraints; } internal sealed class InnerCache { private readonly ActionDescriptorCollection _actions; public InnerCache(ActionDescriptorCollection actions) { _actions = actions; } public ConcurrentDictionary<ActionDescriptor, CacheEntry> Entries { get; } = new ConcurrentDictionary<ActionDescriptor, CacheEntry>(); public int Version => _actions.Version; } internal readonly struct CacheEntry { public CacheEntry(IReadOnlyList<IActionConstraint> actionConstraints) { ActionConstraints = actionConstraints; Items = null; } public CacheEntry(List<ActionConstraintItem> items) { Items = items; ActionConstraints = null; } public IReadOnlyList<IActionConstraint>? ActionConstraints { get; } public List<ActionConstraintItem>? Items { get; } } }