Building a Scalable, Real-World Full-Stack Application
Image recognition has become an essential feature in many modern applications. From security systems and document scanning to e-commerce and social media platforms, the ability to detect and classify images in real-time adds tremendous value. Combining Angular on the frontend with an ASP.NET Core backend is a powerful approach for building a scalable image recognition application.
In this article, we will discuss a practical approach to integrating image recognition into an Angular application using an ASP.NET Core backend. We will cover architectural considerations, implementation strategies, performance optimizations, and production-ready best practices.
1. Architecture Overview
A high-level architecture of the system looks like this:
Angular Frontend
|
| Upload Image / Fetch Results
v
ASP.NET Core API
|
| Calls Image Recognition Model / Service
v
Machine Learning Service
|
| Returns Prediction
v
ASP.NET Core API
|
v
Angular Frontend
1.1 Frontend Responsibilities
Handle user image uploads.
Display processed results.
Show loading state and progress.
Perform client-side validation (file type, size, resolution).
1.2 Backend Responsibilities
Accept image uploads.
Validate image integrity.
Interface with the image recognition model or service (e.g., TensorFlow, ML.NET, OpenCV).
Return predictions as structured JSON.
Optionally, store images and results in database or cloud storage.
1.3 Machine Learning / Recognition Layer
Can be ML.NET for .NET-centric pipelines.
TensorFlow or PyTorch REST API is also a viable option.
Ensure inference is scalable and does not block the API thread.
2. Angular Frontend Implementation
2.1 Creating the Upload Component
We create a component to allow the user to select an image and upload it.
// image-upload.component.ts
import { Component } from '@angular/core';
import { ImageService } from '../services/image.service';
@Component({
selector: 'app-image-upload',
templateUrl: './image-upload.component.html',
styleUrls: ['./image-upload.component.css']
})
export class ImageUploadComponent {
selectedFile!: File;
loading = false;
prediction: any;
constructor(private imageService: ImageService) {}
onFileSelected(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
this.selectedFile = input.files[0];
}
}
uploadImage() {
if (!this.selectedFile) return;
this.loading = true;
this.imageService.uploadImage(this.selectedFile).subscribe({
next: (result) => {
this.prediction = result;
this.loading = false;
},
error: (err) => {
console.error(err);
this.loading = false;
}
});
}
}
2.2 HTML Template
<div class="upload-container">
<input type="file" (change)="onFileSelected($event)" accept="image/*" />
<button (click)="uploadImage()" [disabled]="!selectedFile || loading">
Upload
</button>
<div *ngIf="loading">Processing...</div>
<div *ngIf="prediction">
<h3>Prediction Result</h3>
<p>{{ prediction.label }} - Confidence: {{ prediction.confidence | percent }}</p>
</div>
</div>
2.3 Image Service
// image.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ImageService {
private apiUrl = 'https://localhost:5001/api/images';
constructor(private http: HttpClient) {}
uploadImage(file: File): Observable<any> {
const formData = new FormData();
formData.append('file', file, file.name);
return this.http.post<any>(`${this.apiUrl}/recognize`, formData);
}
}
2.4 Angular Best Practices
Validation: Restrict allowed MIME types and file sizes on client-side before upload.
Loading States: Use a spinner or progress bar.
Error Handling: Provide user-friendly error messages.
OnPush Change Detection: Use OnPush for performance when handling streaming or large results.
Async Pipe: Avoid manual subscriptions if possible; for this example, manual subscription is fine for upload-and-receive patterns.
3. ASP.NET Core Backend Implementation
3.1 Creating a Controller
// Controllers/ImagesController.cs
using Microsoft.AspNetCore.Mvc;
using ImageRecognitionAPI.Services;
namespace ImageRecognitionAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ImagesController : ControllerBase
{
private readonly IImageRecognitionService _recognitionService;
public ImagesController(IImageRecognitionService recognitionService)
{
_recognitionService = recognitionService;
}
[HttpPost("recognize")]
public async Task<IActionResult> Recognize(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("No file uploaded.");
try
{
var result = await _recognitionService.RecognizeAsync(file);
return Ok(result);
}
catch (Exception ex)
{
// Log exception
return StatusCode(500, $"Internal server error: {ex.Message}");
}
}
}
}

Join the conversation! Your thoughts help the community grow.