ASP.NET Core 8: Sending PDF Files from ASP.NET Core API to Angular Client and Open the PDF File in Angular Application

In this article, we will implement the mechanism of sending the PDF file from ASP.NET Core API to Angular client application. The Angular client application will download will open this PDF file directly in the browser. It is always important to show the data stored in PDF document from server application to the client application without explicitly downloading it on the client (User may take a call to further save it). In ASP.NET Core we can make use of the Results class and its File() method to send file from API in the HTTP GET response. This file response can be received by the Angular Client application in the BLOB response type. In the Angular application, we can use the createObjectURL() method of the window.URL interface object to create string URL so that the file can be directly opened in the browser. The Figure 1 shows the implementation type.


Figure 1: The Implementation

In ASP.NET Core, we cand use the File() method of the Result class to write the Stream into the HTTP Response so that the client application (e.g. Angular, React, etc.), can read this stream as BLOB and process it further. The best part of the PDF file is that this file can be directly opened in the browser. This does not need an explicit download of the file to client.

Let's implement the solution.

The ASP.NET Core API project is created named Core_API_DocOpener. This project contains the Files folder. This folder contains 2 PDF documents, named File1.pdf and File2.pdf. These are sample documents those contains images in it. 

The minimal APIs are created with documents and document endpoints. The cod in Listing 1 shows the endpoints.


 
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddCors(options =>
{
    options.AddPolicy("cors", policy =>
    {
        policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
    });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
 
    app.UseSwagger();
    app.UseSwaggerUI();


app.UseHttpsRedirection();
app.UseCors("cors");


app.MapGet("/documents", async (HttpContext context, IWebHostEnvironment env) => {

  List<string> files = new List<string>();

    // 1. Get the directory path
    string dirPath = Path.Combine(env.ContentRootPath, "Files");
    // 2. Read Files
    var documents = Directory.GetFiles(dirPath).ToList();

    // 3. Extract file names
    files = documents.Select(f => Path.GetFileName(f)).ToList();

    return Results.Ok(files);

});

// The Endpoint

app.MapGet("/document/{file}", async (HttpContext context, IWebHostEnvironment env, string file) => {

    // 1. Get the directory path
    string dirPath = Path.Combine(env.ContentRootPath, "Files");
    // 2. Read Files
    var files = Directory.GetFiles(dirPath);

    var fileName = files.FirstOrDefault(f => f.Contains(file));

    // 3. If file is not found return NotFound
    if (fileName == null)
    {
        return Results.NotFound($"File {file}.docx is not available");
    }

    // 4. Define a memory Stream
    var memory = new MemoryStream();

    // 5. Copy the File into the MemoryStream
    using (var stream = new FileStream(fileName, FileMode.Open))
    {
        await stream.CopyToAsync(memory);
    }
    
    memory.Position = 0;

    // 6. The File Response
    return Results.File(memory, "application/pdf", fileName);
});

app.Run();
 

Listing 1: The Endpoints

The departments endpoint contains the code for reading all files from the Files folder. The endpoint returns all file names to the HTTP response. The departments endpoint accesses the filename in the URL parameter. The code in this endpoint verifies if the file name received from the URL parameter in the Files folder and if the file is found then it is copied into the memory stream and this stream is responded into the HTTP response.

Run the application and test these endpoints in the swagger UI as shown in Figure 2



Figure 2: The Swagger UI

 If you carefully see the output the Download File link is shown for the URL parameter as File.pdf. So we have the ASP.NET Core API ready. 

Let's create an Angular application names ngDocViewApp. In this project add bootstrap package.  In the app folder, add a folder named Services. In this folder add a nee Angular Service named app.filehttp.service.ts. In this file, we add code for AppFilehttpService class. In this class we will add methods to make HTTP class to endpoints exposed from ASP.NET Core API. The Angular Service code is shown in Listing 2.


import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AppFilehttpService {
  private url:string;
  constructor(private http:HttpClient) {
    this.url = 'https://localhost:7250';
  }

  getFiles():Observable<string[]> {
     let filesResponse = this.http.get<string[]>(`${this.url}/documents`);
      return filesResponse;
  }
  getDocument(file:string) {
    alert(`In Service: ${file}`);
    return this.http.get(`${this.url}/document/${file}`, { responseType: 'blob' });
  }
}

Listing 2: The AppFilehttpService Service class

The getDocument() method makes call to document endpoint and sends the FileName as a URL parameter. The response is received as blob. The getFiles() method received all file named by making HTTP call to the documents endpoint of the ASP.NET Core API.

Let's modify the code for AppComponent from app.component.ts file to access the AppFilehttpService class methods. The code for AppComponent is shown in Listing 3. 

import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AppFilehttpService } from './services/app.filehttp.service';
import { NgForOf } from '@angular/common';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, NgForOf],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent implements OnInit {
   files: string[] | undefined;
   selectedFile: string;;

   pdfUrl: SafeResourceUrl ;

   constructor(private serv: AppFilehttpService, private sanitizer: DomSanitizer){
    this.selectedFile = '';
    this.pdfUrl =  this.sanitizer.bypassSecurityTrustResourceUrl('');
   }

   ngOnInit(): void {
      this.serv.getFiles().subscribe(files => {
        this.files = files;
      });
   }

    download(event: Event) {
      const selectElement = event.target as HTMLSelectElement;
      this.selectedFile = selectElement.value;
      this.serv.getDocument(this.selectedFile).subscribe(blob => {
        const url = window.URL.createObjectURL(blob);
        this.pdfUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
      });
    }
}


