asp.Netcore mvc 3.1ソース分析(五)
41861 ワード
ApplicationModelの作成が完了したら、C o n t o r l e r A n g o n DescriptorBuilderクラスのBuildメソッドを呼び出して、対応するControllerActionDescriptorを作成します.
internal static class ControllerActionDescriptorBuilder
{
public static IList Build(ApplicationModel application)
{
return ApplicationModelFactory.Flatten(application, CreateActionDescriptor);
}
private static ControllerActionDescriptor CreateActionDescriptor(
ApplicationModel application,
ControllerModel controller,
ActionModel action,
SelectorModel selector)
{
var actionDescriptor = new ControllerActionDescriptor
{
ActionName = action.ActionName,
MethodInfo = action.ActionMethod,
};
actionDescriptor.ControllerName = controller.ControllerName;
actionDescriptor.ControllerTypeInfo = controller.ControllerType;
AddControllerPropertyDescriptors(actionDescriptor, controller);
AddActionConstraints(actionDescriptor, selector);
AddEndpointMetadata(actionDescriptor, selector);
AddAttributeRoute(actionDescriptor, selector);
AddParameterDescriptors(actionDescriptor, action);
AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters);
AddApiExplorerInfo(actionDescriptor, application, controller, action);
AddRouteValues(actionDescriptor, controller, action);
AddProperties(actionDescriptor, action, controller, application);
return actionDescriptor;
}
private static void AddControllerPropertyDescriptors(ActionDescriptor actionDescriptor, ControllerModel controller)
{
actionDescriptor.BoundProperties = controller.ControllerProperties
.Where(p => p.BindingInfo != null)
.Select(CreateParameterDescriptor)
.ToList();
}
private static void AddParameterDescriptors(ActionDescriptor actionDescriptor, ActionModel action)
{
var parameterDescriptors = new List();
foreach (var parameter in action.Parameters)
{
var parameterDescriptor = CreateParameterDescriptor(parameter);
parameterDescriptors.Add(parameterDescriptor);
}
actionDescriptor.Parameters = parameterDescriptors;
}
private static ParameterDescriptor CreateParameterDescriptor(ParameterModel parameterModel)
{
var parameterDescriptor = new ControllerParameterDescriptor()
{
Name = parameterModel.ParameterName,
ParameterType = parameterModel.ParameterInfo.ParameterType,
BindingInfo = parameterModel.BindingInfo,
ParameterInfo = parameterModel.ParameterInfo,
};
return parameterDescriptor;
}
private static ParameterDescriptor CreateParameterDescriptor(PropertyModel propertyModel)
{
var parameterDescriptor = new ControllerBoundPropertyDescriptor()
{
BindingInfo = propertyModel.BindingInfo,
Name = propertyModel.PropertyName,
ParameterType = propertyModel.PropertyInfo.PropertyType,
PropertyInfo = propertyModel.PropertyInfo,
};
return parameterDescriptor;
}
private static void AddApiExplorerInfo(
ControllerActionDescriptor actionDescriptor,
ApplicationModel application,
ControllerModel controller,
ActionModel action)
{
var isVisible =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
application.ApiExplorer?.IsVisible ??
false;
var isVisibleSetOnActionOrController =
action.ApiExplorer?.IsVisible ??
controller.ApiExplorer?.IsVisible ??
false;
// ApiExplorer isn't supported on conventional-routed actions, but we still allow you to configure
// it at the application level when you have a mix of controller types. We'll just skip over enabling
// ApiExplorer for conventional-routed controllers when this happens.
var isVisibleSetOnApplication = application.ApiExplorer?.IsVisible ?? false;
if (isVisibleSetOnActionOrController && !IsAttributeRouted(actionDescriptor))
{
// ApiExplorer is only supported on attribute routed actions.
throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction(
actionDescriptor.DisplayName));
}
else if (isVisibleSetOnApplication && !IsAttributeRouted(actionDescriptor))
{
// This is the case where we're going to be lenient, just ignore it.
}
else if (isVisible)
{
Debug.Assert(IsAttributeRouted(actionDescriptor));
var apiExplorerActionData = new ApiDescriptionActionData()
{
GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName,
};
actionDescriptor.SetProperty(apiExplorerActionData);
}
}
private static void AddProperties(
ControllerActionDescriptor actionDescriptor,
ActionModel action,
ControllerModel controller,
ApplicationModel application)
{
foreach (var item in application.Properties)
{
actionDescriptor.Properties[item.Key] = item.Value;
}
foreach (var item in controller.Properties)
{
actionDescriptor.Properties[item.Key] = item.Value;
}
foreach (var item in action.Properties)
{
actionDescriptor.Properties[item.Key] = item.Value;
}
}
private static void AddActionFilters(
ControllerActionDescriptor actionDescriptor,
IEnumerable actionFilters,
IEnumerable controllerFilters,
IEnumerable globalFilters)
{
actionDescriptor.FilterDescriptors =
actionFilters.Select(f => new FilterDescriptor(f, FilterScope.Action))
.Concat(controllerFilters.Select(f => new FilterDescriptor(f, FilterScope.Controller)))
.Concat(globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global)))
.OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
.ToList();
}
private static void AddActionConstraints(ControllerActionDescriptor actionDescriptor, SelectorModel selectorModel)
{
if (selectorModel.ActionConstraints?.Count > 0)
{
actionDescriptor.ActionConstraints = new List(selectorModel.ActionConstraints);
}
}
private static void AddEndpointMetadata(ControllerActionDescriptor actionDescriptor, SelectorModel selectorModel)
{
if (selectorModel.EndpointMetadata?.Count > 0)
{
actionDescriptor.EndpointMetadata = new List<object>(selectorModel.EndpointMetadata);
}
}
private static void AddAttributeRoute(ControllerActionDescriptor actionDescriptor, SelectorModel selectorModel)
{
if (selectorModel.AttributeRouteModel != null)
{
actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo
{
Template = selectorModel.AttributeRouteModel.Template,
Order = selectorModel.AttributeRouteModel.Order ?? 0,
Name = selectorModel.AttributeRouteModel.Name,
SuppressLinkGeneration = selectorModel.AttributeRouteModel.SuppressLinkGeneration,
SuppressPathMatching = selectorModel.AttributeRouteModel.SuppressPathMatching,
};
}
}
public static void AddRouteValues(
ControllerActionDescriptor actionDescriptor,
ControllerModel controller,
ActionModel action)
{
// Apply all the constraints defined on the action, then controller (for example, [Area])
// to the actions. Also keep track of all the constraints that require preventing actions
// without the constraint to match. For example, actions without an [Area] attribute on their
// controller should not match when a value has been given for area when matching a url or
// generating a link.
foreach (var kvp in action.RouteValues)
{
// Skip duplicates
if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
{
actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
}
}
foreach (var kvp in controller.RouteValues)
{
// Skip duplicates - this also means that a value on the action will take precedence
if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
{
actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
}
}
// Lastly add the 'default' values
if (!actionDescriptor.RouteValues.ContainsKey("action"))
{
actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty);
}
if (!actionDescriptor.RouteValues.ContainsKey("controller"))
{
actionDescriptor.RouteValues.Add("controller", controller.ControllerName);
}
}
private static bool IsAttributeRouted(ActionDescriptor actionDescriptor)
{
return actionDescriptor.AttributeRouteInfo != null;
}
}
public static List Flatten(
ApplicationModel application,
Func flattener)
{
var results = new List();
var errors = new Dictionarystring>>();
var actionsByMethod = new Dictionary>();
var actionsByRouteName = new Dictionary<string, List>(StringComparer.OrdinalIgnoreCase);
var routeTemplateErrors = new List<string>();
foreach (var controller in application.Controllers)
{
foreach (var action in controller.Actions)
{
foreach (var selector in ActionAttributeRouteModel.FlattenSelectors(action))
{
// PostProcess attribute routes so we can observe any errors.
ReplaceAttributeRouteTokens(controller, action, selector, routeTemplateErrors);
// Add to the data structures we use to find errors.
AddActionToMethodInfoMap(actionsByMethod, action, selector);
AddActionToRouteNameMap(actionsByRouteName, action, selector);
var result = flattener(application, controller, action, selector);
Debug.Assert(result != null);
results.Add(result);
}
}
}
var attributeRoutingConfigurationErrors = new Dictionarystring>();
foreach (var (method, actions) in actionsByMethod)
{
ValidateActionGroupConfiguration(
method,
actions,
attributeRoutingConfigurationErrors);
}
if (attributeRoutingConfigurationErrors.Any())
{
var message = CreateAttributeRoutingAggregateErrorMessage(attributeRoutingConfigurationErrors.Values);
throw new InvalidOperationException(message);
}
var namedRoutedErrors = ValidateNamedAttributeRoutedActions(actionsByRouteName);
if (namedRoutedErrors.Any())
{
var message = CreateAttributeRoutingAggregateErrorMessage(namedRoutedErrors);
throw new InvalidOperationException(message);
}
if (routeTemplateErrors.Any())
{
var message = CreateAttributeRoutingAggregateErrorMessage(routeTemplateErrors);
throw new InvalidOperationException(message);
}
return results;
}
internal static class ActionAttributeRouteModel
{
public static IEnumerable 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.First());
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();
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 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 criticial 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 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);
}
}
}
}