Customizing an existing AppService leads to duplication when generating the HTTP layer
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.
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
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
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 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.
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!
@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?
[RemoteService(IsEnabled = false)]
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(IUserAppService))]
public class UserAppService : IdentityUserAppService, IUserAppService, ITransientDependency
@realLiangshiwei that didn't work.
What is your code and what is the error log
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);
}
}`
Hi @lijon2015
Please create a new issue, thanks