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

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

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

# from backend/src/FileViewer.Api
dotnet ef migrations add InitialCreate --project ../FileViewer.Infrastructure --startup-project .
dotnet ef database update --project ../FileViewer.Infrastructure --startup-project .

2) Frontend — Angular app

Tech

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:

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:

UX suggestions:

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