September 6, 2018, 2:00 pm

The .NET Core 2.1 release included a new way to run an SPA (single page application) such as Angular from within an MVC app. Previously in 2.0, the dotnet new generated SPA templates used "WebpackDevMiddleware" which interacted with the webpack config in the ClientApp to provide a dev experience similar to when using the Spa directly. In 2.1 the SPA templates now make use of "SpaProxy" to achieve the same goal. In dev mode, requests are simply proxied over to the dev server which is launched by the SPA's CLI tooling.


The SpaProxy method provides a much looser coupling between the MVC app and the SPA app and mirrors the more recent Angular community usage preferences. The behaviour in production mode is pretty much unchanged, the SPA app is built and the bundles are served using StaticFile middleware.

Using the Angular Spa template as an example, the previous 2.0 generated template had the Spa app and MVC app combined, the webpack config was in the same dir as Program.cs/Startup.cs, webpack made use of an 'aspnet-webpack' middleware component. It was not immediately clear how the webpack integration worked and would have required some work to separate the SPA app from the MVC app in the future. Here is the original announcement on the ASP.NET Blog: https://blogs.msdn.microsoft.com/webdev/2017/02/14/building-single-page-applications-on-asp-net-core-with-javascriptservices/

In the new 2.1 generated templates, everything related to the SPA app is kept inside the ClientApp dir, the MVC app looks just like a normal MVC app but with some extra code in Startup.cs and in the csproj file to handle running and building the SPA app from the .NET side. The SPA app inside the ClientApp dir can be run on its own using it's CLI tooling as intended by it's creators.

How it works in the new 2.1 generated templates

In Dev mode requests are proxied over to the dev server launched by the CLI, in the case of an Angular app there is a helper Middleware "UseAngularCliServer(npmScript: "start");" which takes care of starting the Angular CLI using the "npm start" command before the proxy is enabled.

In Production mode, the built bundles are now served from 'ClientApp/dist' rather than 'wwwroot/dist'. A production build can either be performed directly by the SPA's CLI which outputs to the 'dist' dir, or dotnet publish can be used to initiate the SPA build using the publish config in the csproj file.

The only downside that I can see is that the new method will make it harder to achieve a common page template if you wish to have a mixture of MVC/Razor and SPA pages.

To see the reasoning behind the change refer to this GitHub issue: https://github.com/aspnet/JavaScriptServices/issues/1288

The docs for the new method are here: https://docs.microsoft.com/en-us/aspnet/core/client-side/spa/angular?view=aspnetcore-2.1&tabs=visual-studio

Where has the Aurelia template gone?

The Aurelia SPA template has been dropped: https://github.com/aspnet/Announcements/issues/289

Here is the discussion around the retirement of the SpaTemplates package: https://github.com/aspnet/JavaScriptServices/issues/1522#issuecomment-364333953

The replacement Template package that generates the SpaProxy based templates does not contain an Aurelia template. However due to the new found simplicity and separation it is easy enough to create your own, just create an Angular Spa template and then replace the contents of the ClientApp dir with an Aurelia CLI generated app.

All that is required is a minor tweak inside the publish section in the csproj file and a minor change to Startup.cs.

My docker enabled "ASP.NET Core with Aurelia SPA" repo complete with the Aurelia app transplanted from the previous .NET SPA template is here: https://github.com/chrisckc/DotNetCoreAureliaSPA

Refer to the Readme inside the 'dotnetcore21-spa' dir which contains the .NET Core app for notes on how it was built.

Update: I also needed to change something in the Aurelia CLI generated webpack config to get HMR working, here are the details: https://github.com/aspnet/JavaScriptServices/issues/1743

Next Post Previous Post