using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Http; using System.Reflection; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Controllers; using System.Text.RegularExpressions; namespace EasyTemplate.Tool.Util; /// /// Add Dynamic WebApi /// public static class DynamicControllerRegister { /// /// Use Dynamic WebApi to Configure /// /// /// /// public static IApplicationBuilder UseDynamicWebApi(this IApplicationBuilder application) { var options = new DynamicWebApiOptions(); 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.AssemblyDynamicWebApiOptions = options.AssemblyDynamicWebApiOptions; var partManager = application.ApplicationServices.GetRequiredService(); // Add a custom controller checker var featureProviders = application.ApplicationServices.GetRequiredService(); partManager.FeatureProviders.Add(featureProviders); var mvcOptions = application.ApplicationServices.GetRequiredService>(); var dynamicWebApiConvention = application.ApplicationServices.GetRequiredService(); mvcOptions.Value.Conventions.Add(dynamicWebApiConvention); return application; } public static IServiceCollection AddDynamicController(this IServiceCollection services) where TSelectController : class, ISelectController where TActionRouteFactory : class, IActionRouteFactory { services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); return services; } /// /// Add Dynamic WebApi to Container /// /// /// configuration /// public static IServiceCollection AddDynamicController(this IServiceCollection services, DynamicWebApiOptions 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.GetRestFulActionName = options.GetRestFulActionName; AppConsts.AssemblyDynamicWebApiOptions = options.AssemblyDynamicWebApiOptions; var partManager = services.GetSingletonInstanceOrNull(); if (partManager == null) { throw new InvalidOperationException("\"AddDynamicWebApi\" must be after \"AddMvc\"."); } // Add a custom controller checker partManager.FeatureProviders.Add(new DynamicWebApiControllerFeatureProvider(options.SelectController)); services.Configure(o => { // Register Controller Routing Information Converter o.Conventions.Add(new DynamicWebApiConvention(options.SelectController, options.ActionRouteFactory)); }); return services; } public static IServiceCollection AddDynamicWebApi(this IServiceCollection services) { return services.AddDynamicController(new DynamicWebApiOptions()); } public static IServiceCollection AddDynamicWebApi(this IServiceCollection services, Action optionsAction) { var dynamicWebApiOptions = new DynamicWebApiOptions(); optionsAction?.Invoke(dynamicWebApiOptions); return services.AddDynamicController(dynamicWebApiOptions); } } public class DynamicWebApiOptions { public DynamicWebApiOptions() { RemoveControllerPostfixes = new List() { "AppService", "ApplicationService" }; RemoveActionPostfixes = new List() { "Async" }; FormBodyBindingIgnoredTypes = new List() { typeof(IFormFile) }; DefaultHttpVerb = "POST"; DefaultApiPrefix = "api"; AssemblyDynamicWebApiOptions = new Dictionary(); } /// /// API HTTP Verb. /// /// Default value is "POST". /// public string DefaultHttpVerb { get; set; } public string DefaultAreaName { get; set; } /// /// Routing prefix for all APIs /// /// Default value is "api". /// public string DefaultApiPrefix { get; set; } /// /// Remove the dynamic API class(Controller) name postfix. /// /// Default value is {"AppService", "ApplicationService"}. /// public List RemoveControllerPostfixes { get; set; } /// /// Remove the dynamic API class's method(Action) postfix. /// /// Default value is {"Async"}. /// public List RemoveActionPostfixes { get; set; } /// /// Ignore MVC Form Binding types. /// public List FormBodyBindingIgnoredTypes { get; set; } /// /// The method that processing the name of the action. /// public Func GetRestFulActionName { get; set; } /// /// Specifies the dynamic webapi options for the assembly. /// public Dictionary AssemblyDynamicWebApiOptions { get; } public ISelectController SelectController { get; set; } = new DefaultSelectController(); public IActionRouteFactory ActionRouteFactory { get; set; } = new DefaultActionRouteFactory(); /// /// Verify that all configurations are valid /// 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."); } } /// /// Add the dynamic webapi options for the assembly. /// /// /// /// public void AddAssemblyOptions(Assembly assembly, string apiPreFix = null, string httpVerb = null) { if (assembly == null) { throw new ArgumentException($"{nameof(assembly)} can not be null."); } AssemblyDynamicWebApiOptions[assembly] = new AssemblyDynamicWebApiOptions(apiPreFix, httpVerb); } } /// /// Specifies the dynamic webapi options for the assembly. /// public class AssemblyDynamicWebApiOptions { /// /// Routing prefix for all APIs /// /// Default value is null. /// public string ApiPrefix { get; } /// /// API HTTP Verb. /// /// Default value is null. /// public string HttpVerb { get; } /// /// /// /// Routing prefix for all APIs /// API HTTP Verb. public AssemblyDynamicWebApiOptions(string apiPrefix = null, string httpVerb = null) { ApiPrefix = apiPrefix; HttpVerb = httpVerb; } } public interface ISelectController { bool IsController(Type type); } internal class DefaultSelectController : ISelectController { public bool IsController(Type type) { var typeInfo = type.GetTypeInfo(); if (!typeof(IDynamicWebApi).IsAssignableFrom(type) || !typeInfo.IsPublic || typeInfo.IsAbstract || typeInfo.IsGenericType) { return false; } var attr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch(typeInfo); if (attr == null) { return false; } if (ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch(typeInfo) != null) { return false; } return true; } } public interface IActionRouteFactory { string CreateActionRouteModel(string areaName, string controllerName, ActionModel action); } internal class DefaultActionRouteFactory : IActionRouteFactory { private static string GetApiPreFix(ActionModel action) { var getValueSuccess = AppConsts.AssemblyDynamicWebApiOptions .TryGetValue(action.Controller.ControllerType.Assembly, out AssemblyDynamicWebApiOptions assemblyDynamicWebApiOptions); if (getValueSuccess && !string.IsNullOrWhiteSpace(assemblyDynamicWebApiOptions?.ApiPrefix)) { return assemblyDynamicWebApiOptions.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; } } public class DynamicWebApiConvention : IApplicationModelConvention { private readonly ISelectController _selectController; private readonly IActionRouteFactory _actionRouteFactory; public DynamicWebApiConvention(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 dynamicWebApiAttr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch(type.GetTypeInfo()); if (!(_selectController is DefaultSelectController) && _selectController.IsController(type)) { controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray()); ConfigureDynamicWebApi(controller, dynamicWebApiAttr); } else { if (typeof(IDynamicWebApi).GetTypeInfo().IsAssignableFrom(type)) { controller.ControllerName = controller.ControllerName.RemovePostFix(AppConsts.ControllerPostfixes.ToArray()); ConfigureArea(controller, dynamicWebApiAttr); ConfigureDynamicWebApi(controller, dynamicWebApiAttr); } else { if (dynamicWebApiAttr != null) { ConfigureArea(controller, dynamicWebApiAttr); ConfigureDynamicWebApi(controller, dynamicWebApiAttr); } } } } } private void ConfigureArea(ControllerModel controller, DynamicWebApiAttribute attr) { if (!controller.RouteValues.ContainsKey("area")) { if (attr == null) { throw new ArgumentException(nameof(attr)); } if (!string.IsNullOrEmpty(attr.Module)) { controller.RouteValues["area"] = attr.Module; } else if (!string.IsNullOrEmpty(AppConsts.DefaultAreaName)) { controller.RouteValues["area"] = AppConsts.DefaultAreaName; } } } private void ConfigureDynamicWebApi(ControllerModel controller, DynamicWebApiAttribute 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 /// /// //不映射指定的方法 /// /// /// private bool CheckNoMapMethod(ActionModel action) { bool isExist = false; var noMapMethod = ReflectionHelper.GetSingleAttributeOrDefault(action.ActionMethod); if (noMapMethod != null) { action.ApiExplorer.IsVisible = false;//对应的Api不映射 isExist = true; } return isExist; } private void ConfigureSelector(ControllerModel controller, DynamicWebApiAttribute controllerAttr) { if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null)) { return; } var areaName = string.Empty; if (controllerAttr != null) { areaName = controllerAttr.Module; } 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(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); 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}."); } } } /// /// Processing action name /// /// /// 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; //} return actionName; } private void NormalizeSelectorRoutes(string areaName, string controllerName, ActionModel action) { action.ActionName = GetRestFulActionName(action.ActionName); foreach (var selector in action.Selectors) { //var aa = selector.EndpointMetadata.OfType().FirstOrDefault(); //if (!string.IsNullOrWhiteSpace(aa.Template)) //{ // var res = aa.Template.ToLower().Replace("[controller]", controllerName.ToLower().Replace("service", "")); // res = res.Replace("[action]", action.DisplayName); // selector.EndpointMetadata.Remove(aa); // //selector.EndpointMetadata.Add(new HttpMethodAttribute(new[ "Get" ])); //} var bbbb = AttributeRouteModel.CombineAttributeRouteModel(CreateActionRouteModel(areaName, controllerName, action), selector.AttributeRouteModel); selector.AttributeRouteModel = selector.AttributeRouteModel is null ? CreateActionRouteModel(areaName, controllerName, action) : CreateActionRouteModel(areaName, controllerName, action, selector.AttributeRouteModel); } } private static string GetHttpVerb(ActionModel action) { var getValueSuccess = AppConsts.AssemblyDynamicWebApiOptions .TryGetValue(action.Controller.ControllerType.Assembly, out AssemblyDynamicWebApiOptions assemblyDynamicWebApiOptions); if (getValueSuccess && !string.IsNullOrWhiteSpace(assemblyDynamicWebApiOptions?.HttpVerb)) { return assemblyDynamicWebApiOptions.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, AttributeRouteModel orgin = null) { if (orgin is null) { var route = _actionRouteFactory.CreateActionRouteModel(areaName, controllerName, action); return new AttributeRouteModel(new RouteAttribute(route)); } else { controllerName = controllerName.Replace("Service", ""); var route = orgin.Template.Replace("[Controller]", controllerName); route = route.Replace("[controller]", controllerName); route = route.Replace("[Action]", action.ActionName); route = route.Replace("[action]", action.ActionName); return new AttributeRouteModel(new RouteAttribute(route)); } } } 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 ControllerPostfixes { get; set; } public static List ActionPostfixes { get; set; } public static List FormBodyBindingIgnoredTypes { get; set; } public static Dictionary HttpVerbs { get; set; } public static Func GetRestFulActionName { get; set; } public static Dictionary AssemblyDynamicWebApiOptions { get; set; } static AppConsts() { HttpVerbs = new Dictionary() { ["add"] = "POST", ["create"] = "POST", ["post"] = "POST", ["get"] = "GET", ["find"] = "GET", ["fetch"] = "GET", ["query"] = "GET", ["update"] = "PUT", ["put"] = "PUT", ["delete"] = "DELETE", ["remove"] = "DELETE", }; } } public class DynamicWebApiControllerFeatureProvider : ControllerFeatureProvider { private ISelectController _selectController; public DynamicWebApiControllerFeatureProvider(ISelectController selectController) { _selectController = selectController; } protected override bool IsController(TypeInfo typeInfo) { return _selectController.IsController(typeInfo); } } internal static class ServiceCollectionExtensions { public static bool IsAdded(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(this IServiceCollection services) { return (T)services .FirstOrDefault(d => d.ServiceType == typeof(T)) ?.ImplementationInstance; } public static T GetSingletonInstance(this IServiceCollection services) { var service = services.GetSingletonInstanceOrNull(); 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(this IServiceCollection services, Action builderAction = null) { var serviceProviderFactory = services.GetSingletonInstanceOrNull>(); if (serviceProviderFactory == null) { throw new Exception($"Could not find {typeof(IServiceProviderFactory).FullName} in {services}."); } var builder = serviceProviderFactory.CreateBuilder(services); builderAction?.Invoke(builder); return serviceProviderFactory.CreateServiceProvider(builder); } } internal static class ReflectionHelper { public static TAttribute GetSingleAttributeOrDefaultByFullSearch(TypeInfo info) where TAttribute : Attribute { var attributeType = typeof(TAttribute); if (info.IsDefined(attributeType, true)) { return info.GetCustomAttributes(attributeType, true).Cast().First(); } else { foreach (var implInter in info.ImplementedInterfaces) { var res = GetSingleAttributeOrDefaultByFullSearch(implInter.GetTypeInfo()); if (res != null) { return res; } } } return null; } public static TAttribute GetSingleAttributeOrDefault(MemberInfo memberInfo, TAttribute defaultValue = default, bool inherit = true) where TAttribute : Attribute { var attributeType = typeof(TAttribute); if (memberInfo.IsDefined(typeof(TAttribute), inherit)) { return memberInfo.GetCustomAttributes(attributeType, inherit).Cast().First(); } return defaultValue; } /// /// Gets a single attribute for a member. /// /// Type of the attribute /// The member that will be checked for the attribute /// Include inherited attributes /// Returns the attribute object if found. Returns null if not found. public static TAttribute GetSingleAttributeOrNull(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; } public static TAttribute GetSingleAttributeOfTypeOrBaseTypesOrNull(this Type type, bool inherit = true) where TAttribute : Attribute { var attr = type.GetTypeInfo().GetSingleAttributeOrNull(); if (attr != null) { return attr; } if (type.GetTypeInfo().BaseType == null) { return null; } return type.GetTypeInfo().BaseType.GetSingleAttributeOfTypeOrBaseTypesOrNull(inherit); } } internal static class ExtensionMethods { public static bool IsNullOrEmpty(this string str) { return string.IsNullOrEmpty(str); } public static bool IsNullOrEmpty(this ICollection 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 str.GetPascalCaseFirstWord(); } else { return str.GetCamelCaseFirstWord(); } } } [Serializable] [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)] public class DynamicWebApiAttribute : Attribute { /// /// Equivalent to AreaName /// public string Module { get; set; } internal static bool IsExplicitlyEnabledFor(Type type) { var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); return remoteServiceAttr != null; } internal static bool IsExplicitlyDisabledFor(Type type) { var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); return remoteServiceAttr != null; } internal static bool IsMetadataExplicitlyEnabledFor(Type type) { var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); return remoteServiceAttr != null; } internal static bool IsMetadataExplicitlyDisabledFor(Type type) { var remoteServiceAttr = type.GetTypeInfo().GetSingleAttributeOrNull(); return remoteServiceAttr != null; } internal static bool IsMetadataExplicitlyDisabledFor(MethodInfo method) { var remoteServiceAttr = method.GetSingleAttributeOrNull(); return remoteServiceAttr != null; } internal static bool IsMetadataExplicitlyEnabledFor(MethodInfo method) { var remoteServiceAttr = method.GetSingleAttributeOrNull(); return remoteServiceAttr != null; } } [Serializable] [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method)] public class NonDynamicMethodAttribute : Attribute { } [Serializable] [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Method)] public class NonDynamicWebApiAttribute : Attribute { } public interface IDynamicWebApi { } 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(object obj) { return obj != null && obj.GetType() == typeof(Func); } 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); } } [AttributeUsage(AttributeTargets.Class)] public class DynamicControllerAttribute : Attribute { public DynamicControllerAttribute() { ServiceName = string.Empty; } public DynamicControllerAttribute(string serviceName) { ServiceName = serviceName; } public string ServiceName { get; } }