Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 1521f92

Browse files
committed
Relayer action discovery infrastructure
This change introduces a service facade for creating the application model, running conventions, validating the result, and flattening the model. This is used in the ControllerActionDescriptorProvider and provides the existing functionality for now. The ControllerActionDescriptorProvider will process the results and turn each 'flattened' model into a single action descriptor. The next change will introduce another consumer of this service, that turns the 'flattened' model into an EndpointModel so that it can be exposed via Endpoint Routing's convention system. --- The main considerations here: The flattening semantics of application model are pretty complicated :( The validation that CADP does is actually pretty in depth and might be really low value... Errors with writing route templates do happen, and those will be caught by the routing system eventually.... Errors with duplicate route names are similar... Errors with 'mixed' attribute and conventional routing are not at all common. I don't think I've ever seen an issue get filed about this. I did the work to port all of this stuff forward but I'm not totally sure it's valuable - however, I don't really want to make an argument for its removal. These are just some random thoughts to keep in mind if you're reviewing this 👍
1 parent 5a6d438 commit 1521f92

9 files changed

+681
-631
lines changed

src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionAttributeRouteModel.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,141 @@
33

44
using System.Collections.Generic;
55
using System.Linq;
6+
using Microsoft.AspNetCore.Mvc.ActionConstraints;
7+
using Microsoft.AspNetCore.Mvc.Routing;
68

79
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
810
{
911
internal static class ActionAttributeRouteModel
1012
{
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+
11141
public static IEnumerable<(AttributeRouteModel route, SelectorModel actionSelector, SelectorModel controllerSelector)> GetAttributeRoutes(ActionModel actionModel)
12142
{
13143
var controllerAttributeRoutes = actionModel.Controller.Selectors

0 commit comments

Comments
 (0)