Przeglądaj źródła

工具库新增动态Api

zhontai 3 lat temu
rodzic
commit
c1c86db473
20 zmienionych plików z 1410 dodań i 1 usunięć
  1. 10 0
      src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.HttpApi.Shared/BaseStartup.cs
  2. 4 1
      src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.Service.Contracts/Api/IApiService.cs
  3. 6 0
      src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.Service.Contracts/Base/BaseService.cs
  4. 1 0
      src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.Service/Api/ApiService.cs
  5. 57 0
      src/shared/ZhonTai.Tools/DynamicApi/AppConsts.cs
  6. 33 0
      src/shared/ZhonTai.Tools/DynamicApi/AssemblyDynamicApiOptions.cs
  7. 53 0
      src/shared/ZhonTai.Tools/DynamicApi/Attributes/DynamicApiAttribute.cs
  8. 11 0
      src/shared/ZhonTai.Tools/DynamicApi/Attributes/NonDynamicApiAttribute.cs
  9. 15 0
      src/shared/ZhonTai.Tools/DynamicApi/Attributes/NonDynamicMethodAttribute.cs
  10. 22 0
      src/shared/ZhonTai.Tools/DynamicApi/DynamicApiControllerFeatureProvider.cs
  11. 410 0
      src/shared/ZhonTai.Tools/DynamicApi/DynamicApiConvention.cs
  12. 145 0
      src/shared/ZhonTai.Tools/DynamicApi/DynamicApiOptions.cs
  13. 139 0
      src/shared/ZhonTai.Tools/DynamicApi/DynamicApiServiceExtensions.cs
  14. 191 0
      src/shared/ZhonTai.Tools/DynamicApi/Helpers/ExtensionMethods.cs
  15. 91 0
      src/shared/ZhonTai.Tools/DynamicApi/Helpers/ReflectionHelper.cs
  16. 79 0
      src/shared/ZhonTai.Tools/DynamicApi/Helpers/ServiceCollectionExtensions.cs
  17. 64 0
      src/shared/ZhonTai.Tools/DynamicApi/Helpers/TypeHelper.cs
  18. 31 0
      src/shared/ZhonTai.Tools/DynamicApi/IActionRouteFactory.cs
  19. 7 0
      src/shared/ZhonTai.Tools/DynamicApi/IDynamicApi.cs
  20. 41 0
      src/shared/ZhonTai.Tools/DynamicApi/ISelectController.cs

+ 10 - 0
src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.HttpApi.Shared/BaseStartup.cs

@@ -40,6 +40,7 @@ using ZhonTai.Plate.Admin.HttpApi.Shared.RegisterModules;
 using MapsterMapper;
 using StackExchange.Profiling;
 using System.IO;
+using ZhonTai.Tools.DynamicApi;
 
 namespace ZhonTai.Plate.Admin.HttpApi.Shared
 {
@@ -228,6 +229,7 @@ namespace ZhonTai.Plate.Admin.HttpApi.Shared
 
                     options.ResolveConflictingActions(apiDescription => apiDescription.First());
                     options.CustomSchemaIds(x => x.FullName);
+                    options.DocInclusionPredicate((docName, description) => true);
 
                     string[] xmlFiles = Directory.GetFiles(basePath, "*.xml");
                     if (xmlFiles.Length > 0)
@@ -382,6 +384,14 @@ namespace ZhonTai.Plate.Admin.HttpApi.Shared
             {
                 services.AddMiniProfiler();
             }
+
+            services.AddDynamicApi(options =>
+            {
+                Assembly[] assemblies = DependencyContext.Default.RuntimeLibraries
+                .Where(a => a.Name.EndsWith("Service"))
+                .Select(o => Assembly.Load(new AssemblyName(o.Name))).ToArray();
+                options.AddAssemblyOptions(assemblies);
+            });
         }
 
         public virtual void ConfigureContainer(ContainerBuilder builder)

+ 4 - 1
src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.Service.Contracts/Api/IApiService.cs

@@ -2,13 +2,16 @@ using ZhonTai.Common.Domain.Dto;
 using System.Threading.Tasks;
 using ZhonTai.Plate.Admin.Service.Api.Dto;
 using ZhonTai.Plate.Admin.Domain.Api.Dto;
