- ABP Framework version: v5.2.1
- UI type: Blazor
- DB provider: EF Core
- Tiered (MVC) or Identity Server Separated (Angular): Microservice Pro Template
- Exception message and stack trace: Could not found remote action for method: System.Threading.Tasks.Task`1[MOS.NotificationService.Samples.SampleDto] GetAsync()
- Steps to reproduce the issue:"
- Add a new microservice solution followed the steps in https://docs.abp.io/en/commercial/latest/startup-templates/microservice/add-microservice
- Called the SampleAppService GetAsync method and throw exceptions.
If we change to static Client Proxies using the CLI abp generate-proxy -t csharp -u https://localhost:44741 -m NotificationService which https://localhost:44741 is new NotificationService, problem can be solved.
[DependsOn(
typeof(NotificationServiceApplicationContractsModule),
typeof(AbpHttpClientModule))]
public class NotificationServiceHttpApiClientModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddStaticHttpClientProxies(typeof(NotificationServiceApplicationContractsModule).Assembly,
NotificationServiceRemoteServiceConsts.RemoteServiceName);
// context.Services.AddHttpClientProxies(
// typeof(NotificationServiceApplicationContractsModule).Assembly,
// NotificationServiceRemoteServiceConsts.RemoteServiceName
// );
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<NotificationServiceHttpApiClientModule>();
});
}
}
Sine the generated templated using dynamic proxy, can we use dynamic Client Proxies?
13 Answer(s)
-
0
Sine the generated templated using dynamic proxy, can we use dynamic Client Proxies?
For microservice template, if you change static proxy to dynamic proxy; gateways will need to depend on Http.Api layers of the dynamic proxying microservices which is not a subtle way to handle.
Which generated template is using dynamic proxy? Newly created microservice? Can you share the command you used to create the microservice?
-
0
abp new NotificationService -t microservice-service-pro
-
0
Thank you, I suggest switching to static proxying since dynamic proxying will make your gateways depend on microservices.
But if you want to use dynamic proxying with the ocelot, you can also do so.
-
0
if using dynamic proxying with the ocelot, it will throw a exception : Could not found remote action for method: System.Threading.Tasks.Task`1[MOS.NotificationService.Samples.SampleDto] GetAsync()
ocelot.json
{ "GlobalConfiguration": { "BaseUrl": "https://localhost:44325" }, "Routes": [ { "ServiceKey": "Account Service", "DownstreamPathTemplate": "/api/account/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44322 } ], "UpstreamPathTemplate": "/api/account/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Identity Service", "DownstreamPathTemplate": "/api/identity/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44388 } ], "UpstreamPathTemplate": "/api/identity/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Identity Service", "DownstreamPathTemplate": "/api/identity-server/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44388 } ], "UpstreamPathTemplate": "/api/identity-server/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Identity Service", "DownstreamPathTemplate": "/api/account-admin/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44388 } ], "UpstreamPathTemplate": "/api/account-admin/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Saas Service", "DownstreamPathTemplate": "/api/saas/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44381 } ], "UpstreamPathTemplate": "/api/saas/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/abp/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/abp/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/audit-logging/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/audit-logging/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/language-management/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/language-management/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/text-template-management/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/text-template-management/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/feature-management/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/feature-management/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/permission-management/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/permission-management/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/setting-management/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/setting-management/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Administration Service", "DownstreamPathTemplate": "/api/lepton-theme-management/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44367 } ], "UpstreamPathTemplate": "/api/lepton-theme-management/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Product Service", "DownstreamPathTemplate": "/api/product-service/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44361 } ], "UpstreamPathTemplate": "/api/product-service/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Client Service", "DownstreamPathTemplate": "/api/client-service/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 45304 } ], "UpstreamPathTemplate": "/api/client-service/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] }, { "ServiceKey": "Notification Service", "DownstreamPathTemplate": "/api/notification-service/{everything}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 44741 } ], "UpstreamPathTemplate": "/api/notification-service/{everything}", "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] } ] }
SampleController.cs
[RemoteService(Name = "NotificationService")] [Area("notificationService")] [ControllerName("NotificationService")] [Route("api/notification-service/sample")] public class SampleController : NotificationServiceController, ISampleAppService { private readonly ISampleAppService _sampleAppService; public SampleController(ISampleAppService sampleAppService) { _sampleAppService = sampleAppService; } [HttpGet] public async Task<SampleDto> GetAsync() { return await _sampleAppService.GetAsync(); } [HttpGet] [Route("authorized")] public async Task<SampleDto> GetAuthorizedAsync() { return await _sampleAppService.GetAsync(); } }
-
1
If you are using dynamic proxying, your gateway must depend on the microservice HttpApi layer.
So, you will need add project reference to gateway references and depends on attributes to gateway module.
-
0
Added but still not working.
The API can be found in gateway Swagger UI and use postman to test is work. https://localhost:44325/api/notification-service/sample
But calling the method from blazor will return:
Could not found remote action for method: System.Threading.Tasks.Task`1[MOS.NotificationService.Samples.SampleDto] GetAsync() on the URL: https://localhost:44325/
port 44325 is the gateway port
<Project Sdk="Microsoft.NET.Sdk.Web"> <Import Project="..\..\..\..\common.props" /> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\..\..\..\shared\MOS.Shared.Hosting.Gateways\MOS.Shared.Hosting.Gateways.csproj" /> <ProjectReference Include="..\..\..\..\services\notification\src\MOS.NotificationService.HttpApi\MOS.NotificationService.HttpApi.csproj" /> </ItemGroup> <ItemGroup> <Compile Remove="Logs\**" /> <Content Remove="Logs\**" /> <EmbeddedResource Remove="Logs\**" /> <None Remove="Logs\**" /> </ItemGroup> </Project>
[DependsOn( typeof(MOSSharedHostingGatewaysModule), typeof(NotificationServiceHttpApiModule) )] public class MOSWebGatewayModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { // Enable if you need hosting environment // var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); var hostingEnvironment = context.Services.GetHostingEnvironment(); SwaggerConfigurationHelper.ConfigureWithAuth( context: context, authority: configuration["AuthServer:Authority"], scopes: new Dictionary<string, string> /* Requested scopes for authorization code request and descriptions for swagger UI only */ { { "AccountService", "Account Service API" }, { "IdentityService", "Identity Service API" }, { "AdministrationService", "Administration Service API" }, { "SaasService", "Saas Service API" }, { "ProductService", "Product Service API" }, { "ClientService", "Client Service API" }, { "NotificationService", "Notification Service API" } }, apiTitle: "Web Gateway API" ); context.Services.AddCors(options => { options.AddDefaultPolicy(builder => { builder .WithOrigins( configuration["App:CorsOrigins"] .Split(",", StringSplitOptions.RemoveEmptyEntries) .Select(o => o.Trim().RemovePostFix("/")) .ToArray() ) .WithAbpExposedHeaders() .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseCorrelationId(); app.UseCors(); app.UseSwagger(); app.UseSwaggerUI(options => { var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>(); var routes = configuration.GetSection("Routes").Get<List<OcelotConfiguration>>(); var routedServices = routes .GroupBy(t => t.ServiceKey) .Select(r => r.First()) .Distinct(); foreach (var config in routedServices.OrderBy(q => q.ServiceKey)) { var url = $"{config.DownstreamScheme}://{config.DownstreamHostAndPorts.FirstOrDefault()?.Host}:{config.DownstreamHostAndPorts.FirstOrDefault()?.Port}"; if (!env.IsDevelopment()) { url = $"https://{config.DownstreamHostAndPorts.FirstOrDefault()?.Host}"; } options.SwaggerEndpoint($"{url}/swagger/v1/swagger.json", $"{config.ServiceKey} API"); options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); } }); app.UseAbpSerilogEnrichers(); app.UseRewriter(new RewriteOptions() // Regex for "", "/" and "" (whitespace) .AddRedirect("^(|\\|\\s+)$", "/swagger")); app.UseOcelot().Wait(); } }
-
0
Can you trace the http request from the blazor application by checking the logs? Check the blazor logs: Is your request made to the RemoteService:Default URL which is configured in the appsettings.json (that should be web-gateway)? Check the web-gateway logs: Is your request made here and getting redirected to microservice? Check your microservice logs: Is your request made here and returned 404?
-
0
blazor log:
2022-04-28 16:23:00.480 +08:00 [INF] Start processing HTTP request GET "https://localhost:44325/api/abp/api-definition" 2022-04-28 16:23:00.481 +08:00 [INF] Sending HTTP request GET "https://localhost:44325/api/abp/api-definition" 2022-04-28 16:23:00.678 +08:00 [INF] Received HTTP response headers after 197.4185ms - 200 2022-04-28 16:23:00.678 +08:00 [INF] End processing HTTP request after 198.2306ms - 200 2022-04-28 16:23:00.694 +08:00 [WRN] Unhandled exception rendering component: Could not found remote action for method: System.Threading.Tasks.Task`1[MOS.NotificationService.Samples.SampleDto] GetAsync() on the URL: https://localhost:44325/ Volo.Abp.AbpException: Could not found remote action for method: System.Threading.Tasks.Task`1[MOS.NotificationService.Samples.SampleDto] GetAsync() on the URL: https://localhost:44325/ at Volo.Abp.Http.Client.DynamicProxying.ApiDescriptionFinder.FindActionAsync(HttpClient client, String baseUrl, Type serviceType, MethodInfo method) at Volo.Abp.Http.Client.DynamicProxying.DynamicHttpProxyInterceptor`1.GetActionApiDescriptionModel(IAbpMethodInvocation invocation) at Volo.Abp.Http.Client.DynamicProxying.DynamicHttpProxyInterceptor`1.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed) at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo) at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync() at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed) at MOS.NotificationService.Blazor.Pages.NotificationService.Test.SendEmail() in C:\Users\isaac.yip\Desktop\MOS\MOS_20220420\services\notification\src\MOS.NotificationService.Blazor\Pages\NotificationService\Test.razor.cs:line 52 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Radzen.Blazor.RadzenButton.OnClick(MouseEventArgs args) at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) 2022-04-28 16:23:00.699 +08:00 [ERR] Unhandled exception in circuit '6PBxuosO4MUMXMmuyj7DgkoJVB-SLUH8afvlBwE4kl8'. Volo.Abp.AbpException: Could not found remote action for method: System.Threading.Tasks.Task`1[MOS.NotificationService.Samples.SampleDto] GetAsync() on the URL: https://localhost:44325/ at Volo.Abp.Http.Client.DynamicProxying.ApiDescriptionFinder.FindActionAsync(HttpClient client, String baseUrl, Type serviceType, MethodInfo method) at Volo.Abp.Http.Client.DynamicProxying.DynamicHttpProxyInterceptor`1.GetActionApiDescriptionModel(IAbpMethodInvocation invocation) at Volo.Abp.Http.Client.DynamicProxying.DynamicHttpProxyInterceptor`1.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed) at Castle.DynamicProxy.AsyncInterceptorBase.ProceedAsynchronous[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo) at Volo.Abp.Castle.DynamicProxy.CastleAbpMethodInvocationAdapterWithReturnValue`1.ProceedAsync() at Volo.Abp.Validation.ValidationInterceptor.InterceptAsync(IAbpMethodInvocation invocation) at Volo.Abp.Castle.DynamicProxy.CastleAsyncAbpInterceptorAdapter`1.InterceptAsync[TResult](IInvocation invocation, IInvocationProceedInfo proceedInfo, Func`3 proceed) at MOS.NotificationService.Blazor.Pages.NotificationService.Test.SendEmail() in C:\Users\isaac.yip\Desktop\MOS\MOS_20220420\services\notification\src\MOS.NotificationService.Blazor\Pages\NotificationService\Test.razor.cs:line 52 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Radzen.Blazor.RadzenButton.OnClick(MouseEventArgs args) at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) 2022-04-28 16:23:00.776 +08:00 [INF] Executed endpoint '/_blazor' 2022-04-28 16:23:00.777 +08:00 [INF] Request finished HTTP/1.1 GET https://localhost:44314/_blazor?id=pczk1P-5gdFs1D621NRtHA - - - 101 - - 68037.4493ms
web-gateway log:
2022-04-28 16:23:00.490 +08:00 [INF] Request starting HTTP/1.1 GET https://localhost:44325/api/abp/api-definition - - 2022-04-28 16:23:00.490 +08:00 [DBG] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: ocelot pipeline started 2022-04-28 16:23:00.490 +08:00 [DBG] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: Upstream url path is /api/abp/api-definition 2022-04-28 16:23:00.490 +08:00 [DBG] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: downstream templates are /api/abp/{everything} 2022-04-28 16:23:00.491 +08:00 [INF] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: EndpointRateLimiting is not enabled for /api/abp/{everything} 2022-04-28 16:23:00.491 +08:00 [INF] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: No authentication needed for /api/abp/api-definition 2022-04-28 16:23:00.491 +08:00 [INF] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: /api/abp/{everything} route does not require user to be authorized 2022-04-28 16:23:00.491 +08:00 [DBG] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: Downstream url is https://localhost:44367/api/abp/api-definition 2022-04-28 16:23:00.677 +08:00 [INF] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: 200 (OK) status code, request uri: https://localhost:44367/api/abp/api-definition 2022-04-28 16:23:00.677 +08:00 [DBG] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: setting http response message 2022-04-28 16:23:00.677 +08:00 [DBG] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: no pipeline errors, setting and returning completed response 2022-04-28 16:23:00.677 +08:00 [DBG] requestId: 0HMH8U0DU0PFU:00000002, previousRequestId: no previous request id, message: ocelot pipeline finished 2022-04-28 16:23:00.678 +08:00 [INF] Request finished HTTP/1.1 GET https://localhost:44325/api/abp/api-definition - - - 200 54537 application/json;+charset=utf-8 188.1015ms
microservice no request made here
-
0
Could not found remote action for method: System.Threading.Tasks.Task`1[MOS.NotificationService.Samples.SampleDto] GetAsync() on the URL: https://localhost:44325/
Can you call the sample endpoint on gateway from the URL: https://localhost:44325/api/notification-service/sample ?
If so, try adding the [Route] attribute to
GetAsync
method in SampleController:[HttpGet] public async Task<SampleDto> GetAsync() { return await _sampleAppService.GetAsync(); }
There can be a convention problem with other methods.
-
0
Can you call the sample endpoint on gateway from the URL: https://localhost:44325/api/notification-service/sample ?
Can
If so, try adding the [Route] attribute to GetAsync method in SampleController:
Added. And throw the same exception.
-
0
Hi isaac.yip@cpy.com.hk
Unfortunately, we couldn't reproduce this issue. Can you please share a sample project with this issue number (2963) to my email address?
enis.necipoglu@volosoft.com
-
1
In the Gateway ocelot configuration, that pattern
"/api/abp/{everything}"
is proxying/api/abp/api-definition
path to Administration Service.That works fine while using static client proxies because they don't check that endpoint at runtime. If you go with dynamic client proxies, add the following mapping to OnApplicationInitialization method in your Gateway Module class.
app.MapWhen( ctx => ctx.Request.Path.ToString().StartsWith("/api/abp/api-definition") || ctx.Request.Path.ToString().TrimEnd('/').Equals(""), app2 => { app2.UseRouting(); app2.UseConfiguredEndpoints(); } );
Make sure it is placed right before
app.UseOcelot().Wait();
-
0
It works now. Thank you.