Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overview page for SPAs #26373

Closed
javiercn opened this issue Jul 9, 2022 · 7 comments · Fixed by #28047
Closed

Overview page for SPAs #26373

javiercn opened this issue Jul 9, 2022 · 7 comments · Fixed by #28047
Assignees
Labels
doc-enhancement Pri1 High priority, do before Pri2 and Pri3 Pri---0.5 Don't delete without permission seQUESTered Identifies that an issue has been imported into Quest. Source - Docs.ms Docs Customer feedback via GitHub Issue

Comments

@javiercn
Copy link
Member

javiercn commented Jul 9, 2022

Can we provide an overview page for SPAs so that we can add common information about SPA development with ASP.NET Core? As well as docs on how the templates work during development and production? (I will be providing the content).

Could we also remove the JavaScript Services section from the docs, since that is not something we longer recommend?

/cc @danroth27


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.


Associated WorkItem - 58766

@dotnet-bot dotnet-bot added ⌚ Not Triaged Source - Docs.ms Docs Customer feedback via GitHub Issue labels Jul 9, 2022
@wadepickett wadepickett added Pri1 High priority, do before Pri2 and Pri3 and removed P0.5 labels Oct 31, 2022
@Rick-Anderson Rick-Anderson added the Pri---0.5 Don't delete without permission label Nov 2, 2022
@Rick-Anderson Rick-Anderson removed the Pri---0.5 Don't delete without permission label Nov 2, 2022
@Rick-Anderson
Copy link
Contributor

@javiercn can you write the overview page for SPAs ?
We don't like to delete articles, but we have a prominent warning at the top of the page.

@javiercn javiercn self-assigned this Nov 3, 2022
@javiercn javiercn added re-Artak @mkArtakMSFT must review and removed re-Artak @mkArtakMSFT must review labels Nov 3, 2022
@javiercn
Copy link
Member Author

Architecture of Single Page Application templates

The SPA templates for Angular and React offer the ability to develop Angular and React applications that are hosted inside a .NET backend server.

At publish time, the files of the Angular and React app are copied to the wwwroot folder and are served via the static files middleware.

A fallback route handles unknown requests to the backend and serves the index.html for the SPA.

During development, the application is setup to leverage the frontend proxy provided by React and Angular (which is in fact the same).

When the app launches, we open the index page in the browser. There, a special middleware that is only plugged in during development, intercepts the incoming requests, checks whether the proxy is running, and redirects to the URL for the proxy if its running or launches a new instance and returns a page to the browser that will autorefresh every few seconds until the proxy is up and the browser is redirected.

sequenceDiagram
participant Browser
participant Proxy
participant Server
Browser->>Server: GET /
alt Proxy is running
  Server->>Browser: 301 Redirect <<Proxy-URL>>
else Proxy is not running
  Server->>Proxy: Launch
  par Browser checks with server if the proxy is running
    loop Until SPA proxy is running
      Server->>Browser: 200 OK <html>...</html>
      Browser->>Browser: Wait
      Browser->>Server: GET /
    end
  and Server checks if proxy is ready
    loop Until SPA proxy is ready
      Server->>Proxy: GET <<Proxy-Url>>
      alt Proxy not ready
        Proxy->>Server: Error
      else Proxy ready
        Proxy->>Server: 200 OK <html>...</html>
      end
    end
  end
  Server->>Browser: 301 Redirect <<Proxy-URL>>
end
Browser->>Proxy: GET <<Proxy-URL>>
Proxy->>Browser: 200 OK <html>...</html>
loop Other resources and requests
  Browser->>Proxy: HTTP Request
  Proxy->>Browser: HTTP Response
end

The main work that our templates do during development (other than launching the proxy if it is not already running) consists of setting up HTTPS and configuring some requests to be proxied back to the backend ASP.NET Core server.

When the browser sends a request for a backend endpoint, like /weatherforecast in our templates. The SPA proxy receives the request and sends it back to the server transparently. The server responds and the SPA proxy sends the request back to the browser.

sequenceDiagram
participant Browser
participant Proxy
participant Server
Browser->>Proxy: GET /weatherforecast
Proxy->>Server: GET <<Server-Url>>/weatherforecast
Server->>Proxy: 200 OK <<json>> 
Proxy->>Browser: 200 OK <<json>> 

Published Single Page Applications

As mentioned in the beginning of the document. When the application is published, the SPA becomes a collection of files inside the wwwroot folder.

There is no runtime component required to serve the app.

  • app.UseStaticFiles() in Program.cs takes care of making sure the files are served.
  • app.MapFallbackToFile("index.html") in Program.cs takes care of serving the default document for any unknown request the server receives.

When we publish the app via dotnet publish the following tasks in the csproj file take care of ensuring that npm restore runs and that the appropriate npm script runs to generate the production artifacts

  <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
  </Target>
  
  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --configuration production" />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>
</Project>

Developing Single Page Applications

The project file defines a few properties that control the behavior of the app during development. These properties are:

  • SpaProxyServerUrl: Controls the URL where the server expects the SPA proxy to be running. This is the URL the server pings after launching the proxy to know if it is ready, and the URL where it redirects the browser after a successful response.
  • SpaProxyLaunchCommand: This is the command the server uses to launch the SPA proxy when it detects the proxy is not running.