+using ZhonTai.Tools.DynamicApi;
+using ZhonTai.Tools.DynamicApi.Attributes;
 
 namespace ZhonTai.Plate.Admin.Service.Api
 {
     /// <summary>
     /// ½Ó¿Ú·þÎñ
     /// </summary>
-    public interface IApiService
+    [DynamicApi(Area = "admin")]
+    public interface IApiService: IDynamicApi
     {
         /// <summary>
         /// »ñµÃÒ»Ìõ¼Ç¼

+ 6 - 0
src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.Service.Contracts/Base/BaseService.cs

@@ -7,6 +7,8 @@ using MapsterMapper;
 using ZhonTai.Common.Auth;
 using ZhonTai.Tools.Cache;
 using ZhonTai.Common.Extensions;
+using ZhonTai.Tools.DynamicApi.Attributes;
+using Microsoft.AspNetCore.Mvc;
 
 namespace ZhonTai.Plate.Admin.Service
 {
@@ -69,6 +71,8 @@ namespace ZhonTai.Plate.Admin.Service
         /// </summary>
         /// <typeparam name="TService">服务接口</typeparam>
         /// <returns></returns>
+        [NonDynamicApi]
+        [ApiExplorerSettings(IgnoreApi = true)]
         public virtual TService LazyGetRequiredService<TService>()
         {
             return (TService)LazyGetRequiredService(typeof(TService));
@@ -79,6 +83,8 @@ namespace ZhonTai.Plate.Admin.Service
         /// </summary>
         /// <param name="serviceType">服务类型</param>
         /// <returns></returns>
+        [NonDynamicApi]
+        [ApiExplorerSettings(IgnoreApi = true)]
         public virtual object LazyGetRequiredService(Type serviceType)
         {
             return CachedServices.GetOrAdd(serviceType, () => ServiceProvider.GetRequiredService(serviceType));

+ 1 - 0
src/plates/ZhonTai.Plate.Admin/ZhonTai.Plate.Admin.Service/Api/ApiService.cs

@@ -9,6 +9,7 @@ using ZhonTai.Plate.Admin.Domain.Api.Dto;
 
 namespace ZhonTai.Plate.Admin.Service.Api
 {
+    
     public class ApiService : BaseService, IApiService
     {
         private readonly IApiRepository _apiRepository;

+ 57 - 0
src/shared/ZhonTai.Tools/DynamicApi/AppConsts.cs

@@ -0,0 +1,57 @@
+using ZhonTai.Tools.DynamicApi.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace ZhonTai.Tools.DynamicApi
+{
+    public static class AppConsts
+    {
+        public static string DefaultHttpVerb { get; set; }
+
+        public static string DefaultAreaName { get; set; } 
+ 
+        public static string DefaultApiPreFix { get; set; }
+
+        public static List<string> ControllerPostfixes { get; set; }
+        public static List<string> ActionPostfixes { get; set; }
+
+        public static List<Type> FormBodyBindingIgnoredTypes { get; set; }
+
+        public static Dictionary<string,string> HttpVerbs { get; set; }
+
+        public static bool PascalToKebabCase { get; set; } = true;
+
+        public static Func<string, string> GetRestFulControllerName { get; set; }
+
+        public static Func<string, string> GetRestFulActionName { get; set; }
+
+        public static Dictionary<Assembly, AssemblyDynamicApiOptions> AssemblyDynamicApiOptions { get; set; }
+
+        static AppConsts()
+        {
+            HttpVerbs=new Dictionary<string, string>()
+            {
+                ["add"] = "POST",
+                ["create"] = "POST",
+                ["insert"] = "POST",
+                ["submit"] = "POST",
+                ["post"] = "POST",
+
+                ["get"] = "GET",
+                ["find"] = "GET",
+                ["fetch"] = "GET",
+                ["query"] = "GET",
+
+                ["update"] = "PUT",
+                ["change"] = "PUT",
+                ["put"] = "PUT",
+
+                ["delete"] = "DELETE",
+                ["softdelete"] = "DELETE",
+                ["remove"] = "DELETE",
+                ["clear"] = "DELETE",
+            };
+        }
+    }
+}

+ 33 - 0
src/shared/ZhonTai.Tools/DynamicApi/AssemblyDynamicApiOptions.cs

@@ -0,0 +1,33 @@
+namespace ZhonTai.Tools.DynamicApi
+{
+    /// <summary>
+    /// Specifies the dynamic webapi options for the assembly.
+    /// </summary>
+    public class AssemblyDynamicApiOptions
+    {
+        /// <summary>
+        /// Routing prefix for all APIs
+        /// <para></para>
+        /// Default value is null.
+        /// </summary>
+        public string ApiPrefix { get; }
+
+        /// <summary>
+        /// API HTTP Verb.
+        /// <para></para>
+        /// Default value is null.
+        /// </summary>
+        public string HttpVerb { get; }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="apiPrefix">Routing prefix for all APIs</param>
+        /// <param name="httpVerb">API HTTP Verb.</param>
+        public AssemblyDynamicApiOptions(string apiPrefix = null, string httpVerb = null)
+        {
+            ApiPrefix = apiPrefix;
+            HttpVerb = httpVerb;
+        }
+    }
+}

+ 53 - 0
src/shared/ZhonTai.Tools/DynamicApi/Attributes/DynamicApiAttribute.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Reflection;
+using ZhonTai.Tools.DynamicApi.Helpers;
+
+namespace ZhonTai.Tools.DynamicApi.Attributes
+{
+
+    [Serializable]
+    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
+    public class DynamicApiAttribute : Attribute
+    {
+        /// <summary>
+        /// Equivalent to AreaName
+        /// </summary>
+        public string Area { get; set; }
+
+        internal static bool IsExplicitlyEnabledFor(Type type)
+        {
+            var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull<DynamicApiAttribute>();
+            return remoteServiceAttr != null;
+        }
+
+        internal static bool IsExplicitlyDisabledFor(Type type)
+        {
+            var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull<DynamicApiAttribute>();
+            return remoteServiceAttr != null;
+        }
+
+        internal static bool IsMetadataExplicitlyEnabledFor(Type type)
+        {
+            var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull<DynamicApiAttribute>();
+            return remoteServiceAttr != null;
+        }
+
+        internal static bool IsMetadataExplicitlyDisabledFor(Type type)
+        {
+            var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull<DynamicApiAttribute>();
+            return remoteServiceAttr != null;
+        }
+
+        internal static bool IsMetadataExplicitlyDisabledFor(MethodInfo method)
+        {
+            var remoteServiceAttr = method.GetSingleAttributeOrNull<DynamicApiAttribute>();
+            return remoteServiceAttr != null;
+        }
+
+        internal static bool IsMetadataExplicitlyEnabledFor(MethodInfo method)
+        {
+            var remoteServiceAttr = method.GetSingleAttributeOrNull<DynamicApiAttribute>();
+            return remoteServiceAttr != null;
+        }
+    }
+}

+ 11 - 0
src/shared/ZhonTai.Tools/DynamicApi/Attributes/NonDynamicApiAttribute.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace ZhonTai.Tools.DynamicApi.Attributes
+{
+    [Serializable]
+    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method)]
+    public class NonDynamicApiAttribute:Attribute
+    {
+        
+    }
+}

+ 15 - 0
src/shared/ZhonTai.Tools/DynamicApi/Attributes/NonDynamicMethodAttribute.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ZhonTai.Tools.DynamicApi.Attributes
+{
+    [Serializable]
+    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method)]
+    public class NonDynamicMethodAttribute : Attribute
+    {
+
+    }
+}

+ 22 - 0
src/shared/ZhonTai.Tools/DynamicApi/DynamicApiControllerFeatureProvider.cs

@@ -0,0 +1,22 @@
+using System.Reflection;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using ZhonTai.Tools.DynamicApi.Attributes;
+using ZhonTai.Tools.DynamicApi.Helpers;
+
+namespace ZhonTai.Tools.DynamicApi
+{
+    public class DynamicApiControllerFeatureProvider: ControllerFeatureProvider
+    {
+        private ISelectController _selectController;
+
+        public DynamicApiControllerFeatureProvider(ISelectController selectController)
+        {
+            _selectController = selectController;
+        }
+
+        protected override bool IsController(TypeInfo typeInfo)
+        {
+            return _selectController.IsController(typeInfo);
+        }
+    }
+}

+ 410 - 0
src/shared/ZhonTai.Tools/DynamicApi/DynamicApiConvention.cs

@@ -0,0 +1,410 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ActionConstraints;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.DependencyInjection;
+using ZhonTai.Tools.DynamicApi.Attributes;
+using ZhonTai.Tools.DynamicApi.Helpers;
+
+namespace ZhonTai.Tools.DynamicApi
+{
+    public class DynamicApiConvention : IApplicationModelConvention
+    {
+        private readonly ISelectController _selectController;
+        private readonly IActionRouteFactory _actionRouteFactory;
+
+        public DynamicApiConvention(ISelectController selectController, IActionRouteFactory actionRouteFactory)
+        {
+            _selectController = selectController;
+            _actionRouteFactory = actionRouteFactory;
+        }
+
+        public void Apply(ApplicationModel application)
+        {
+            foreach (var controller in application.Controllers)
+            {
+                var type = controller.ControllerType.AsType();
+                var DynamicApiAttr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<DynamicApiAttribute>(type.GetTypeInfo());
+
+                if (!(_selectController is DefaultSelectController) && _selectController.IsController(type))
+                {
+                    controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray());
+                    if (AppConsts.PascalToKebabCase)
+                    {
+                        controller.ControllerName = PascalToKebabCase(controller.ControllerName);
+                    }
+                    else
+                    {
+                        controller.ControllerName = GetRestFulControllerName(controller.ControllerName);
+                    }
+                    
+                    ConfigureDynamicApi(controller, DynamicApiAttr);
+                }
+                else
+                {
+                    if (typeof(IDynamicApi).GetTypeInfo().IsAssignableFrom(type))
+                    {
+                        controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray());
+                        if (AppConsts.PascalToKebabCase)
+                        {
+                            controller.ControllerName = PascalToKebabCase(controller.ControllerName);
+                        }
+                        else
+                        {
+                            controller.ControllerName = GetRestFulControllerName(controller.ControllerName);
+                        }
+                        ConfigureArea(controller, DynamicApiAttr);
+                        ConfigureDynamicApi(controller, DynamicApiAttr);
+                    }
+                    else
+                    {
+                        if (DynamicApiAttr != null)
+                        {
+                            ConfigureArea(controller, DynamicApiAttr);
+                            ConfigureDynamicApi(controller, DynamicApiAttr);
+                        }
+                    }
+                }
+            }
+        }
+
+        public string PascalToKebabCase(string value)
+        {
+            if (string.IsNullOrWhiteSpace(value))
+                return value;
+
+            return Regex.Replace(
+                value,
+                "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
+                "-$1",
+                RegexOptions.Compiled)
+                .Trim()
+                .ToLower();
+        }
+
+        private void ConfigureArea(ControllerModel controller, DynamicApiAttribute attr)
+        {
+            if (!controller.RouteValues.ContainsKey("area"))
+            {
+                if (attr == null)
+                {
+                    throw new ArgumentException(nameof(attr));
+                }
+
+                if (!string.IsNullOrEmpty(attr.Area))
+                {
+                    controller.RouteValues["area"] = attr.Area;
+                }
+                else if (!string.IsNullOrEmpty(AppConsts.DefaultAreaName))
+                {
+                    controller.RouteValues["area"] = AppConsts.DefaultAreaName;
+                }
+            }
+
+        }
+
+        private void ConfigureDynamicApi(ControllerModel controller, DynamicApiAttribute controllerAttr)
+        {
+            ConfigureApiExplorer(controller);
+            ConfigureSelector(controller, controllerAttr);
+            ConfigureParameters(controller);
+        }
+
+
+        private void ConfigureParameters(ControllerModel controller)
+        {
+            foreach (var action in controller.Actions)
+            {
+                if (!CheckNoMapMethod(action))
+                    foreach (var para in action.Parameters)
+                    {
+                        if (para.BindingInfo != null)
+                        {
+                            continue;
+                        }
+
+                        if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(para.ParameterInfo.ParameterType))
+                        {
+                            if (CanUseFormBodyBinding(action, para))
+                            {
+                                para.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
+                            }
+                        }
+                    }
+            }
+        }
+
+
+        private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
+        {
+            if (AppConsts.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType)))
+            {
+                return false;
+            }
+
+            foreach (var selector in action.Selectors)
+            {
+                if (selector.ActionConstraints == null)
+                {
+                    continue;
+                }
+
+                foreach (var actionConstraint in selector.ActionConstraints)
+                {
+
+                    var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint;
+                    if (httpMethodActionConstraint == null)
+                    {
+                        continue;
+                    }
+
+                    if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD")))
+                    {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+
+        #region ConfigureApiExplorer
+
+        private void ConfigureApiExplorer(ControllerModel controller)
+        {
+            if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
+            {
+                controller.ApiExplorer.GroupName = controller.ControllerName;
+            }
+
+            if (controller.ApiExplorer.IsVisible == null)
+            {
+                controller.ApiExplorer.IsVisible = true;
+            }
+
+            foreach (var action in controller.Actions)
+            {
+                if (!CheckNoMapMethod(action))
+                    ConfigureApiExplorer(action);
+            }
+        }
+
+        private void ConfigureApiExplorer(ActionModel action)
+        {
+            if (action.ApiExplorer.IsVisible == null)
+            {
+                action.ApiExplorer.IsVisible = true;
+            }
+        }
+
+        #endregion
+        /// <summary>
+        /// //不映射指定的方法
+        /// </summary>
+        /// <param name="action"></param>
+        /// <returns></returns>
+        private bool CheckNoMapMethod(ActionModel action)
+        {
+            bool isExist = false;
+            var noMapMethod = ReflectionHelper.GetSingleAttributeOrDefault<NonDynamicMethodAttribute>(action.ActionMethod);
+
+            if (noMapMethod != null)
+            {
+                action.ApiExplorer.IsVisible = false;//对应的Api不映射
+                isExist = true;
+            }
+
+            return isExist;
+        }
+        private void ConfigureSelector(ControllerModel controller, DynamicApiAttribute controllerAttr)
+        {
+
+            if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
+            {
+                return;
+            }
+
+            var areaName = string.Empty;
+
+            if (controllerAttr != null)
+            {
+                areaName = controllerAttr.Area;
+            }
+
+            foreach (var action in controller.Actions)
+            {
+                if (!CheckNoMapMethod(action))
+                    ConfigureSelector(areaName, controller.ControllerName, action);
+            }
+        }
+
+        private void ConfigureSelector(string areaName, string controllerName, ActionModel action)
+        {
+
+            var nonAttr = ReflectionHelper.GetSingleAttributeOrDefault<NonDynamicApiAttribute>(action.ActionMethod);
+
+            if (nonAttr != null)
+            {
+                return;
+            }
+
+            if (action.Selectors.IsNullOrEmpty() || action.Selectors.Any(a => a.ActionConstraints.IsNullOrEmpty()))
+            {
+                if (!CheckNoMapMethod(action))
+                    AddAppServiceSelector(areaName, controllerName, action);
+            }
+            else
+            {
+                NormalizeSelectorRoutes(areaName, controllerName, action);
+            }
+        }
+
+        private void AddAppServiceSelector(string areaName, string controllerName, ActionModel action)
+        {
+
+            var verb = GetHttpVerb(action);
+            if (AppConsts.PascalToKebabCase)
+            {
+                action.ActionName = PascalToKebabCase(action.ActionName);
+            }
+            else
+            {
+                action.ActionName = GetRestFulActionName(action.ActionName);
+            }
+
+            var appServiceSelectorModel = action.Selectors[0];
+
+            if (appServiceSelectorModel.AttributeRouteModel == null)
+            {
+                appServiceSelectorModel.AttributeRouteModel = CreateActionRouteModel(areaName, controllerName, action);
+            }
+
+            if (!appServiceSelectorModel.ActionConstraints.Any())
+            {
+                appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb }));
+                switch (verb)
+                {
+                    case "GET":
+                        appServiceSelectorModel.EndpointMetadata.Add(new HttpGetAttribute());
+                        break;
+                    case "POST":
+                        appServiceSelectorModel.EndpointMetadata.Add(new HttpPostAttribute());
+                        break;
+                    case "PUT":
+                        appServiceSelectorModel.EndpointMetadata.Add(new HttpPutAttribute());
+                        break;
+                    case "DELETE":
+                        appServiceSelectorModel.EndpointMetadata.Add(new HttpDeleteAttribute());
+                        break;
+                    default:
+                        throw new Exception($"Unsupported http verb: {verb}.");
+                }
+            }
+
+
+        }
+
+
+
+        /// <summary>
+        /// Processing action name
+        /// </summary>
+        /// <param name="actionName"></param>
+        /// <returns></returns>
+        private static string GetRestFulActionName(string actionName)
+        {
+            // custom process action name
+            var appConstsActionName = AppConsts.GetRestFulActionName?.Invoke(actionName);
+            if (appConstsActionName != null)
+            {
+                return appConstsActionName;
+            }
+
+            // default process action name.
+
+            // Remove Postfix
+            actionName = actionName.RemovePostFix(AppConsts.ActionPostfixes.ToArray());
+
+            // Remove Prefix
+            var verbKey = actionName.GetPascalOrCamelCaseFirstWord().ToLower();
+            if (AppConsts.HttpVerbs.ContainsKey(verbKey))
+            {
+                if (actionName.Length == verbKey.Length)
+                {
+                    return "";
+                }
+                else
+                {
+                    return actionName.Substring(verbKey.Length);
+                }
+            }
+            else
+            {
+                return actionName;
+            }
+        }
+
+        private static string GetRestFulControllerName(string controllerName)
+        {
+            // custom process action name
+            var appConstsControllerName = AppConsts.GetRestFulControllerName?.Invoke(controllerName);
+            if (appConstsControllerName != null)
+            {
+                return appConstsControllerName;
+            }
+            else
+            {
+                return controllerName;
+            }
+        }
+
+        private void NormalizeSelectorRoutes(string areaName, string controllerName, ActionModel action)
+        {
+            if (AppConsts.PascalToKebabCase)
+            {
+                action.ActionName = PascalToKebabCase(action.ActionName);
+            }
+            else
+            {
+                action.ActionName = GetRestFulActionName(action.ActionName);
+            }
+            
+            foreach (var selector in action.Selectors)
+            {
+                selector.AttributeRouteModel = selector.AttributeRouteModel == null ?
+                     CreateActionRouteModel(areaName, controllerName, action) :
+                     AttributeRouteModel.CombineAttributeRouteModel(CreateActionRouteModel(areaName, controllerName, action), selector.AttributeRouteModel);
+            }
+        }
+
+        private static string GetHttpVerb(ActionModel action)
+        {
+            var getValueSuccess = AppConsts.AssemblyDynamicApiOptions
+                .TryGetValue(action.Controller.ControllerType.Assembly, out AssemblyDynamicApiOptions assemblyDynamicApiOptions);
+            if (getValueSuccess && !string.IsNullOrWhiteSpace(assemblyDynamicApiOptions?.HttpVerb))
+            {
+                return assemblyDynamicApiOptions.HttpVerb;
+            }
+
+
+            var verbKey = action.ActionName.GetPascalOrCamelCaseFirstWord().ToLower();
+
+            var verb = AppConsts.HttpVerbs.ContainsKey(verbKey) ? AppConsts.HttpVerbs[verbKey] : AppConsts.DefaultHttpVerb;
+            return verb;
+        }
+
+        private AttributeRouteModel CreateActionRouteModel(string areaName, string controllerName, ActionModel action)
+        {
+            var route =  _actionRouteFactory.CreateActionRouteModel(areaName, controllerName, action);
+
+            return new AttributeRouteModel(new RouteAttribute(route));
+        }
+    }
+}

