前面讲到ApplicationModel对象,那接下来看看ApplicationModel
/// <summary> /// A model for configuring controllers in an MVC application. /// </summary> [DebuggerDisplay("ApplicationModel: Controllers: {Controllers.Count}, Filters: {Filters.Count}")] public class ApplicationModel : IPropertyModel, IFilterModel, IApiExplorerModel { /// <summary> /// Initializes a new instance of <see cref="ApplicationModel"/>. /// </summary> public ApplicationModel() { ApiExplorer = new ApiExplorerModel(); Controllers = new List<ControllerModel>(); Filters = new List<IFilterMetadata>(); Properties = new Dictionary<object, object>(); } /// <summary> /// Gets or sets the <see cref="ApiExplorerModel"/> for the application. /// </summary> /// <remarks> /// <see cref="ApplicationModel.ApiExplorer"/> allows configuration of default settings /// for ApiExplorer that apply to all actions unless overridden by /// <see cref="ControllerModel.ApiExplorer"/> or <see cref="ActionModel.ApiExplorer"/>. /// /// If using <see cref="ApplicationModel.ApiExplorer"/> to set <see cref="ApiExplorerModel.IsVisible"/> to /// <c>true</c>, this setting will only be honored for actions which use attribute routing. /// </remarks> public ApiExplorerModel ApiExplorer { get; set; } /// <summary> /// Gets the <see cref="ControllerModel"/> instances. /// </summary> public IList<ControllerModel> Controllers { get; } /// <summary> /// Gets the global <see cref="IFilterMetadata"/> instances. /// </summary> public IList<IFilterMetadata> Filters { get; } /// <summary> /// Gets a set of properties associated with all actions. /// These properties will be copied to <see cref="Abstractions.ActionDescriptor.Properties"/>. /// </summary> public IDictionary<object, object> Properties { get; } }
上面是ApplicationModel的关系图
首先看下DefaultApplicationModelProvider如何创建ApplicationModel
public void OnProvidersExecuting(ApplicationModelProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } foreach (var filter in _mvcOptions.Filters) { context.Result.Filters.Add(filter); } foreach (var controllerType in context.ControllerTypes) { var controllerModel = CreateControllerModel(controllerType); if (controllerModel == null) { continue; } context.Result.Controllers.Add(controllerModel); controllerModel.Application = context.Result; foreach (var propertyHelper in PropertyHelper.GetProperties(controllerType.AsType())) { var propertyInfo = propertyHelper.Property; var propertyModel = CreatePropertyModel(propertyInfo); if (propertyModel != null) { propertyModel.Controller = controllerModel; controllerModel.ControllerProperties.Add(propertyModel); } } foreach (var methodInfo in controllerType.AsType().GetMethods()) { var actionModel = CreateActionModel(controllerType, methodInfo); if (actionModel == null) { continue; } actionModel.Controller = controllerModel; controllerModel.Actions.Add(actionModel); foreach (var parameterInfo in actionModel.ActionMethod.GetParameters()) { var parameterModel = CreateParameterModel(parameterInfo); if (parameterModel != null) { parameterModel.Action = actionModel; actionModel.Parameters.Add(parameterModel); } } } } }
先添加全局过滤器
然后创建ControllerModel
internal ControllerModel CreateControllerModel(TypeInfo typeInfo) { if (typeInfo == null) { throw new ArgumentNullException(nameof(typeInfo)); } // For attribute routes on a controller, we 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; do { routeAttributes = currentTypeInfo .GetCustomAttributes(inherit: false) .OfType<IRouteTemplateProvider>() .ToArray(); if (routeAttributes.Length > 0) { // Found 1 or more route attributes. break; } currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); } while (currentTypeInfo != objectTypeInfo); // CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object> // is needed to so that the result of ToArray() is object var attributes = typeInfo.GetCustomAttributes(inherit: true); // This is fairly complicated so that we maintain referential equality between items in // ControllerModel.Attributes and ControllerModel.Attributes[*].Attribute. var filteredAttributes = new List<object>(); 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.Selectors, CreateSelectors(attributes)); controllerModel.ControllerName = typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ? typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) : typeInfo.Name; AddRange(controllerModel.Filters, attributes.OfType<IFilterMetadata>()); foreach (var routeValueProvider in attributes.OfType<IRouteValueProvider>()) { controllerModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue); } var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault(); if (apiVisibility != null) { controllerModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi; } var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().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; }
再构建Controller的PropertyModel
internal PropertyModel CreatePropertyModel(PropertyInfo propertyInfo) { if (propertyInfo == null) { throw new ArgumentNullException(nameof(propertyInfo)); } var attributes = propertyInfo.GetCustomAttributes(inherit: true); // BindingInfo for properties can be either specified by decorating the property with binding specific attributes. // ModelMetadata also adds information from the property's type and any configured IBindingMetadataProvider. var modelMetadata = _modelMetadataProvider.GetMetadataForProperty(propertyInfo.DeclaringType, propertyInfo.Name); var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); if (bindingInfo == null) { // Look for BindPropertiesAttribute on the handler type if no BindingInfo was inferred for the property. // This allows a user to enable model binding on properties by decorating the controller type with BindPropertiesAttribute. var declaringType = propertyInfo.DeclaringType; var bindPropertiesAttribute = declaringType.GetCustomAttribute<BindPropertiesAttribute>(inherit: true); if (bindPropertiesAttribute != null) { var requestPredicate = bindPropertiesAttribute.SupportsGet ? _supportsAllRequests : _supportsNonGetRequests; bindingInfo = new BindingInfo { RequestPredicate = requestPredicate, }; } } var propertyModel = new PropertyModel(propertyInfo, attributes) { PropertyName = propertyInfo.Name, BindingInfo = bindingInfo, }; return propertyModel; }
再创建Controller下面的ActionModel
internal ActionModel CreateActionModel( TypeInfo typeInfo, MethodInfo methodInfo) { if (typeInfo == null) { throw new ArgumentNullException(nameof(typeInfo)); } if (methodInfo == null) { throw new ArgumentNullException(nameof(methodInfo)); } if (!IsAction(typeInfo, methodInfo)) { return null; } // CoreCLR returns IEnumerable<Attribute> from GetCustomAttributes - the OfType<object> // is needed to so that the result of ToArray() is object var attributes = methodInfo.GetCustomAttributes(inherit: true); var actionModel = new ActionModel(methodInfo, attributes); AddRange(actionModel.Filters, attributes.OfType<IFilterMetadata>()); var actionName = attributes.OfType<ActionNameAttribute>().FirstOrDefault(); if (actionName?.Name != null) { actionModel.ActionName = actionName.Name; } else { actionModel.ActionName = CanonicalizeActionName(methodInfo.Name); } var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault(); if (apiVisibility != null) { actionModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi; } var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault(); if (apiGroupName != null) { actionModel.ApiExplorer.GroupName = apiGroupName.GroupName; } foreach (var routeValueProvider in attributes.OfType<IRouteValueProvider>()) { actionModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue); } // Now we need to determine the action selection info (cross-section of routes and constraints) // // For attribute routes on a action, we want to support 'overriding' routes on a // virtual method, but allow 'overriding'. So we need to walk up the hierarchy looking // for the first definition to define routes. // // Then we want to 'filter' the set of attributes, so that only the effective routes apply. var currentMethodInfo = methodInfo; IRouteTemplateProvider[] routeAttributes; while (true) { routeAttributes = currentMethodInfo .GetCustomAttributes(inherit: false) .OfType<IRouteTemplateProvider>() .ToArray(); if (routeAttributes.Length > 0) { // Found 1 or more route attributes. break; } // GetBaseDefinition returns 'this' when it gets to the bottom of the chain. var nextMethodInfo = currentMethodInfo.GetBaseDefinition(); if (currentMethodInfo == nextMethodInfo) { break; } currentMethodInfo = nextMethodInfo; } // This is fairly complicated so that we maintain referential equality between items in // ActionModel.Attributes and ActionModel.Attributes[*].Attribute. var applicableAttributes = new List<object>(); foreach (var attribute in attributes) { if (attribute is IRouteTemplateProvider) { // This attribute is a route-attribute, leave it out. } else { applicableAttributes.Add(attribute); } } applicableAttributes.AddRange(routeAttributes); AddRange(actionModel.Selectors, CreateSelectors(applicableAttributes)); return actionModel; }
再创建Action参数的ParameterModel
internal ParameterModel CreateParameterModel(ParameterInfo parameterInfo) { if (parameterInfo == null) { throw new ArgumentNullException(nameof(parameterInfo)); } var attributes = parameterInfo.GetCustomAttributes(inherit: true); BindingInfo bindingInfo; if (_modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) { var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameterInfo); bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); } else { // GetMetadataForParameter should only be used if the user has opted in to the 2.1 behavior. bindingInfo = BindingInfo.GetBindingInfo(attributes); } var parameterModel = new ParameterModel(parameterInfo, attributes) { ParameterName = parameterInfo.Name, BindingInfo = bindingInfo, }; return parameterModel; }