diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs index 364bb76c1f..1cd4b88fb8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -12,6 +12,8 @@ public class AbpAspNetCoreMvcOptions public HashSet IgnoredControllersOnModelExclusion { get; } + public HashSet ControllersToRemove { get; } + public bool AutoModelValidation { get; set; } public bool EnableRazorRuntimeCompilationOnDevelopment { get; set; } @@ -22,6 +24,7 @@ public class AbpAspNetCoreMvcOptions { ConventionalControllers = new AbpConventionalControllerOptions(); IgnoredControllersOnModelExclusion = new HashSet(); + ControllersToRemove = new HashSet(); AutoModelValidation = true; EnableRazorRuntimeCompilationOnDevelopment = true; ChangeControllerModelApiExplorerGroupName = true; 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 9a78479b9c..255e517be2 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 @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Controllers; using Volo.Abp.DependencyInjection; using Volo.Abp.GlobalFeatures; using Volo.Abp.Http; @@ -80,6 +81,36 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency { var controllerModelsToRemove = new List(); + if (Options.ControllersToRemove.Any()) + { + var removeControllerModels = GetControllers(application) + .Where(cm => Options.ControllersToRemove.Contains(cm.ControllerType)) + .ToArray(); + + if (removeControllerModels.Any()) + { + controllerModelsToRemove.AddRange(removeControllerModels); + Logger.LogInformation($"Removing the controller{(removeControllerModels.Length > 1 ? "s" : "")} {removeControllerModels.Select(c => c.ControllerType.AssemblyQualifiedName).JoinAsString(", ")} from the application model"); + } + } + + foreach (var controllerModel in GetControllers(application)) + { + var replaceControllersAttr = ReflectionHelper.GetSingleAttributeOrDefault(controllerModel.ControllerType); + if (replaceControllersAttr != default) + { + var replaceControllerModels = GetControllers(application) + .Where(cm => replaceControllersAttr.ControllerTypes.Contains(cm.ControllerType)) + .ToArray(); + + if (replaceControllerModels.Any()) + { + controllerModelsToRemove.AddRange(replaceControllerModels); + Logger.LogInformation($"Removing the controller{(replaceControllerModels.Length > 1 ? "s" : "")} {replaceControllersAttr.ControllerTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(replaceControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}"); + } + } + } + foreach (var controllerModel in GetControllers(application)) { if (!controllerModel.ControllerType.IsDefined(typeof(ExposeServicesAttribute), false)) @@ -99,9 +130,12 @@ public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency .Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType)) .ToArray(); - controllerModelsToRemove.AddRange(exposedControllerModels); - Logger.LogInformation($"Removing the controller{(exposedControllerModels.Length > 1 ? "s" : "")} {exposeServicesAttr.ServiceTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(exposedControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}"); - continue; + if (exposedControllerModels.Any()) + { + controllerModelsToRemove.AddRange(exposedControllerModels); + Logger.LogInformation($"Removing the controller{(exposedControllerModels.Length > 1 ? "s" : "")} {exposeServicesAttr.ServiceTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(exposedControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}"); + continue; + } } var baseControllerTypes = controllerModel.ControllerType diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs new file mode 100644 index 0000000000..4a7e7d6e4f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Controllers/ReplaceControllersAttribute.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Abp.AspNetCore.Controllers; + +[AttributeUsage(AttributeTargets.Class)] +public class ReplaceControllersAttribute : Attribute +{ + public Type[] ControllerTypes { get; } + + public ReplaceControllersAttribute(params Type[] controllerTypes) + { + ControllerTypes = controllerTypes ?? Type.EmptyTypes; + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController.cs new file mode 100644 index 0000000000..205f522436 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Controllers; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +namespace Volo.Abp.AspNetCore.Mvc.Controllers; + +[Area("abp")] +[RemoteService(Name = "abp")] +[ReplaceControllers(typeof(AbpApplicationConfigurationController), typeof(AbpApplicationLocalizationController))] +public class ReplaceBuiltInController : AbpController +{ + [HttpGet("api/abp/application-configuration")] + public virtual Task GetAsync(MyApplicationConfigurationRequestOptions options) + { + return Task.FromResult(new MyApplicationConfigurationDto() + { + Random = options.Random + }); + } + + [HttpGet("api/abp/application-localization")] + public virtual Task GetAsync(MyApplicationLocalizationRequestDto input) + { + return Task.FromResult(new MyApplicationLocalizationDto() + { + Random = input.Random + }); + } +} + +public class MyApplicationConfigurationRequestOptions : ApplicationConfigurationRequestOptions +{ + public string Random { get; set; } +} + +public class MyApplicationConfigurationDto : ApplicationConfigurationDto +{ + public string Random { get; set; } +} + +public class MyApplicationLocalizationRequestDto : ApplicationLocalizationRequestDto +{ + public string Random { get; set; } +} + +public class MyApplicationLocalizationDto : ApplicationLocalizationDto +{ + public string Random { get; set; } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController_Tests.cs new file mode 100644 index 0000000000..46aa0369e3 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Controllers/ReplaceBuiltInController_Tests.cs @@ -0,0 +1,32 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Controllers; + +public class ReplaceBuiltInController_Tests : AspNetCoreMvcTestBase +{ + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + services.Configure(options => + { + options.ControllersToRemove.Add(typeof(AbpLanguagesController)); + }); + } + + [Fact] + public async Task Test() + { + var random = Guid.NewGuid().ToString("N"); + + (await GetResponseAsObjectAsync("api/abp/application-configuration?random=" + random)).Random.ShouldBe(random); + (await GetResponseAsObjectAsync("api/abp/application-localization?CultureName=en&random=" + random)).Random.ShouldBe(random); + + (await GetResponseAsync("Abp/Languages/Switch", HttpStatusCode.NotFound)).StatusCode.ShouldBe(HttpStatusCode.NotFound); + } +}