RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.77k stars 1.29k forks source link

How do I get the filename for IActionResult from a typescript proxy generated by nswag #834

Closed Farshidgolkarihagh closed 7 years ago

Farshidgolkarihagh commented 7 years ago

I am using nswag to generate a typescript proxy file for my Web api Web service. I have a export method to expose some data within an excel file. Similar to the code below:

    [HttpGet]
    [Route("ExportProductDetails")]
    [SwaggerResponse(typeof(IActionResult))]
    public async Task<FileStreamResult> ExportDetailsAsync()
    {
        var data = await _productRepository.GetDetailsAsync();
        var stream = new MemoryStream();
        _StreamManager.Write(data, stream);
        stream.Position = 0;
        return new FileStreamResult(stream, new MediaTypeHeaderValue("application/octet-stream"))
        {
            FileDownloadName = "Export Details.xlsx"
        };
    }

The nwag file generate looks like this:

    exportProductDetails(): Observable<Blob> {
            let url_ = this.baseUrl + "/api/Product/ExportProductDetails";
            url_ = url_.replace(/[?&]$/, "");
            let options_ = {
                method: "get",
                responseType: ResponseContentType.Blob,
                headers: new Headers({
                    "Content-Type": "application/json", 
                    "Accept": "application/json"
                })
            };

            return this.http.request(url_, options_).flatMap((response_) => {
                return this.processExportProductDetails(response_);
            }).catch((response_: any) => {
                if (response_ instanceof Response) {
                    try {
                        return this.processExportProductDetails(response_);
                    } catch (e) {
                        return <Observable<Blob>><any>Observable.throw(e);
                    }
                } else
                    return <Observable<Blob>><any>Observable.throw(response_);
            });
        }

protected processExportProductDetails(response: Response): Observable<Blob> {
    const status = response.status; 

    if (status === 200) {
        return Observable.of(response.blob());
    } else if (status !== 200 && status !== 204) {
        return blobToText(response.blob()).flatMap(_responseText => {
        return throwException("An unexpected server error occurred.", status, _responseText);
        });
    }
    return Observable.of<Blob>(<any>null);
}

I cannt seem to find an intuitive way of getting the downloaded file to have the correct filename. Any idea how u can get nswag to expose the file name.

     this.productapi.exportProductDetails().subscribe((data) => {             
        if(data){
            let blob = new Blob([data], {
                type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-16le"
            });
            FileSaver.saveAs(blob, <FILENAME IS MISSING>); 
        }
    }); 
RicoSuter commented 7 years ago

Is there any way the get the filename in this line?

return Observable.of(response.blob());

i.e can we read a filename response header and set it on the blob object?

Farshidgolkarihagh commented 7 years ago

Hi

Thank you for the reply.

The entire typescript proxy file is automatically generated so prefer to not change them as changing it means I need to keep updating it with the future. Is there a way to either change the template or influence what gets generated from nswag.

Kind regards, Farshid

This email is sent from phone so apologies for any typo


