abp icon indicating copy to clipboard operation
abp copied to clipboard

Angular proxy generator ignoring [Produces] attribute

Open antonGritsenko opened this issue 3 months ago • 12 comments

Is there an existing issue for this?

  • [x] I have searched the existing issues

Description

Right now, the Angular proxy generator checking only response type, but not response connect type, in case if type is string. This cause weird behavior.

Reproduction Steps

Create simple service:

[Produces("application/json")]
public Task<string> GetStatus()
{
	return Task.FromResult("Open")
}

The problem is in the generated code:

  getStatus = (config?: Partial<Rest.Config>) =>
    this.restService.request<any, string>({
      method: 'GET',
      responseType: 'string',  // <<< WRONG
      url: '/api/app/testService/status',
    },
    { apiName: this.apiName,...config });

responseType: 'string' is wrong in this case, it must be 'json'

It related to this change: https://github.com/abpframework/abp/pull/6352

Expected behavior

var status = this.testService.getStatus(); // 
console.log("status")

Should be value Open

Actual behavior

var status = this.testService.getStatus(); // 
console.log("status")

Returned "Open" (note to double brackets)

Regression?

No response

Known Workarounds

No response

Version

8.1.1

User Interface

Angular

Database Provider

EF Core (Default)

Tiered or separate authentication server

None (Default)

Operation System

Windows (Default)

Other information

No response

antonGritsenko avatar Sep 15 '25 17:09 antonGritsenko

hi

What is the JSON output of the api/abp/api-definition endpoint?

Thanks

maliming avatar Sep 16 '25 01:09 maliming

@maliming

"GetStatus": {
              "uniqueName": "GetStatus",
              "name": "GetStatus",
              "httpMethod": "GET",
              "url": "api/app/test-service/status",
              "supportedVersions": [],
              "parametersOnMethod": [],
              "parameters": [],
              "returnValue": {
                "type": "System.String",
                "typeSimple": "string"
              },
              "allowAnonymous": null,
              "implementFrom": "My.TestService"
            }

antonGritsenko avatar Sep 16 '25 12:09 antonGritsenko

hi @antonGritsenko

Produces will convert the return value to JSON.

The status string to JSON will be "status"

A string is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes. A character is represented as a single character string. A string is very much like a C or Java string.

[Produces("application/json")]
public Task<string> GetStatus()

Why do you use Produces?

Thanks.

maliming avatar Sep 17 '25 01:09 maliming

Why do you use Produces

@maliming Because I thought that you are using it to generate correct dataType (which is expected), but then I did check the code and this is not the case. The actual problem that even without [Produces("application/json")] ABP generates JSON response (with double brackets) like "Ready".

antonGritsenko avatar Sep 17 '25 10:09 antonGritsenko

ok, what is the Content-Type of the GetStatus HTTP request?

maliming avatar Sep 17 '25 12:09 maliming

@maliming you know, I think problem is deeper. I have found another sample where is works strange (and I pretty sure it works in v6 of ABP in another way, because this app was migrated from v6). I have endpoint defined like that:

        public async Task<IRemoteStreamContent> GetScreenshot(string channelId, [FromQuery] DateTime? time)
        {
            var stream = await _someService.GetScreenshoot(channelId, time);
            return new RemoteStreamContent(stream, fileName: "screenhoot.jpg", contentType: "image/jpeg");
        }

Corresponding TS code generated by the proxy:

  getScreenshotByChannelIdAndTime = (channelId: string, time: string, config?: Partial<Rest.Config>) =>
    this.restService.request<any, Blob>({
      method: 'GET',
      responseType: 'blob',
      url: `/api/app/sec-service/screenshot/${channelId}`,
      params: { time },
    },
    { apiName: this.apiName,...config });

Looks good so far. The actual problem that request sent by this call is totally wrong:

:method GET :path /api/app/sec-service/screenshot/Ia7HtFlm_cVhDG5pu?time=2025-09-17T19:41:16.218Z :scheme https accept application/json, text/plain, /

With that request I'm getting following in the body:

{"fileName":"screenhoot.jpg","contentType":"image/jpeg","contentLength":786763}

So not an image, but just JSON, but this kind expected - server reply exactly what we've requested with application/json: the JSON object.

RestService sending application/json first, but must sent application/octet-steam. I did check with the Postman, if Accept set to application/octet-steam then actual image is returned.

If I do call like this:

this.localHttpClient.get(`${environment.apis.default.url}/api/app/sec-service/screenshot/${this.selectedRow.channelId}`,{
      responseType: 'blob',
      headers: {
        'Accept': 'application/octet-stream',
        'Authorization': `Bearer ${this.oAuthService.getAccessToken()}` ,
    },
    params: { time: time.toISOString() }
  }
    ).subscribe(r => blob => {
      let objectURL = URL.createObjectURL(blob);
      this.screenShotUrl = this.sanitizer.bypassSecurityTrustUrl(objectURL);
    });

then all works as expected.

Looks like Angular's HttpClient always sending application/json.

antonGritsenko avatar Sep 17 '25 20:09 antonGritsenko

hi

Let's go back to the original: what is the Content-Type of the GetStatus HTTP request?

Image

maliming avatar Sep 18 '25 03:09 maliming

application/json, as requested by the client Image

antonGritsenko avatar Sep 18 '25 11:09 antonGritsenko

Remove [Produces("application/json")] and check the Content-Type again

maliming avatar Sep 18 '25 11:09 maliming

I'm doing all tests now without [Produces("application/json")], with pure ApplicationService class. This is correct behavior of the backend from my PoV: it's returning exactly what requested. The Accept set by RestService: Image

The first in Accept is application/json, if backed support it then it must return it by the standard and ignore all other.

IMHO the proxy generator must add 'Accept' header in case if responseType != 'json'.

antonGritsenko avatar Sep 18 '25 12:09 antonGritsenko

hi @erdemcaygor

Do you have any idea? I saw you have a similar PR

Thanks.

maliming avatar Sep 23 '25 09:09 maliming

One more huge issue with this: if response: 'text' then all errors are ignored by standard ABP error handler in the Angular, and error is hidden.

antonGritsenko avatar Oct 02 '25 09:10 antonGritsenko