Due to their low weight and high performance, Single-Page Applications, or SPAs, have recently become the most sought-after client facing application stacks. In this architecture, the client application renders the fetched data onto a fluid and dynamic layout while the server concentrates on data logic and provides data to the client in the form of RESTful APIs.

In addition to improving user experience, this can lessen the load placed on the webserver when rendering and serving HTML content over the network. In order to create the most effective SPAs, client-side frameworks like Angular, ReactJs, and Ionic have advanced significantly. Let’s discuss how to integrate an SPA powered by Angular inside of an existing ASP.NET Core application in this article.

Assume that the data source for an Angular application is an ASP.NET Core application. We now have two options for deployment: running two instances of both the client and server applications, with the client application configured with the server endpoint. Most of the real-world application deployments we see today are done in this manner.

A webserver like IIS or Apache must once again host an SPA, which is simply an index.html page with a few js files for runtime and client logic.

An innovative method has been developed that sandwiched an angular SPA inside an ASP.NET Core API because we would also need to require the client application to maintain the server address for communication.

With this method, both applications can be created and deployed on a single instance, and they are both local to one another. When a user calls the appropriate routes, the ASPNETCORE application also serves the client application to them. A few libraries must be added to the ASP.NET Core API, and the csproj file must be modified to allow for angular build activities.

Steps to Implement Code in ASP.NET Core

1. Start by copying the Angular application into the ClientApp subdirectory of the ASPNETCORE project. Together with the controllers and other projects, this is located in the root directory. Just keep in mind that since we’re using an ASPNETCORE API project, we don’t have a wwwroot folder or Views folder.

2. Install the package listed below, which supports SPA building and rendering, in the ASPNETCORE project.

<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.3" />

3. To support SPA, we would need to add a few middlewares and services to the Startup class. The server application would route to an SPA when making specific requests technically because it is just a collection of static files under the ASPNETCORE project. We would also add support for the server to access these static files in order to make this possible.

The middleware functions UseStaticFiles() and UseSpa() for SPA support accomplish this.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
            
    // In production, the Angular files will be served from this directory
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "ClientApp/dist";
    });
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStaticFiles();
    
    // In Production Environment
    // Serve Spa static files
    if (!env.IsDevelopment())
    {
        app.UseSpaStaticFiles();
    }

    // other ASPNETCORE Routing Code
    // Spa middleware to enable
    // SPA request handling
    app.UseSpa(spa =>
    {
        // The directory from which the 
        // SPA files shall be served for
        // client requests
        spa.Options.SourcePath = "ClientApp";

        // When in Development,
        // Since the SPA app is not build
        // use npm start command to run 
        // the node server for local run
        if (env.IsDevelopment())
        {
            spa.UseAngularCliServer(npmScript: "start");
        }
    });
}

4. We make sure the angular build command configuration in the angular.json file of the client application, which is under the ClientApp directory, so that on angular build, the files are created under the /dist folder relative to the Client app path.

        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "progress": false,
            "extractCss": true,
            "outputPath": "dist", <-- ensure this path
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": ["src/assets"],
            "styles": [
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "src/styles.css"
            ],
            "scripts": []
          }
        }

This is significant because, when running, the ASPNETCORE application searches for the Spa files under the services-specified path /ClientApp/dist folder.Method AddSpaStaticFiles().

5. Finally, using the csproj and the changes listed below, we wire up the ClientApp and ASPNETCORE app together during project build.

<ItemGroup>
    <!-- Don't publish the SPA source files, but do show them in the project files list -->
    <Content Remove="$(SpaRoot)**" />
    <None Remove="$(SpaRoot)**" />
    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules**" />
  </ItemGroup>
<!-- Debug run configuration -->
  <!-- Check for Nodejs installation, install in the ClientApp -->
  <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>
<!-- Publish time tasks: run npm build along with project publish -->
  <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 -- --prod" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist**; $(SpaRoot)dist-server**" />
      <DistFiles Include="$(SpaRoot)node_modules**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

In each of these configurations, we define $(SpaRoot) at the top of the csproj file as the ClientApp rootpath:

<PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
    <IsPackable>false</IsPackable>

    <!-- the path picked up by all the processing -->
    <SpaRoot>ClientApp</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules**</DefaultItemExcludes>
    <!-- Set this to true if you enable server-side prerendering -->
    <BuildServerSideRenderer>false</BuildServerSideRenderer>
</PropertyGroup>

When everything is finished, we can simply publish our project.

and run to see our changes coming into effect.

Leave a comment

Your email address will not be published. Required fields are marked *