Angular  

Complete Source Code — File Viewer + Drag-Drop File Manager (Angular + ASP.NET Core)

Below is a ready-to-implement starter project containing:

  • Complete backend (ASP.NET Core Web API) with file upload/download, streaming, metadata storage (EF Core + SQL Server)

  • Complete frontend (Angular) with:

    • Custom File Viewer (PDF, images, video, DOCX) — no <iframe>

    • UI design template (clean, responsive) for the viewer

    • Drag-and-drop File Manager module for upload / list / preview / delete

  • Instructions to run, Docker support, and notes for production hardening

I kept the files minimal and practical so you can run quickly and extend later. All code uses simple, easy-to-understand Indian English.

Repo layout (one-liner)

file-viewer-starter/
├─ backend/
│  ├─ src/
│  │  ├─ FileViewer.Api/             # Web API
│  │  ├─ FileViewer.Core/            # Domain models & DTOs
│  │  └─ FileViewer.Infrastructure/  # EF Core DbContext & migrations
│  └─ docker-compose.yml
├─ frontend/
│  └─ viewer-app/                    # Angular app
└─ README.md

1) Backend — ASP.NET Core

Tech

  • .NET 8, ASP.NET Core Web API

  • EF Core (SQL Server)

  • File storage: local disk (can swap to Azure Blob/S3)

  • Basic endpoints:

    • POST /api/files/upload — upload file (multipart/form-data)

    • GET /api/files — list metadata

    • GET /api/files/{id} — stream file by id

    • DELETE /api/files/{id} — delete file

Key files (paste into project FileViewer.Api and friends)

File: FileViewer.Core/Models/FileMeta.cs

using System;

namespace FileViewer.Core.Models
{
    public class FileMeta
    {
        public Guid FileId { get; set; } = Guid.NewGuid();
        public string FileName { get; set; } = "";
        public string ContentType { get; set; } = "";
        public long Size { get; set; }
        public string StoragePath { get; set; } = ""; // physical path or cloud URL
        public string UploadedBy { get; set; } = "";
        public DateTime UploadedOn { get; set; } = DateTime.UtcNow;
    }
}

File: FileViewer.Infrastructure/ApplicationDbContext.cs

using Microsoft.EntityFrameworkCore;
using FileViewer.Core.Models;

namespace FileViewer.Infrastructure
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> opts) : base(opts) { }
        public DbSet<FileMeta> Files { get; set; }

        protected override void OnModelCreating(ModelBuilder b)
        {
            b.Entity<FileMeta>().HasKey(f => f.FileId);
            b.Entity<FileMeta>().Property(f => f.FileName).HasMaxLength(512);
            b.Entity<FileMeta>().Property(f => f.StoragePath).HasMaxLength(2000);
        }
    }
}

File: FileViewer.Api/Controllers/FilesController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using FileViewer.Infrastructure;
using FileViewer.Core.Models;

