Base solution for your next web application
Open Closed

Dual Request to Kestrel Server from Angular Download #9251


User avatar
0
mmorales created

On the angular side I have the below code. Once the method subscribes (InitializeDownload()) I can see the download on the developer tools under network once the transfer is done the code under subscribe method executes

when looking at the serve logs

WARN 2020-06-17 09:57:07,055 [23 ] nostics.DeveloperExceptionPageMiddleware - The response has already started, the error page middleware will not be executed. ERROR 2020-06-17 09:57:07,056 [23 ] Microsoft.AspNetCore.Server.Kestrel - Connection id "0HM0IRS3OGV70", Request id "0HM0IRS3OGV70:00000035": An unhandled exception was thrown by the application.

Second part of code is the asp.netcore

I can't figure out why it makes two request and why it takes really long for the download to start.

Angular: downlodFile(id): void {

    let filedownloadInput = new FileDownloadInput();
    filedownloadInput.id = id;

    this._FileManagerService.downloadFile(filedownloadInput).subscribe(item => {
         this.InitializeDownload(item.fileName, item.fileToken).subscribe((data: any) => {

            this.blob = new Blob([data], { type: 'application/pdf' });

            var downloadURL = window.URL.createObjectURL(data);
            var link = document.createElement('a');
            link.href = downloadURL;
            link.download = item.fileName;
            link.click();

        });
    });
}
InitializeDownload(fileName:string, fileToken:string) {
    const url = AppConsts.remoteServiceBaseUrl + '/FileManager/GetFile';
    this.token = this._tokenService.getToken();
    let params = new HttpParams();

    params = params.append('fileName', fileName);
    params = params.append('fileToken', fileToken);
    const httpOptions = {
        responseType: 'blob' as 'json',
        headers: new HttpHeaders({
            "Authorization": "Bearer " + this.token
        }),
        params: params
    };

    return this._httpClient.get(url, httpOptions);
}

**
necore**

 public async Task GetFile( string fileName, string fileToken)
    {
        Stream stream = null;
        int bufferSize = 1048576;
        byte[] buffer = new byte[bufferSize];
        int length;
        long lengthToRead;

        try
        {
            string filePath = Path.Combine(_env.WebRootPath, $"Common{Path.DirectorySeparatorChar}", $"Files{Path.DirectorySeparatorChar}", fileToken);

            stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            long dj = stream.Length / bufferSize;
            if (stream.Length % bufferSize > 0)
            {
                dj += 1;
            }



            dj = dj * bufferSize;
            lengthToRead = stream.Length;
            
            Response.ContentType = "application/pdf";
            Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
            Response.Headers.Add("content-length", dj.ToString());
           // Response.ContentLength = dj;

            while( lengthToRead > 0)
            {
                length = stream.Read(buffer, 0, bufferSize);

               await Response.BodyWriter.WriteAsync(buffer);

               await Response.BodyWriter.FlushAsync();

                lengthToRead = lengthToRead - length;

               
            }
        }
        catch(Exception exp)
        {
            Response.ContentType = "text/html";
            
        }
        finally
        {
            if(stream != null)
            {
                stream.Close();
            }
        }

    }
}

