Superman.Lopez
Superman.Lopez

Reputation: 1600

Asp.net core and Angular in production: Spa is not being served

My web application was scaffolded from the dotnet core 3.0 Angular template. I want to deploy the first version into production but I am struggling to have the Angular app served. I am using Kestrel as the webserver (it's an intranet application).

I have published the application with dotnet publish -c Release -o ../app-dist which build the Asp.net application and it triggered ng to build a production release of the client. The resulting folder structure includes a app-dist/wwwroot folder which has some assets like favicon and it has a app-dist/ClientApp/dist/app folder with the index.html and the compiled js and css assets. If I start the application by running dotnet app.dll in app-dist, Kestrel starts but it fails to serve the client application. I can access the minor assets from wwwroot as well as the Razor pages that are used for user authentication.

In my docker deployment, I get the following error when trying to access https://host/ or https://host/index.html (as well as http equivalents):

System.InvalidOperationException: The SPA default page middleware could not return the default page '/index.html' because it was not found, and no other middleware handled the request. Your application is running in Production mode, so make sure it has been published, or that you have built your SPA manually. Alternatively you may wish to switch to the Development environment.

When I run the same command in app-dist on my macOS development system I get the same error (after first setting export ASPNETCORE_ENVIRONMENT=production).

In my Startup.cs I have the following:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(); 
...

}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStaticFiles();
    if (!env.IsDevelopment())
    {
        app.UseSpaStaticFiles();
    }

...

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller}/{action=Index}/{id?}");
        endpoints.MapRazorPages();
    });
    app.UseSpa(spa =>
    {
        spa.Options.SourcePath = "ClientApp";
        if (env.IsDevelopment())
        {
            spa.UseAngularCliServer(npmScript: "start");
        }
    });
}

In my csproj:

<PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
    <IsPackable>false</IsPackable>
    <SpaRoot>ClientApp\</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
    <BuildServerSideRenderer>false</BuildServerSideRenderer>
</PropertyGroup>

...

<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <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' " />
    <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>

I have tried copying the Angular index.html and compiled files to the wwwroot as well as the ClientApp folder, but neither made a difference. I also tried setting spa.Options.SourcePath = "ClientApp/dist/app";. What seems incorrectly configured, or what should I investigate next?

Upvotes: 5

Views: 6737

Answers (3)

James Poulose
James Poulose

Reputation: 3833

In my case this was because of a trailing slash in the Angular Api root

Instead of

http://localhost:81

i had

http://localhost:81/

Remove the slash and you will be fine. I have answered this here

Upvotes: 1

Milan Tenk
Milan Tenk

Reputation: 2715

If you want to change the directory of the served app in production you can do it in ConfigureServices following way:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    // In production, the Angular files will be served from this directory
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "ClientApp/dist";
    });
}

Aborve the configuration.RootPath should be changed to your wished path.

Upvotes: 3

Superman.Lopez
Superman.Lopez

Reputation: 1600

The location of the files was wrong. I moved the files to ClientApp\dist\ and that was the location that the middleware was looking for. Strange configuration.

Upvotes: 1

Related Questions