| File: ApplicationModels\ActionAttributeRouteModel.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.Linq; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Routing; namespace Microsoft.AspNetCore.Mvc.ApplicationModels; internal static class ActionAttributeRouteModel { public static IEnumerable<SelectorModel> FlattenSelectors(ActionModel actionModel) { // Loop through all attribute routes defined on the controller. // These perform a cross-product with all of the action-level attribute routes. var controllerSelectors = actionModel.Controller.Selectors .Where(sm => sm.AttributeRouteModel != null) .ToList(); // We also include metadata and action constraints from the controller // even when there are no routes, or when an action overrides the route template. SelectorModel? additionalSelector = null; if (actionModel.Controller.Selectors.Count > 0) { // This logic seems arbitrary but there's a good reason for it. // // When we build the controller level selectors, any metadata or action constraints // that aren't IRouteTemplateProvider will be included in all selectors. So we // pick any selector and then grab all of the stuff that isn't IRouteTemplateProvider // then we've found all of the items that aren't routes. // // This is fragile wrt application model customizing the data - but no one has // run into an issue with this and its pretty esoteric. additionalSelector = new SelectorModel(actionModel.Controller.Selectors[0]); additionalSelector.AttributeRouteModel = null; for (var i = additionalSelector.ActionConstraints.Count - 1; i >= 0; i--) { if (additionalSelector.ActionConstraints[i] is IRouteTemplateProvider) { additionalSelector.ActionConstraints.RemoveAt(i); } } for (var i = additionalSelector.EndpointMetadata.Count - 1; i >= 0; i--) { if (additionalSelector.EndpointMetadata[i] is IRouteTemplateProvider) { additionalSelector.EndpointMetadata.RemoveAt(i); } } } var actionConstraints = new List<IActionConstraintMetadata>(); foreach (var actionSelector in actionModel.Selectors) { var actionRouteModel = actionSelector.AttributeRouteModel; // We check the action to see if the template allows combination behavior // (It doesn't start with / or ~/) so that in the case where we have multiple // [Route] attributes on the controller we don't end up creating multiple if (actionRouteModel != null && actionRouteModel.IsAbsoluteTemplate) { // We're overriding the routes from the controller, but any *unbound* constraints // still apply. var selector = new SelectorModel(actionSelector); selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel( left: null, right: actionRouteModel); AddActionConstraints(selector, additionalSelector?.ActionConstraints); AddEndpointMetadata(selector, additionalSelector?.EndpointMetadata); yield return selector; } else if (controllerSelectors.Count > 0) { for (var i = 0; i < controllerSelectors.Count; i++) { var controllerSelector = controllerSelectors[i]; // We're using the attribute routes from the controller var selector = new SelectorModel(actionSelector); selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel( controllerSelector.AttributeRouteModel, actionRouteModel); AddActionConstraints(selector, controllerSelector.ActionConstraints); AddEndpointMetadata(selector, controllerSelector.EndpointMetadata); // No need to include the additional selector here because it would duplicate // data in controllerSelector. yield return selector; } } else { // There are no routes on the controller, but any *unbound* constraints // still apply. var selector = new SelectorModel(actionSelector); selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel( left: null, right: actionRouteModel); AddActionConstraints(selector, additionalSelector?.ActionConstraints); AddEndpointMetadata(selector, additionalSelector?.EndpointMetadata); yield return selector; } } } private static void AddActionConstraints(SelectorModel selector, IList<IActionConstraintMetadata>? actionConstraints) { if (actionConstraints != null) { for (var i = 0; i < actionConstraints.Count; i++) { selector.ActionConstraints.Add(actionConstraints[i]); } } } private static void AddEndpointMetadata(SelectorModel selector, IList<object>? controllerMetadata) { if (controllerMetadata != null) { // It is critical to get the order in which metadata appears in endpoint metadata correct. More significant metadata // must appear later in the sequence. In this case, the values in `controllerMetadata` should have their order // preserved, but appear earlier than the entries in `selector.EndpointMetadata`. for (var i = 0; i < controllerMetadata.Count; i++) { selector.EndpointMetadata.Insert(i, controllerMetadata[i]); } } } public static IEnumerable<(AttributeRouteModel? route, SelectorModel actionSelector, SelectorModel? controllerSelector)> GetAttributeRoutes(ActionModel actionModel) { var controllerAttributeRoutes = actionModel.Controller.Selectors .Where(sm => sm.AttributeRouteModel != null) .Select(sm => sm.AttributeRouteModel) .ToList(); foreach (var actionSelectorModel in actionModel.Selectors) { var actionRouteModel = actionSelectorModel.AttributeRouteModel; // We check the action to see if the template allows combination behavior // (It doesn't start with / or ~/) so that in the case where we have multiple // [Route] attributes on the controller we don't end up creating multiple if (actionRouteModel != null && actionRouteModel.IsAbsoluteTemplate) { var route = AttributeRouteModel.CombineAttributeRouteModel( left: null, right: actionRouteModel); yield return (route, actionSelectorModel, null); } else if (controllerAttributeRoutes.Count > 0) { for (var i = 0; i < actionModel.Controller.Selectors.Count; i++) { // We're using the attribute routes from the controller var controllerSelector = actionModel.Controller.Selectors[i]; var route = AttributeRouteModel.CombineAttributeRouteModel( controllerSelector.AttributeRouteModel, actionRouteModel); yield return (route, actionSelectorModel, controllerSelector); } } else { var route = AttributeRouteModel.CombineAttributeRouteModel( left: null, right: actionRouteModel); yield return (route, actionSelectorModel, null); } } } }