// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.AspNet.Mvc.ApiExplorer; using Microsoft.AspNet.Authorization; using Microsoft.AspNet.Cors.Core; using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Routing; using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.ApplicationModels { /// /// A default implementation of . /// public class DefaultControllerModelBuilder : IControllerModelBuilder { private readonly IActionModelBuilder _actionModelBuilder; private readonly AuthorizationOptions _authorizationOptions; /// /// Creates a new . /// /// The used to create actions. public DefaultControllerModelBuilder( IActionModelBuilder actionModelBuilder, IOptions authorizationOptions) { _actionModelBuilder = actionModelBuilder; _authorizationOptions = authorizationOptions?.Options ?? new AuthorizationOptions(); } /// public ControllerModel BuildControllerModel([NotNull] TypeInfo typeInfo) { var controllerModel = CreateControllerModel(typeInfo); var controllerType = typeInfo.AsType(); foreach (var methodInfo in controllerType.GetMethods()) { var actionModels = _actionModelBuilder.BuildActionModels(typeInfo, methodInfo); if (actionModels != null) { foreach (var actionModel in actionModels) { actionModel.Controller = controllerModel; controllerModel.Actions.Add(actionModel); } } } foreach (var propertyHelper in PropertyHelper.GetProperties(controllerType)) { var propertyInfo = propertyHelper.Property; var propertyModel = CreatePropertyModel(propertyInfo); if (propertyModel != null) { propertyModel.Controller = controllerModel; controllerModel.ControllerProperties.Add(propertyModel); } } return controllerModel; } /// /// Creates an for the given . /// /// The . /// A for the given . protected virtual ControllerModel CreateControllerModel([NotNull] TypeInfo typeInfo) { // For attribute routes on a controller, we want want to support 'overriding' routes on a derived // class. So we need to walk up the hierarchy looking for the first class to define routes. // // Then we want to 'filter' the set of attributes, so that only the effective routes apply. var currentTypeInfo = typeInfo; var objectTypeInfo = typeof(object).GetTypeInfo(); IRouteTemplateProvider[] routeAttributes = null; do { routeAttributes = currentTypeInfo .GetCustomAttributes(inherit: false) .OfType() .ToArray(); if (routeAttributes.Length > 0) { // Found 1 or more route attributes. break; } currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); } while (currentTypeInfo != objectTypeInfo); // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType // is needed to so that the result of ToArray() is object var attributes = typeInfo.GetCustomAttributes(inherit: true).OfType().ToArray(); // This is fairly complicated so that we maintain referential equality between items in // ControllerModel.Attributes and ControllerModel.Attributes[*].Attribute. var filteredAttributes = new List(); foreach (var attribute in attributes) { if (attribute is IRouteTemplateProvider) { // This attribute is a route-attribute, leave it out. } else { filteredAttributes.Add(attribute); } } filteredAttributes.AddRange(routeAttributes); attributes = filteredAttributes.ToArray(); var controllerModel = new ControllerModel(typeInfo, attributes); AddRange( controllerModel.AttributeRoutes, routeAttributes.Select(a => new AttributeRouteModel(a))); controllerModel.ControllerName = typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ? typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) : typeInfo.Name; AddRange(controllerModel.ActionConstraints, attributes.OfType()); AddRange(controllerModel.Filters, attributes.OfType()); AddRange(controllerModel.RouteConstraints, attributes.OfType()); var enableCors = attributes.OfType().SingleOrDefault(); if (enableCors != null) { controllerModel.Filters.Add(new CorsAuthorizationFilterFactory(enableCors.PolicyName)); } var disableCors = attributes.OfType().SingleOrDefault(); if (disableCors != null) { controllerModel.Filters.Add(new DisableCorsAuthorizationFilter()); } var policy = AuthorizationPolicy.Combine(_authorizationOptions, attributes.OfType()); if (policy != null) { controllerModel.Filters.Add(new AuthorizeFilter(policy)); } var apiVisibility = attributes.OfType().FirstOrDefault(); if (apiVisibility != null) { controllerModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi; } var apiGroupName = attributes.OfType().FirstOrDefault(); if (apiGroupName != null) { controllerModel.ApiExplorer.GroupName = apiGroupName.GroupName; } // Controllers can implement action filter and result filter interfaces. We add // a special delegating filter implementation to the pipeline to handle it. // // This is needed because filters are instantiated before the controller. if (typeof(IAsyncActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo) || typeof(IActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo)) { controllerModel.Filters.Add(new ControllerActionFilter()); } if (typeof(IAsyncResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo) || typeof(IResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo)) { controllerModel.Filters.Add(new ControllerResultFilter()); } return controllerModel; } /// /// Creates a for the given . /// /// The . /// A for the given . protected virtual PropertyModel CreatePropertyModel([NotNull] PropertyInfo propertyInfo) { // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType // is needed to so that the result of ToArray() is object var attributes = propertyInfo.GetCustomAttributes(inherit: true).OfType().ToArray(); var propertyModel = new PropertyModel(propertyInfo, attributes); var bindingInfo = BindingInfo.GetBindingInfo(attributes); propertyModel.BindingInfo = bindingInfo; propertyModel.PropertyName = propertyInfo.Name; return propertyModel; } private static void AddRange(IList list, IEnumerable items) { foreach (var item in items) { list.Add(item); } } } }