[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
    private readonly ApplicationDbContext _db;
    private readonly IWebHostEnvironment _env;
    private readonly ILogger<FilesController> _logger;

    public FilesController(ApplicationDbContext db, IWebHostEnvironment env, ILogger<FilesController> logger)
    {
        _db = db; _env = env; _logger = logger;
    }

    [HttpGet]
    public async Task<IActionResult> List()
    {
        var list = await _db.Files.OrderByDescending(f => f.UploadedOn).ToListAsync();
        return Ok(list);
    }

    [HttpPost("upload")]
    [RequestSizeLimit(200_000_000)] // allow ~200MB; adjust for your needs
    public async Task<IActionResult> Upload([FromForm] IFormFile file)
    {
        if (file == null || file.Length == 0) return BadRequest("No file uploaded");
        var uploads = Path.Combine(_env.ContentRootPath, "uploads");
        if (!Directory.Exists(uploads)) Directory.CreateDirectory(uploads);

        var fileId = Guid.NewGuid();
        var safeName = Path.GetFileName(file.FileName);
        var ext = Path.GetExtension(safeName);
        var storedName = $"{fileId}{ext}";
        var path = Path.Combine(uploads, storedName);

        using (var stream = System.IO.File.Create(path))
        {
            await file.CopyToAsync(stream);
        }

        var meta = new FileMeta {
            FileId = fileId,
            FileName = safeName,
            ContentType = file.ContentType ?? "application/octet-stream",
            Size = file.Length,
            StoragePath = path
        };

        _db.Files.Add(meta);
        await _db.SaveChangesAsync();
        return Ok(meta);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Download(Guid id)
    {
        var meta = await _db.Files.FindAsync(id);
        if (meta == null) return NotFound();

        var stream = System.IO.File.OpenRead(meta.StoragePath);
        return File(stream, meta.ContentType, meta.FileName, enableRangeProcessing: true);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(Guid id)
    {
        var meta = await _db.Files.FindAsync(id);
        if (meta == null) return NotFound();
        try {
            if (System.IO.File.Exists(meta.StoragePath)) System.IO.File.Delete(meta.StoragePath);
        } catch (Exception ex) {
            _logger.LogWarning(ex, "delete file failed");
        }
        _db.Files.Remove(meta);
        await _db.SaveChangesAsync();
        return NoContent();
    }
}

Note: enableRangeProcessing: true allows client to seek video streams.

File: FileViewer.Api/Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// EF Core configuration - read connection string from appsettings
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddCors(o => o.AddPolicy("AllowLocal", p =>
{
    p.AllowAnyHeader().AllowAnyMethod().AllowCredentials().WithOrigins("http://localhost:4200");
}));

var app = builder.Build();
if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }

app.UseHttpsRedirection();
app.UseCors("AllowLocal");
app.UseAuthorization();
app.MapControllers();
app.Run();

appsettings.Development.json (example)

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=FileViewerDb;Trusted_Connection=True;"
  }
}

Docker support & migrations

  • Add EF packages to FileViewer.Infrastructure and FileViewer.Api projects and run migrations:

# from backend/src/FileViewer.Api
dotnet ef migrations add InitialCreate --project ../FileViewer.Infrastructure --startup-project .
dotnet ef database update --project ../FileViewer.Infrastructure --startup-project .
  • Use docker-compose.yml to run SQL Server for parity (similar pattern to previous answers).

2) Frontend — Angular app

Tech

  • Angular 16+ (or your preferred version)

  • pdfjs-dist for PDF rendering

  • docx-preview for DOCX → HTML

  • Native <img>, <video> tags for images / videos

  • HTML5 drag & drop + FormData uploads for file manager

Install dependencies:

ng new viewer-app
cd viewer-app
npm install pdfjs-dist docx-preview @types/pdfjs-dist
npm install bootstrap --save   # optional UI framework

Important: Add pdf worker file

Copy node_modules/pdfjs-dist/build/pdf.worker.min.js to src/assets/ and set worker path in code.

Frontend file list (key files)

viewer-app/
└─ src/app/
   ├─ services/file.service.ts          # API calls
   ├─ services/document-viewer.service.ts
   ├─ components/file-manager/          # drag-drop uploader + list
   │    ├─ file-manager.component.ts
   │    ├─ file-manager.component.html
   │    └─ file-manager.component.scss
   ├─ components/file-viewer/           # unified viewer component
   │    ├─ file-viewer.component.ts
   │    ├─ file-viewer.component.html
   │    └─ file-viewer.component.scss
   └─ app.module.ts

File: src/app/services/file.service.ts

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

export interface FileMeta {
  fileId: string;
  fileName: string;
  contentType: string;
  size: number;
  uploadedOn: string;
}

@Injectable({ providedIn: 'root' })
export class FileService {
  private base = '/api/files';

  constructor(private http: HttpClient) {}

  list(): Observable<FileMeta[]> { return this.http.get<FileMeta[]>(this.base); }