+ 145 - 0
src/shared/ZhonTai.Tools/DynamicApi/DynamicApiOptions.cs

@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.AspNetCore.Http;
+
+namespace ZhonTai.Tools.DynamicApi
+{
+    public class DynamicApiOptions
+    {
+        public DynamicApiOptions()
+        {
+            RemoveControllerPostfixes = new List<string>() { "AppService", "ApplicationService", "ApiController", "Controller", "Services", "Service" };
+            RemoveActionPostfixes = new List<string>() { "Async" };
+            FormBodyBindingIgnoredTypes = new List<Type>() { typeof(IFormFile) };
+            DefaultHttpVerb = "POST";
+            DefaultApiPrefix = "api";
+            AssemblyDynamicApiOptions = new Dictionary<Assembly, AssemblyDynamicApiOptions>();
+        }
+
+
+        /// <summary>
+        /// API HTTP Verb.
+        /// <para></para>
+        /// Default value is "POST".
+        /// </summary>
+        public string DefaultHttpVerb { get; set; }
+
+        public string DefaultAreaName { get; set; }
+
+        /// <summary>
+        /// Routing prefix for all APIs
+        /// <para></para>
+        /// Default value is "api".
+        /// </summary>
+        public string DefaultApiPrefix { get; set; }
+
+        /// <summary>
+        /// Remove the dynamic API class(Controller) name postfix.
+        /// <para></para>
+        /// Default value is {"AppService", "ApplicationService"}.
+        /// </summary>
+        public List<string> RemoveControllerPostfixes { get; set; }
+
+        /// <summary>
+        /// Remove the dynamic API class's method(Action) postfix.
+        /// <para></para>
+        /// Default value is {"Async"}.
+        /// </summary>
+        public List<string> RemoveActionPostfixes { get; set; }
+
+        /// <summary>
+        /// Ignore MVC Form Binding types.
+        /// </summary>
+        public List<Type> FormBodyBindingIgnoredTypes { get; set; }
+
+        /// <summary>
+        /// Both the controller name and the action name are  pascal to kebabCase
+        /// </summary>
+        public bool PascalToKebabCase { get; set; } = true;
+
+        /// <summary>
+        /// The method that processing the name of the action.
+        /// </summary>
+        public Func<string, string> GetRestFulActionName { get; set; }
+
+        /// <summary>
+        /// The method that processing the name of the controller.
+        /// </summary>
+        public Func<string, string> GetRestFulControllerName { get; set; }
+
+        /// <summary>
+        /// Specifies the dynamic webapi options for the assembly.
+        /// </summary>
+        public Dictionary<Assembly, AssemblyDynamicApiOptions> AssemblyDynamicApiOptions { get; }
+
+        public ISelectController SelectController { get; set; } = new DefaultSelectController();
+        public IActionRouteFactory ActionRouteFactory { get; set; } = new DefaultActionRouteFactory();
+
+        /// <summary>
+        /// Verify that all configurations are valid
+        /// </summary>
+        public void Valid()
+        {
+            if (string.IsNullOrEmpty(DefaultHttpVerb))
+            {
+                throw new ArgumentException($"{nameof(DefaultHttpVerb)} can not be empty.");
+            }
+
+            if (string.IsNullOrEmpty(DefaultAreaName))
+            {
+                DefaultAreaName = string.Empty;
+            }
+
+            if (string.IsNullOrEmpty(DefaultApiPrefix))
+            {
+                DefaultApiPrefix = string.Empty;
+            }
+
+            if (FormBodyBindingIgnoredTypes == null)
+            {
+                throw new ArgumentException($"{nameof(FormBodyBindingIgnoredTypes)} can not be null.");
+            }
+
+            if (RemoveControllerPostfixes == null)
+            {
+                throw new ArgumentException($"{nameof(RemoveControllerPostfixes)} can not be null.");
+            }
+        }
+
+        /// <summary>
+        /// Add the dynamic webapi options for the assembly.
+        /// </summary>
+        /// <param name="assembly"></param>
+        /// <param name="apiPreFix"></param>
+        /// <param name="httpVerb"></param>
+        public void AddAssemblyOptions(Assembly assembly, string apiPreFix = null, string httpVerb = null)
+        {
+            if (assembly == null)
+            {
+                throw new ArgumentException($"{nameof(assembly)} can not be null.");
+            }
+
+            this.AssemblyDynamicApiOptions[assembly] = new AssemblyDynamicApiOptions(apiPreFix, httpVerb);
+        }
+
+        /// <summary>
+        /// Add the dynamic webapi options for the assemblies.
+        /// </summary>
+        /// <param name="assemblies"></param>
+        /// <param name="apiPreFix"></param>
+        /// <param name="httpVerb"></param>
+        public void AddAssemblyOptions(Assembly[] assemblies, string apiPreFix = null, string httpVerb = null)
+        {
+            if (assemblies == null)
+            {
+                throw new ArgumentException($"{nameof(assemblies)} can not be null.");
+            }
+
+            foreach (var assembly in assemblies)
+            {
+                AddAssemblyOptions(assembly, apiPreFix, httpVerb);
+            }
+        }
+    }
+}

