Search code examples
asp.netangularasp.net-core

Change Angular output directory from wwwroot to another folder in ASP.NET


Currently my ASP .NET project with angular frontend (inside one Project) is putting the Angular output folder (dist content) inside /wwwroot folder when published.

Website_ASP.exe
web.config
wwwroot
    index.html
    scripts ....
    etc..

Now I don't want the output inside wwwroot folder at all. this way it's just not tidy and clean. I want it inside a different folder in root like ClientApp/dist and serve that when opening base url : http://localhost:5000.

I don't know which and how many files I need to edit for this to happen, I've tried setting a staticfile directory on said folder but then the Angular doesn't work properly.

Currently:

app.UseDefaultFiles();
app.UseStaticFiles();

and I have tried :

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
       Path.Combine(builder.Environment.ContentRootPath, "ClientApp")),
RequestPath = "/"
});

How many files do I need to edit in Angular and ASP to completely change the frontend path to a different folder outside wwwroot, or at the very least inside wwwroot/dist.


Solution

  • My test sample:

    Program.cs or your Startup.cs file

    using Microsoft.Extensions.FileProviders;
    
    namespace AngularSPA
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                var builder = WebApplication.CreateBuilder(args);
    
                // Add services to the container.
                builder.Services.AddControllersWithViews();
    
                var app = builder.Build();
    
                // Configure the HTTP request pipeline.
                if (!app.Environment.IsDevelopment())
                {
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                //app.UseStaticFiles();
                // ==========> change here
                app.UseStaticFiles(new StaticFileOptions
                {
                    FileProvider = new PhysicalFileProvider(
                    Path.Combine(app.Environment.ContentRootPath, "ClientApp", "dist")),
                    RequestPath = ""
                });
                app.UseRouting();
    
    
                app.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
    
                app.MapFallbackToFile("index.html");
    
                app.Run();
            }
        }
    }
    

    angular.json

    {
      "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
      "version": 1,
      "newProjectRoot": "projects",
      "projects": {
        "AngularSPA": {
          "projectType": "application",
          "schematics": {
            "@schematics/angular:application": {
              "strict": true
            }
          },
          "root": "",
          "sourceRoot": "src",
          "prefix": "app",
          "architect": {
            "build": {
              "builder": "@angular-devkit/build-angular:browser",
              "options": {
                "progress": false,
                //"outputPath": "dist",
                //  ======> change here
                "outputPath": "../ClientApp/dist",
                "index": "src/index.html",
                "main": "src/main.ts",
                "polyfills": "src/polyfills.ts",
                "tsConfig": "tsconfig.app.json",
                "allowedCommonJsDependencies": [
                  "oidc-client"
                ],
                "assets": [
                  "src/assets"
                ],
                "styles": [
                  "node_modules/bootstrap/dist/css/bootstrap.min.css",
                  "src/styles.css"
                ],
                "scripts": []
              },
              "configurations": {
                "production": {
                  "budgets": [
                    {
                      "type": "initial",
                      "maximumWarning": "500kb",
                      "maximumError": "1mb"
                    },
                    {
                      "type": "anyComponentStyle",
                      "maximumWarning": "2kb",
                      "maximumError": "4kb"
                    }
                  ],
                  "fileReplacements": [
                    {
                      "replace": "src/environments/environment.ts",
                      "with": "src/environments/environment.prod.ts"
                    }
                  ],
                  "outputHashing": "all"
                },
                "development": {
                  "buildOptimizer": false,
                  "optimization": false,
                  "vendorChunk": true,
                  "extractLicenses": false,
                  "sourceMap": true,
                  "namedChunks": true
                }
              },
              "defaultConfiguration": "production"
            },
            "serve": {
              "builder": "@angular-devkit/build-angular:dev-server",
              "configurations": {
                "production": {
                  "browserTarget": "AngularSPA:build:production"
                },
                "development": {
                  "browserTarget": "AngularSPA:build:development",
                  "proxyConfig": "proxy.conf.js"
                }
              },
              "defaultConfiguration": "development"
            },
            "extract-i18n": {
              "builder": "@angular-devkit/build-angular:extract-i18n",
              "options": {
                "browserTarget": "AngularSPA:build"
              }
            },
            "test": {
              "builder": "@angular-devkit/build-angular:karma",
              "options": {
                "main": "src/test.ts",
                "polyfills": "src/polyfills.ts",
                "tsConfig": "tsconfig.spec.json",
                "karmaConfig": "karma.conf.js",
                "assets": [
                  "src/assets"
                ],
                "styles": [
                  "src/styles.css"
                ],
                "scripts": []
              }
            },
            "server": {
              "builder": "@angular-devkit/build-angular:server",
              "options": {
                "outputPath": "dist-server",
                "main": "src/main.ts",
                "tsConfig": "tsconfig.server.json"
              },
              "configurations": {
                "dev": {
                  "optimization": true,
                  "outputHashing": "all",
                  "sourceMap": false,
                  "namedChunks": false,
                  "extractLicenses": true,
                  "vendorChunk": true
                },
                "production": {
                  "optimization": true,
                  "outputHashing": "all",
                  "sourceMap": false,
                  "namedChunks": false,
                  "extractLicenses": true,
                  "vendorChunk": false
                }
              }
            }
          }
        }
      },
      "defaultProject": "AngularSPA",
      "cli": {
        "analytics": false
      }
    }
    

    .csproj

    <Project Sdk="Microsoft.NET.Sdk.Web">
    
      <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <IsPackable>false</IsPackable>
        <SpaRoot>ClientApp\</SpaRoot>
        <SpaProxyServerUrl>https://localhost:44440</SpaProxyServerUrl>
        <SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="7.0.3" />
      </ItemGroup>
      <!-- ==============> add code here to include client\dist folder -->
      <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\**" />
    
        <Content Include="ClientApp\dist\**">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </Content>
      </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 -- --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>
    

    My test result

    enter image description here

    enter image description here