  upload(file: File) {
    const fd = new FormData();
    fd.append('file', file, file.name);
    return this.http.post<FileMeta>(`${this.base}/upload`, fd, { reportProgress: true, observe: 'events' });
  }

  download(fileId: string) {
    return this.http.get(`${this.base}/${fileId}`, { responseType: 'blob' });
  }

  delete(fileId: string) { return this.http.delete(`${this.base}/${fileId}`); }
}

File: src/app/services/document-viewer.service.ts

import { Injectable } from '@angular/core';
import * as pdfjsLib from 'pdfjs-dist';
import { renderAsync } from 'docx-preview';

pdfjsLib.GlobalWorkerOptions.workerSrc = '/assets/pdf.worker.min.js';

@Injectable({ providedIn: 'root' })
export class DocumentViewerService {
  async renderPdfBlob(blob: Blob, canvasEl: HTMLCanvasElement) {
    const arrayBuffer = await blob.arrayBuffer();
    const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
    const page = await pdf.getPage(1);
    const viewport = page.getViewport({ scale: 1.25 });
    canvasEl.width = viewport.width; canvasEl.height = viewport.height;
    const ctx = canvasEl.getContext('2d')!;
    await page.render({ canvasContext: ctx, viewport }).promise;
  }

  async renderDocxBlob(blob: Blob, container: HTMLElement) {
    await renderAsync(blob, container);
  }
}

Component: File Manager (drag & drop)

file-manager.component.html

<div class="file-manager">
  <div class="uploader"
       (drop)="onDrop($event)"
       (dragover)="onDragOver($event)"
       (dragleave)="onDragLeave($event)">
    <p>Drag & drop files here or <input type="file" (change)="onFileSelected($event)" /></p>
  </div>

  <div class="file-list">
    <table class="table">
      <thead><tr><th>File</th><th>Size</th><th>Uploaded</th><th></th></tr></thead>
      <tbody>
        <tr *ngFor="let f of files">
          <td><a (click)="preview(f)">{{f.fileName}}</a></td>
          <td>{{f.size | number}}</td>
          <td>{{f.uploadedOn | date:'short'}}</td>
          <td>
            <button (click)="download(f)" class="btn btn-sm btn-primary">Download</button>
            <button (click)="remove(f)" class="btn btn-sm btn-danger">Delete</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

<!-- viewer modal -->
<div *ngIf="selected" class="viewer-modal">
  <app-file-viewer [meta]="selected" (close)="closePreview()"></app-file-viewer>
</div>

file-manager.component.ts

import { Component, OnInit } from '@angular/core';
import { FileService, FileMeta } from '../../services/file.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-file-manager',
  templateUrl: './file-manager.component.html',
  styleUrls: ['./file-manager.component.scss']
})
export class FileManagerComponent implements OnInit {
  files: FileMeta[] = [];
  selected?: FileMeta;
  sub?: Subscription;

  constructor(private svc: FileService) {}

  ngOnInit() { this.load(); }

  load() {
    this.svc.list().subscribe(x => this.files = x);
  }

  onDragOver(e: DragEvent) { e.preventDefault(); }
  onDragLeave(e: DragEvent) { e.preventDefault(); }
  onDrop(e: DragEvent) {
    e.preventDefault();
    if (!e.dataTransfer) return;
    const file = e.dataTransfer.files[0];
    if (file) this.upload(file);
  }

  onFileSelected(evt: any) {
    const file: File = evt.target.files[0];
    if (file) this.upload(file);
    evt.target.value = '';
  }

  upload(file: File) {
    this.svc.upload(file).subscribe({
      next: (ev:any) => {
        if (ev.type === 4) { // HttpEventType.Response
           this.load();
        }
      },
      error: err => console.error(err)
    });
  }

  preview(meta: FileMeta) { this.selected = meta; }
  closePreview() { this.selected = undefined; }