9 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team

    hi

    You should use mvc FileStreamResult instead of operating on Response object.

    https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.file?view=aspnetcore-3.1

    eg:

    public Task<FileStreamResult> GetFile(....)
    {
        var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
        
        var fileStreamResult = new FileStreamResult(stream, ""application/pdf");
        
        fileStreamResult.FileDownloadName = fileName;
        
        ///....
        
        return fileStreamResult;
    }
    
    
    
  • User Avatar
    0
    mmorales created

    I no longer get the kestreI error but still get the same result. I can see the content download on dev tools (zone.js as XHR) then once transfer is complete the download starts.

  • User Avatar
    0
    maliming created
    Support Team

    hi mmorales

    Please share the code of downloadFile method.

    this._FileManagerService.downloadFile()
    
  • User Avatar
    0
    mmorales created
        public async Task&lt;FileDownloadOutput&gt; DownloadFile(FileDownloadInput input)
        {
           var fileInfo = await _filesObjectsRepository.FirstOrDefaultAsync(c => c.Id == input.Id);
    
            var output = new FileDownloadOutput()
            {
                FileName = fileInfo.FileName,
                FileToken = fileInfo.Identifier
            };
    
            return output;
           
        }
    
  • User Avatar
    0
    maliming created
    Support Team

    hi mmorales

    I mean Angular code. : )

     this._FileManagerService.downloadFile(filedownloadInput)
     {
     }
    
  • User Avatar
    0
    mmorales created

    this was generated with the refresh.bat command.

    /**
     * @param body (optional) 
     * @return Success
     */
    downloadFile(body: FileDownloadInput | undefined): Observable<FileDownloadOutput> {
        let url_ = this.baseUrl + "/api/services/app/FileManager/DownloadFile";
        url_ = url_.replace(/[?&]$/, "");
    
        const content_ = JSON.stringify(body);
    
        let options_ : any = {
            body: content_,
            observe: "response",
            responseType: "blob",			
            headers: new HttpHeaders({
                "Content-Type": "application/json-patch+json", 
                "Accept": "text/plain"
            })
        };
    
        return this.http.request("post", url_, options_).pipe(_observableMergeMap((response_ : any) => {
            return this.processDownloadFile(response_);
        })).pipe(_observableCatch((response_: any) => {
            if (response_ instanceof HttpResponseBase) {
                try {
                    return this.processDownloadFile(<any>response_);
                } catch (e) {
                    return <Observable<FileDownloadOutput>><any>_observableThrow(e);
                }
            } else
                return <Observable<FileDownloadOutput>><any>_observableThrow(response_);
        }));
    }
    
    protected processDownloadFile(response: HttpResponseBase): Observable<FileDownloadOutput> {
        const status = response.status;
        const responseBlob = 
            response instanceof HttpResponse ? response.body : 
            (<any>response).error instanceof Blob ? (<any>response).error : undefined;
    
        let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }};
        if (status === 200) {
            return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
            let result200: any = null;
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
            result200 = FileDownloadOutput.fromJS(resultData200);
            return _observableOf(result200);
            }));
        } else if (status !== 200 && status !== 204) {
            return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
            }));
        }
        return _observableOf<FileDownloadOutput>(<any>null);
    }
    
    
  • User Avatar
    0
    musa.demir created

    Hi @mmorales

    Sorry but i dont understand the exact problem. Can you please explain it more detailed?

    If what you ask is what is 204, it is prefligh request. The main goal is to determinate whether the actual request is safe to send. It comes from angular, requests are preflighted since they may have implications to user data etc.(https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request)

  • User Avatar
    0
    mmorales created

    Hello @musa.demir

    I have the below method. The problem is that when I execute the download method it takes a long time to actually see the file start downloading on my screen. After further looking at development tools when executing downloadFile method I see the content in the background transferring as seen below.

    once the transfer is complete the code under subscribe executes and I can see the actual file downloading as below

    Please advise. I haven't been able to figure how to skip the transfer content as it seems redudant to perform the content transfer and the download.

    downlodFile(id): void {

    let filedownloadInput = new FileDownloadInput();
    filedownloadInput.id = id;
    
    this._FileManagerService.downloadFile(filedownloadInput).subscribe(item => {
         this.InitializeDownload(item.fileName, item.fileToken).subscribe((data: any) => {
    
            this.blob = new Blob([data], { type: 'application/pdf' });
    
            var downloadURL = window.URL.createObjectURL(data);
            var link = document.createElement('a');
            link.href = downloadURL;
            link.download = item.fileName;
            link.click();
    
        });
    });
    

    } InitializeDownload(fileName:string, fileToken:string) { const url = AppConsts.remoteServiceBaseUrl + '/FileManager/GetFile'; this.token = this._tokenService.getToken(); let params = new HttpParams();

    params = params.append('fileName', fileName);
    params = params.append('fileToken', fileToken);
    const httpOptions = {
        responseType: 'blob' as 'json',
        headers: new HttpHeaders({
            "Authorization": "Bearer " + this.token
        }),
        params: params
    };
    
    return this._httpClient.get(url, httpOptions);
    

    }

  • User Avatar
    0
    musa.demir created

    What we do in chat module is download it from server via redirecting user to host project's GetUploadedObject.

    https://github.com/aspnetzero/aspnet-zero-core/blob/7b48ab6dd9b7aad3bde44f4a331b56c85d222a77/aspnet-core/src/MyCompanyName.AbpZeroTemplate.Web.Host/Controllers/ChatController.cs#L19-L32