Search code examples
angularasp.net-web-apisingle-page-application.net-8.0

Spa Root path configuration in .net8


I read a lot about this but none of thew questions here or elsewhere did not help me to achieve the result. We need to Release and Run our application (ASP.NET API + Angular client) as single application and run Angular application from ClientApp folder.

It seems .net 8 ignores configuration and application is always running from wwwroot (env.WebContentRoot) folder instead. We had configured this in .net 3.1 and it worked - I tried the same configuration but .net 8 ignores it. I tried sample with:

  • registering FileProvider in UseDefaultFiles, UseStaticFiles middlewares
  • configuration from .net 3.1
  • and few other modifications of steps mentioned above I found on the internet

None of them helped.

Please can someone give me an advice how to achieve this in .net8?

Thansk a lot

What I tried to do:

  • I tried to configure SPA application in Production to run from ClientApp folder in .net 8
  • ClientApp folder should be sibling of wwwroot not child

Configuration from .net 3.1:

string AngularAppRootPath = "ClientApp"

services.AddSpaStaticFiles(configuration => {
                    logger.LogInformation($"AddSpaStaticFiles: configuration.RootPath='{AngularAppRootPath}'");
                    configuration.RootPath = AngularAppRootPath;
                });
 app.MapFallbackToFile("/index.html");

What I expected:

  • When I run released application to run SPA application from CLientApp folder

Result:

  • Application tries to run SPA app from wwwroot instead

Solution

  • This is the following configuration I use, that works fine for me. Note that you could have to adapt it to your needs.

    Program.cs :

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/Backend/swagger.json", "Backend");
        });
    
        app.MapFallbackToFile("/index.html");
        app.UseDefaultFiles();
        app.UseStaticFiles();
    }
    else
    {
        app.UseHttpsRedirection();
    
        var customWebRootPath = Path.Combine(app.Environment.ContentRootPath, "ClientApp", "dist");
        var fileProvider = new PhysicalFileProvider(customWebRootPath);
    
        app.MapFallbackToFile("/index.html", new StaticFileOptions { FileProvider = fileProvider });
        app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = fileProvider });
        app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider });
    }
    

    And in your API.csproj :

    <Project Sdk="Microsoft.NET.Sdk.Web">
        <PropertyGroup>
            <TargetFramework>net8.0</TargetFramework>
            <SpaRoot>ClientApp\</SpaRoot>
            <SpaProxyServerUrl>https://localhost:44123</SpaProxyServerUrl>
            <SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
            <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
            <Configurations>Debug;Release;Staging</Configurations>
            <Platforms>AnyCPU</Platforms>
        </PropertyGroup>
    
        <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>
    
        <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 Condition="'$(Configuration)' == 'Staging'" WorkingDirectory="$(SpaRoot)" Command="npm run build -- --configuration staging" />
            <Exec Condition="'$(Configuration)' == 'Release'" WorkingDirectory="$(SpaRoot)" Command="npm run build -- --configuration production" />
    
            <!-- Include the newly-built files in the publish output -->
            <ItemGroup>
                <DistFiles Include="$(SpaRoot)dist\**" />
                <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
                    <RelativePath>%(DistFiles.Identity)</RelativePath>
                    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
                    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
                </ResolvedFileToPublish>
            </ItemGroup>
        </Target>
    </Project>
    

    I use a proxy for dev environment, based on the base template when you create your .NET + Angular web app with visual studio.