private void UpdateCollection() { // Using the lock to initialize writes means that we serialize changes. This eliminates // the potential for changes to be processed out of order - the risk is that newer data // could be overwritten by older data. lock (_lock) { var context = new ActionDescriptorProviderContext(); for (var i = 0; i < _actionDescriptorProviders.Length; i++) { _actionDescriptorProviders[i].OnProvidersExecuting(context); } for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--) { _actionDescriptorProviders[i].OnProvidersExecuted(context); } // The sequence for an update is important because we don't want anyone to obtain // the new change token but the old action descriptor collection. // 1. Obtain the old cancellation token source (don't trigger it yet) // 2. Set the new action descriptor collection // 3. Set the new change token // 4. Trigger the old cancellation token source // // Consumers who poll will observe a new action descriptor collection at step 2 - they will see // the new collection and ignore the change token. // // Consumers who listen to the change token will requery at step 4 - they will see the new collection // and new change token. // // Anyone who acquires the collection and change token between steps 2 and 3 will be notified of // a no-op change at step 4. // Step 1. var oldCancellationTokenSource = _cancellationTokenSource; // Step 2. _collection = new ActionDescriptorCollection( new ReadOnlyCollection <ActionDescriptor>(context.Results), _version++); // Step 3. _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); // Step 4 - might be null if it's the first time. oldCancellationTokenSource?.Cancel(); } }
public static ActionSelectionTable <ActionDescriptor> Create(ActionDescriptorCollection actions) { return(CreateCore <ActionDescriptor>( // We need to store the version so the cache can be invalidated if the actions change. version: actions.Version, // For action selection, ignore attribute routed actions items: actions.Items.Where(a => a.AttributeRouteInfo == null), getRouteKeys: a => a.RouteValues.Keys, getRouteValue: (a, key) => { a.RouteValues.TryGetValue(key, out var value); return value ?? string.Empty; })); }
private ApiDescriptionGroupCollection GetCollection(ActionDescriptorCollection actionDescriptors) { var context = new ApiDescriptionProviderContext(actionDescriptors.Items); foreach (var provider in _apiDescriptionProviders) { provider.OnProvidersExecuting(context); } for (var i = _apiDescriptionProviders.Length - 1; i >= 0; i--) { _apiDescriptionProviders[i].OnProvidersExecuted(context); } var groups = context.Results .GroupBy(d => d.GroupName) .Select(g => new ApiDescriptionGroup(g.Key, g.ToArray())) .ToArray(); return new ApiDescriptionGroupCollection(groups, actionDescriptors.Version); }
public CustomActionDescriptorCollectionProvider(ControllerActionDescriptor[] actionDescriptors) { ActionDescriptors = new ActionDescriptorCollection(actionDescriptors, version: 1); }
public void ActionDescriptors_UpdatesAndResubscripes_WhenChangeTokenTriggers() { // Arrange var actionDescriptorProvider = new Mock <IActionDescriptorProvider>(); var expected1 = new ActionDescriptor(); var expected2 = new ActionDescriptor(); var expected3 = new ActionDescriptor(); var invocations = 0; actionDescriptorProvider .Setup(p => p.OnProvidersExecuting(It.IsAny <ActionDescriptorProviderContext>())) .Callback((ActionDescriptorProviderContext context) => { if (invocations == 0) { context.Results.Add(expected1); } else if (invocations == 1) { context.Results.Add(expected2); } else { context.Results.Add(expected3); } invocations++; }); var changeProvider = new TestChangeProvider(); var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider.Object }, new[] { changeProvider }); // Act - 1 var changeToken1 = actionDescriptorCollectionProvider.GetChangeToken(); var collection1 = actionDescriptorCollectionProvider.ActionDescriptors; ActionDescriptorCollection captured = null; changeToken1.RegisterChangeCallback((_) => { captured = actionDescriptorCollectionProvider.ActionDescriptors; }, null); // Assert - 1 Assert.False(changeToken1.HasChanged); Assert.Equal(0, collection1.Version); Assert.Collection(collection1.Items, item => Assert.Same(expected1, item)); // Act - 2 changeProvider.TokenSource.Cancel(); var changeToken2 = actionDescriptorCollectionProvider.GetChangeToken(); var collection2 = actionDescriptorCollectionProvider.ActionDescriptors; changeToken2.RegisterChangeCallback((_) => { captured = actionDescriptorCollectionProvider.ActionDescriptors; }, null); // Assert - 2 Assert.NotSame(changeToken1, changeToken2); Assert.True(changeToken1.HasChanged); Assert.False(changeToken2.HasChanged); Assert.NotSame(collection1, collection2); Assert.NotNull(captured); Assert.Same(captured, collection2); Assert.Equal(1, collection2.Version); Assert.Collection(collection2.Items, item => Assert.Same(expected2, item)); // Act - 3 changeProvider.TokenSource.Cancel(); var changeToken3 = actionDescriptorCollectionProvider.GetChangeToken(); var collection3 = actionDescriptorCollectionProvider.ActionDescriptors; // Assert - 3 Assert.NotSame(changeToken2, changeToken3); Assert.True(changeToken2.HasChanged); Assert.False(changeToken3.HasChanged); Assert.NotSame(collection2, collection3); Assert.NotNull(captured); Assert.Same(captured, collection3); Assert.Equal(2, collection3.Version); Assert.Collection(collection3.Items, item => Assert.Same(expected3, item)); }
public static IEnumerable <PageActionDescriptor> PageActionDescriptors(this ActionDescriptorCollection actionDescriptorCollection) => actionDescriptorCollection.Items.OfType <PageActionDescriptor>();
public static IEnumerable <ControllerActionDescriptor> ControllerActionDescriptors(this ActionDescriptorCollection actionDescriptorCollection) => actionDescriptorCollection.Items.OfType <ControllerActionDescriptor>();
public Cache(ActionDescriptorCollection actions) { // We need to store the version so the cache can be invalidated if the actions change. Version = actions.Version; // We need to build two maps for all of the route values. OrdinalEntries = new Dictionary <string[], List <ActionDescriptor> >(StringArrayComparer.Ordinal); OrdinalIgnoreCaseEntries = new Dictionary <string[], List <ActionDescriptor> >(StringArrayComparer.OrdinalIgnoreCase); // We need to first identify of the keys that action selection will look at (in route data). // We want to only consider conventionally routed actions here. var routeKeys = new HashSet <string>(StringComparer.OrdinalIgnoreCase); for (var i = 0; i < actions.Items.Count; i++) { var action = actions.Items[i]; if (action.AttributeRouteInfo == null) { // This is a conventionally routed action - so make sure we include its keys in the set of // known route value keys. foreach (var kvp in action.RouteValues) { routeKeys.Add(kvp.Key); } } } // We need to hold on to an ordered set of keys for the route values. We'll use these later to // extract the set of route values from an incoming request to compare against our maps of known // route values. RouteKeys = routeKeys.ToArray(); for (var i = 0; i < actions.Items.Count; i++) { var action = actions.Items[i]; if (action.AttributeRouteInfo != null) { // This only handles conventional routing. Ignore attribute routed actions. continue; } // This is a conventionally routed action - so we need to extract the route values associated // with this action (in order) so we can store them in our dictionaries. var routeValues = new string[RouteKeys.Length]; for (var j = 0; j < RouteKeys.Length; j++) { action.RouteValues.TryGetValue(RouteKeys[j], out routeValues[j]); } if (!OrdinalIgnoreCaseEntries.TryGetValue(routeValues, out var entries)) { entries = new List <ActionDescriptor>(); OrdinalIgnoreCaseEntries.Add(routeValues, entries); } entries.Add(action); // We also want to add the same (as in reference equality) list of actions to the ordinal entries. // We'll keep updating `entries` to include all of the actions in the same equivalence class - // meaning, all conventionally routed actions for which the route values are equal ignoring case. // // `entries` will appear in `OrdinalIgnoreCaseEntries` exactly once and in `OrdinalEntries` once // for each variation of casing that we've seen. if (!OrdinalEntries.ContainsKey(routeValues)) { OrdinalEntries.Add(routeValues, entries); } } }