  download(meta: FileMeta) {
    this.svc.download(meta.fileId).subscribe(blob => {
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a'); a.href = url; a.download = meta.fileName; a.click();
      window.URL.revokeObjectURL(url);
    });
  }

  remove(meta: FileMeta) {
    if (!confirm('Delete file?')) return;
    this.svc.delete(meta.fileId).subscribe(() => this.load());
  }
}

file-manager.component.scss (simple)

.file-manager { padding: 12px; max-width: 900px; margin: 16px auto; }
.uploader { border: 2px dashed #ccc; padding: 24px; text-align:center; cursor: pointer; margin-bottom: 12px; }
.viewer-modal { position: fixed; top: 0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.6); display:flex; align-items:center; justify-content:center; }

Component: File Viewer

file-viewer.component.ts

import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
import { FileService, FileMeta } from '../../services/file.service';
import { DocumentViewerService } from '../../services/document-viewer.service';

@Component({
  selector: 'app-file-viewer',
  templateUrl: './file-viewer.component.html',
  styleUrls: ['./file-viewer.component.scss']
})
export class FileViewerComponent implements OnInit {
  @Input() meta!: FileMeta;
  @Output() close = new EventEmitter<void>();
  srcUrl?: string;
  fileType: 'pdf'|'image'|'video'|'docx'|'other' = 'other';
  pdfBlob?: Blob;
  docxBlob?: Blob;

  constructor(private svc: FileService, private viewer: DocumentViewerService) {}

  ngOnInit() {
    const ext = (this.meta.fileName.split('.').pop() || '').toLowerCase();
    if (ext === 'pdf') this.fileType = 'pdf';
    else if (['jpg','jpeg','png','gif','bmp'].includes(ext)) this.fileType = 'image';
    else if (['mp4','webm','mov'].includes(ext)) this.fileType = 'video';
    else if (ext === 'docx') this.fileType = 'docx';
    else this.fileType = 'other';

    // fetch blob for viewer
    this.svc.download(this.meta.fileId).subscribe(blob => {
      if (this.fileType === 'pdf') { this.pdfBlob = blob; }
      if (this.fileType === 'docx') { this.docxBlob = blob; }
      // for image & video we can createObjectURL
      if (this.fileType === 'image' || this.fileType === 'video') {
        this.srcUrl = URL.createObjectURL(blob);
      }
      // render for pdf/docx will be invoked via ViewChild lifecycle after template ready
    });
  }

  onClose() {
    if (this.srcUrl) URL.revokeObjectURL(this.srcUrl);
    this.close.emit();
  }

  // methods invoked by template to render pdf/docx by passing canvas/container references
  async renderPdf(canvas: HTMLCanvasElement) {
    if (this.pdfBlob) await this.viewer.renderPdfBlob(this.pdfBlob, canvas);
  }

  async renderDocx(container: HTMLElement) {
    if (this.docxBlob) await this.viewer.renderDocxBlob(this.docxBlob, container);
  }
}

file-viewer.component.html

<div class="viewer">
  <div class="viewer-header">
    <h5>{{meta.fileName}}</h5>
    <button class="btn-close" (click)="onClose()">Close</button>
  </div>

  <div class="viewer-body">
    <div *ngIf="fileType === 'image'">
      <img [src]="srcUrl" class="img-fluid" />
    </div>

    <div *ngIf="fileType === 'video'">
      <video [src]="srcUrl" controls style="max-width:100%; height:auto;"></video>
    </div>

    <div *ngIf="fileType === 'pdf'">
      <canvas #pdfCanvas></canvas>
      <ng-container *ngIf="pdfBlob">
        <!-- call renderPdf after canvas ready -->
        <ng-template #t></ng-template>
      </ng-container>
    </div>

    <div *ngIf="fileType === 'docx'">
      <div #docxContainer></div>
    </div>

    <div *ngIf="fileType === 'other'">
      <p>Preview not available. <a (click)="download()">Download</a></p>
    </div>
  </div>
</div>