From: Rico Suter notifications@github.com Sent: Wednesday, July 5, 2017 5:43:40 PM To: NSwag/NSwag Cc: Farshid; Author Subject: Re: [NSwag/NSwag] How do I get the filename for IActionResult from a typescript proxy generated by nswag (#834)

Is there any way the get the filename in this line?

return Observable.of(response.blob());

i.e can we read a filename response header and set it on the blob object?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/NSwag/NSwag/issues/834#issuecomment-313159489, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AAZAjtaFrJgGNvJ2L2QHS1jv2pf7HTLWks5sK708gaJpZM4OOlc0.

RicoSuter commented 7 years ago

The question is how we can retrieve the filename from a blob response so that we can extend the generated code so that it returns the filename?

Farshidgolkarihagh commented 7 years ago

The WebApi response has the filename but it is not inside the blob. The NSwag should be able to see that the response is a FileStreamResult and hence try to read the filename from the header of the response.

Content-Disposition:attachment; filename="Export Details.xlsx"; filename*=UTF-8''Export Details.xlsx Content-Type:application/octet-stream Persistent-Auth:true Server:Kestrel Transfer-Encoding:chunked X-Powered-By:ASP.NET

Is that clear?

RicoSuter commented 7 years ago

The file operation methods now return FileResponse instead of Blob:

let response = await client.getFile().toPromise();
let blog = response.data;
let filename = response.fileName;

Its a breaking change, but I think in the long term this must be done for better flexibility... You can test this with the latest CI artifacts. What do you think?

Farshidgolkarihagh commented 7 years ago

Sure I can test it. Have you committed your changes!?

On 7 Jul 2017 7:40 am, "Rico Suter" notifications@github.com wrote:

The file operation methods now return FileResponse instead of Blob:

let response = await client.getFile().toPromise(); let blog = response.data; let filename = response.fileName;

Its a breaking change, but I think in the long term this must be done for better flexibility... You can test this with the latest CI artifacts. What do you think?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/NSwag/NSwag/issues/834#issuecomment-313600410, or mute the thread https://github.com/notifications/unsubscribe-auth/AAZAjhNV6cnbMyZ2vrQTy7XgQ92h3Oumks5sLdL3gaJpZM4OOlc0 .

RicoSuter commented 7 years ago

Yes. You can test it with these binaries: https://ci.appveyor.com/project/rsuter/nswag-25x6o/build/artifacts

Farshidgolkarihagh commented 7 years ago

Hey RSuter

I have tested the code using the tool and it seems to produce the correct output.

protected processExportProductDetails(response: Response): Promise<FileResponse> {
    const status = response.status;
    if (status === 200) {
        const contentDisposition = response.headers.get("content-disposition");
        const fileNameMatch = contentDisposition ? /filename="?([^"]*)"?;/g.exec(contentDisposition) : undefined;
        const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
        let headers: any = {}; if (response.headers.forEach) { response.headers.forEach((v, k) => headers[k] = v); };
        return response.blob().then(blob => { return { fileName: fileName, data: blob, headers: headers }; });
    } else if (status !== 200 && status !== 204) {
        return response.text().then((_responseText) => {
        return throwException("An unexpected server error occurred.", status, _responseText);
        });
    }
    return Promise.resolve<FileResponse>(<any>null);
}

Thanks man for the awesome library and responding so fast. Very much looking forward to getting my hands on the latest release.

Thank you so much

Farshidgolkarihagh commented 7 years ago

How can I download the latest 11.3 via nuget? thanks

RicoSuter commented 7 years ago

This version is not yet released... I have to do more tests but i think i will release in the next days...

RicoSuter commented 7 years ago

v11.3 released...

rsheasby commented 5 years ago

Just ran into a similar issue wherein I was unable to read the filename of a file returned by IActionResult and thought it was an NSwag bug. The issue turned out to be that the browser was blocking the header due to CORS "Access-Control-Expose-Headers". For anyone who happens to stumble upon this page having a similar issue, take a look at this stackoverflow question and make sure that your headers aren't getting blocked by the browser: https://stackoverflow.com/questions/47160234/angular-4-new-httpclient-content-disposition-header

mutzl commented 5 years ago

Thanks @rsheasby ! For dotnet core backend, I had to add .WithExposedHeaders("Content-Disposition")

            services.AddCors(options =>
            {
                options.AddPolicy("AllowAllHeaders",
                    builder =>
                    {
                        builder.AllowAnyOrigin()
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials()
                            .WithExposedHeaders("Content-Disposition")
                            .Build();
                    });
            });
RicoSuter commented 5 years ago

Maybe you can update the wiki with this information?

Azkarell commented 5 years ago

Is it possible that filename-utf8 is ignored?

Feargalicious commented 4 months ago

Just in case someone else finds this: You might also be able to fix the issue without touching CORS by manually adding the following header before returning the File:

Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");

before you return the file with the filename:

return File(file.FileContent, file.ContentType, file.FileName);