Why UseSpa() is still needed if UseSpaStaticFiles() should serve the Angular page?
Asked Answered
C

2

5

I read some articles/blog posts and questions here but I am still confused about the usage of UseSpaStaticFiles() and UseSpa() middlewares in ASP.NET Core 2.1. (References: What is the difference between UseStaticFiles, UseSpaStaticFiles, and UseSpa in ASP.NET Core 2.1? https://blog.steadycoding.com/usedefaultfiles-usestaticfiles-usespastaticfiles-usespa/)

I am using the Angular project template with ASP.NET Core 2.1 which comes with a predefined set of middlewares added to the pipeline in the Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSpaStaticFiles(configuration =>
               {
                   configuration.RootPath = "ClientApp/dist";
               });      
}

public void Configure(IApplicationBuilder app)
{   

    app.UseStaticFiles();
    app.UseSpaStaticFiles();

   
    app.UseSpa(spa =>
    {
        if (env.IsDevelopment())
        {
            spa.UseProxyToSpaDevelopmentServer("http://localhost:4000");
        }
    });
    
}

My confusion is the following: The Angular project is compiled externally and the compiled js files are located in the ClientApp/dist folder, the environment is also set to production, so I would assume that the UseSpaStaticFiles() will serve the Angular page since the ClientApp/dist folder has the proper content for that. Also, the UseSpa() middleware is useless in this situation so I could remove that one.

However, if the UseSpa() is removed, the Angular page is not loaded at all, despite of the fact that the UseSpaStaticFiles() should serve the page based on the content of the ClientApp/dist. (If the UseSpaStaticFiles() and UseSpa() both added to the middleware pipeline, the Angular page is successfully served from the ClientApp/dist folder which means that the UseSpaStaticFiles() serves it.)

If the environment is set to production, the UseSpa() is not configured at all, it is just added to the middleware pipeline.

Can you help me to clarify why the UseSpaStaticFiles() cannot serve the page without the UseSpa() and why UseSpa() should be always added to the pipeline even if it doesn't serve anything? Do I miss something from my configuration that causes this behaviour?

Carpi answered 24/2, 2021 at 19:10 Comment(1)
Does this answer your question? What is the difference between UseStaticFiles, UseSpaStaticFiles, and UseSpa in ASP.NET Core 2.1?Yuille
E
6

The UseSpa is essentially a catch-all route handler that forwards all previously unhandled routes to your single page application. That way, your SPA can do things like client-side routing without your server application having to know all the routes the client application understands.

In contrast, UseSpaStaticFiles is a static files handler that only serves the compiled application files, e.g. application bundles like app.js or vendor.js, under their respective name, just like UseStaticFiles works for static files in the wwwroot folder.

Elisabeth answered 24/2, 2021 at 22:10 Comment(4)
Can we say that UseSpaStaticFiles returns index.html in production so we don't need UseSpa in production?Dermatosis
@MohammadBarbast Not exactly. The UseSpaStaticFiles middleware makes your (compiled) SPA files available, so that the browser can find them. But the UseSpa middleware ensures that your SPA is actually reached whenever a non-API and non-static file request hits your server. UseSpa still does something in a production environment (just without the proxy server).Elisabeth
So what about the opposite question - if UseSpa is going to serve up all the files from your SPA directory by default, why do you need to UseSpaStaticFiles?Gaivn
@Gaivn If you don’t have UseSpaStaticFiles, then requests going e.g. to app.js would also be served by your SPA application’s entry file (e.g. index.html) expecting the SPA to handle that route on the client. But there wouldn’t be anything because neither the SPA (which needs that JS file) nor the entry file is served by the server. – So UseSpa doesn’t serve anything, it just forwards all the requests to the SPA which files are served by UseSpaStaticFiles.Elisabeth
A
2

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

  1. 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!

  2. 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...

  1. 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!

  2. 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!

Antihero answered 25/4, 2023 at 15:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.