From 346979baedc2369b1dfa4670aca1507ab77b18f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 24 Oct 2020 19:04:20 +0300 Subject: [PATCH] #5325: Create auto controller routes in kebap-case --- .../AbpConventionalControllerOptions.cs | 12 +- .../Mvc/Conventions/AbpServiceConvention.cs | 96 +---------- .../ConventionalControllerSetting.cs | 12 +- .../Conventions/ConventionalRouteBuilder.cs | 160 ++++++++++++++++++ .../Conventions/IConventionalRouteBuilder.cs | 16 ++ 5 files changed, 203 insertions(+), 93 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IConventionalRouteBuilder.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs index e21e6ee939..d22fb78aab 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs @@ -12,7 +12,13 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions public ConventionalControllerSettingList ConventionalControllerSettings { get; } public List FormBodyBindingIgnoredTypes { get; } - + + /// + /// Set true to use the old style URL path style. + /// Default: false. + /// + public bool UseV3UrlStyle { get; set; } + public AbpConventionalControllerOptions() { ConventionalControllerSettings = new ConventionalControllerSettingList(); @@ -24,7 +30,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions } public AbpConventionalControllerOptions Create( - Assembly assembly, + Assembly assembly, [CanBeNull] Action optionsAction = null) { var setting = new ConventionalControllerSetting( @@ -39,4 +45,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions return this; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs index ab2003a595..3198e73e45 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs @@ -23,12 +23,15 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions { public ILogger Logger { get; set; } - private readonly AbpAspNetCoreMvcOptions _options; + protected AbpAspNetCoreMvcOptions Options { get; } + protected IConventionalRouteBuilder ConventionalRouteBuilder { get; } public AbpServiceConvention( - IOptions options) + IOptions options, + IConventionalRouteBuilder conventionalRouteBuilder) { - _options = options.Value; + ConventionalRouteBuilder = conventionalRouteBuilder; + Options = options.Value; Logger = NullLogger.Instance; } @@ -131,7 +134,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions return false; } - if (_options.ConventionalControllers + if (Options.ConventionalControllers .FormBodyBindingIgnoredTypes .Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) { @@ -306,95 +309,14 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions [CanBeNull] protected virtual ConventionalControllerSetting GetControllerSettingOrNull(Type controllerType) { - return _options.ConventionalControllers.ConventionalControllerSettings.GetSettingOrNull(controllerType); + return Options.ConventionalControllers.ConventionalControllerSettings.GetSettingOrNull(controllerType); } protected virtual AttributeRouteModel CreateAbpServiceAttributeRouteModel(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { return new AttributeRouteModel( new RouteAttribute( - CalculateRouteTemplate(rootPath, controllerName, action, httpMethod, configuration) - ) - ); - } - - 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 - var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); - if (idParameterModel != null) - { - if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true)) - { - url += "/{id}"; - } - else - { - var properties = idParameterModel - .ParameterType - .GetProperties(BindingFlags.Instance | BindingFlags.Public); - - foreach (var property in properties) - { - url += "/{" + property.Name + "}"; - } - } - } - - //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; - } - - protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) - { - var actionNameInUrl = HttpMethodHelper - .RemoveHttpMethodPrefix(action.ActionName, httpMethod) - .RemovePostFix("Async"); - - if (configuration?.UrlActionNameNormalizer == null) - { - return actionNameInUrl; - } - - return configuration.UrlActionNameNormalizer( - new UrlActionNameNormalizerContext( - rootPath, - controllerName, - action, - actionNameInUrl, - httpMethod - ) - ); - } - - protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) - { - if (configuration?.UrlControllerNameNormalizer == null) - { - return controllerName; - } - - return configuration.UrlControllerNameNormalizer( - new UrlControllerNameNormalizerContext( - rootPath, - controllerName + ConventionalRouteBuilder.Build(rootPath, controllerName, action, httpMethod, configuration) ) ); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs index bae2522d20..5c2ae1e36c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalControllerSetting.cs @@ -18,6 +18,12 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions [NotNull] public HashSet ControllerTypes { get; } //TODO: Internal? + /// + /// Set true to use the old style URL path style. + /// Default: null (uses the value of the ). + /// + public bool? UseV3UrlStyle { get; set; } + [NotNull] public string RootPath { @@ -57,9 +63,9 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions public List ApiVersions { get; } public Action ApiVersionConfigurer { get; set; } - + public ConventionalControllerSetting( - [NotNull] Assembly assembly, + [NotNull] Assembly assembly, [NotNull] string rootPath, [NotNull] string remoteServiceName) { @@ -104,4 +110,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions return false; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs new file mode 100644 index 0000000000..4da714302f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/ConventionalRouteBuilder.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; +using Volo.Abp.Reflection; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public class ConventionalRouteBuilder : IConventionalRouteBuilder, ITransientDependency + { + protected AbpConventionalControllerOptions Options { get; } + + public ConventionalRouteBuilder(IOptions options) + { + Options = options.Value; + } + + public virtual string Build( + string rootPath, + string controllerName, + ActionModel action, + string httpMethod, + [CanBeNull] ConventionalControllerSetting configuration) + { + var controllerNameInUrl = NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration); + + var url = $"api/{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}"; + + //Add {id} path if needed + var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); + if (idParameterModel != null) + { + if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true)) + { + url += "/{id}"; + } + else + { + var properties = idParameterModel + .ParameterType + .GetProperties(BindingFlags.Instance | BindingFlags.Public); + + foreach (var property in properties) + { + url += "/{" + NormalizeIdPropertyNameCase(property, configuration) + "}"; + } + } + } + + //Add action name if needed + var actionNameInUrl = NormalizeUrlActionName(rootPath, controllerName, action, httpMethod, configuration); + if (!actionNameInUrl.IsNullOrEmpty()) + { + url += $"/{NormalizeActionNameCase(actionNameInUrl, configuration)}"; + + //Add secondary Id + var secondaryIds = action.Parameters + .Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList(); + if (secondaryIds.Count == 1) + { + url += $"/{{{NormalizeSecondaryIdNameCase(secondaryIds[0], configuration)}}}"; + } + } + + return url; + } + + protected virtual string NormalizeUrlActionName(string rootPath, string controllerName, ActionModel action, + string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) + { + var actionNameInUrl = HttpMethodHelper + .RemoveHttpMethodPrefix(action.ActionName, httpMethod) + .RemovePostFix("Async"); + + if (configuration?.UrlActionNameNormalizer == null) + { + return actionNameInUrl; + } + + return configuration.UrlActionNameNormalizer( + new UrlActionNameNormalizerContext( + rootPath, + controllerName, + action, + actionNameInUrl, + httpMethod + ) + ); + } + + protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, + string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) + { + if (configuration?.UrlControllerNameNormalizer == null) + { + return controllerName; + } + + return configuration.UrlControllerNameNormalizer( + new UrlControllerNameNormalizerContext( + rootPath, + controllerName + ) + ); + } + + protected virtual string NormalizeControllerNameCase(string controllerName, [CanBeNull] ConventionalControllerSetting configuration) + { + if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle) + { + return controllerName.ToCamelCase(); + } + else + { + return controllerName.ToKebabCase(); + } + } + + protected virtual string NormalizeActionNameCase(string actionName, [CanBeNull] ConventionalControllerSetting configuration) + { + if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle) + { + return actionName.ToCamelCase(); + } + else + { + return actionName.ToKebabCase(); + } + } + + protected virtual string NormalizeIdPropertyNameCase(PropertyInfo property, [CanBeNull] ConventionalControllerSetting configuration) + { + if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle) + { + return property.Name; + } + else + { + return property.Name.ToKebabCase(); + } + } + + protected virtual string NormalizeSecondaryIdNameCase(ParameterModel secondaryId, [CanBeNull] ConventionalControllerSetting configuration) + { + if (configuration?.UseV3UrlStyle ?? Options.UseV3UrlStyle) + { + return secondaryId.ParameterName; + } + else + { + return secondaryId.ParameterName.ToKebabCase(); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IConventionalRouteBuilder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IConventionalRouteBuilder.cs new file mode 100644 index 0000000000..c30d7c5693 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/IConventionalRouteBuilder.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public interface IConventionalRouteBuilder + { + string Build( + string rootPath, + string controllerName, + ActionModel action, + string httpMethod, + [CanBeNull] ConventionalControllerSetting configuration + ); + } +}