VSATutorialSeptember 2026 · 9 min read

VSA Todo App — Part 14: Vue.js & DataTables.js — Reactive Frontends for VSA APIs

TL;DR

Part 14 builds the reactive MVC frontend for VSA APIs using Vue.js and DataTables.js, following the MVC Project Manager's Todo feature. We cover mounting Vue 3 apps per page, server-side DataTables integration, modal dialogs for item CRUD, drag-and-drop file upload with base64 encoding, the image gallery with prev/next navigation, and print-to-PDF support.

Part 14 of 20 in the VSA Todo App Tutorial Series|← Previous: Part 13|Next: Part 15 →

Help Us Grow

Love this tutorial? Explore our ready-to-use enterprise starter kits built with VSA in .NET 10.

The MVC Project Manager proves that ASP.NET Core MVC doesn't have to feel dated. Each Razor view pairs with a small .cshtml.js file that mounts a Vue 3 application, turning server-rendered pages into reactive UIs that talk to VSA API endpoints. This part shows how to build that frontend: mounting Vue apps, integrating DataTables.js for server-side grids, handling drag-and-drop file uploads, building an image gallery, and supporting print-to-PDF.

The architecture is simple: the Razor view provides the HTML skeleton and initial data, the .cshtml.js file enhances it with Vue reactivity, and the VSA endpoints handle all data operations. Pages work even without JavaScript (progressive enhancement), but become fully interactive when Vue is present. This is a key advantage of the MVC approach to VSA frontends.

Mounting a Vue 3 App per Page

Each page mounts its own Vue app with a mount point in the Razor view. The create form's Vue app manages the entire form state — fields, validation, items, attachments, and submission:

// Create.cshtml.js
const app = createApp({
    data() {
        return {
            form: { name: '', description: '', priority: 'Medium',
                    category: 'Personal', progress: 0, tags: '',
                    dueDate: null, dueTime: null, ownerUserId: null },
            items: [], imageAttachments: [], fileAttachments: [],
            submitting: false
        }
    },
    methods: {
        async submitForm() {
            this.submitting = true;
            const response = await fetch('/api/todo', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(this.form)
            });
            if (response.ok) { window.location.href = '/Main/Todo/Index'; }
        }
    }
});
app.mount('#app-create');

The form object is the Vue data model. It maps exactly to the CreateTodoRequest DTO on the backend — the JSON body sent to POST /api/todo deserializes directly into the request. This symmetry between the Vue data model and the VSA request DTO is what makes the frontend predictable.

Server-Side DataTables Integration

The index page uses DataTables.js with server-side processing. The table sends pagination, search, and sort parameters to the VSA endpoint and renders the JSON response:

$('#todoTable').DataTable({
    serverSide: true,
    processing: true,
    ajax: { url: '/api/todo', dataSrc: 'data.data' },
    columns: [
        { data: 'autoNumber', title: 'No' },
        { data: 'name', title: 'Name' },
        { data: 'priority', title: 'Priority', render: priorityBadge },
        { data: 'progress', title: 'Progress', render: progressBar },
        { data: 'status', title: 'Status', render: statusBadge },
        { data: 'itemsCount', title: 'Items' }
    ],
    order: [[1, 'asc']]
});

Custom render functions transform raw values into visual components: priority becomes a color-coded badge, progress becomes a bootstrap progress bar, and status becomes a completed/pending chip. The DataTable calls the GetTodoListHandler we built in Part 8 — search, sort, and pagination all happen server-side in the VSA handler.

Drag-and-Drop File Upload with Base64

The MVC frontend handles file uploads entirely in the browser. When the user drops a file, Vue.js reads it as a base64 string, validates the type and size, and adds it to the reactive attachments array:

async handleFileDrop(event, type) {
    const files = event.dataTransfer.files;
    for (const file of files) {
        if (type === 'image' && !file.name.endsWith('.png')) {
            this.uploadError = 'Only PNG images are allowed';
            return;
        }
        if (file.size > 5 * 1024 * 1024) {
            this.uploadError = 'File must be under 5MB';
            return;
        }
        const base64 = await this.readFileAsBase64(file);
        this.imageAttachments.push({
            id: null, fileName: file.name, data: base64
        });
    }
}

