← Back to Articles         
How would you upload an image to ASP.NET Core?

How would you upload an image to ASP.NET Core?

Published on 02 Oct 2026     4 min read Angular
Scenario Based Questions

These are common Angular + ASP.NET Core Web API scenario-based interview questions. Here are concise interview-ready answers.

1. How would you upload an image to ASP.NET Core?

In Angular, I would use FormData to send the image as multipart/form-data. ASP.NET Core receives it using IFormFile, validates the file, and saves it to storage such as local storage, Azure Blob Storage, or another file store.

uploadImage(file: File) {
  const formData = new FormData();
  formData.append('file', file);

  return this.http.post(
    `${environment.apiUrl}/api/images`,
    formData
  );
}

ASP.NET Core:

[HttpPost]
public async Task<IActionResult> Upload(IFormFile file)
{
    if (file == null || file.Length == 0)
        return BadRequest("File is required.");

    var fileName = Path.GetFileName(file.FileName);
    var path = Path.Combine("wwwroot/images", fileName);

    await using var stream = new FileStream(path, FileMode.Create);
    await file.CopyToAsync(stream);

    return Ok(new { fileName });
}

Key point: Don't manually set Content-Type in Angular when using FormData; the browser generates the multipart boundary.


2. How would you implement a loading spinner for API calls?

For an application-wide spinner, I would use an HTTP interceptor. The interceptor increments a loading counter when a request starts and decrements it when the request completes using finalize().

export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
  const loading = inject(LoadingService);

  loading.show();

  return next(req).pipe(
    finalize(() => loading.hide())
  );
};

Register it:

provideHttpClient(
  withInterceptors([loadingInterceptor])
)

A counter is preferable to a simple boolean because several HTTP requests can execute simultaneously.

Interview answer:

I use an HTTP interceptor for a global loading spinner so individual components don't need to manage loading state for every API call.


3. How would you handle an API that is temporarily unavailable?

I would handle temporary failures using RxJS retry logic, centralized error handling, and a user-friendly message. For transient failures such as 503 Service Unavailable, retrying with a delay or exponential backoff can be appropriate.

this.http.get<Product[]>('/api/products').pipe(
  retry({
    count: 3,
    delay: 2000
  }),
  catchError(error => {
    console.error(error);
    return throwError(() => error);
  })
);

For production systems, I would generally not blindly retry every error:

  • 503, 502, network errors → potentially retry
  • 400 → don't retry
  • 401 → authentication/refresh-token handling
  • 403 → don't retry
  • 404 → usually don't retry

The ASP.NET Core side can additionally use resilience mechanisms such as timeouts and circuit breakers when it calls downstream services.


4. How would you avoid duplicate API requests?

It depends on why the duplicates occur. For multiple Angular subscribers requesting the same data, I can share the HTTP result using shareReplay().

private products$?: Observable<Product[]>;

getProducts(): Observable<Product[]> {

  if (!this.products$) {
    this.products$ = this.http
      .get<Product[]>('/api/products')
      .pipe(
        shareReplay(1)
      );
  }

  return this.products$;
}

Now multiple subscribers can reuse the result instead of generating another HTTP request.

For search boxes, I would commonly use:

searchControl.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(search =>
    this.http.get<Product[]>(
      `/api/products?search=${search}`
    )
  )
);

Here:

debounceTime() → reduces excessive requests
distinctUntilChanged() → prevents repeated identical searches
switchMap() → cancels the previous request when a newer search arrives.

For duplicate POST/payment/order operations, frontend deduplication alone isn't sufficient; the backend should support idempotency.


5. How would you cache API data on the Angular side?

For simple in-memory caching, I can store the API result in a service using shareReplay().

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

  private products$?: Observable<Product[]>;

  constructor(private http: HttpClient) {}

  getProducts(): Observable<Product[]> {

    if (!this.products$) {
      this.products$ = this.http
        .get<Product[]>('/api/products')
        .pipe(
          shareReplay(1)
        );
    }

    return this.products$;
  }

  clearCache() {
    this.products$ = undefined;
  }
}

First call:

Component
   ↓
ProductService
   ↓
ASP.NET Core API
   ↓
shareReplay()
   ↓
Cache

Subsequent calls:

Component
   ↓
ProductService
   ↓
Cached result

For different requirements, I could use:

Requirement Approach
Simple API cache shareReplay()
Application state Signals / RxJS service
Complex global state NgRx
Persist across refresh sessionStorage / localStorage where appropriate
Structured offline/client database IndexedDB
Server-side caching ASP.NET Core IMemoryCache / distributed cache such as Redis

Important: Client-side caching should have an invalidation strategy. After creating, updating, or deleting a product, for example, clear or refresh the cached product data.

Quick interview summary

Image upload: FormData → ASP.NET Core IFormFile.
Loading spinner: HTTP interceptor + loading service + finalize().
Temporary API failure: selective retry/backoff + centralized error handling.
Duplicate requests: shareReplay(), debounceTime(), distinctUntilChanged(), and switchMap() depending on the scenario.
Angular caching: service-level cache using shareReplay() or Signals/RxJS state, with proper cache invalidation.

← Back to Articles