Open Closed

Is there a way to customize what is seen by the auto api? #866


User avatar
0
  • ABP Framework version: v4.2.0
  • UI type: MVC
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Seperated (Angular): yes
  • Exception message and stack trace:
  • Steps to reproduce the issue: N/A

Is there a way to customize how the auto api is looking at methods, or mark methods with an attribute so that we don't get the usual "Only one complex type allowed as argument to a controller" message without having to split all my app service interfaces just to remove methods that don't work with the auto api?

2021-02-01 16:07:38.495 -05:00 [INF] Executed endpoint 'Volo.Abp.AspNetCore.Mvc.ProxyScripting.AbpServiceProxyScriptController.GetAll (Volo.Abp.AspNetCore.Mvc)'
2021-02-01 16:07:38.496 -05:00 [INF] AUDIT LOG: [200: GET    ] /Abp/ServiceProxyScript
- UserName - UserId                 : admin - fa5c8917-753d-f0bd-9ac6-39fa5bf3f2aa
- ClientIpAddress        : ::1
- ExecutionDuration      : 43

2021-02-01 16:07:38.496 -05:00 [ERR] An unhandled exception has occurred while executing the request.
Volo.Abp.AbpException: Only one complex type allowed as argument to a controller action that's binding source is 'Body'. But SaveReportContentAsync (api/app/generatedreport/{id}) contains more than one!
   at Volo.Abp.Http.ProxyScripting.Generators.ProxyScriptingHelper.GenerateBody(ActionApiDescriptionModel action)
   at Volo.Abp.Http.ProxyScripting.Generators.JQuery.JQueryProxyScriptGenerator.AddAjaxCallParameters(StringBuilder script, ActionApiDescriptionModel action)
   at Volo.Abp.Http.ProxyScripting.Generators.JQuery.JQueryProxyScriptGenerator.AddActionScript(StringBuilder script, String controllerName, ActionApiDescriptionModel action, String normalizedActionName)
   at Volo.Abp.Http.ProxyScripting.Generators.JQuery.JQueryProxyScriptGenerator.AddControllerScript(StringBuilder script, ControllerApiDescriptionModel controller)
   at Volo.Abp.Http.ProxyScripting.Generators.JQuery.JQueryProxyScriptGenerator.AddModuleScript(StringBuilder script, ModuleApiDescriptionModel module)
   at Volo.Abp.Http.ProxyScripting.Generators.JQuery.JQueryProxyScriptGenerator.CreateScript(ApplicationApiDescriptionModel model)
   at Volo.Abp.Http.ProxyScripting.ProxyScriptManager.CreateScript(ProxyScriptingModel scriptingModel)
   at Volo.Abp.Http.ProxyScripting.ProxyScriptManager.<>c__DisplayClass6_0.<GetScript>b__0()
   at System.Collections.Generic.AbpDictionaryExtensions.<>c__DisplayClass6_0`2.&lt;GetOrAdd&gt;b__0(TKey k)
   at System.Collections.Generic.AbpDictionaryExtensions.GetOrAdd[TKey,TValue](IDictionary`2 dictionary, TKey key, Func`2 factory)
   at System.Collections.Generic.AbpDictionaryExtensions.GetOrAdd[TKey,TValue](IDictionary`2 dictionary, TKey key, Func`1 factory)
   at Volo.Abp.Http.ProxyScripting.ProxyScriptManagerCache.GetOrAdd(String key, Func`1 factory)
   at Volo.Abp.Http.ProxyScripting.ProxyScriptManager.GetScript(ProxyScriptingModel scriptingModel)
   at Volo.Abp.AspNetCore.Mvc.ProxyScripting.AbpServiceProxyScriptController.GetAll(ServiceProxyGenerationModel model)
   at lambda_method1950(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResourceFilter()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Volo.Abp.AspNetCore.Serilog.AbpSerilogMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Volo.Abp.AspNetCore.Auditing.AbpAuditingMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Volo.Abp.AspNetCore.MultiTenancy.MultiTenancyMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
2021-02-01 16:07:38.497 -05:00 [INF] Request finished HTTP/2 GET https://localhost:44311/Abp/ServiceProxyScript - - - 500 - text/plain 45.2770ms

4 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Only one complex type allowed as argument to a controller action that's binding source is 'Body'. But SaveReportContentAsync (api/app/generatedreport/{id}) contains more than one!

    Can you share the source code of SaveReportContentAsync?

    What specific use case do you want to implement?

  • User Avatar
    0

    So, first - we're using the concept of strongly typed id's from here:

    So a simple class of ours might look like:

    public class GeneratedReport : GeneratedReportBase<GeneratedReportStatus, GeneratedReportFileType>, IEntity<GeneratedReport_Id>
    {
        public GeneratedReport_Id Id { get; set; }
        public int SortOrder { get; set; }
        public object[] GetKeys()
        {
            return new object[] { Id };
        }
    }
    

    where GeneratedReport_Id is:

    /// <summary>
    /// https://andrewlock.net/strongly-typed-id-updates/
    /// </summary>
    [StronglyTypedId(jsonConverter: StronglyTypedIdJsonConverter.SystemTextJson | StronglyTypedIdJsonConverter.NewtonsoftJson, backingType: StronglyTypedIdBackingType.Guid)]
    public partial struct GeneratedReport_Id { }
    

    And that works great. We use tons of guids and in migrating our solution (mix of .net48 with WebForm/Mvc) and we really wanted to enable ourselves to catch errors where Guids were being used incorrectly. To this we had to write a data layer that interacted with the db which was relatively easy to do with the extensibility of abp. The data engineer had everything but the site working (started on abp free, moved to commercial) and now we're at the point that I've created the commercial solution and we're trying to start working on the UI which is primarily Kendo UI based (instead of Datatables). I have a blog post coming about that for the community.

    So, on to the method in question. Obviously our app services have interfaces and those interfaces can contain these strongly typed ids:

    [HttpPost]
    [Route("{id}")]
    public Task SaveReportContentAsync(GeneratedReport_Id Id, byte[] ReportContent, CancellationToken cancellationToken = default)
    {
        // TODO: Copy implementation
        throw new NotImplementedException();
    }
    

    I see that the api-generation code is looking at GeneratedReport_Id and noticing it's not a primitive type so it breaks the inherent rule but ..

    Since SaveReportContentAsync is detailed in the IGeneratedReportAppService interface, it's required when creating the GeneratedReportsController in the HttpApi project. Since I only expect the API methods to be able to get lists of reports and not make them (report content is only created by a background job that uses Telerik Reporting), I don't really want that to be part of the API but I don't see any documentation letting me attribute certain methods to ignore them.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    You can try to specify the binding source for the parameter.

    [FromQuery] - Gets values from the query string.
    [FromRoute] - Gets values from route data.
    [FromForm] - Gets values from posted form fields.
    [FromBody] - Gets values from the request body.
    [FromHeader] - Gets values from HTTP headers.
    
    SaveReportContentAsync([FromQuery]GeneratedReport_Id Id, [FromBody]byte[] ReportContent)
    

    https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0#sources

    If it still doesn't work, maybe you can share a simple project to me. liming.ma@volosoft.com

  • User Avatar
    0

    Perfect, that seems to have done the trick and also (bonus) not required me to split interfaces! Thanks!

Made with ❤️ on ABP v9.1.0-preview. Updated on November 11, 2024, 11:11