diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs index feaf44e07a..24addb659f 100644 --- a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs +++ b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs @@ -65,7 +65,7 @@ namespace AbpDesk.Web.Mvc services.Configure(options => { - options.AppServiceControllers.CreateFor(typeof(AbpDeskApplicationModule).Assembly); + options.AppServiceControllers.Create(typeof(AbpDeskApplicationModule).Assembly); }); } diff --git a/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs new file mode 100644 index 0000000000..d72ec24d95 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.AspNetCore.Mvc.Versioning.Conventions; +using Volo.Abp.AspNetCore.Mvc; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class AbpApiVersioningOptionsExtensions + { + public static void AddAbpModules(this ApiVersioningOptions options, IServiceCollection services) + { + services.Configure(op => + { + foreach (var setting in op.AppServiceControllers.ControllerAssemblySettings) + { + foreach (var controllerType in setting.ControllerTypes) + { + var controllerBuilder = typeof(ApiVersionConventionBuilder) + .GetMethod(nameof(ApiVersionConventionBuilder.Controller), BindingFlags.Instance | BindingFlags.Public) + .MakeGenericMethod(controllerType) + .Invoke(options.Conventions, null); + + typeof(ControllerApiVersionConventionBuilder<>) + .MakeGenericType(controllerType) + .GetMethod("HasApiVersion") + .Invoke(controllerBuilder, new object[] { setting.ApiVersion }); + } + } + }); + } + } +} diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj index 746c1b2bef..0eada12839 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs index 376268eef9..409ad3241f 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceControllerFeatureProvider.cs @@ -34,7 +34,12 @@ namespace Volo.Abp.AspNetCore.Mvc } //TODO: Move this to a lazy loaded field for efficiency. - var configuration = _application.ServiceProvider.GetRequiredService>().Value.AppServiceControllers.ControllerAssemblySettings.GetSettingOrNull(type); + var configuration = _application.ServiceProvider + .GetRequiredService>().Value + .AppServiceControllers + .ControllerAssemblySettings + .GetSettingOrNull(type); + return configuration != null && (configuration.TypePredicate == null || configuration.TypePredicate(type)); } } diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs index cad6332995..eb3cdd144b 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs @@ -1,7 +1,12 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Volo.Abp.Application.Services; +using Volo.Abp.Reflection; namespace Volo.Abp.AspNetCore.Mvc { @@ -11,7 +16,19 @@ namespace Volo.Abp.AspNetCore.Mvc public Assembly Assembly { get; } [NotNull] - public string RootPath { get; } + public List ControllerTypes { get; } + + [NotNull] + public string RootPath + { + get => _rootPath; + set + { + Check.NotNull(value, nameof(value)); + _rootPath = value; + } + } + private string _rootPath; [CanBeNull] public Func TypePredicate { get; set; } @@ -25,12 +42,44 @@ namespace Volo.Abp.AspNetCore.Mvc [CanBeNull] public Func UrlActionNameNormalizer { get; set; } + public ApiVersion ApiVersion { get; set; } + public AbpControllerAssemblySetting([NotNull] Assembly assembly, [NotNull] string rootPath) { Check.NotNull(assembly, rootPath); Assembly = assembly; RootPath = rootPath; + + ControllerTypes = new List(); + + ApiVersion = new ApiVersion(1, 0); + } + + public void Initialize() + { + ControllerTypes.AddRange( + Assembly.GetTypes() + .Where(IsRemoteService) + .WhereIf(TypePredicate != null, TypePredicate) + ); + } + + private static bool IsRemoteService(Type type) + { + if (!typeof(IRemoteService).IsAssignableFrom(type) || !type.IsPublic || type.IsAbstract || type.IsGenericType) + { + return false; + } + + var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOrDefault(type); + + if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type)) + { + return false; + } + + return true; } } } \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs deleted file mode 100644 index ea45af6181..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySettingBuilder.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace Volo.Abp.AspNetCore.Mvc -{ - public class AbpControllerAssemblySettingBuilder : IAbpControllerAssemblySettingBuilder - { - private readonly AbpControllerAssemblySetting _setting; - - public AbpControllerAssemblySettingBuilder(AbpControllerAssemblySetting setting) - { - _setting = setting; - } - - public AbpControllerAssemblySettingBuilder Where(Func predicate) - { - _setting.TypePredicate = predicate; - return this; - } - - public AbpControllerAssemblySettingBuilder ConfigureControllerModel(Action configurer) - { - _setting.ControllerModelConfigurer = configurer; - return this; - } - - public AbpControllerAssemblySettingBuilder NormalizeControllerNameInUrl(Func normalizer) - { - _setting.UrlControllerNameNormalizer = normalizer; - return this; - } - - public AbpControllerAssemblySettingBuilder NormalizeActionNameInUrl(Func normalizer) - { - _setting.UrlActionNameNormalizer = normalizer; - return this; - } - } -} \ 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 index 8e118ae561..4c677fb8e0 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using JetBrains.Annotations; using Microsoft.AspNetCore.Http; using Volo.Abp.Http.Modeling; @@ -22,11 +23,13 @@ namespace Volo.Abp.AspNetCore.Mvc }; } - public AbpControllerAssemblySettingBuilder CreateFor(Assembly assembly, string rootPath = ModuleApiDescriptionModel.DefaultRootPath) + public AppServiceControllerOptions Create(Assembly assembly, [CanBeNull] Action optionsAction = null) { - var setting = new AbpControllerAssemblySetting(assembly, rootPath); + var setting = new AbpControllerAssemblySetting(assembly, ModuleApiDescriptionModel.DefaultRootPath); + optionsAction?.Invoke(setting); + setting.Initialize(); ControllerAssemblySettings.Add(setting); - return new AbpControllerAssemblySettingBuilder(setting); + return this; } } } \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs deleted file mode 100644 index ccd85b3b9a..0000000000 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/IAbpControllerAssemblySettingBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace Volo.Abp.AspNetCore.Mvc -{ - public interface IAbpControllerAssemblySettingBuilder - { - AbpControllerAssemblySettingBuilder Where(Func predicate); - - AbpControllerAssemblySettingBuilder ConfigureControllerModel(Action configurer); - } -} \ No newline at end of file diff --git a/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs b/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs index aca4f8ca56..01b14f1216 100644 --- a/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs +++ b/src/Volo.Abp.Identity.HttpApi.Host/AbpIdentityHttpApiHostModule.cs @@ -1,11 +1,21 @@ -using Microsoft.AspNetCore.Builder; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.RazorPages.Internal; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.AspNetCore.Mvc.Versioning.Conventions; using Microsoft.EntityFrameworkCore; +using Microsoft.Examples; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp.AspNetCore.Modularity; +using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -19,7 +29,7 @@ namespace Volo.Abp.Identity.HttpApi.Host { public override void ConfigureServices(IServiceCollection services) { - var hostingEnvironment = services.GetSingletonInstance(); + var hostingEnvironment = services.GetSingletonInstance(); //TOD: Move to BuildConfiguration method var configuration = BuildConfiguration(hostingEnvironment); services.Configure(configuration); @@ -33,14 +43,56 @@ namespace Volo.Abp.Identity.HttpApi.Host }); }); - services.AddMvc(); + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + services.AddMvcCore().AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV"); - services.AddSwaggerGen(options => + services.AddMvc(options => { - options.SwaggerDoc("v1", new Info { Title = "Volo.Abp.Identity API", Version = "v1" }); - options.DocInclusionPredicate((docName, description) => true); + }); + services.AddApiVersioning(o => + { + o.ReportApiVersions = true; + o.Conventions.Controller().IsApiVersionNeutral(); + //o.Conventions.Controller().HasApiVersion(new ApiVersion(3, 0)); //We can do that based on controller's AbpApiVersion attribute! + o.AssumeDefaultVersionWhenUnspecified = true; + o.DefaultApiVersion = new ApiVersion(3, 0); //Default: 1.0 //We can not rely on that, application should do. + + //o.ErrorResponses //TOD: We can override error response generator (to solve https://github.com/Microsoft/aspnet-api-versioning/issues/195) + + //o.Conventions.Controller().HasApiVersion(2, 0); + //o.Conventions.Controller().IsApiVersionNeutral(); + + o.AddAbpModules(services); + }); + + services.AddSwaggerGen( + options => + { + //options.SwaggerDoc("v1", new Info { Title = "Volo.Abp.Identity API", Version = "v1" }); + //options.DocInclusionPredicate((docName, description) => true); + + + // resolve the IApiVersionDescriptionProvider service + // note: that we have to build a temporary service provider here because one has not been created yet + var provider = services.BuildServiceProvider().GetRequiredService(); + + // add a swagger document for each discovered API version + // note: you might choose to skip or document deprecated API versions differently + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + + // add a custom operation filter which sets default values + options.OperationFilter(); + + // integrate xml comments + //options.IncludeXmlComments(XmlCommentsFilePath); //TODO: Add XML comments! + }); + services.AddAssemblyOf(); } @@ -57,10 +109,20 @@ namespace Volo.Abp.Identity.HttpApi.Host app.UseStaticFiles(); + var provider = context.ServiceProvider.GetRequiredService(); + app.UseSwagger(); - app.UseSwaggerUI(c => + app.UseSwaggerUI(options => { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Volo.Abp.Identity API"); + //options.SwaggerEndpoint("/swagger/v1/swagger.json", "Volo.Abp.Identity API"); + + + + // build a swagger endpoint for each discovered API version + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); + } }); app.UseMvcWithDefaultRoute(); @@ -75,5 +137,25 @@ namespace Volo.Abp.Identity.HttpApi.Host return builder.Build(); } + + private static Info CreateInfoForApiVersion(ApiVersionDescription description) + { + var info = new Info() + { + Title = $"Sample API {description.ApiVersion}", + Version = description.ApiVersion.ToString(), + Description = "A sample application with Swagger, Swashbuckle, and API versioning.", + Contact = new Contact() { Name = "Bill Mei", Email = "bill.mei@somewhere.com" }, + TermsOfService = "Shareware", + License = new License() { Name = "MIT", Url = "https://opensource.org/licenses/MIT" } + }; + + if (description.IsDeprecated) + { + info.Description += " This API version has been deprecated."; + } + + return info; + } } } diff --git a/src/Volo.Abp.Identity.HttpApi.Host/Controllers/HomeController.cs b/src/Volo.Abp.Identity.HttpApi.Host/Controllers/HomeController.cs index 34283e4d36..2a8c3aed0b 100644 --- a/src/Volo.Abp.Identity.HttpApi.Host/Controllers/HomeController.cs +++ b/src/Volo.Abp.Identity.HttpApi.Host/Controllers/HomeController.cs @@ -5,6 +5,8 @@ namespace Volo.Abp.Identity.HttpApi.Host.Controllers { public class HomeController : AbpController { + [HttpPost] + [Route("/api/v1/users/create")] public IActionResult Index() { return Redirect("/swagger"); diff --git a/src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs b/src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs new file mode 100644 index 0000000000..11045d85a6 --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi.Host/SwaggerDefaultValues.cs @@ -0,0 +1,41 @@ +namespace Microsoft.Examples +{ + using Swashbuckle.AspNetCore.Swagger; + using Swashbuckle.AspNetCore.SwaggerGen; + using System.Linq; + + /// + /// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter. + /// + /// This is only required due to bugs in the . + /// Once they are fixed and published, this class can be removed. + public class SwaggerDefaultValues : IOperationFilter + { + /// + /// Applies the filter to the specified operation using the given context. + /// + /// The operation to apply the filter to. + /// The current operation filter context. + public void Apply(Operation operation, OperationFilterContext context) + { + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 + foreach (var parameter in operation.Parameters.OfType()) + { + var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + if (parameter.Description == null) + { + parameter.Description = description.ModelMetadata.Description; + } + + if (parameter.Default == null) + { + parameter.Default = description.RouteInfo?.DefaultValue; + } + + parameter.Required |= description.RouteInfo?.IsOptional == true; + } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs deleted file mode 100644 index cf590e964a..0000000000 --- a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/CallsController.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.Application.Dtos; -using Volo.Abp.AspNetCore.Mvc; - -namespace Volo.Abp.Identity.HttpApi.Host.VersioningTests -{ - [Route("api/calls")] - public class CallsController : AbpController - { - private static List _calls = new List - { - new CallDto {Id = 1, Number = "123456"}, - new CallDto { Id = 2, Number = "123457" } - }; - - [HttpGet] - public List GetList() - { - return _calls; - } - } - - public class CallDto : EntityDto - { - public string Number { get; set; } - } -} diff --git a/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs new file mode 100644 index 0000000000..79073d8ba7 --- /dev/null +++ b/src/Volo.Abp.Identity.HttpApi.Host/VersioningTests/V1/CallsController.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Abp.Identity.HttpApi.Host.VersioningTests.V1 +{ + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/calls")] + public class CallsController : AbpController + { + private static List _calls = new List + { + new CallDto {Id = 1, Number = "123456"}, + new CallDto { Id = 2, Number = "123457" } + }; + + [HttpGet] + public List GetList() + { + return _calls; + } + } + + public class CallDto : EntityDto + { + public string Number { get; set; } + } + + [ApiVersion("2.0")] + [Route("api/v{api-version:apiVersion}/calls")] + [ControllerName("Calls")] + public class Calls2Controller : AbpController + { + private static List _calls = new List + { + new CallDto {Id = 1, Number = "123456000"}, + new CallDto { Id = 2, Number = "123457000" } + }; + + [HttpGet] + public List GetList() + { + return _calls; + } + + [HttpGet] + [Route("by-filter")] + public List GetList(string num) + { + return _calls.Where(c => c.Number.Contains(num)).ToList(); + } + } +} diff --git a/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj b/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj index 581749f373..18f3574de5 100644 --- a/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj +++ b/src/Volo.Abp.Identity.HttpApi.Host/Volo.Abp.Identity.HttpApi.Host.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj b/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj index 73ab6aa798..8763a68a14 100644 --- a/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj +++ b/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs index 30e3996bc1..e0e74595e6 100644 --- a/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs +++ b/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Modularity; @@ -14,10 +15,12 @@ namespace Volo.Abp.Identity services.Configure(options => { - options - .AppServiceControllers - .CreateFor(typeof(AbpIdentityApplicationModule).Assembly, "identity") - .NormalizeControllerNameInUrl(context => context.ControllerName.RemovePreFix("Identity")); + options.AppServiceControllers.Create(typeof(AbpIdentityApplicationModule).Assembly, opts => + { + opts.RootPath = "identity"; + opts.UrlControllerNameNormalizer = context => context.ControllerName.RemovePreFix("Identity"); + opts.ApiVersion = new ApiVersion(2, 0, "beta"); + }); }); } } diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs index a3d999671e..e6cf1598b8 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs @@ -25,15 +25,13 @@ namespace Volo.Abp.AspNetCore.App services.Configure(options => { - options - .AppServiceControllers - .CreateFor(typeof(TestAppModule).Assembly) - .NormalizeActionNameInUrl( - context => - string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase) - ? "phones" - : context.ActionNameInUrl - ); + options.AppServiceControllers.Create(typeof(TestAppModule).Assembly, opts => + { + opts.UrlActionNameNormalizer = context => + string.Equals(context.ActionNameInUrl, "phone", StringComparison.OrdinalIgnoreCase) + ? "phones" + : context.ActionNameInUrl; + }); }); services.AddAssemblyOf();