+ 139 - 0
src/shared/ZhonTai.Tools/DynamicApi/DynamicApiServiceExtensions.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using ZhonTai.Tools.DynamicApi.Helpers;
+
+namespace ZhonTai.Tools.DynamicApi
+{
+    /// <summary>
+    /// Add Dynamic WebApi
+    /// </summary>
+    public static class DynamicApiServiceExtensions
+    {
+        /// <summary>
+        /// Use Dynamic WebApi to Configure
+        /// </summary>
+        /// <param name="application"></param>
+        /// <param name="options"></param>
+        /// <returns></returns>
+        public static IApplicationBuilder UseDynamicApi(this IApplicationBuilder application, Action<IServiceProvider,DynamicApiOptions> optionsAction)
+        {
+            var options = new DynamicApiOptions();
+
+            optionsAction?.Invoke(application.ApplicationServices,options);
+
+            options.Valid();
+
+            AppConsts.DefaultAreaName = options.DefaultAreaName;
+            AppConsts.DefaultHttpVerb = options.DefaultHttpVerb;
+            AppConsts.DefaultApiPreFix = options.DefaultApiPrefix;
+            AppConsts.ControllerPostfixes = options.RemoveControllerPostfixes;
+            AppConsts.ActionPostfixes = options.RemoveActionPostfixes;
+            AppConsts.FormBodyBindingIgnoredTypes = options.FormBodyBindingIgnoredTypes;
+            AppConsts.GetRestFulActionName = options.GetRestFulActionName;
+            AppConsts.AssemblyDynamicApiOptions = options.AssemblyDynamicApiOptions;
+
+            var partManager = application.ApplicationServices.GetRequiredService<ApplicationPartManager>();
+
+            // Add a custom controller checker
+            var featureProviders = application.ApplicationServices.GetRequiredService<DynamicApiControllerFeatureProvider>();
+            partManager.FeatureProviders.Add(featureProviders);
+
+            foreach(var assembly in options.AssemblyDynamicApiOptions.Keys)
+            {
+                var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
+
+                foreach(var part in partFactory.GetApplicationParts(assembly))
+                {
+                    partManager.ApplicationParts.Add(part);
+                }
+            }
+
+
+            var mvcOptions = application.ApplicationServices.GetRequiredService<IOptions<MvcOptions>>();
+            var DynamicApiConvention = application.ApplicationServices.GetRequiredService<DynamicApiConvention>();
+
+            mvcOptions.Value.Conventions.Add(DynamicApiConvention);
+
+            return application;
+        }
+
+        public static IServiceCollection AddDynamicApiCore<TSelectController, TActionRouteFactory>(this IServiceCollection services)
+            where TSelectController: class,ISelectController
+            where TActionRouteFactory: class, IActionRouteFactory
+        {
+            services.AddSingleton<ISelectController, TSelectController>();
+            services.AddSingleton<IActionRouteFactory, TActionRouteFactory>();
+            services.AddSingleton<DynamicApiConvention>();
+            services.AddSingleton<DynamicApiControllerFeatureProvider>();
+            return services;
+        }
+
+        /// <summary>
+        /// Add Dynamic WebApi to Container
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="options">configuration</param>
+        /// <returns></returns>
+        public static IServiceCollection AddDynamicApi(this IServiceCollection services, DynamicApiOptions options)
+        {
+            if (options == null)
+            {
+                throw new ArgumentException(nameof(options));
+            }
+
+            options.Valid();
+
+            AppConsts.DefaultAreaName = options.DefaultAreaName;
+            AppConsts.DefaultHttpVerb = options.DefaultHttpVerb;
+            AppConsts.DefaultApiPreFix = options.DefaultApiPrefix;
+            AppConsts.ControllerPostfixes = options.RemoveControllerPostfixes;
+            AppConsts.ActionPostfixes = options.RemoveActionPostfixes;
+            AppConsts.FormBodyBindingIgnoredTypes = options.FormBodyBindingIgnoredTypes;
+            AppConsts.PascalToKebabCase = options.PascalToKebabCase;
+            AppConsts.GetRestFulControllerName = options.GetRestFulControllerName;
+            AppConsts.GetRestFulActionName = options.GetRestFulActionName;
+
+            AppConsts.AssemblyDynamicApiOptions = options.AssemblyDynamicApiOptions;
+
+            var partManager = services.GetSingletonInstanceOrNull<ApplicationPartManager>();
+
+            if (partManager == null)
+            {
+                throw new InvalidOperationException("\"AddDynamicApi\" must be after \"AddMvc\".");
+            }
+
+            // Add a custom controller checker
+            partManager.FeatureProviders.Add(new DynamicApiControllerFeatureProvider(options.SelectController));
+
+            services.Configure<MvcOptions>(o =>
+            {
+                // Register Controller Routing Information Converter
+                o.Conventions.Add(new DynamicApiConvention(options.SelectController, options.ActionRouteFactory));
+            });
+
+            return services;
+        }
+
+        public static IServiceCollection AddDynamicApi(this IServiceCollection services)
+        {
+            return AddDynamicApi(services, new DynamicApiOptions());
+        }
+
+        public static IServiceCollection AddDynamicApi(this IServiceCollection services, Action<DynamicApiOptions> optionsAction)
+        {
+            var DynamicApiOptions = new DynamicApiOptions();
+
+            optionsAction?.Invoke(DynamicApiOptions);
+
+            return AddDynamicApi(services, DynamicApiOptions);
+        }
+
+    }
+}