Listing 3: The AppComponent

In this component we have used DomSanitizer class and SafeResourceUrl interface. DomSanitizer is a class in Angular that helps prevent Cross-Site Scripting (XSS) attacks by sanitizing values to be safe for use in different DOM contexts. It provides methods to sanitize or bypass security for HTML, styles, scripts, URLs, and resource URLs. E.g. when binding a URL in an <iframe [src]="somevalue"> hyperlink, someValue will be sanitized that prevent an attacker from injecting a malicious javascript: URL. In rare cases when we want to disable sanitization, we can use methods like bypassSecurityTrustHtml , but this should be done with extreme caution to avoid security risks. 

The AppComponent has the download() method that will receive the file as a blob as from it the URL is created. This is assigned to the pdfUrl property. This property is bound with the iframe HTML element. The AppComponent has the ngOnInit() method that access the getFiles() method from the AppFilehttpService class to receive all file names.   Modify the app.component.html to contain the HTML select element that shows all file names and iframe element to show received PDF document. The code of the app.component.html is shown in Listing 4.


<h1>The Document Viewer</h1>

<div class="alert alert-warning">
  <h4>Select the File That you want to open</h4>
</div>

<div class="container">
  <label for="selectFile">Select File To View:</label>
   <select name="selectFile" id="selectFile" (change)="download($event)">
      <option value="">Select File</option>
      <option *ngFor="let file of files" value="{{file}}">{{file}}</option>
   </select>
</div>
<br/>
<div class="alert alert-warning">
  <iframe [src]="pdfUrl" width="100%" height="600px" title="Document Viewer"></iframe>
</div>


Listing 4: The app.component.html file

Modify the app.config.ts file to register provideHttpClient() that will resolve the HttpClient class that is injected in AppFilehttpService class. The code of app.config.ts is shown in Listing 5


import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes),
 provideHttpClient(), provideClientHydration()]
};

Listing 5: The app.config.ts code

Run the ASP.NET Core API and Angular application. The Angular UI is shown in browser as shown in Figure 3.



Figure 3: The UI in Browser

 Select the file from the Dropdown, this will show the file opened in the iFrame as shown in Figure 4



Figure 4: The File in Browser  

The file is opened in the browser. 

Code for this article can be downloaded for Angular and ASP.NET Core links.

Conclusion: Using the Blob response type in the stream from the ASP.NET Core API, we can directly stream in the HTTP Response. In the Angular client application, we can read the stream as BLOB and that can be directly bind with HTML.

 


      

              

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach