Code Endeavor's Blog

ASP.NET Vue MPA Visual Studio Template Released

I have been involved developing front end frameworks for multiple Content Managements Systems over the last couple decades. DotNetNuke was the beginning of this, where I was in charge of developing its ClientAPI. A couple years after this I created my own open source CMS called Videre where I introduced the concept of Client Side Presenters. Each of these efforts was about allowing a Microsoft back end web framework (ASP.NET) efficiently interact with a rich user interface. This template represents my latest approach to providing a solution to developing a multi-page application with a reactive front end.

Quick Start VSCode

Install the template

dotnet new install codeendeavors-aspnetvuempa

Create your new project

dotnet new codeendeavors-aspnetvuempa -o "myvueapp"

After the app installs you will be prompted to do a npm install

Processing post-creation actions...
Template is configured to run the following action:
Description: npm install
Actual command: dotnet build -t:npmInstall
Do you want to run this action [Y(yes)|N(no)]?

If you decide not to run this script you will need to navigate to your myvueapp.Web folder and manually run a npm install.

Once installed simply open up the folder you installed the files to in VSCode and hit F5.

Anatomy of a Page

Script / Style Registration

Most ASP.NET MVC sites utilize a common _Layout.cshtml file to render things like the common script, css, and general layout of their sites. This template makes this assumption as well. This is where the Vue runtime, vue component css, and vue component js scripts are registered.

<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />  @* CSS from static/css (esbuild compile)  *@
<link rel="stylesheet" href="~/js/style.css" asp-append-version="true" />      @* CSS from vue components (vite compile) *@
<script src="~/js/vue.global.prod.js"></script>
<script src="~/js/myapp.umd.js" asp-append-version="true"></script>

App Initialization

The myapp.js (name is configurable) file contains common methods, including the one to initialize the app with Vue. It exposes itself by adding an instance to the global (window) object. So you can simply call myapp.initApp to initialize the vue runtime.

Layout Page

Here is a simplified version of the _layout page. Hopefully vue developers recognize this to feel a lot like the Composition API.

<head>
...
    <script>
        (() => {    //internalize all variables
            const { SunIcon, MoonIcon } = myapp.icons;
            const { ProgressBar } = myapp.components;
            const { ref, watch } = Vue;

            const layoutApp = {
                components: { SunIcon, MoonIcon, ProgressBar },
                setup() {
                    const darkMode = ref(false);

                    const toggleDarkMode = () => {
                        darkMode.value = !darkMode.value;
                    }

                    return {
                        darkMode,
                        toggleDarkMode,
                    }   
                }
            }
            myapp.initApp(layoutApp);

        })();       //internalize all variables
    </script>
</head>
<body id="app">
...
    <main role="main">
        <progress-bar v-if="loading"></progress-bar>    @* normal vue component (imported in static\vue\myapp.js) *@
        @RenderBody()
    </main>
...
@await RenderSectionAsync("Scripts", required: false)
</body>

View page

Normal MVC View pages look even more like a standard vue page (with a cshtml extension). Just remember that the razor syntax already claims the @ sign, so if you need an event handler you need to double up your @@

@{
    var COMPONENT_NAME = "customer-search";
    var componentId = Context.GetId(COMPONENT_NAME);
}
<template id="@componentId">
    <table v-if="customers" class="myapp-table">
        <thead>
            <th></th>
            <th>First Name</th>
            <th>Last Name</th>
        </thead>
        <tr v-for="customer in customers">
            <td style="width: 30px; text-align: center"><a :href="`/customer/${customer.id}`"><pencil-square-icon class="myapp-icon"></pencil-square-icon></a></td>
            <td>{{ customer.firstName }}</td>
            <td>{{ customer.lastName }}</td>
        </tr>
    </table>
    <div v-else>Please Wait</div>
</template>

@section Scripts {
<script>
    (() => {
        const { ref } = Vue;
        const { PencilSquareIcon } = myapp.icons;

        myapp.registerComponent('@COMPONENT_NAME',
            document.getElementById('@componentId'),
            {
                components: { PencilSquareIcon },
                setup() {
                   const customers = ref(null);
                   const refreshCustomers = async () => {
                        customers.value = await myapp.ajax({url: '~/Customer/GetCustomers' });
                    }
                    refreshCustomers();
                    return  {
                        customers,
                    }
                }
            }
        );
    })();
</script>
}