The package Microsoft.AspNetCore.SpaProxy included as a reference in the template, is the one responsible for the logic described above to detect the proxy and redirect the browser to it.

It uses a Hosting Startup Assembly defined inside Properties\launchSettings.json to automatically add the required components during development necessary to detect if the proxy is running and launch it otherwise.

Setup for the client Application

This setup is specific to the frontend framework the app is using, however many aspects of the configuration are similar.

Angular setup

  • Inside package.json, on the scripts section, the following scripts take care of launching the angular development server.
{
    "prestart": "node aspnetcore-https",
    "start": "run-script-os",
    "start:windows": "ng serve --port 44416 --ssl --ssl-cert %APPDATA%\\ASP.NET\\https\\%npm_package_name%.pem --ssl-key %APPDATA%\\ASP.NET\\https\\%npm_package_name%.key",
    "start:default": "ng serve --port 44416 --ssl --ssl-cert $HOME/.aspnet/https/${npm_package_name}.pem --ssl-key $HOME/.aspnet/https/${npm_package_name}.key",
}
  • The prestart script invokes aspnetcore-https.js in the project, which is responsible for ensuring the dev server HTTPS certificate is available to the SPA proxy server.
  • The start:windows and start:default launch the Angular dev server via ng serve and provide the port (this matches the port in the csproj file) as well as the options to use HTTPS and the path to the certificate and the associated key.

Inside angular.json, the serve command includes a proxyconfig element in the development configuration to indicate that proxy.conf.js should be used to configure the frontend proxy.

"serve": {
  "builder": "@angular-devkit/build-angular:dev-server",
  "configurations": {
    "development": {
      "browserTarget": "AngularApp70:build:development",
      "proxyConfig": "proxy.conf.js"
    }
  },

proxy.conf.js is included in the project and defines the routes that need to be proxied back to the server backend. The general set of options is defined https://github.com/chimurai/http-proxy-middleware for react and angular since they both use the same proxy under the hood.

The snippet below uses logic based on the environment variables set during development to determine the port the backend is running on.

const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
  env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:8141';

React setup

  • Inside package.json, on the scripts section, the following scripts take care of launching the react app during development.
{
    "prestart": "node aspnetcore-https && node aspnetcore-react",
    "start": "rimraf ./build && react-scripts start",
}
  • The prestart script invokes aspnetcore-https.js in the project, which is responsible for ensuring the dev server HTTPS certificate is available to the SPA proxy server.
  • The prestart script also invokes aspnetcore-react.js to setup the appropriate .env.development.local file to use the HTTPS local dev certificate, by adding SSL_CRT_FILE=<<certificate-path>> and
    SSL_KEY_FILE=<<key-path>> to the file.

Inside the .env.development file, we define the port for the development server as well as indicate that we want to use HTTPS.

Finally, inside src/setupProxy.js we configure the SPA proxy to forward the requests to the backend. The general set of options is defined https://github.com/chimurai/http-proxy-middleware.

The snippet below uses logic based on the environment variables set during development to determine the port the backend is running on.

const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
  env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:8141';

@javiercn
Copy link
Member Author

I guess these can be linked #25028

@Rick-Anderson Rick-Anderson added this to To do in Next sprint via automation Nov 10, 2022
@Rick-Anderson Rick-Anderson added the Pri---0.5 Don't delete without permission label Nov 10, 2022
@mkArtakMSFT
Copy link
Member

@Rick-Anderson reassigning this to you, as there isn't pending work for @javiercn here.

Thanks!

@Rick-Anderson Rick-Anderson added the reQUEST Triggers an issue to be imported into Quest label Jan 10, 2023
@github-actions github-actions bot added seQUESTered Identifies that an issue has been imported into Quest. and removed reQUEST Triggers an issue to be imported into Quest labels Jan 10, 2023
@ghidalgo3
Copy link

@javiercn FWIW your comment here detailing the proxy functionality (specifically the need to modify launchSettings.json) was enough to unblock me. If you could link that information somewhere into the SPA documentation on docs.microsoft.com it would be more authoritative than an open GitHub issue :)

@Rick-Anderson
Copy link
Contributor

Rick-Anderson commented Jan 10, 2023

@javiercn FWIW your comment here detailing the proxy functionality (specifically the need to modify launchSettings.json) was enough to unblock me. If you could link that information somewhere into the SPA documentation on docs.microsoft.com it would be more authoritative than an open GitHub issue :)

I'll take care of that in #28047

@javiercn
Copy link
Member Author

javiercn commented Jan 10, 2023

@ghidalgo3 the intention of opening and detailing these issues is to update the docs, we collaborate with our docs teams to figure out the language, grammar, etc. Since we are not professional writers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
doc-enhancement Pri1 High priority, do before Pri2 and Pri3 Pri---0.5 Don't delete without permission seQUESTered Identifies that an issue has been imported into Quest. Source - Docs.ms Docs Customer feedback via GitHub Issue
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

6 participants