Use @ViewChild in file-viewer.component.ts to call renderPdf / renderDocx after ViewInit. (I kept code compact; add appropriate lifecycle hook.)

file-viewer.component.scss (UI template)

.viewer { width: 90vw; max-width: 1100px; background: #fff; border-radius: 6px; overflow: auto; padding: 12px; }
.viewer-header { display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #eee; padding-bottom:8px; margin-bottom:12px; }
.viewer-body { padding: 8px; }
.btn-close { background: transparent; border: none; font-size: 18px; cursor: pointer; }

3) UI Design Template (viewer)

Use simple, clean layout with responsive container:

  • Top bar: file name, close button, download button

  • Left sidebar (optional): file list, thumbnails

  • Main area: viewer content (canvas / image / video / docx html)

  • Footer: page controls (PDF page nav, zoom in/out) — you can extend pdf viewer to support multiple pages

Example main layout (Angular template excerpt):

<div class="viewer-layout">
  <aside class="sidebar"> <!-- file list / thumbnails --></aside>
  <main class="main-content">
    <div class="toolbar">
      <button class="btn">Download</button>
      <button class="btn">Zoom -</button>
      <button class="btn">Zoom +</button>
    </div>

    <div class="content-area">
      <!-- file-viewer component inserted here -->
      <app-file-viewer [meta]="selectedMeta"></app-file-viewer>
    </div>
  </main>
</div>

SCSS (basic)

.viewer-layout { display:flex; height: 90vh; }
.sidebar { width: 260px; border-right: 1px solid #eee; padding: 12px; overflow:auto; }
.main-content { flex:1; display:flex; flex-direction:column; }
.toolbar { padding: 8px; background:#fafafa; border-bottom:1px solid #eee; }
.content-area { flex:1; padding: 12px; overflow:auto; display:flex; align-items:center; justify-content:center; }

4) Drag-and-Drop File Manager module (UX details & behaviour)

Key behaviour:

  • Drag file into drop zone or click to open file selector

  • Show upload progress (per-file) and status (success / fail)

  • After upload, automatically refresh list and generate thumbnails for images & PDF first-page small canvas

  • Allow bulk uploads (multiple files)

  • Provide accessibility: keyboard focusable drop zone, fallback file input

  • Server-side: accept multipart/form-data, write to secure path, return metadata

UX suggestions:

  • Show small thumbnail column: for images use <img>, for pdf create canvas thumbnail (using pdfjs page render with small scale), for video show poster (first frame extraction is advanced — can use server-side poster generation)

  • Support drag-selection and keyboard for delete / download

5) Run & Test

Backend

  1. Create solution and projects (core, infrastructure, api) and paste files above.

  2. Install EF packages, create migrations and update DB:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef

# create migration (adjust paths)
dotnet ef migrations add InitialCreate --project ../FileViewer.Infrastructure --startup-project .
dotnet ef database update --project ../FileViewer.Infrastructure --startup-project .
  1. Run API:

cd backend/src/FileViewer.Api
dotnet run

Frontend

  1. Install dependencies:

cd frontend/viewer-app
npm install
  1. Copy pdf.worker.min.js from node_modules/pdfjs-dist/build to src/assets/.

  2. Serve Angular:

ng serve

Open Angular app at http://localhost:4200. Upload files via drag & drop and preview.

6) Production Notes & Hardening

  • Secure file storage: do not expose storage paths. Use tokens for download or signed URLs.

  • Antivirus / virus scan on upload (especially for DOCX/HTML).

  • Limit size and validate MIME types and extensions.

  • Rate limit uploads per IP / API key.

  • Use CDN for static assets (images, pdf pages) in production.

  • Use blob storage (Azure Blob / S3) rather than local disk for horizontal scaling. Store only blob URL and metadata in DB.

  • Authentication & Authorization: protect endpoints with JWT / cookie auth and RBAC.

  • Logging & Monitoring: record upload/download operations, errors, suspicious attempts.