From 9a7507ce312e8a70c7ab4b66a9676a481df21356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 12 Sep 2017 10:22:09 +0300 Subject: [PATCH] Support secondary Ids and allow to normalize action in url. --- .../AspNetCore/Mvc/AbpAppServiceConvention.cs | 27 ++++++++++-- .../AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs | 32 +-------------- .../Mvc/AppServiceControllerOptions.cs | 41 +++++++++++++++++++ .../Mvc/DefaultUrlActionNameNormalizer.cs | 18 ++++++++ .../Mvc/IUrlActionNameNormalizer.cs | 7 ++++ .../Mvc/UrlActionNameNormalizerContext.cs | 27 ++++++++++++ .../Volo/Abp/AspNetCore/App/AppModule.cs | 1 + .../App/PhoneBookUrlActionNameNormalizer.cs | 17 ++++++++ .../AspNetCore/Mvc/PersonAppService_Tests.cs | 11 ++--- 9 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DefaultUrlActionNameNormalizer.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IUrlActionNameNormalizer.cs create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs create mode 100644 test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/PhoneBookUrlActionNameNormalizer.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs index f12e6388af..c04e563097 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs @@ -261,7 +261,7 @@ namespace Volo.Abp.AspNetCore.Mvc return new AttributeRouteModel(new RouteAttribute(url)); } - private static string CalculateUrl(string moduleName, string controllerName, ActionModel action, string httpMethod) + protected virtual string CalculateUrl(string moduleName, string controllerName, ActionModel action, string httpMethod) { var url = $"api/{moduleName}/{controllerName}"; @@ -272,15 +272,34 @@ namespace Volo.Abp.AspNetCore.Mvc } //Add action name if needed - var actionName = HttpMethodConventionHelper.RemoveHttpMethodPrefix(action.ActionName, httpMethod); - if (!actionName.IsNullOrEmpty()) + var actionNameInUrl = NormalizeUrlActionName(moduleName, controllerName, action, httpMethod); + if (!actionNameInUrl.IsNullOrEmpty()) { - url += $"/{actionName}"; + url += $"/{actionNameInUrl}"; + + //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 moduleName, string controllerName, ActionModel action, string httpMethod) + { + var context = new UrlActionNameNormalizerContext(moduleName, controllerName, action, httpMethod, action.ActionName); + + foreach (var normalizer in _options.AppServiceControllers.UrlActionNameNormalizers) + { + normalizer.Normalize(context); + } + + return context.ActionNameInUrl; + } + protected virtual void RemoveEmptySelectors(IList selectors) { selectors diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs index ec9ee7a1d7..781824846a 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -1,9 +1,4 @@ -using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace Volo.Abp.AspNetCore.Mvc +namespace Volo.Abp.AspNetCore.Mvc { public class AbpAspNetCoreMvcOptions { @@ -14,29 +9,4 @@ namespace Volo.Abp.AspNetCore.Mvc AppServiceControllers = new AppServiceControllerOptions(); } } - - public class AppServiceControllerOptions - { - public ControllerAssemblySettingList ControllerAssemblySettings { get; } - public List FormBodyBindingIgnoredTypes { get; } - - public AppServiceControllerOptions() - { - ControllerAssemblySettings = new ControllerAssemblySettingList(); - FormBodyBindingIgnoredTypes = new List - { - typeof(IFormFile) - }; - } - - public AbpControllerAssemblySettingBuilder CreateFor( - Assembly assembly, - string moduleName = AbpControllerAssemblySetting.DefaultServiceModuleName, - bool useConventionalHttpVerbs = true) - { - var setting = new AbpControllerAssemblySetting(moduleName, assembly, useConventionalHttpVerbs); - ControllerAssemblySettings.Add(setting); - return new AbpControllerAssemblySettingBuilder(setting); - } - } } \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs new file mode 100644 index 0000000000..dd1944b3f8 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Http; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class AppServiceControllerOptions + { + public ControllerAssemblySettingList ControllerAssemblySettings { get; } + + public List FormBodyBindingIgnoredTypes { get; } + + public List UrlActionNameNormalizers { get; } + + public AppServiceControllerOptions() + { + ControllerAssemblySettings = new ControllerAssemblySettingList(); + + FormBodyBindingIgnoredTypes = new List + { + typeof(IFormFile) + }; + + UrlActionNameNormalizers = new List + { + new DefaultUrlActionNameNormalizer() + }; + } + + public AbpControllerAssemblySettingBuilder CreateFor( + Assembly assembly, + string moduleName = AbpControllerAssemblySetting.DefaultServiceModuleName, + bool useConventionalHttpVerbs = true) + { + var setting = new AbpControllerAssemblySetting(moduleName, assembly, useConventionalHttpVerbs); + ControllerAssemblySettings.Add(setting); + return new AbpControllerAssemblySettingBuilder(setting); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DefaultUrlActionNameNormalizer.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DefaultUrlActionNameNormalizer.cs new file mode 100644 index 0000000000..941a6fc834 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DefaultUrlActionNameNormalizer.cs @@ -0,0 +1,18 @@ +using System; +using Volo.Abp.Http; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class DefaultUrlActionNameNormalizer : IUrlActionNameNormalizer + { + public void Normalize(UrlActionNameNormalizerContext context) + { + if (context.ActionNameInUrl.IsNullOrEmpty()) + { + return; + } + + context.ActionNameInUrl = HttpMethodConventionHelper.RemoveHttpMethodPrefix(context.ActionNameInUrl, context.HttpMethod); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IUrlActionNameNormalizer.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IUrlActionNameNormalizer.cs new file mode 100644 index 0000000000..ca6a9df877 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IUrlActionNameNormalizer.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.AspNetCore.Mvc +{ + public interface IUrlActionNameNormalizer + { + void Normalize(UrlActionNameNormalizerContext context); + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs new file mode 100644 index 0000000000..5cd373c091 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/UrlActionNameNormalizerContext.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Volo.Abp.AspNetCore.Mvc +{ + public class UrlActionNameNormalizerContext + { + public string ModuleName { get; } + + public string ControllerName { get; } + + public ActionModel Action { get; } + + public string HttpMethod { get; } + + public string ActionNameInUrl { get; set; } + + public UrlActionNameNormalizerContext(string moduleName, string controllerName, ActionModel action, string httpMethod, string actionNameInUrl) + { + ModuleName = moduleName; + ControllerName = controllerName; + Action = action; + HttpMethod = httpMethod; + + ActionNameInUrl = actionNameInUrl; + } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AppModule.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AppModule.cs index 8cb8a2c903..9d69110408 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AppModule.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AppModule.cs @@ -25,6 +25,7 @@ namespace Volo.Abp.AspNetCore.App services.Configure(options => { options.AppServiceControllers.CreateFor(typeof(TestAppModule).Assembly); + options.AppServiceControllers.UrlActionNameNormalizers.Add(new PhoneBookUrlActionNameNormalizer()); }); services.AddAssemblyOf(); diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/PhoneBookUrlActionNameNormalizer.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/PhoneBookUrlActionNameNormalizer.cs new file mode 100644 index 0000000000..d497643932 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/PhoneBookUrlActionNameNormalizer.cs @@ -0,0 +1,17 @@ +using System; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Abp.AspNetCore.App +{ + public class PhoneBookUrlActionNameNormalizer : IUrlActionNameNormalizer + { + public void Normalize(UrlActionNameNormalizerContext context) + { + if (string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase)) + { + context.ActionNameInUrl = "phones"; + return; + } + } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PersonAppService_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PersonAppService_Tests.cs index 28b5143e17..f4a9860834 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PersonAppService_Tests.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PersonAppService_Tests.cs @@ -129,9 +129,8 @@ namespace Volo.Abp.AspNetCore.Mvc var postData = _jsonSerializer.Serialize(new PhoneDto { Type = PhoneType.Mobile, Number = phoneNumberToAdd }); - //Ideally should be [POST] /api/people/{id}/phones var response = await Client.PostAsync( - $"/api/app/people/{personToAddNewPhone.Id}/phone", + $"/api/app/people/{personToAddNewPhone.Id}/phones", new StringContent(postData, Encoding.UTF8, "application/json") ); @@ -154,8 +153,7 @@ namespace Volo.Abp.AspNetCore.Mvc { var douglas = _personRepository.GetList().First(p => p.Name == "Douglas"); - //Ideally should be [GET] /api/people/{id}/phones?type=office - var result = await GetResponseAsObjectAsync>($"/api/app/people/{douglas.Id}/Phones"); + var result = await GetResponseAsObjectAsync>($"/api/app/people/{douglas.Id}/phones"); result.Items.Count.ShouldBe(douglas.Phones.Count); } @@ -165,11 +163,10 @@ namespace Volo.Abp.AspNetCore.Mvc var douglas = _personRepository.GetList().First(p => p.Name == "Douglas"); var firstPhone = douglas.Phones.First(); - //Ideally should be [DELETE] /api/app/people/{id}/phones/{phoneId} - await Client.DeleteAsync($"/api/app/people/{douglas.Id}/Phone?phoneId=" + firstPhone.Id); + await Client.DeleteAsync($"/api/app/people/{douglas.Id}/phones/{firstPhone.Id}"); douglas = _personRepository.GetList().First(p => p.Name == "Douglas"); douglas.Phones.Any(p => p.Id == firstPhone.Id).ShouldBeFalse(); } } -} +} \ No newline at end of file