Home > Software engineering >  How to get images in Angular from an ASP.NET Web API?
How to get images in Angular from an ASP.NET Web API?

Time:06-27

I'm a beginner programmer and I've been spending the entire week to understand how to get images from an ASP.NET Web API and display them in the browser using an Angular project, and didn't understand how to do it at all.

  • ASP.NET Framework Web API 4.7
  • Angular CLI: 13.3.7
  • Angular: 13.3.11

Web API side:

Model class:

public class MotorTestImgDTO
{
    public byte[] Image { get; set; }
}

Controller:

[EnableCors(origins: "*", headers: "*", methods: "*")]
public class MotorTestImgController : ApiController
{
    private NavEcommerceDBfirstEntities db = new NavEcommerceDBfirstEntities();
    
    //Have been trying to convert the byte array to base64String but Swagger showed 
    //error massage. So I removed it and tried to get the images as byte[] in the 
    //Angular side and then tried to use Blob but too complicated for me.
    private Base64StrConverter base64Str = new Base64StrConverter();

    public IQueryable<MotorTestImgDTO> Get()
    {
        var allMotorImgs = db.Motorcycles
            .Select(m => new MotorTestImgDTO
            {
                Image = m.Image
            });

        return allMotorImgs;
    }
}

Angular side:

Model interface:

export interface MotorTestImage {
    image: Byte[];
}

Service:

@Injectable({
  providedIn: 'root'
})
export class MotorTestImgService {

imageUrl = 'https://localhost:*****/api/MotorTestImg';
  constructor(private http: HttpClient) { }

  getAllMotorsTestImg(): Observable<MotorTestImage[]> {
    return this.http.get<MotorTestImage[]>(this.imageUrl);
  }
}

app.component.ts:

import { Component, OnInit } from '@angular/core';
import { HomePage } from './Model/home-Page.model';
import {MotorTestImage} from './Model/motor-test-img.model';
import {MotorTestImgService} from './Service/motor-test-img.service';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent  implements OnInit {
  title = 'AngularUI';
motorTestImg: MotorTestImage[] = [];

  constructor(private motorTestImgService: MotorTestImgService) {}

  ngOnInit(): void{
  this.allMotorsImgsTest();
  }

  allMotorsImgsTest(){
    this.motorTestImgService.getAllMotorsTestImg().subscribe(
      response => {
        this.motorTestImg = response;
        console.log(response);
      }
    ) 
  }
  
}

app.component.html:

<h4>{{title}}</h4>

<div *ngFor="let item of motorTestImg">
    <img src="{{item.image}}" />
</div>

Outputs

Swagger:

Swagger Get Response

Angular output to browser:

Angular Output To The Browser

Devtools console:

DevTools Console Pane

Appreciate for your help in advance.

CodePudding user response:

There are several options for sending images down from an API; send file content result, turn them into base64 strings, etc.

I would show examples, but it looks like your response is already giving you base64 strings as the response.

In this case, the only thing those strings are missing is the beginning part of the data URL.

If you just prefix your results, e.g. with data:image/jpeg;base64,..., it might just work.

One tip I'll give in general for Angular is to do any data transformation before using it. This is because Angular will attempt to evaluate things x times per second. You don't want it evaluating and prefixing your entire image library x times per second.

So, get your response and set up your strings:

this.motorTestImgService.getAllMotorsTestImg().subscribe(
    response => this.onImagesResponse(response)
)

...


public images: string[];
private onImagesResponse(response: MotorTestImage[]): void {
    this.images = [];
    response.forEach((img: MotorTestImage) => {
        this.images.push(`data:image/png;base64,${img.image}`);
    });
}

...

<div *ngFor="let image of images">
    <img [src]="image">
</div>

Something like that.

If that quick version doesn't work, then I'll dig out the actual various examples I have lying around somewhere that do the kind of thing you want.


Per request, a little extra detail on another method. Emphasis on little, there's some fairly irrelevant detail missing (e.g. some props that can be ignored).

Example controller action for sending a file:

[HttpGet]
public async Task<ActionResult<FileDownloadResult>> FileDownload([FromRoute] FileDownloadRequest request)
{
    // Get your file - key part is getting your file as a Stream
    var result = await _fileService.GetFile(request);

    // Replace the octet-stream with whatever type you desire, we decided on the basic, generic form because... well, it all is, isn't it?
    return File(result.FileStream, "application/octet-stream", result.FileName);
}

Where in the above our basic FileDownloadResult type there is just:

public class FileDownloadResult : ResponseResult
{
    public string FileName { get; set; }
    public Stream FileStream { get; set; }
}

But you can obviously do whatever you want - you just need your Stream and a file name and you're good to go.

Images are simple to turn into a stream:

// create some image from your source
var image = new Image();
var ms = new MemoryStream();
image.Save(ms, ImageFormat.Xyz);
ms.Position = 0;
return ms;

Angular side, we have a wrapper for the basic HttpClient provided, but it's set up to emit progress as files come down and then flag once it's actually complete:

public getDownload(apiResource: string, queryParams?: any): Observable<DownloadResult> {
    this.logger.verbose('HttpService.getDownload', this.logSystems);

    let url = this.constructUrl(apiResource);

    const response = this.httpClient.request('GET', url, {
        params: this.encodeQueryParams(queryParams),
        observe: 'events',
        reportProgress: true,
        responseType: 'blob'
    }).pipe(
        map(event => this.getEventMessage(event)),
        catchError(err => {
            const errorResult: DownloadResult = {
                message: err?.message || 'Download failed',
                isComplete: true
            };

            return of(errorResult);
        })
    );

    return response;
}

private getEventMessage(event: HttpEvent<any>) : DownloadResult {
    switch (event.type) {
      case HttpEventType.DownloadProgress:
        // Compute and show the % done:
        const percentDone = Math.round(100 * event.loaded / (event.total ?? 1));
        return { message: 'Download in progress', loadedData: event.loaded, progress: event.total ? undefined : percentDone };
  
      case HttpEventType.Response:
        const contentDisposition = event.headers.get('Content-Disposition');
        const fileName = this.getFileName(contentDisposition);
        return  { progress: 100, isComplete:true, blob: event.body, fileName: fileName };
  
      default:
        return { message: `Event type ${event.type}` };
    }
} 

Where our DownloadResult looks something like this:

export interface DownloadResult extends ResponseResult {
    blob? : Blob;
    fileName?: string;
    isComplete?: boolean;
    progress?: number;
    loadedData?: number;
}

Finally, with usage:

public getDownload(token: string, reportProgress: boolean = false) {
    const sub = this.httpService.getDownload(`${this.downloadUrl}/${token}`, null)
    .subscribe({
        next: (response) => {
            if (response.isComplete)
            {
                sub.unsubscribe();    
                // response is also complete in the case of an error
                if (!response.errorMessage)
                    this.blobDownloaderService.downloadBlob(response.blob, response.fileName);
            }

            if (reportProgress || response.isComplete) {
                ServiceHelper.emit(this.downloadObs, response);
            }
        },
        error: (err) => {
            sub.unsubscribe();
            ServiceHelper.emit(this.downloadObs, err);
        }
    });
}

The blobDownloaderService does the ol' trick of creating an <a> tag and clicking it to start a 'download' of the file we just got.

For this purpose, instead of doing that, you'd probably want to turn your newly acquired blob data (response.blob) into an image, per this question.

const objectUrl = URL.createObjectUrl(response.blob);
this.image = this.sanitizer.bypassSecurityTrustUrl(objectUrl);
  • Related