Combine Angular and ASP.NET into One Website using "UseSpa"
This answer might be a little long but the devil is in the details :)
For starters, realize Angular has two main modes...Development and Production
Development - In this mode, your code will NOT compile Angular's build to the "dist" folder as the Angular CLI and the dev server runs the build ONLY in-memory. So the "dist" (distribution) or JavaScript build folder does not exist when running in development!
Production - In this mode, it WILL compile the Angular build to the "dist" folder and run your Angular website from the "dist" folder's raw JavaScript static files found in that folder.
So, these two ways of working in Angular are completely alien to each other. This is what confuses developers and is never explained clearly online.
Angular inside ASP.NET
In an ASP.NET Core
website project, when you add Angular
to your ASP.NET Core project and try and run your website, Angular does NOT follow the same rules as ASP.NET and will not build or load without special command line or other scripts. In the Development Environment of .NET it does not load with the ASP.NET, does not start a "build" process, does not appear in your local IIS/IIsExpress/Kestrel test servers, or even as physical files without special help.
Most Angular developers run a command line process (Angular CLI) to do all that in Angular for you, but the UseSpa
tool and extensions allows you to force C# to call Angular's special build and server routines, then load them into the development test environment/browser without separate command line tricks.
For starters, Angular in development has to run inside its own web server ("Angular Development Server") as well as build itself and load its own special TypeScript-to-JavaScript compile/transpile inside its own in-memory space. None of this is seen or observed unless you try and compile Angular via the CLI using a command prompt separate from ASP.NET and Visual Studio.
Microsoft has struggled with several ways to try and merge and mush Angular and its odd command line build process into various templates, which you will notice keep changing and getting rewritten every few years. The current iteration divides the project into two separate projects which I like, but you still cannot integrate Angular into one website or test its production build from ASP.NET.
Past versions used elaborate "proxy" calls to keep the Angular dev server separate from any WebAPI or MVC project that contained data or the "dist" build of Angular for production. This has the advantage that you have two separate browser windows and servers, but its very difficult to untangle the SpaProxy calls and code from the project.
The program.cs
file inside a single ASP.NET is really the best place to manage how Angular interfaces with ASP.NET, because you are not limited by Microsoft's templates nor Angular's proprietary build system to run it in a browser. The UseSpa
, AddSpaStaticFiles
, UseSpaStaticFiles
code blocks in .NET Core can be combined to run both Development (in-memory) and Production (dist files) in a single ASP.NET Core web application. You just need to modify the program file and follow these rules...
When developing in Angular on your local PC, you only need to run UserSpa
code which will load Angular in memory for testing. That is it!
In production you need to turn on AddSpaStaticFiles
, UseSpaStaticFiles
, and UseSpa
(all three) since you need to tell ASP.NET to prioritize the Single Page Application of "dist" and load them on every browser visit.
Why? AddSpaStaticFiles
, UseSpaStaticFiles
represent the delivery of Angular's STATIC FILES or the "dist" build that points to its index.html file that holds the Angular compiled JavaScript links.
In development, its different. In development you only need to call the UseSpa
with its call to "start" the Angular CLI build, since in development you need to only run Angular in-memory. The UseSpa
call at the end of your program.cs file will make sure your website routes to the Angular application (in-memory or static files).
Using this strategy, you can now run both development Angular and the static production built Angular from one ASP.NET website and test both. The code to do that is below.
The Magic Code...
First create your ASP.NET website. I chose a WebAPI one. Then go ahead and drag into it your Angular folder of files into the root folder of your ASP.NET site. Do NOT INSTALL ANY OF THE MICROSOFT TEMPLATES for ASP.NET Core with Angular templates since we will be manually wiring ASP.NET to Angular ourselves!
Be sure to install these two Nuget Packages
first inside your project in Visual Studio:
Microsoft.AspNetCore.SpaServices.Extensions
Microsoft.AspNetCore.SpaServices
If you want to go ahead and build your Angular production "dist" or build folder using the command line tool of your choice and the script below, go ahead. But the code below allows you to just change the ENVIRONMENT C# call and create it on-the-fly during the ASP.NET build.
To do so, open up a command prompt
, change directory to your Angular root folder (see below), then run the build script (see below). This creates the "dist" folder of physical files for Production-only testing:
cd c:\{path to your Angular root folder}
ng build
Now in your ASP.NET project, modify your program.cs
file as follows. This code will allow you to test BOTH your Angular Development and Angular Production code in your localhost browser but just change the environment variable at the top. (How cool is that!):
using Microsoft.AspNetCore.SpaServices.AngularCli;
// Add a way to quickly change Development and Production
// Just comment out one and enable the other to
// run either of the Angular builds locally.
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = Environments.Development
//EnvironmentName = Environments.Production
});
builder.Services.AddControllers();
// PRODUCTION ONLY
if (builder.Environment.IsProduction())
{
builder.Services.AddSpaStaticFiles(configuration =>
{
// PRODUCTION
configuration.RootPath = "AngularRootFolder/dist/YourProjectName";
});
} else {
// You do not need to serve static Angular
// in development or the SPA root path!
}
// Turn off all Swagger calls!!
//builder.Services.AddEndpointsApiExplorer();
//builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
// Turn off all Swagger calls!!
//app.UseSwagger();
//app.UseSwaggerUI();
}
// PRODUCTION ONLY
if (builder.Environment.IsProduction())
{
// You do not need access to static Angular files
// in Development as you are only triggering
// the "ng start" call below which runs Angular
// builds in-memory.
app.UseSpaStaticFiles();
}
app.UseHttpsRedirection();
// Be sure to call the Routing and Endpoint Middleware!
// This allows your WebApi to route to endpoints for
// data Angular calls inside the same project, but avoid
// ASP.NET setting a default homepage in the WebAPI.
// This is the key to letting the Single Page Application
// be the default route in the pipeline.
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
// At the end of the pipeline you always
// need some Single Page Application mapping
// to occur so in the browser your Angular app
// pops up in both environments.
// That is what "UserSpa" does. In development
// however it also calls the Angular CLI via the
// "ng start" command inside "packages.json"
// in your Angular folder.
app.UseSpa(spa =>
{
if (builder.Environment.IsDevelopment())
{
// DEVELOPMENT
// This gets the web root, and adds your path to your Angular folder.
spa.Options.SourcePath = System.IO.Path.Combine(builder.Environment.ContentRootPath, "AngularRootFolder");
// This calls the Angular CLI to build your Angular
// project and call it via its server. This will
// Load the Angular app into the browser, too,
// And call your WebAPI for data.
spa.UseAngularCliServer(npmScript: "start");
} else {
// PRODUCTION
// DO NOT CALL THE Angular CLI and dev server in-memory
// when in Production as will call only the static files!
}
});
app.Run();
You should be able to test both development and production versions of Angular from one ASP.NET website, and call data from the same site and its WebAPI endpoints from Angular!