Search code examples
c#asp.net-coreasp.net-spa

.NET Core WebAPI VueJS template publish issue


I'm trying out the "VueJS with Asp.Net Core 3.1 Web API Template" found here and it works quite smooth during development. However, I've wanted to see how it handles publishing and I can't manage to get it working.

When running publish to folder, it doesn't move the clientapp/dist folder to the output directory, which is OK, so I thought I'd do it manually. So I've tried moving the contents of the dist folder to output directory with the following paths:

  • "/publish/clientapp/dist"
  • "/publish/dist"
  • "/publish/clientapp"

But none of the above seems to work, I get the following error when running the .dll file:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HLTD93CRG52F", Request id "0HLTD93CRG52F:00000001": An unhandled exception was thrown by the application.
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.

   at Microsoft.AspNetCore.SpaServices.SpaDefaultPageMiddleware.<>c__DisplayClass0_0.<Attach>b__1(HttpContext context, Func`1 next)
   at Microsoft.AspNetCore.Builder.UseExtensions.<>c__DisplayClass0_1.<Use>b__1(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.TryServeStaticFile(HttpContext context, String contentType, PathString subPath)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Builder.UseExtensions.<>c__DisplayClass0_2.<Use>b__2()
   at Microsoft.AspNetCore.SpaServices.SpaDefaultPageMiddleware.<>c__DisplayClass0_0.<Attach>b__0(HttpContext context, Func`1 next)
   at Microsoft.AspNetCore.Builder.UseExtensions.<>c__DisplayClass0_1.<Use>b__1(HttpContext context)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

This is my "UseSpa" in Startup.cs:

app.UseSpa(spa =>
        {
            if (env.IsDevelopment())
                spa.Options.SourcePath = "ClientApp";
            else
                spa.Options.SourcePath = "clientapp/dist";

            if (env.IsDevelopment())
            {
                spa.UseVueCli(npmScript: "serve");
            }

        });

With the above code, I would assume my dist folder should be located in /publish/clientapp/dist, which I've tried, but even then, I get the error mentioned above.

I hope someone can point me in the right direction - thanks in advance :)


Solution

  • There seems a bug in the template: the name of ClientApp folder is clientapp. However, all the related codes in startup treat it as ClientApp.

    1. The template didn't configure a task that builds the Vuejs for you. To do that, add a task in your csproj file:

        <PropertyGroup>
          <SpaRoot>clientapp\</SpaRoot>
          <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
        </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 WorkingDirectory="$(SpaRoot)" Command="npm run build" />
      
          <!-- 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>
      
    2. The code above are mostly copied from the standard ASP.NET Core Angular template. It will build your Vuejs under clientapp/dist folder after published. In order to make ASP.NET Core know this, configure your SpaStaticFiles Service as below:

      services.AddSpaStaticFiles(configuration =>
      {
          configuration.RootPath = "ClientApp";
          configuration.RootPath = "clientapp/dist";
      }
      
    3. Finally, you don't need source path when in production environment since it has been built automatically:

      app.UseSpa(spa =>
      {
          if (env.IsDevelopment())
              spa.Options.SourcePath = "ClientApp";
              spa.Options.SourcePath = "clientapp";
          else
              spa.Options.SourcePath = "dist"