zoukankan      html  css  js  c++  java
  • 5. abp集成asp.net core

    一、前言

    参照前篇《4. abp中的asp.net core模块剖析》,首先放张图,这也是asp.net core框架上MVC模块的扩展点

    img

    二、abp的mvc对象

    AbpAspNetCoreMvcOptions类

    从这个类的名称来看,这个是abp框架里面的asp.net core配置mvc选项类,是abp对asp.net core mvc的封装。源码如下:

    public class AbpAspNetCoreMvcOptions
    {
        public ConventionalControllerOptions ConventionalControllers { get; }
    
        public AbpAspNetCoreMvcOptions()
        {
            ConventionalControllers = new ConventionalControllerOptions();
        }
    }
    

    这个类只有一个默认构造函数,用于实例化一个名为ConventionalControllerOptions的类,从名称来看(得益于变量和类的命名规范化)这是Controller的规约配置。

    ConventionalControllerOptions类

    该类源码如下:

    public class ConventionalControllerOptions
    {
        public ConventionalControllerSettingList ConventionalControllerSettings { get; }
    
        public List<Type> FormBodyBindingIgnoredTypes { get; }
        
        public ConventionalControllerOptions()
        {
            ConventionalControllerSettings = new ConventionalControllerSettingList();
    
            FormBodyBindingIgnoredTypes = new List<Type>
            {
                typeof(IFormFile)
            };
        }
    
        public ConventionalControllerOptions Create(Assembly assembly, [CanBeNull] Action<ConventionalControllerSetting> optionsAction = null)
        {
            var setting = new ConventionalControllerSetting(assembly, ModuleApiDescriptionModel.DefaultRootPath);
            optionsAction?.Invoke(setting);
            setting.Initialize();
            ConventionalControllerSettings.Add(setting);
            return this;
        }
    }
    

    在这里要提下asp.net core的options模式,一般XXXOptions类都会在默认的构造函数中实例化一些对象,Options类的作用就是将一个POCO类注册到服务容器中,使得我们可以在控制器的构造函数中通过IOptions获取到TOptions类的实例。

    这个类只有一个Create方法,返回当前TOptions类的实例,当然,在这个方法中构造了规约控制器的配置(ConventionalControllerSetting)<对于这个类的描述请查看第三点>。在这个Create方法中,首先实例化一个ConventionalControllerSetting类,参数就是传过来的规约控制器所在的程序集以及url路由中默认的根目录(app)。接下来再调用委托,参数就是前面实例化的ConventionalControllerSetting,然后就是实例化(Initialize)操作,检索规约控制器集合。

    ConventionalControllerSetting类

    这个规约控制器的配置如下:

    public class ConventionalControllerSetting
    {
        [NotNull]
        public Assembly Assembly { get; }
        [NotNull]
        public HashSet<Type> ControllerTypes { get; } //TODO: Internal?
        [NotNull]
        public string RootPath
        {
            get => _rootPath;
            set
            {
                Check.NotNull(value, nameof(value));
                _rootPath = value;
            }
        }
        private string _rootPath;
        [CanBeNull]
        public Action<ControllerModel> ControllerModelConfigurer { get; set; }
        [CanBeNull]
        public Func<UrlControllerNameNormalizerContext, string> UrlControllerNameNormalizer { get; set; }
        [CanBeNull]
        public Func<UrlActionNameNormalizerContext, string> UrlActionNameNormalizer { get; set; }
        public Action<ApiVersioningOptions> ApiVersionConfigurer { get; set; }
        public ConventionalControllerSetting([NotNull] Assembly assembly, [NotNull] string rootPath)
        {
            Assembly = assembly;
            RootPath = rootPath;
            ControllerTypes = new HashSet<Type>();
            ApiVersions = new List<ApiVersion>();
        }
    
        public void Initialize()
        {
            var types = Assembly.GetTypes()
                .Where(IsRemoteService)
                .WhereIf(TypePredicate != null, TypePredicate);
    
            foreach (var type in types)
            {
                ControllerTypes.Add(type);
            }
        }
    
        private static bool IsRemoteService(Type type)
        {
            if (!type.IsPublic || type.IsAbstract || type.IsGenericType)
            {
                return false;
            }
            var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(type);
            if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type))
            {
                return false;
            }
    
            if (typeof(IRemoteService).IsAssignableFrom(type))
            {
                return true;
            }
    
            return false;
        }
    }
    

    在这个类中有几个重要的成员变量,首先是Assembly,这个是规约控制器所在的程序集,abp通过这个程序集去检索规约控制器;第二个就是ControllerTypes,它用于存储规约控制器类型,而这些类型就是从Assembly程序集中检索出来的;最后就是RootPath,它表示默认的根目录,在abp中是"app"。接下来就是两个方法了,首先是IsRemoteService,顾名思义就是检索RemoteService,从代码来看,主要就是检索RemoteAttribute和继承自IRemoteService接口的类,为什么要根据这两个来检索呢?很简单,看看IAppService的定义:

     public interface IApplicationService : 
            IRemoteService
    {
    }
    

    再来看看Initialize方法:

    public void Initialize()
    {
        var types = Assembly.GetTypes()
            .Where(IsRemoteService)
            .WhereIf(TypePredicate != null, TypePredicate);
    
        foreach (var type in types)
        {
            ControllerTypes.Add(type);
        }
    }
    

    它正是通过调用IsRemoteService方法来检索规约控制器,然后添加到ControllerTypes中的。

    三、abp中的应用模型规约

    在最上面的aspnetcore mvc扩展图中,规约模块(Convention)可以调换掉mvc框架的默认应用模型(Model),从而自定义的控制器等。abp中封装了这么一个规约类,源码如下:

    public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency
    {
        private readonly AbpAspNetCoreMvcOptions _options;
    
        public AbpServiceConvention(IOptions<AbpAspNetCoreMvcOptions> options)
        {
            _options = options.Value;
        }
    
        public void Apply(ApplicationModel application)
        {
            ApplyForControllers(application);
        }
    
        protected virtual void ApplyForControllers(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                var controllerType = controller.ControllerType.AsType();
                var configuration = GetControllerSettingOrNull(controllerType);
    
                //TODO: We can remove different behaviour for ImplementsRemoteServiceInterface. If there is a configuration, then it should be applied!
                //TODO: But also consider ConventionalControllerSetting.IsRemoteService method too..!
    
                if (ImplementsRemoteServiceInterface(controllerType))
                {
                    controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes);
                    configuration?.ControllerModelConfigurer?.Invoke(controller);
                    ConfigureRemoteService(controller, configuration);
                }
                else
                {
                    var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo());
                    if (remoteServiceAttr != null && remoteServiceAttr.IsEnabledFor(controllerType))
                    {
                        ConfigureRemoteService(controller, configuration);
                    }
                }
            }
        }
    
        protected virtual void ConfigureRemoteService(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration)
        {
            ConfigureApiExplorer(controller);
            ConfigureSelector(controller, configuration);
            ConfigureParameters(controller);
        }
    }
    
    IAbpServiceConvention接口

    看看IAbpServiceConvention接口的定义:

    public interface IAbpServiceConvention : IApplicationModelConvention
    {
    }
    

    可以看到这个接口是继承自aspnet core的IApplicationModelConvention。这个接口有一个Apply方法,该方法,可以简单的理解为应用规约替换默认的应用模型。源码如下:

    public interface IApplicationModelConvention
    {
        //
        // 摘要:
        //     Called to apply the convention to the Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel.
        //
        // 参数:
        //   application:
        //     The Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel.
        void Apply(ApplicationModel application);
    }
    
    AbpServiceConvention类

    回到AbpServiceConvention类,这个类的构造函数就是用过Options模式获取到aspnetcoremvcoption类的实例,主要就是在ApplyForController方法上,顾名思义,就是应用于控制器。先看看这个方法:

    protected virtual void ApplyForControllers(ApplicationModel application)
    {
        foreach (var controller in application.Controllers)
        {
            var controllerType = controller.ControllerType.AsType();
            var configuration = GetControllerSettingOrNull(controllerType);
    
            //TODO: We can remove different behaviour for ImplementsRemoteServiceInterface. If there is a configuration, then it should be applied!
            //TODO: But also consider ConventionalControllerSetting.IsRemoteService method too..!
    
            if (ImplementsRemoteServiceInterface(controllerType))
            {
                controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes);
                configuration?.ControllerModelConfigurer?.Invoke(controller);
                ConfigureRemoteService(controller, configuration);
            }
            else
            {
                var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo());
                if (remoteServiceAttr != null && remoteServiceAttr.IsEnabledFor(controllerType))
                {
                    ConfigureRemoteService(controller, configuration);
                }
            }
        }
    }
    

    在这个方法里面遍历应用模型里面的控制器(Controller)集合,根据控制器去检索规约控制器配置(ConventionalControllerSetting),上面也提到了这个类,就是一些约定的配置,如果我们配置了控制器模型(ConventionModel),那么就会在这里被调用。接下来最重要的就是ConfigureRemoteService方法。

    ConfigureRemoteService方法

    源码如下:

    protected virtual void ConfigureRemoteService(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration)
    {
        ConfigureApiExplorer(controller);
        ConfigureSelector(controller, configuration);
        ConfigureParameters(controller);
    }
    

    在这里就是为我们的远程服务也就是XXXAppServices类配置详细的api信息。首先就是配置ApiExplorer,主要就是开放Api检索,swagger就是调用这个的。Selector就是配置Api的HTTPMethod和路由模型。Parameters则配置Action的参数,主要就是配置复杂类型的参数。

    ConfigureApiExplorer

    The ApiExplorer contains functionality for discovering and exposing metadata about your MVC application. 这句话是摘自博客 Introduction to the ApiExplorer in ASP.NET Core。我们翻译过来就是:ApiExplorer包含发现和公开MVC应用程序元数据的功能。从命名我们也能看出来这用来检索Api的。abp中是如何处理ApiExplorer的呢?

    protected virtual void ConfigureApiExplorer(ControllerModel controller)
    {
        if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
        {
            controller.ApiExplorer.GroupName = controller.ControllerName;
        }
    
        if (controller.ApiExplorer.IsVisible == null)
        {
            var controllerType = controller.ControllerType.AsType();
            var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo());
            if (remoteServiceAtt != null)
            {
                controller.ApiExplorer.IsVisible =
                    remoteServiceAtt.IsEnabledFor(controllerType) &&
                    remoteServiceAtt.IsMetadataEnabledFor(controllerType);
            }
            else
            {
                controller.ApiExplorer.IsVisible = true;
            }
        }
    
        foreach (var action in controller.Actions)
        {
            ConfigureApiExplorer(action);
        }
    }
    
    protected virtual void ConfigureApiExplorer(ActionModel action)
    {
        if (action.ApiExplorer.IsVisible == null)
        {
            var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
            if (remoteServiceAtt != null)
            {
                action.ApiExplorer.IsVisible =
                    remoteServiceAtt.IsEnabledFor(action.ActionMethod) &&
                    remoteServiceAtt.IsMetadataEnabledFor(action.ActionMethod);
            }
        }
    }
    

    这个方法中并没有做其余的事情,只是检索RemoteAttribute,然后去配置ApiExplorerModel类的IsVisible,默认的是true,也就是开放出来,提供检索。swagger就是通过这个来枚举api的。

    ConfigureSelector

    这个比较难理解,先看看aspnet core中的SelectorModel源码:

    public class SelectorModel
    {
        public SelectorModel();
        public SelectorModel(SelectorModel other);
    
        public IList<IActionConstraintMetadata> ActionConstraints { get; }
        public AttributeRouteModel AttributeRouteModel { get; set; }
        //
        // 摘要:
        //     Gets the Microsoft.AspNetCore.Mvc.ApplicationModels.SelectorModel.EndpointMetadata
        //     associated with the Microsoft.AspNetCore.Mvc.ApplicationModels.SelectorModel.
        public IList<object> EndpointMetadata { get; }
    }
    

    分析下这个类,首先是ActionConstrains,这是一个接口其中就有一个实现HttpMethodActionConstraint,这个类就是约束了Action的HTTP类型,也就是平时在action上标记的[HTTPGet],一般标记了此特性,aspnetcore会默认实例化一个SelectorModel对象。然后就是最重要的AttributeRouteModel,这个就是路由特性,即平时在action上标记的[Route("xxx/xxx")],同时也实例化了一个SelectorModel对象。看看ConfigureSelector方法:

    protected virtual void ConfigureSelector(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration)
    {
        if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
        {
            return;
        }
    
        var rootPath = GetRootPathOrDefault(controller.ControllerType.AsType());
    
        foreach (var action in controller.Actions)
        {
            ConfigureSelector(rootPath, controller.ControllerName, action, configuration);
        }
    }
    
    protected virtual void ConfigureSelector(string rootPath, string controllerName, ActionModel action, [CanBeNull] ConventionalControllerSetting configuration)
    {
        if (!action.Selectors.Any())
        {
            AddAbpServiceSelector(rootPath, controllerName, action, configuration);
        }
        else
        {
            NormalizeSelectorRoutes(rootPath, controllerName, action, configuration);
        }
    }
    
    protected virtual void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, [CanBeNull] ConventionalControllerSetting configuration)
    {
        var httpMethod = SelectHttpMethod(action, configuration);
    
        var abpServiceSelectorModel = new SelectorModel
        {
            AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration),
            ActionConstraints = { new HttpMethodActionConstraint(new[] { httpMethod }) }
        };
    
        action.Selectors.Add(abpServiceSelectorModel);
    }
    

    如果我们配置了路由特性,那么直接返回,否则,我们首先获取到默认的根目录(默认是app)。接下来就去配置abp的Selector,首先是选择HTTPMethod,这个是按照约定来的选择的,如下:

    public static Dictionary<string, string[]> ConventionalPrefixes { get; set; } = new Dictionary<string, string[]>
    {
        {"GET", new[] {"GetList", "GetAll", "Get"}},
        {"PUT", new[] {"Put", "Update"}},
        {"DELETE", new[] {"Delete", "Remove"}},
        {"POST", new[] {"Create", "Add", "Insert", "Post"}},
        {"PATCH", new[] {"Patch"}}
    };
    

    根据Action的名称来选择(默认是POST),然后实例化一个HttpMethodActionConstraint类,传入的参数就是HTTPMethod,这个就是前面说到的SelectorModel,最后就是创建路由模型了,我们会去计算一个路由模板,根据这个模板实例化RouteAttribute,再通过这个去实例化AttributeRouteModel,从而构造了SelectorModel的两个重要属性。路由模板的计算规则如下:

    protected virtual string CalculateRouteTemplate(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration)
    {
        var controllerNameInUrl = NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
    
        var url = $"api/{rootPath}/{controllerNameInUrl.ToCamelCase()}";
    
        //Add {id} path if needed
        if (action.Parameters.Any(p => p.ParameterName == "id"))
        {
            url += "/{id}";
        }
    
        //Add action name if needed
        var actionNameInUrl = NormalizeUrlActionName(rootPath, controllerName, action, httpMethod, configuration);
        if (!actionNameInUrl.IsNullOrEmpty())
        {
            url += $"/{actionNameInUrl.ToCamelCase()}";
    
            //Add secondary Id
            var secondaryIds = action.Parameters.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList();
            if (secondaryIds.Count == 1)
            {
                url += $"/{{{secondaryIds[0].ParameterName}}}";
            }
        }
    
        return url;
    }
    

    首先,Abp的动态控制器约束是以AppService、ApplicationService、Service结尾的控制器,在这里要注意两点,如果action参数是id,或者以id结尾且仅有一个参数,那么路由就是:

    api/app/xxx/{id}/{action}
    或
    api/app/xxx/{action}/{id}
    

    构造完url之后就去实例化RouteAttribute特性,构造路由:

    return new AttributeRouteModel(
        new RouteAttribute(
            CalculateRouteTemplate(rootPath, controllerName, action, httpMethod, configuration)
        )
    );
    

    如果没有按照abp的action命名约束命名,并标记了HTTPMethod特性,那么就会调用aspnet core默认的路由,源码如下:

    protected virtual void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, [CanBeNull] ConventionalControllerSetting configuration)
    {
        foreach (var selector in action.Selectors)
        {
            var httpMethod = selector.ActionConstraints
                .OfType<HttpMethodActionConstraint>()
                .FirstOrDefault()?
                .HttpMethods?
                .FirstOrDefault();
    
            if (httpMethod == null)
            {
                httpMethod = SelectHttpMethod(action, configuration);
            }
    
            if (selector.AttributeRouteModel == null)
            {
                selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration);
            }
    
            if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
            {
                selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] {httpMethod}));
            }
        }
    }
    
    ConfigureParameters

    顾名思义,这是用来配置action的参数,默认是调用aspnetcore mvc本身的参数绑定机制:

    protected virtual void ConfigureParameters(ControllerModel controller)
    {
        /* Default binding system of Asp.Net Core for a parameter
         * 1. Form values
         * 2. Route values.
         * 3. Query string.
         */
    
        foreach (var action in controller.Actions)
        {
            foreach (var prm in action.Parameters)
            {
                if (prm.BindingInfo != null)
                {
                    continue;
                }
    
                if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType))
                {
                    if (CanUseFormBodyBinding(action, prm))
                    {
                        prm.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
                    }
                }
            }
        }
    }
    

    如此,整个abp集成aspnetcore mvc创建并管理自己的api流程便大致的分析完了。

  • 相关阅读:
    在家赚钱,威客网站的使用方法
    帅哥编年史
    有些话不吐不快,事情不是这样做滴
    面向对象与形而上学
    Asp.net Mvc问题索引
    Asp.net Mvc视频教程 页面传值基础(get/post/UpdateModel)
    Asp.net Mvc 入门视频教程(试水)
    Linq to Entities in Ado.net EF的事务
    Json.net/Newtonsoft 3.0 新特性JObject/Linq to Json
    Entity SQL Language六 数字函数、时间函数及其它
  • 原文地址:https://www.cnblogs.com/zhiyong-ITNote/p/11991995.html
Copyright © 2011-2022 走看看