Kevin
Kevin

Reputation: 267

Extending existing ABP controllers

I am using version 3.3.2 of the ABP Framework. How can I add new methods to an existing controller? I want to extend the IdentityUserController. Following the docs I am creating my own implementation as following:

    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IdentityUserController))]
    public class MyIdentityUserController : IdentityUserController
    {
        public MyIdentityUserController(IIdentityUserAppService userAppService) : base(userAppService)
        {
        }

        public override Task<PagedResultDto<IdentityUserDto>> GetListAsync(GetIdentityUsersInput input)
        {
            return base.GetListAsync(input);
        }

        [HttpGet]
        [Route("my-method")]
        public Task<string> MyMethod()
        {
            return Task.FromResult("Works");
        }
    }

The overrides actually work but my custom method is not visible in Swagger and when I try to access it with Postman it is not accessible either. Any ideas how I can extend existing controllers? I don't want to create a whole new controller since I have a combination with overrides and new methods. I would like to keep everything together.

Upvotes: 2

Views: 1249

Answers (2)

aaron
aaron

Reputation: 43098

First, set IncludeSelf = true — we will use this to determine whether to replace the existing controller with the extended controller, and ASP.NET Core will resolve your controller by class.
Optionally, add [ControllerName("User")] from IdentityUserController since it is not inherited:

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IdentityUserController), IncludeSelf = true)]
[ControllerName("User")]
public class MyIdentityUserController : IdentityUserController

Option 1

Subclass AbpServiceConvention and override RemoveDuplicateControllers to remove the existing controller(s) instead of your extended controller:

var exposeServicesAttr = ReflectionHelper.GetSingleAttributeOrDefault<ExposeServicesAttribute>(controllerModel.ControllerType);
if (exposeServicesAttr.IncludeSelf)
{
    var existingControllerModels = application.Controllers
        .Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType))
        .ToArray();
    derivedControllerModels.AddRange(existingControllerModels);
    Logger.LogInformation($"Removing the controller{(existingControllerModels.Length > 1 ? "s" : "")} {exposeServicesAttr.ServiceTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(existingControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}");
    continue;
}

Full code of subclass:

public class MyAbpServiceConvention : AbpServiceConvention
{
    public MyAbpServiceConvention(
        IOptions<AbpAspNetCoreMvcOptions> options,
        IConventionalRouteBuilder conventionalRouteBuilder)
        : base(options, conventionalRouteBuilder)
    {
    }

    protected override void RemoveDuplicateControllers(ApplicationModel application)
    {
        var derivedControllerModels = new List<ControllerModel>();

        foreach (var controllerModel in application.Controllers)
        {
            if (!controllerModel.ControllerType.IsDefined(typeof(ExposeServicesAttribute), false))
            {
                continue;
            }

            if (Options.IgnoredControllersOnModelExclusion.Contains(controllerModel.ControllerType))
            {
                continue;
            }

            var exposeServicesAttr = ReflectionHelper.GetSingleAttributeOrDefault<ExposeServicesAttribute>(controllerModel.ControllerType);
            if (exposeServicesAttr.IncludeSelf)
            {
                var existingControllerModels = application.Controllers
                    .Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType))
                    .ToArray();
                derivedControllerModels.AddRange(existingControllerModels);
                Logger.LogInformation($"Removing the controller{(existingControllerModels.Length > 1 ? "s" : "")} {exposeServicesAttr.ServiceTypes.Select(c => c.AssemblyQualifiedName).JoinAsString(", ")} from the application model since {(existingControllerModels.Length > 1 ? "they are" : "it is")} replaced by the controller: {controllerModel.ControllerType.AssemblyQualifiedName}");
                continue;
            }

            var baseControllerTypes = controllerModel.ControllerType
                .GetBaseClasses(typeof(Controller), includeObject: false)
                .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);
    }
}

Option 2

Implement IApplicationModelConvention to add your extended controller to IgnoredControllersOnModelExclusion and remove the existing controller:

public class ExtendedControllerApplicationModelConvention : IApplicationModelConvention
{
    private readonly Lazy<IOptions<AbpAspNetCoreMvcOptions>> _lazyOptions;

    public ExtendedControllerApplicationModelConvention (IServiceCollection services)
    {
        _lazyOptions = services.GetRequiredServiceLazy<IOptions<AbpAspNetCoreMvcOptions>>();
    }

    public void Apply(ApplicationModel application)
    {
        var controllerModelsToRemove = new List<ControllerModel>();
        var ignoredControllersOnModelExclusion = _lazyOptions.Value.Value.IgnoredControllersOnModelExclusion;

        foreach (var controllerModel in application.Controllers)
        {
            var exposeServicesAttr = ReflectionHelper.GetSingleAttributeOrDefault<ExposeServicesAttribute>(controllerModel.ControllerType);
            if (exposeServicesAttr != null && exposeServicesAttr.IncludeSelf)
            {
                ignoredControllersOnModelExclusion.AddIfNotContains(controllerModel.ControllerType);

                var existingControllerModels = application.Controllers
                    .Where(cm => exposeServicesAttr.ServiceTypes.Contains(cm.ControllerType));
                controllerModelsToRemove.AddIfNotContains(existingControllerModels);
            }
        }

        application.Controllers.RemoveAll(controllerModelsToRemove);
    }
}

In your module, insert ExtendedServiceApplicationModelConvention before AbpServiceConventionWrapper:

public override void ConfigureServices(ServiceConfigurationContext context)
{
    // ...
    Configure<MvcOptions>(options =>
    {
        var abpServiceConvention = options.Conventions.OfType<AbpServiceConventionWrapper>().First();
        options.Conventions.InsertBefore(abpServiceConvention, new ExtendedControllerApplicationModelConvention (context.Services));
    });
}

Upvotes: 3

oli_taz
oli_taz

Reputation: 199

I created a test project using the same version of ABP v3.3.2 and managed to get this working.

You can override the original methods in a new class that inherits from the original IdentityUserController, but you need to create your own controller to 'add' new methods to it. If you create a new controller that includes the same class attributes as IdentityUserController then it will appear like it has been extended.

[RemoteService(Name = IdentityRemoteServiceConsts.RemoteServiceName)]
[Area("identity")]
[ControllerName("User")]
[Route("api/identity/users")]
[ExposeServices(typeof(MyIdentityUserController))]
public class MyIdentityUserController : AbpController, IApplicationService, IRemoteService
{
    [HttpGet("my-method")]
    public Task<string> MyMethod()
    {
        return Task.FromResult("Works");
    }
}

Upvotes: 1

Related Questions