|
3 | 3 |
|
4 | 4 | using System.Collections.Generic;
|
5 | 5 | using System.Linq;
|
| 6 | +using Microsoft.AspNetCore.Mvc.ActionConstraints; |
| 7 | +using Microsoft.AspNetCore.Mvc.Routing; |
6 | 8 |
|
7 | 9 | namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
8 | 10 | {
|
9 | 11 | internal static class ActionAttributeRouteModel
|
10 | 12 | {
|
| 13 | + public static IEnumerable<SelectorModel> FlattenSelectors(ActionModel actionModel) |
| 14 | + { |
| 15 | + // Loop through all attribute routes defined on the controller. |
| 16 | + // These perform a cross-product with all of the action-level attribute routes. |
| 17 | + var controllerSelectors = actionModel.Controller.Selectors |
| 18 | + .Where(sm => sm.AttributeRouteModel != null) |
| 19 | + .ToList(); |
| 20 | + |
| 21 | + // We also include metadata and action constraints from the controller |
| 22 | + // even when there are no routes, or when an action overrides the route template. |
| 23 | + SelectorModel additionalSelector = null; |
| 24 | + if (actionModel.Controller.Selectors.Count > 0) |
| 25 | + { |
| 26 | + // This logic seems arbitrary but there's a good reason for it. |
| 27 | + // |
| 28 | + // When we build the controller level selectors, any metadata or action constraints |
| 29 | + // that aren't IRouteTemplateProvider will be included in all selectors. So we |
| 30 | + // pick any selector and then grab all of the stuff that isn't IRouteTemplateProvider |
| 31 | + // then we've found all of the items that aren't routes. |
| 32 | + // |
| 33 | + // This is fragile wrt application model customizing the data - but no one has |
| 34 | + // run into an issue with this and its pretty esoteric. |
| 35 | + additionalSelector = new SelectorModel(actionModel.Controller.Selectors.First()); |
| 36 | + additionalSelector.AttributeRouteModel = null; |
| 37 | + |
| 38 | + for (var i = additionalSelector.ActionConstraints.Count - 1; i >= 0; i--) |
| 39 | + { |
| 40 | + if (additionalSelector.ActionConstraints[i] is IRouteTemplateProvider) |
| 41 | + { |
| 42 | + additionalSelector.ActionConstraints.RemoveAt(i); |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + for (var i = additionalSelector.EndpointMetadata.Count - 1; i >= 0; i--) |
| 47 | + { |
| 48 | + if (additionalSelector.EndpointMetadata[i] is IRouteTemplateProvider) |
| 49 | + { |
| 50 | + additionalSelector.EndpointMetadata.RemoveAt(i); |
| 51 | + } |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + var actionConstraints = new List<IActionConstraintMetadata>(); |
| 56 | + |
| 57 | + foreach (var actionSelector in actionModel.Selectors) |
| 58 | + { |
| 59 | + var actionRouteModel = actionSelector.AttributeRouteModel; |
| 60 | + |
| 61 | + // We check the action to see if the template allows combination behavior |
| 62 | + // (It doesn't start with / or ~/) so that in the case where we have multiple |
| 63 | + // [Route] attributes on the controller we don't end up creating multiple |
| 64 | + if (actionRouteModel != null && actionRouteModel.IsAbsoluteTemplate) |
| 65 | + { |
| 66 | + // We're overriding the routes from the controller, but any *unbound* constraints |
| 67 | + // still apply. |
| 68 | + var selector = new SelectorModel(actionSelector); |
| 69 | + |
| 70 | + selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel( |
| 71 | + left: null, |
| 72 | + right: actionRouteModel); |
| 73 | + |
| 74 | + AddActionConstraints(selector, additionalSelector?.ActionConstraints); |
| 75 | + AddEndpointMetadata(selector, additionalSelector?.EndpointMetadata); |
| 76 | + |
| 77 | + yield return selector; |
| 78 | + } |
| 79 | + else if (controllerSelectors.Count > 0) |
| 80 | + { |
| 81 | + for (var i = 0; i < controllerSelectors.Count; i++) |
| 82 | + { |
| 83 | + var controllerSelector = controllerSelectors[i]; |
| 84 | + |
| 85 | + // We're using the attribute routes from the controller |
| 86 | + var selector = new SelectorModel(actionSelector); |
| 87 | + |
| 88 | + selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel( |
| 89 | + controllerSelector.AttributeRouteModel, |
| 90 | + actionRouteModel); |
| 91 | + |
| 92 | + AddActionConstraints(selector, controllerSelector.ActionConstraints); |
| 93 | + AddEndpointMetadata(selector, controllerSelector.EndpointMetadata); |
| 94 | + |
| 95 | + // No need to include the additional selector here because it would duplicate |
| 96 | + // data in controllerSelector. |
| 97 | + |
| 98 | + yield return selector; |
| 99 | + } |
| 100 | + } |
| 101 | + else |
| 102 | + { |
| 103 | + // There are no routes on the controller, but any *unbound* constraints |
| 104 | + // still apply. |
| 105 | + var selector = new SelectorModel(actionSelector); |
| 106 | + |
| 107 | + selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel( |
| 108 | + left: null, |
| 109 | + right: actionRouteModel); |
| 110 | + |
| 111 | + AddActionConstraints(selector, additionalSelector?.ActionConstraints); |
| 112 | + AddEndpointMetadata(selector, additionalSelector?.EndpointMetadata); |
| 113 | + |
| 114 | + yield return selector; |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + private static void AddActionConstraints(SelectorModel selector, IList<IActionConstraintMetadata> actionConstraints) |
| 120 | + { |
| 121 | + if (actionConstraints != null) |
| 122 | + { |
| 123 | + for (var i = 0; i < actionConstraints.Count;i++) |
| 124 | + { |
| 125 | + selector.ActionConstraints.Add(actionConstraints[i]); |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + private static void AddEndpointMetadata(SelectorModel selector, IList<object> metadata) |
| 131 | + { |
| 132 | + if (metadata != null) |
| 133 | + { |
| 134 | + for (var i = 0; i < metadata.Count; i++) |
| 135 | + { |
| 136 | + selector.EndpointMetadata.Add(metadata[i]); |
| 137 | + } |
| 138 | + } |
| 139 | + } |
| 140 | + |
11 | 141 | public static IEnumerable<(AttributeRouteModel route, SelectorModel actionSelector, SelectorModel controllerSelector)> GetAttributeRoutes(ActionModel actionModel)
|
12 | 142 | {
|
13 | 143 | var controllerAttributeRoutes = actionModel.Controller.Selectors
|
|
0 commit comments