+ 191 - 0
src/shared/ZhonTai.Tools/DynamicApi/Helpers/ExtensionMethods.cs

@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace ZhonTai.Tools.DynamicApi.Helpers
+{
+    internal static class ExtensionMethods
+    {
+        public static bool IsNullOrEmpty(this string str)
+        {
+            return string.IsNullOrEmpty(str);
+        }
+
+        public static bool IsNullOrEmpty<T>(this ICollection<T> source)
+        {
+            return source == null || source.Count <= 0;
+        }
+
+        public static bool IsIn(this string str, params string[] data)
+        {
+            foreach (var item in data)
+            {
+                if (str == item)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public static string RemovePostFix(this string str, params string[] postFixes)
+        {
+            if (str == null)
+            {
+                return null;
+            }
+
+            if (str == string.Empty)
+            {
+                return string.Empty;
+            }
+
+            if (postFixes.IsNullOrEmpty())
+            {
+                return str;
+            }
+
+            foreach (var postFix in postFixes)
+            {
+                if (str.EndsWith(postFix))
+                {
+                    return str.Left(str.Length - postFix.Length);
+                }
+            }
+
+            return str;
+        }
+
+        public static string RemovePreFix(this string str, params string[] preFixes)
+        {
+            if (str == null)
+            {
+                return null;
+            }
+
+            if (str == string.Empty)
+            {
+                return string.Empty;
+            }
+
+            if (preFixes.IsNullOrEmpty())
+            {
+                return str;
+            }
+
+            foreach (var preFix in preFixes)
+            {
+                if (str.StartsWith(preFix))
+                {
+                    return str.Right(str.Length - preFix.Length);
+                }
+            }
+
+            return str;
+        }
+
+
+        public static string Left(this string str, int len)
+        {
+            if (str == null)
+            {
+                throw new ArgumentNullException("str");
+            }
+
+            if (str.Length < len)
+            {
+                throw new ArgumentException("len argument can not be bigger than given string's length!");
+            }
+
+            return str.Substring(0, len);
+        }
+
+
+        public static string Right(this string str, int len)
+        {
+            if (str == null)
+            {
+                throw new ArgumentNullException("str");
+            }
+
+            if (str.Length < len)
+            {
+                throw new ArgumentException("len argument can not be bigger than given string's length!");
+            }
+
+            return str.Substring(str.Length - len, len);
+        }
+
+        public static string GetCamelCaseFirstWord(this string str)
+        {
+            if (str == null)
+            {
+                throw new ArgumentNullException(nameof(str));
+            }
+
+            if (str.Length == 1)
+            {
+                return str;
+            }
+
+            var res = Regex.Split(str, @"(?=\p{Lu}\p{Ll})|(?<=\p{Ll})(?=\p{Lu})");
+
+            if (res.Length < 1)
+            {
+                return str;
+            }
+            else
+            {
+                return res[0];
+            }
+        }
+
+        public static string GetPascalCaseFirstWord(this string str)
+        {
+            if (str == null)
+            {
+                throw new ArgumentNullException(nameof(str));
+            }
+
+            if (str.Length == 1)
+            {
+                return str;
+            }
+
+            var res = Regex.Split(str, @"(?=\p{Lu}\p{Ll})|(?<=\p{Ll})(?=\p{Lu})");
+
+            if (res.Length < 2)
+            {
+                return str;
+            }
+            else
+            {
+                return res[1];
+            }
+        }
+
+        public static string GetPascalOrCamelCaseFirstWord(this string str)
+        {
+            if (str == null)
+            {
+                throw new ArgumentNullException(nameof(str));
+            }
+
+            if (str.Length <= 1)
+            {
+                return str;
+            }
+
+            if (str[0] >= 65 && str[0] <= 90)
+            {
+                return GetPascalCaseFirstWord(str);
+            }
+            else
+            {
+                return GetCamelCaseFirstWord(str);
+            }
+        }
+
+
+    }
+}

+ 91 - 0
src/shared/ZhonTai.Tools/DynamicApi/Helpers/ReflectionHelper.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace ZhonTai.Tools.DynamicApi.Helpers
+{
+    internal static class ReflectionHelper
+    {
+
+        public static TAttribute GetSingleAttributeOrDefaultByFullSearch<TAttribute>(TypeInfo info)
+            where TAttribute : Attribute
+        {
+            var attributeType = typeof(TAttribute);
+            if (info.IsDefined(attributeType, true))
+            {
+                return info.GetCustomAttributes(attributeType, true).Cast<TAttribute>().First();
+            }
+            else
+            {
+                foreach (var implInter in info.ImplementedInterfaces)
+                {
+                    var res = GetSingleAttributeOrDefaultByFullSearch<TAttribute>(implInter.GetTypeInfo());
+
+                    if (res != null)
+                    {
+                        return res;
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        public static TAttribute GetSingleAttributeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute defaultValue = default(TAttribute), bool inherit = true)
+       where TAttribute : Attribute
+        {
+            var attributeType = typeof(TAttribute);
+            if (memberInfo.IsDefined(typeof(TAttribute), inherit))
+            {
+                return memberInfo.GetCustomAttributes(attributeType, inherit).Cast<TAttribute>().First();
+            }
+
+            return defaultValue;
+        }
+
+
+        /// <summary>
+        /// Gets a single attribute for a member.
+        /// </summary>
+        /// <typeparam name="TAttribute">Type of the attribute</typeparam>
+        /// <param name="memberInfo">The member that will be checked for the attribute</param>
+        /// <param name="inherit">Include inherited attributes</param>
+        /// <returns>Returns the attribute object if found. Returns null if not found.</returns>
+        public static TAttribute GetSingleAttributeOrNull<TAttribute>(this MemberInfo memberInfo, bool inherit = true)
+            where TAttribute : Attribute
+        {
+            if (memberInfo == null)
+            {
+                throw new ArgumentNullException(nameof(memberInfo));
+            }
+
+            var attrs = memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).ToArray();
+            if (attrs.Length > 0)
+            {
+                return (TAttribute)attrs[0];
+            }
+
+            return default(TAttribute);
+        }
+
+
+        public static TAttribute GetSingleAttributeOfTypeOrBaseTypesOrNull<TAttribute>(this Type type, bool inherit = true)
+            where TAttribute : Attribute
+        {
+            var attr = type.GetTypeInfo().GetSingleAttributeOrNull<TAttribute>();
+            if (attr != null)
+            {
+                return attr;
+            }
+
+            if (type.GetTypeInfo().BaseType == null)
+            {
+                return null;
+            }
+
+            return type.GetTypeInfo().BaseType.GetSingleAttributeOfTypeOrBaseTypesOrNull<TAttribute>(inherit);
+        }
+
+    }
+}

+ 79 - 0
src/shared/ZhonTai.Tools/DynamicApi/Helpers/ServiceCollectionExtensions.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace ZhonTai.Tools.DynamicApi.Helpers
+{
+    internal static class ServiceCollectionExtensions
+    {
+        public static bool IsAdded<T>(this IServiceCollection services)
+        {
+            return services.IsAdded(typeof(T));
+        }
+
+        public static bool IsAdded(this IServiceCollection services, Type type)
+        {
+            return services.Any(d => d.ServiceType == type);
+        }
+
+        public static T GetSingletonInstanceOrNull<T>(this IServiceCollection services)
+        {
+            return (T)services
+                .FirstOrDefault(d => d.ServiceType == typeof(T))
+                ?.ImplementationInstance;
+        }
+
+        public static T GetSingletonInstance<T>(this IServiceCollection services)
+        {
+            var service = services.GetSingletonInstanceOrNull<T>();
+            if (service == null)
+            {
+                throw new InvalidOperationException("Could not find singleton service: " + typeof(T).AssemblyQualifiedName);
+            }
+
+            return service;
+        }
+
+        public static IServiceProvider BuildServiceProviderFromFactory(this IServiceCollection services)
+        {
+            foreach (var service in services)
+            {
+                var factoryInterface = service.ImplementationInstance?.GetType()
+                    .GetTypeInfo()
+                    .GetInterfaces()
+                    .FirstOrDefault(i => i.GetTypeInfo().IsGenericType &&
+                                         i.GetGenericTypeDefinition() == typeof(IServiceProviderFactory<>));
+
+                if (factoryInterface == null)
+                {
+                    continue;
+                }
+
+                var containerBuilderType = factoryInterface.GenericTypeArguments[0];
+                return (IServiceProvider)typeof(ServiceCollectionExtensions)
+                    .GetTypeInfo()
+                    .GetMethods()
+                    .Single(m => m.Name == nameof(BuildServiceProviderFromFactory) && m.IsGenericMethod)
+                    .MakeGenericMethod(containerBuilderType)
+                    .Invoke(null, new object[] { services, null });
+            }
+
+            return services.BuildServiceProvider();
+        }
+
+        public static IServiceProvider BuildServiceProviderFromFactory<TContainerBuilder>(this IServiceCollection services, Action<TContainerBuilder> builderAction = null)
+        {
+
+            var serviceProviderFactory = services.GetSingletonInstanceOrNull<IServiceProviderFactory<TContainerBuilder>>();
+            if (serviceProviderFactory == null)
+            {
+                throw new Exception($"Could not find {typeof(IServiceProviderFactory<TContainerBuilder>).FullName} in {services}.");
+            }
+
+            var builder = serviceProviderFactory.CreateBuilder(services);
+            builderAction?.Invoke(builder);
+            return serviceProviderFactory.CreateServiceProvider(builder);
+        }
+    }
+}

+ 64 - 0
src/shared/ZhonTai.Tools/DynamicApi/Helpers/TypeHelper.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Reflection;
+
+namespace ZhonTai.Tools.DynamicApi.Helpers
+{
+    internal static class TypeHelper
+    {
+        public static bool IsFunc(object obj)
+        {
+            if (obj == null)
+            {
+                return false;
+            }
+
+            var type = obj.GetType();
+            if (!type.GetTypeInfo().IsGenericType)
+            {
+                return false;
+            }
+
+            return type.GetGenericTypeDefinition() == typeof(Func<>);
+        }
+
+        public static bool IsFunc<TReturn>(object obj)
+        {
+            return obj != null && obj.GetType() == typeof(Func<TReturn>);
+        }
+
+        public static bool IsPrimitiveExtendedIncludingNullable(Type type, bool includeEnums = false)
+        {
+            if (IsPrimitiveExtended(type, includeEnums))
+            {
+                return true;
+            }
+
+            if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+            {
+                return IsPrimitiveExtended(type.GenericTypeArguments[0], includeEnums);
+            }
+
+            return false;
+        }
+
+        private static bool IsPrimitiveExtended(Type type, bool includeEnums)
+        {
+            if (type.GetTypeInfo().IsPrimitive)
+            {
+                return true;
+            }
+
+            if (includeEnums && type.GetTypeInfo().IsEnum)
+            {
+                return true;
+            }
+
+            return type == typeof(string) ||
+                   type == typeof(decimal) ||
+                   type == typeof(DateTime) ||
+                   type == typeof(DateTimeOffset) ||
+                   type == typeof(TimeSpan) ||
+                   type == typeof(Guid);
+        }
+    }
+}

+ 31 - 0
src/shared/ZhonTai.Tools/DynamicApi/IActionRouteFactory.cs

@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+
+namespace ZhonTai.Tools.DynamicApi
+{
+    public interface IActionRouteFactory
+    {
+        string CreateActionRouteModel(string areaName, string controllerName, ActionModel action);
+    }
+
+    internal class DefaultActionRouteFactory : IActionRouteFactory
+    {
+        private static string GetApiPreFix(ActionModel action)
+        {
+            var getValueSuccess = AppConsts.AssemblyDynamicApiOptions
+                .TryGetValue(action.Controller.ControllerType.Assembly, out AssemblyDynamicApiOptions assemblyDynamicApiOptions);
+            if (getValueSuccess && !string.IsNullOrWhiteSpace(assemblyDynamicApiOptions?.ApiPrefix))
+            {
+                return assemblyDynamicApiOptions.ApiPrefix;
+            }
+
+            return AppConsts.DefaultApiPreFix;
+        }
+
+        public string CreateActionRouteModel(string areaName, string controllerName, ActionModel action)
+        {
+            var apiPreFix = GetApiPreFix(action);
+            var routeStr = $"{apiPreFix}/{areaName}/{controllerName}/{action.ActionName}".Replace("//", "/");
+            return routeStr;        }
+    }
+}

+ 7 - 0
src/shared/ZhonTai.Tools/DynamicApi/IDynamicApi.cs

@@ -0,0 +1,7 @@
+namespace ZhonTai.Tools.DynamicApi
+{
+    public interface IDynamicApi
+    {
+        
+    }
+}

+ 41 - 0
src/shared/ZhonTai.Tools/DynamicApi/ISelectController.cs

@@ -0,0 +1,41 @@
+using ZhonTai.Tools.DynamicApi.Attributes;
+using ZhonTai.Tools.DynamicApi.Helpers;
+using System;
+using System.Reflection;
+
+namespace ZhonTai.Tools.DynamicApi
+{
+    public interface ISelectController
+    {
+        bool IsController(Type type);
+    }
+
+    internal class DefaultSelectController : ISelectController
+    {
+        public bool IsController(Type type)
+        {
+            var typeInfo = type.GetTypeInfo();
+
+            if (!typeof(IDynamicApi).IsAssignableFrom(type) ||
+                !typeInfo.IsPublic || typeInfo.IsAbstract || typeInfo.IsGenericType)
+            {
+                return false;
+            }
+
+
+            var attr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<DynamicApiAttribute>(typeInfo);
+
+            if (attr == null)
+            {
+                return false;
+            }
+
+            if (ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<NonDynamicApiAttribute>(typeInfo) != null)
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}