The base64 string is included in the JSON payload, matching the CreateTodoImageAttachmentRequest DTO with its Data field (Part 7). The backend's FileStorageService decodes and saves the file. A thumbnail preview renders immediately using a data URL, giving instant visual feedback.

Image Gallery with Prev/Next Navigation

The detail page's image gallery uses a Bootstrap modal with prev/next navigation. Clicking a thumbnail opens the modal; the arrows cycle through the attachments. The gallery is powered by Vue's reactive index state:

<div class="modal fade" id="imageGallery">
  <img :src="galleryImages[galleryIndex].dataUrl" />
  <button @click="galleryIndex = (galleryIndex - 1 + galleryImages.length)
      % galleryImages.length">Prev</button>
  <button @click="galleryIndex = (galleryIndex + 1) % galleryImages.length">Next</button>
</div>

The modulo arithmetic wraps the index around, creating infinite prev/next navigation. This is a small but polished UX detail that makes the MVC frontend feel as refined as a dedicated SPA.

Print-to-PDF Support

The detail page supports print-to-PDF via the browser's native print functionality, enhanced with a clean print stylesheet. The window.print() call produces a PDF-friendly layout — the audit trail card, attachments, and items table all print cleanly. This is a zero-dependency approach that works everywhere: no print library, no server-side PDF generation, no license costs.

Form Enhancements: Tom Select and Flatpickr

The create and edit forms use Tom Select for searchable dropdowns (priority, owner, assigned-to) and flatpickr for date/time pickers. The Edit page's Vue app loads existing data from the VSA endpoints, re-initializes Tom Select and flatpickr after data load (with proper destroy/recreate to avoid stale state), and submits via PUT /api/todo. The form handles the full lifecycle: load → edit → submit → reload.

Key Takeaways

  • Each MVC page mounts its own Vue 3 app via a .cshtml.js file — progressive enhancement with zero build pipeline
  • The Vue data model maps directly to the VSA request DTO — JSON symmetry between frontend and backend
  • Server-side DataTables sends pagination/sort/search to the VSA handler — custom renderers build badges, bars, and chips
  • Drag-and-drop uploads with base64 encoding and thumbnail previews give instant visual feedback
  • Image galleries with modulo prev/next and print-to-PDF via window.print() add polished UX with no extra dependencies

Frequently Asked Questions

Q: Why Vue.js with VSA MVC instead of a full SPA?

Vue.js gives you reactivity without a build pipeline or client-side routing. Each page mounts its own Vue app, so pages stay server-rendered (great for SEO and initial load), yet forms and tables feel like a SPA. It's the best of both worlds — and it works with the same VSA API endpoints.

Q: How to integrate DataTables.js with VSA APIs?

Enable serverSide: true and point the ajax URL at the VSA list endpoint. DataTables sends start, length, search[value], and order[i][column] — exactly the DataTableRequest the handler expects (Part 8). Use dataSrc to map the response shape and render functions for custom cell content.

Q: How to handle file upload with Vue.js and VSA?

Read the dropped file as a base64 string in the browser, validate type/size client-side, and include it in the JSON request body. The VSA handler's FileStorageService decodes and saves it (Part 7). Render an instant thumbnail preview using a data URL for immediate feedback.

Q: Can I use Vue.js with Blazor VSA apps too?

Technically yes, but it's redundant — Blazor already provides reactivity. Vue.js shines in MVC apps where you want server-rendered pages plus client-side interactivity. If you're using Blazor, MudBlazor components are the idiomatic choice (Part 13).

Part 14 of 20 in the VSA Todo App Tutorial Series|← Previous: Part 13|Next: Part 15 →

Ready to study real VSA code?

Every Indotalent product is a complete .NET 10 application built with Vertical Slice Architecture. Complete source code — $21 each.

Explore Products

Looking for a ready-to-use traditional monolithic multilayered clean architecture?

Monolithic Clean Architecture — 1,300+ devs, 500+ forks. Clean Arch + CQRS + Repository Pattern. Free & open source for commercial use.

Star on GitHub