abp icon indicating copy to clipboard operation
abp copied to clipboard

Customizing an existing AppService leads to duplication when generating the HTTP layer

Open agustinsilvano opened this issue 1 year ago • 7 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Description

I'm customizing the IdentityUserAppService as described in the documentation.

While doing that, the underlying services are available and the ability to extend the app service by introducing new methods in the app service works fine.

The issue is that when the swagger documentation (HttpApi layer) is generated, either the IdentityUserAppService (existing AbpIdentity module) and either the UserAppService (AppService that extends the existing one) endpoints are shown.

image

My understanding is that by using [Dependency(ReplaceServices = true)] that should replace the default registered service with the custom one on the DI container. I'm not sure if that DI configuration should affect something on the endpoint discovery phase in order to ignore the endpoints defined in the base class (existing identity module).

As an aside observation, the endpoint generation differs from the existing module to the custom one. The FindBy methods are generating a different output. So, I realized that you have a controller there that is the one that is generating the endpoints prefixed with /api/identity image

The question is, is there a way to get rid of that controller? I tried overriding a controller but couldn't avoid the controller layer generation that is the one that is causing.

For adding new endpoints to the existing module, is the process customizing the AppService, excluding the AppService from the Api discovery phase, and exposing it through a customized controller?

I do not see the added value of controllers here they are being used as a facade. Is there any reason?

Reproduction Steps

Minimal sample project available here.

Expected behavior

The endpoint generation by extending (by inheritance) an existing AppService shouldn't generate duplicated endpoints on the Swagger Documentation.

Actual behavior

No response

Regression?

No response

Known Workarounds

No response

Version

8.1.3

User Interface

Common (Default)

Database Provider

EF Core (Default)

Tiered or separate authentication server

None (Default)

Operation System

Windows (Default)

Other information

No response

agustinsilvano avatar Jun 06 '24 19:06 agustinsilvano

This is because you are using the Auto API Controllers: https://docs.abp.io/en/abp/latest/API/Auto-API-Controllers

You can try:

[RemoteService(IsEnabled = false)]
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(UserAppService))]
public class UserAppService : IdentityUserAppService

and add a new controller to expose the endpoint

realLiangshiwei avatar Jun 07 '24 03:06 realLiangshiwei

@realLiangshiwei got it.

If I add a custom method on the AppService, I must create the corresponding endpoint on the extended controller, right?

Just want to understand what is the recommended approach.

agustinsilvano avatar Jun 07 '24 13:06 agustinsilvano

To wrap up, I was able to get it working properly by adding the [RemoteService(IsEnabled = false)] to the AppService and by having the following code on the controller:

 [Dependency(ReplaceServices = true)]
 [ExposeServices(typeof(IdentityUserController), IncludeSelf = true)]
 public class UserController : IdentityUserController, IUserAppService
 {
     protected IUserAppService CustomUserAppService { get; }

     public UserController(IIdentityUserAppService identityUserAppService, IUserAppService userAppService) : base(identityUserAppService)
     {
         CustomUserAppService = userAppService;
     }

     [HttpGet]
     [Route("custom-endpoint")]
     public virtual async Task<int> CustomEndpointAsync()
     {
         return await CustomUserAppService.CustomEndpointAsync();
     }
 }

@realLiangshiwei thanks!

agustinsilvano avatar Jun 07 '24 14:06 agustinsilvano

@realLiangshiwei Sorry, circling back on this, that change updates correctly the HTTP layer but there is an issue while using the new app service. It throws:

2024-06-07 14:39:21.224 -03:00 [ERR] An exception was thrown while activating Acme.BookStore.Controllers.UserController.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating Acme.BookStore.Controllers.UserController.
 ---> Autofac.Core.DependencyResolutionException: None of the constructors found on type 'Acme.BookStore.Controllers.UserController' can be invoked with the available services and parameters:
Cannot resolve parameter 'Acme.BookStore.IUserAppService userAppService' of constructor 'Void .ctor(Volo.Abp.Identity.IIdentityUserAppService, Acme.BookStore.IUserAppService)'.

This is my AppService class definition:

 [RemoteService(IsEnabled = false)]
 [Dependency(ReplaceServices = true)]
 [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(UserAppService))]
 public class UserAppService : IdentityUserAppService, IUserAppService, ITransientDependency

I was able to get it working by manually registering the DI on the Application module.

 public override void ConfigureServices(ServiceConfigurationContext context)
{
    //...
    context.Services.AddScoped<IUserAppService>(
        sp => sp.GetRequiredService<UserAppService>()
    );
}

Is that expected? What's the correct setup?

agustinsilvano avatar Jun 07 '24 17:06 agustinsilvano

[RemoteService(IsEnabled = false)]
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(IUserAppService))]
public class UserAppService : IdentityUserAppService, IUserAppService, ITransientDependency

realLiangshiwei avatar Jun 08 '24 03:06 realLiangshiwei

@realLiangshiwei that didn't work.

agustinsilvano avatar Jun 10 '24 12:06 agustinsilvano

What is your code and what is the error log

realLiangshiwei avatar Jun 12 '24 01:06 realLiangshiwei

The [RemoteService(IsEnabled = false)] attribute disables the use of remote services. It can only display the old API endpoints in Swagger (without repeating them), but any newly added endpoints in the class will also be hidden. How can we override the existing API endpoints while keeping the new extensions visible and functional?

`[RemoteService(IsEnabled = false)] [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(UserAppService))] public class UserAppService : IdentityUserAppService { public UserAppService(IdentityUserManager userManager, IIdentityUserRepository userRepository, IIdentityRoleRepository roleRepository, IOptions<IdentityOptions> identityOptions, IPermissionChecker permissionChecker) : base(userManager, userRepository, roleRepository, identityOptions, permissionChecker) { }

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

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

}`

Image

lijon2015 avatar Feb 02 '25 04:02 lijon2015

Hi @lijon2015

Please create a new issue, thanks

realLiangshiwei avatar Feb 03 '25 01:02 realLiangshiwei