abpframework / abp

Open-source web application framework for ASP.NET Core! Offers an opinionated architecture to build enterprise software solutions with best practices on top of the .NET. Provides the fundamental infrastructure, cross-cutting-concern implementations, startup templates, application modules, UI themes, tooling and documentation.
https://abp.io
GNU Lesser General Public License v3.0
12.76k stars 3.41k forks source link

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

Open agustinsilvano opened 3 months ago

agustinsilvano commented 3 months ago

Is there an existing issue for this?

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

realLiangshiwei commented 3 months ago

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

agustinsilvano commented 3 months ago

@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 commented 3 months ago

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 commented 3 months ago

@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?

realLiangshiwei commented 3 months ago
[RemoteService(IsEnabled = false)]
 [Dependency(ReplaceServices = true)]
 [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(IUserAppService))]
 public class UserAppService : IdentityUserAppService, IUserAppService, ITransientDependency
agustinsilvano commented 3 months ago

@realLiangshiwei that didn't work.

realLiangshiwei commented 3 months ago

What is your code and what is the error log