From 71b1384598128b7432491422f1b64837c7fb97af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 8 Oct 2020 16:24:53 +0300 Subject: [PATCH] Resolved #5269: allow to easily override a controller. --- ...Application-Modules-Overriding-Services.md | 35 +++++++++++++++++++ .../Mvc/Conventions/AbpServiceConvention.cs | 33 +++++++++++++++-- .../Volo.Abp.Core/System/AbpTypeExtensions.cs | 25 +++++++++---- .../Generic/AbpCollectionExtensions.cs | 16 ++++++++- .../System/AbpTypeExtensions_Tests.cs | 10 +++++- 5 files changed, 109 insertions(+), 10 deletions(-) diff --git a/docs/en/Customizing-Application-Modules-Overriding-Services.md b/docs/en/Customizing-Application-Modules-Overriding-Services.md index d0d8c8a8a1..b8706eea61 100644 --- a/docs/en/Customizing-Application-Modules-Overriding-Services.md +++ b/docs/en/Customizing-Application-Modules-Overriding-Services.md @@ -162,6 +162,41 @@ This example class inherits from the `IdentityUserManager` [domain service](Doma Check the [localization system](Localization.md) to learn how to localize the error messages. +### Example: Overriding a Controller + +````csharp +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.Account; +using Volo.Abp.DependencyInjection; + +namespace MyProject.Controllers +{ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(AccountController))] + public class MyAccountController : AccountController + { + public MyAccountController(IAccountAppService accountAppService) + : base(accountAppService) + { + + } + + public override async Task SendPasswordResetCodeAsync( + SendPasswordResetCodeDto input) + { + Logger.LogInformation("Your custom logic..."); + + await base.SendPasswordResetCodeAsync(input); + } + } +} +```` + +This example replaces the `AccountController` (An API Controller defined in the [Account Module](Modules/Account.md)) and overrides the `SendPasswordResetCodeAsync` method. + +**`[ExposeServices(typeof(AccountController))]` is essential** here since it registers this controller for the `AccountController` in the dependency injection system. `[Dependency(ReplaceServices = true)]` is also recommended to clear the old registration (even the ASP.NET Core DI system selects the last registered one). + ### Overriding Other Classes Overriding controllers, framework services, view component classes and any other type of classes registered to dependency injection can be overridden just like the examples above. 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 f19de585b9..e3bd8790f9 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 @@ -7,24 +7,30 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; using Volo.Abp.GlobalFeatures; using Volo.Abp.Http; using Volo.Abp.Http.Modeling; -using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Reflection; namespace Volo.Abp.AspNetCore.Mvc.Conventions { public class AbpServiceConvention : IAbpServiceConvention, ITransientDependency { + public ILogger Logger { get; set; } + private readonly AbpAspNetCoreMvcOptions _options; - public AbpServiceConvention(IOptions options) + public AbpServiceConvention( + IOptions options) { _options = options.Value; + + Logger = NullLogger.Instance; } public void Apply(ApplicationModel application) @@ -34,9 +40,12 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions protected virtual void ApplyForControllers(ApplicationModel application) { + RemoveDuplicateControllers(application); + foreach (var controller in application.Controllers) { var controllerType = controller.ControllerType.AsType(); + var configuration = GetControllerSettingOrNull(controllerType); //TODO: We can remove different behaviour for ImplementsRemoteServiceInterface. If there is a configuration, then it should be applied! @@ -59,6 +68,26 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions } } + protected virtual void RemoveDuplicateControllers(ApplicationModel application) + { + var derivedControllerModels = new List(); + + foreach (var controllerModel in application.Controllers) + { + var baseControllerTypes = controllerModel.ControllerType + .GetBaseClasses(typeof(Controller)) + .Where(t => !t.IsAbstract) + .ToArray(); + if (baseControllerTypes.Length > 0) + { + derivedControllerModels.Add(controllerModel); + Logger.LogInformation($"Removing the controller {controllerModel.ControllerType.AssemblyQualifiedName} from the application model since it replaces the controller(s): {baseControllerTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")}"); + } + } + + application.Controllers.RemoveAll(derivedControllerModels); + } + protected virtual void ConfigureRemoteService(ControllerModel controller, [CanBeNull] ConventionalControllerSetting configuration) { ConfigureApiExplorer(controller); diff --git a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs index e7c600eff6..26bfed5de4 100644 --- a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs @@ -55,14 +55,27 @@ namespace System return types.ToArray(); } + /// + /// Gets all base classes of this type. + /// + /// The type to get its base classes. + /// A type to stop going to the deeper base classes. This type will be be included in the returned array + public static Type[] GetBaseClasses([NotNull] this Type type, Type stoppingType) + { + Check.NotNull(type, nameof(type)); + + var types = new List(); + AddTypeAndBaseTypesRecursively(types, type.BaseType, true, stoppingType); + return types.ToArray(); + } + private static void AddTypeAndBaseTypesRecursively( [NotNull] List types, - [CanBeNull] Type type, - bool includeObject) + [CanBeNull] Type type, + bool includeObject, + [CanBeNull] Type stoppingType = null) { - Check.NotNull(types, nameof(types)); - - if (type == null) + if (type == stoppingType) { return; } @@ -72,7 +85,7 @@ namespace System return; } - AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject); + AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject, stoppingType); types.Add(type); } } diff --git a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpCollectionExtensions.cs b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpCollectionExtensions.cs index 2a858f7626..e014ee9e21 100644 --- a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpCollectionExtensions.cs @@ -105,5 +105,19 @@ namespace System.Collections.Generic return items; } + + /// + /// Removes all items from the collection those satisfy the given . + /// + /// Type of the items in the collection + /// The collection + /// Items to be removed from the list + public static void RemoveAll([NotNull] this ICollection source, IEnumerable items) + { + foreach (var item in items) + { + source.Remove(item); + } + } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Core.Tests/System/AbpTypeExtensions_Tests.cs b/framework/test/Volo.Abp.Core.Tests/System/AbpTypeExtensions_Tests.cs index 382440a3f4..dceeda72b2 100644 --- a/framework/test/Volo.Abp.Core.Tests/System/AbpTypeExtensions_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/System/AbpTypeExtensions_Tests.cs @@ -6,7 +6,7 @@ namespace System public class AbpTypeExtensions_Tests { [Fact] - public void GetBaseClasses() + public void GetBaseClasses_Excluding_Object() { var baseClasses = typeof(MyClass).GetBaseClasses(includeObject: false); baseClasses.Length.ShouldBe(2); @@ -14,6 +14,14 @@ namespace System baseClasses[1].ShouldBe(typeof(MyBaseClass2)); } + [Fact] + public void GetBaseClasses_With_StoppingType() + { + var baseClasses = typeof(MyClass).GetBaseClasses(typeof(MyBaseClass1)); + baseClasses.Length.ShouldBe(1); + baseClasses[0].ShouldBe(typeof(MyBaseClass2)); + } + public abstract class MyBaseClass1 {