9

Am trying to create a VueJS SPA with ASP.NET core in the backend

Since dotnet SDK version 3.0.100 no longer supports Vue, I created a React app

dotnet new react

and swapped the ClientApp with a Vue app

rm -rf ClientApp
vue create frontend

Everything works perfectly except for 1 thing

Whenever I launch the app http://localhost:5000 and click on any Vue Router link, say http://localhost:5000/about (it works) but if I refresh I get an expected 404

Cannot GET /about

Same goes if I typed the URL into the browser and access directly

It's worth noting that the default React app doesn't face this problem, I compared everything between the files of the two ASP.NET apps using this tool and found no differences

Now Vue Router handles routes by Javascript because it's using the #!hash behind the scenes, so ASP.NET router tries to match the URL to an AboutController@index which doesn't exist hence the 404

Researching revealed that I should set a wildcard fallback to the SPA router in Startup.cs, but all the answers I found here, here, here, this question, answer here, this question and this one are using app.UseMvc() and I don't have that enabled, I am using app.UseEndpoints instead

I also tried searching GitHub but same results

This Answer recommends I stick to app.UseEndPoints and Microsoft migration guide too

The Vue Router Documentation talks about this very issue and proposes a solution for .NET apps using web.config for IIS like the answer here but am running Kestrel server on a Linux machine so this takes no effect

Here's my Startup.cs file

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;

namespace spa
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllersWithViews();

            // In production, the Vue files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "frontend/dist";
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseSpaStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "frontend";

                if (env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "serve:bs");
                }
            });
        }
    }
}

I pushed this app to GitHub here to make it easy to reproduce just in case I forgot something

How can I achieve the same effect of this

app.UseMvc(routes =>
{
  routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
  routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" });
});

using app.UseEndpoints?


Just in case this is relevant, here's my Vue router

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import NotFound from './views/NotFound.vue'

Vue.use(Router)

export default new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [{
            path: '/',
            name: 'home',
            component: Home
        },
        {
            path: '/about',
            name: 'about',
            // route level code-splitting
            // this generates a separate chunk (about.[hash].js) for this route
            // which is lazy-loaded when the route is visited.
            component: () => import( /* webpackChunkName: "about" */ './views/About.vue')
        },
        {
            path: '/forecast',
            name: 'forecast',
            component: () => import( /* webpackChunkName: "forecast" */ './views/Forecast.vue')
        },
        {
            path: '*',
            component: NotFound
        }
    ]
})
Salim
  • 9,886
  • 5
  • 23
  • 55

1 Answers1

1

Try this template https://github.com/SoftwareAteliers/asp-net-core-vue-starter. It's recently updated and support .Net Core 3.0 and Vue CLI 3.0.