Error msb3073 the command npm install exited with code 127

I am creating the azure devops pipeline to build an asp.net web app with react and then create a Docker image. I use below azure-pipeline.yml, and docker file, but I have issue on npm package. Can

I am creating the azure devops pipeline to build an asp.net web app with react and then create a Docker image. I use below azure-pipeline.yml, and docker file, but I have issue on npm package.
Can anybody support me how can I create it:
Docker file to restore, build, push and create image:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ["./react.csproj", "./"]
RUN dotnet restore "./react.csproj"
COPY . .
WORKDIR "/src/"
RUN dotnet build "react.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "react.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "react.dll"]

azure pipeline to create an image and push it to azure container registry.

pool:
  name: Default

steps:
- task: NuGetToolInstaller@0
  displayName: 'Use NuGet 4.4.1'
  inputs:
    versionSpec: 4.4.1

- task: NuGetCommand@2
  displayName: 'NuGet restore'
  inputs:
    restoreSolution: '$(Parameters.solution)'

- task: DockerCompose@0
  displayName: 'Build services'
  inputs:
    azureSubscription: '$(Parameters.azureSubscriptionEndpoint)'
    azureContainerRegistry: '$(Parameters.azureContainerRegistry)'
    dockerComposeFile: '$(Parameters.dockerComposeFile)'
    dockerComposeFileArgs: 'DOCKER_BUILD_SOURCE='
    action: 'Build services'
    additionalImageTags: '$(Build.BuildId)'
    includeLatestTag: true

- task: DockerCompose@0
  displayName: 'Push services'
  inputs:
    azureSubscription: '$(Parameters.azureSubscriptionEndpoint)'
    azureContainerRegistry: '$(Parameters.azureContainerRegistry)'
    dockerComposeFile: '$(Parameters.dockerComposeFile)'
    additionalDockerComposeFiles: 'docker-compose.ci.yml'
    dockerComposeFileArgs: 'DOCKER_BUILD_SOURCE='
    action: 'Push services'
    additionalImageTags: '$(Build.BuildId)'
    includeLatestTag: true

- task: DockerCompose@0
  displayName: 'Lock services'
  inputs:
    azureSubscription: '$(Parameters.azureSubscriptionEndpoint)'
    azureContainerRegistry: '$(Parameters.azureContainerRegistry)'
    dockerComposeFile: '$(Parameters.dockerComposeFile)'
    additionalDockerComposeFiles: 'docker-compose.ci.yml'
    dockerComposeFileArgs: 'DOCKER_BUILD_SOURCE='
    action: 'Lock services'
    outputDockerComposeFile: '$(Build.ArtifactStagingDirectory)/docker-compose.yml'

- task: CopyFiles@2
  displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)'
  inputs:
    Contents: |
     **/docker-compose.env.yml
     **/docker-compose.env.*.yml
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: docker-compose'
  inputs:
    ArtifactName: 'docker-compose'

You may find the react.csproj here:

<Project Sdk="Microsoft.NET.Sdk.Web">

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

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

  <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)build**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>

</Project>

When you try do dockerize ASP.NET Core application with Angular, you most probably faced with this error.

The command “npm install” exited with code 127.

So, why it happens ?

Because of publish  stage happens on based microsoft/dotnet:2.1-sdk image. So, this dockerfile contains only dotnet sdk but not npm installed this base image.

What is the solution ?

We should use other image in our dockerfile too. In order to be able build our frontend.

I seperated npm side as different build stage on dockerfile. So, firstly you should remove npm commands on publish in *.csproj file. So, i removed all red lines entirely  from my *.csproj file.

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
 <TargetFramework>netcoreapp2.1</TargetFramework>
 <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
 <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
 <IsPackable>false</IsPackable>
 <SpaRoot>ClientApp</SpaRoot>
 <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules**</DefaultItemExcludes>

<!-- Set this to true if you enable server-side prerendering -->
 <BuildServerSideRenderer>false</BuildServerSideRenderer>
 <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
 <UserSecretsId>780680aa-e49b-4ae0-b3bc-868cb1e482aa</UserSecretsId>
 </PropertyGroup>

<ItemGroup>
 <PackageReference Include="Microsoft.AspNetCore.App" />
 <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
 <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.1.1" />
 <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
 </ItemGroup>

<ItemGroup>
 <!-- Don't publish the SPA source files, but do show them in the project files list -->
 <Content 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 -- --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>
 </ResolvedFileToPublish>
 </ItemGroup>
 </Target>

</Project>

So, try to build docker file again, it should be build without any error.

docker build -t angularapp2 .

Yes, it was successfull. But on this way we builded dockerfile only with backend but not with frontend side. We should build and publish our frontend in dockerfile. So, my Dockerfile looks like this:

FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY ["angularApp2.csproj", "angularApp2/"]
RUN dotnet restore "angularApp2/angularApp2.csproj"
COPY . angularApp2/
WORKDIR "/src/angularApp2"
RUN dotnet build "angularApp2.csproj" -c Release -o /app

FROM build AS publish
RUN dotnet publish "angularApp2.csproj" -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "angularApp2.dll"]

I added frontend build stage to this file, after changes my code will look like below:

FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY ["angularApp2.csproj", "angularApp2/"]
RUN dotnet restore "angularApp2/angularApp2.csproj"
COPY . angularApp2/
WORKDIR "/src/angularApp2"
RUN dotnet build "angularApp2.csproj" -c Release -o /app

FROM build AS publish
RUN dotnet publish "angularApp2.csproj" -c Release -o /app

#Angular build
FROM node as nodebuilder

# set working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app

# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH


# install and cache app dependencies
COPY ClientApp/package.json /usr/src/app/package.json
RUN npm install
RUN npm install -g @angular/cli@1.7.0 --unsafe

# add app

COPY ClientApp/. /usr/src/app

RUN npm run build

#End Angular build

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
RUN mkdir -p /app/ClientApp/dist
COPY --from=nodebuilder /usr/src/app/dist/. /app/ClientApp/dist/
ENTRYPOINT ["dotnet", "angularApp2.dll"]

And build you docker image again and run it.

docker build -t angularapp2 .
docker run -p 80:80 -d angularapp2

yaay it works 😊


Jun 25, 2018
4 min read |
React,
ASP.Net Core,
.Net,
Docker

I started a new project this week, a SPA written in React with an ASP.Net Core 2.1.1 backend hosted in a Docker container. This is the tale of how I got all those pieces working together.

The running code from this article can be seen on GitHub.

Creating a ASP.Net Core project with React from a dotnet template

Microsoft has helpfully created a template for this very scenario.

dotnet new react -o react-app
cd react-app

I created the app from the template and changed directory. From there I ran:

The result was a failed build with the following error:

error NU1605: Detected package downgrade: Microsoft.AspNetCore.SpaServices.Extensions from 2.1.1 to 2.1.0. Reference the package directly from the project to select a different version.

The .csproj-file contained these references:

<ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.App" />
   <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="2.1.0" />
</ItemGroup>

The Microsoft.AspNetCore.App meta-package is new from ASP.Net 2.1.1. This package should only be reference without a version number, thus needing no change when the version number is upped by Microsoft.

No such luck with Microsoft.AspNetCore.SpaServices.Extensions which only works with .Net Core version 2.1.0. I removed this reference:

<ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

And tried dotnet run again:

Running React in an ASP.Net Core Docker-container

The app ran locally, now was the time to create a Docker container for testing and publishing.

Creating Dockerfile

Avid readers of this blog will know that I use multi-stage Dockerfiles for my containers. I’m basing these on the official dotnet images supplied by Microsoft.

You’ll also remember that we need a .dockerignore-file to prevent locally built items to be copied.

Creating a Dockerfile for building and running a regular ASP.Net Core 2.1.1 app is easy:

FROM microsoft/dotnet:2.1.301-sdk AS builder
WORKDIR /source

COPY *.csproj .
RUN dotnet restore

COPY ./ ./

RUN dotnet publish "./react-app.csproj" --output "./dist" --configuration Release --no-restore

FROM microsoft/dotnet:2.1.1-aspnetcore-runtime
WORKDIR /app
COPY --from=builder /source/dist .
EXPOSE 80
ENTRYPOINT ["dotnet", "react-app.dll"]

And .dockerignore is equally simple:

Building an image with the React-app from the template using this Dockerfile, resulted in:

docker build -t hjerpbakk/react-app .

react-app -> /source/bin/Release/netcoreapp2.1/react-app.Views.dll
  /bin/sh: 2: /tmp/tmp9dfbab16bdf54855b9b5da0e68821afa.exec.cmd: npm: not found
/source/react-app.csproj(34,5): error MSB3073: The command "npm install" exited with code 127.
The command '/bin/sh -c dotnet publish "./react-app.csproj" --output "./dist" --configuration Release --no-restore' returned a non-zero code: 1

I needed to install Node.js in order to build react. Thus, the Dockerfile needed to be:

FROM microsoft/dotnet:2.1.301-sdk AS builder
WORKDIR /source

RUN curl -sL https://deb.nodesource.com/setup_10.x |  bash -
RUN apt-get install -y nodejs

COPY *.csproj .
RUN dotnet restore

COPY ./ ./

RUN dotnet publish "./react-app.csproj" --output "./dist" --configuration Release --no-restore

FROM microsoft/dotnet:2.1.1-aspnetcore-runtime
WORKDIR /app
COPY --from=builder /source/dist .
EXPOSE 80
ENTRYPOINT ["dotnet", "react-app.dll"]

But the build did not succeed yet.

Getting Node.js to restore correct versions of NPM modules

With the updated Dockerfile, the command docker build -t hjerpbakk/react-app . gave the following error now:

  > react_app@0.1.0 build /source/ClientApp
  > react-scripts build
  
  sh: 1: react-scripts: not found
  npm ERR! file sh
  npm ERR! code ELIFECYCLE
  npm ERR! errno ENOENT
  npm ERR! syscall spawn
  npm ERR! react_app@0.1.0 build: react-scripts build
  npm ERR! spawn ENOENT
  npm ERR!
  npm ERR! Failed at the react_app@0.1.0 build script.
  npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
  
  npm ERR! A complete log of this run can be found in:
  npm ERR!     /root/.npm/_logs/2018-06-25T11_26_23_802Z-debug.log
/source/react-app.csproj(35,5): error MSB3073: The command "npm run build" exited with code 1.
The command '/bin/sh -c dotnet publish "./react-app.csproj" --output "./dist" --configuration Release --no-restore' returned a non-zero code: 1

This was not enough information for me to debug this issue. I needed to run commands using bash in the partially built container.

To do this, make a note of the last stage of the build process that succeeded. In this case, it was the dotnet publish "./react-app.csproj" --output "./dist" --configuration Release --no-restore command that failed. Scrolling up in the output, we find the id of the stage on which this command was run:

Bash could then run in the container using the following command:

docker run --rm -it 2d7680490d55 bash -il

This will start Bash on the partially built container using interactive mode. You don’t need to run Bash, all applications present on the container can be run in this way as per this answer on StackOverflow.

I read the logs from the npm install command and remembered that I forgot to exclude the locally built Node modules with .dockerignore. Thus, modules built for debug mode on macOS was used in production mode on Debian. I updated .dockerignore:

**/obj/
**/bin/
**/node_modules/

And finally docker build -t hjerpbakk/react-app . completed successfully!

The container could the be run with:

docker run -p 80:80 hjerpbakk/react-app

Conclusion

ASP.Net, React and Docker are great tools to use together. However, they are also moving targets in constant motion. Thus at the time of writing, the following was needed to go from Hello World! locally to Hello World! running in a Docker container:

  1. Remove reference added by the template that is no longer needed.
  2. The container image needed more tools than the image from Microsoft provided.
  3. Never include locally built files in the build context used by the Dockerfile. .dockerignore truly is your friend!

Hello.
Follow the instruction (with Linux containers) with one exception: Publish
to your own Docker Hub, not Azure Container Registry.

It will fail:

#18 2.611 Determining projects to restore…
#18 3.276 All projects are up-to-date for restore.
#18 4.580 Project1 -> /src/Project1/bin/Release/netcoreapp3.1/Project1.dll
#18 4.585 Project1 ->
/src/Project1/bin/Release/netcoreapp3.1/Project1.Views.dll
#18 4.695 /bin/sh: 2: /tmp/tmp8d3852f4619c4e30a4fee57e39728197.exec.cmd:
npm: not found
#18 4.697 /src/Project1/Project1.csproj(38,5): error MSB3073: The command
«npm install» exited with code 127.
#18 ERROR: executor failed running [/bin/sh -c dotnet publish
«Project1.csproj» -c Release -o /app/publish]: exit code: 1
——

[publish 1/1] RUN dotnet publish «Project1.csproj» -c Release -o

/app/publish:
——
executor failed running [/bin/sh -c dotnet publish «Project1.csproj» -c
Release -o /app/publish]: exit code: 1
2>Build failed. Check the Output window for more details.
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
========== Publish: 0 succeeded, 1 failed, 0 skipped ==========

Thanks in advance, Gunnar Siréus

Den tis 3 aug. 2021 kl 00:26 skrev Nathan Carlson ***@***.***

This post is the second part in a two-part article on managing Javascript and CSS dependencies within a multiple-page application written in ASP.NET Core MVC.
In the first part we’ve specified our front-end dependencies, bumped the version numbers and set up a webpack build system.
In this part we’ll be tackling performance issues and we’ll make sure that the entire project (front-end and back-end) can be built using a single command.

Performance

While everything seems to be working ok, there are a few improvements to be made. When fast clicking between pages (i.e., ‘Home’ and ‘Privacy’) you might notice that CSS gets applied after the page is rendered by the browser. That’s because the npm package style-loader plugs the CSS into the page after it was loaded causing the browser to re-render the page!
On top of that, the Javascript bundle — which contains the CSS — is very large. It contains the entire Bootstrap CSS as well as some Bootstrap Javascript functions and all of JQuery!

Let’s take care of this. Our aim is the following:

  • Bundling CSS into a separate CSS file that can be
    statically referenced from an HTML link tag
  • Splitting up the Javascript code into separate
    bundles. Many pages have Javascript code that is unique to them and not every page needs JQuery

As mentioned in part 1, we’re only keeping JQuery around because ASP.NET Core’s client-side form validation depends on it.

To make sure that the solution that I’m about to present here fits all use cases, let’s set up a simple prototype. These are the pages we’re going to use:

Alt Text

All of the pages include the site’s CSS as well as a bit of Javascript that is common to all pages. The individual pages are:

  • The Index page, which has a bit of custom
    Javascript code running on it (index.js)
  • The Privacy page, which has a Bootstrap component
    on it that needs special Bootstrap JavaScript code to
    function. We’ll load that code from bootstrap_js.js — not to be
    confused with Bootstrap’s CSS, which is used everywhere
    on the site.
  • The Contact page, which has a form on it that is
    backed by ASP.NET Core’s form validation. Validation
    can be done both server-side and client-side.
    We’ll load client-side validation code from validation.js.

With all of this in the pipeline you might be wondering why we went down this road in the first place. We will end up with individual CSS/JS files that are hard-referenced from the HTML pages… that’s what we started with! Well, sort of, but not quite. Here’s what’s different:

  • We’re referencing our libraries with a specific version number
  • The dependencies are not placed inside the project tree
  • We’ll end up with a performance gain (in the standard MVC template all of Bootstrap and JQuery are referenced from all pages)
  • Our build system is extensible: Want Sass? No problem! Want to use the very latest ECMAScript features? You got it! Need minification or obfuscation? No problemo!

Just imagine what it would be like if all of this was already present in the standard MVC template. Then you’d have all of this modern front-end goodness without having to set it up yourself. Ha!

Ok, let’s go.

Splitting up the bundle

This is the current state of things:

$ ll wwwroot/dist/
total 3164
-rw-rw-r-- 1 lars lars 1482707 Mar 10 13:48 site.entry.js
-rw-rw-r-- 1 lars lars 1756579 Mar 10 13:48 site.entry.js.map

Enter fullscreen mode

Exit fullscreen mode

That’s over 1400K worth of Javascript and CSS code.

These are the separate blocks that we had identified in the diagram above:

  • Sitewide CSS (Bootstrap CSS and custom CSS)
  • Sitewide Javascript
  • Bootstrap’s JS code: for fancy popup buttons and the like
  • Validation scripts (basically JQuery with some extras):
    for forms that use ASP.NET Core’s form validation
  • A sample Javascript code block that is unique to a
    specific page (let’s take ‘Home’, so index.js)

This is what our view files look like after we move all of the common parts into _Layout.cshtml:

Alt Text

To split things up we’ll dive into ClientApp/src/js/ and turn site.js into four files:

  • site.js: Javascript and CSS code that is needed
    on each page (currently contains Bootstrap CSS and
    custom CSS). Will result in a separate JS and CSS
    file as seen in the diagram
  • bootstrap_js.js: Bootstrap’s Javascript code
  • validation.js: JQuery, including the validation scripts
    for our forms
  • index.js: some dummy code that’s only applicable to ‘Home’

Here’s what they look like:

site.js

This file lost a few lines when compared to the previous version. CSS is needed on every page of the application so we’re including all of our CSS here:

import 'bootstrap/dist/css/bootstrap.css';

// Custom CSS imports
import '../css/site.css';

console.log('The 'site' bundle has been loaded!');

Enter fullscreen mode

Exit fullscreen mode

bootstrap_js.js

Here, we’re including bootstrap’s Javascript code. If an import statement doesn’t include a file extension, then it’s a JS file:

import '@popperjs/core';
import 'bootstrap';

console.log('The 'bootstrap_js' bundle has been loaded!');

Enter fullscreen mode

Exit fullscreen mode

validation.js

These import lines were previously in site.js. We’re putting them into their own file so that they can be included separately:

import 'jquery';
import 'jquery-validation';
import 'jquery-validation-unobtrusive';

console.log('The 'validation' bundle has been loaded!');

Enter fullscreen mode

Exit fullscreen mode

index.js

… some dummy code:

console.log('The 'index' bundle has been loaded!');

Enter fullscreen mode

Exit fullscreen mode

Configuring the webpack build

Separate files means separate entries in webpack. Each entry is handled as a separate module and will result in a separate Javascript file. The resulting file for each entry will be named after the entry followed by the .entry.js suffix.

While we’re at it, we’ll extract the CSS out of the Javascript bundle. Instead of using the style-loader npm package we’ll use mini-css-extract-plugin, which takes care of the extraction.

Brace yourself, webpack.config.js is coming…

 const path = require('path');
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");

 module.exports = {
     entry: {
-        site: './src/js/site.js'
+        site: './src/js/site.js',
+        bootstrap_js: './src/js/bootstrap_js.js',
+        validation: './src/js/validation.js',
+        index: './src/js/index.js'
     },
     output: {
         filename: '[name].entry.js',
         path: path.resolve(__dirname, '..', 'wwwroot', 'dist')
     },
     devtool: 'source-map',
     mode: 'development',
     module: {
         rules: [
             {
                 test: /.css$/,
-                use: ['style-loader', 'css-loader'],
+                use: [{ loader: MiniCssExtractPlugin.loader }, 'css-loader'],
             },
             {
                 test: /.(eot|woff(2)?|ttf|otf|svg)$/i,
                 type: 'asset'
             },
         ]
-    }
+    },
+    plugins: [
+        new MiniCssExtractPlugin({
+            filename: "[name].css"
+        })
+    ]
 };

Enter fullscreen mode

Exit fullscreen mode

At the very top and the very bottom you can see we’re importing an npm package and adding it as a plugin respectively. Most plugins have a wide range of configuration options, but we only need to specify which filename to use for CSS files ([name].css).

In the entry section the different entries are defined and near the center of the file we’ve replaced style-loader with the plugin.

So one npm package is being replaced by another. Update package.json accordingly:

 {
     "name": "Net6NpmWebpack",
     "description": "ASP.NET Core MVC project with npm and webpack front-end configuration.",
     "repository": "https://gitlab.com/kdg-ti/integratieproject-1/guides/net6npmwebpack",
     "license": "MIT",
     "version": "4.0.0",
     "dependencies": {
         "@popperjs/core": "^2.11.2",
         "jquery": "^3.6.0",
         "jquery-validation": "^1.19.3",
         "jquery-validation-unobtrusive": "^3.2.12",
         "bootstrap": "^5.1.3"
     },
     "devDependencies": {
         "webpack": "^5.70.0",
         "webpack-cli": "^4.9.2",
         "css-loader": "^6.7.1",
-        "style-loader": "^3.3.1"
+        "mini-css-extract-plugin": "^2.6.0"
     },
     "scripts": {
         "build": "webpack"
     }
 }

Enter fullscreen mode

Exit fullscreen mode

Let’s generate those new bundles. From the ClientApp directory, enter:

$ npm install
$ npm run build

Enter fullscreen mode

Exit fullscreen mode

Which build artifacts were generated this time?

$ ll ../wwwroot/dist/
total 2116
-rw-rw-r-- 1 lars lars 301988 Mar 13 14:14 bootstrap_js.entry.js
-rw-rw-r-- 1 lars lars 273306 Mar 13 14:14 bootstrap_js.entry.js.map
-rw-rw-r-- 1 lars lars    270 Mar 13 14:14 index.entry.js
-rw-rw-r-- 1 lars lars    223 Mar 13 14:14 index.entry.js.map
-rw-rw-r-- 1 lars lars 207495 Mar 13 14:14 site.css
-rw-rw-r-- 1 lars lars 522699 Mar 13 14:14 site.css.map
-rw-rw-r-- 1 lars lars   3141 Mar 13 14:14 site.entry.js
-rw-rw-r-- 1 lars lars   1868 Mar 13 14:14 site.entry.js.map
-rw-rw-r-- 1 lars lars 365376 Mar 13 14:14 validation.entry.js
-rw-rw-r-- 1 lars lars 470538 Mar 13 14:14 validation.entry.js.map

Enter fullscreen mode

Exit fullscreen mode

The Views

After splitting up the bundle into multiple smaller bundles we now have to review our link and script tags.

With mini-css-extract-plugin in the picture, the CSS will have to be imported statically. CSS is used everywhere so we jump into _Layout.cshtml:

     ...

     <title>@ViewData["Title"] - net6npmwebpack</title>
     <script src="~/dist/site.entry.js" defer></script>
+    <link rel="stylesheet" href="~/dist/site.css">
 </head>
 <body>
     <header>

     ...

Enter fullscreen mode

Exit fullscreen mode

The Home/Index.cshtml page has custom Javascript:

 @{
     ViewData["Title"] = "Home Page";
 }

+@section Scripts
+{
+    <script src="~/dist/index.entry.js" defer></script>
+}
+
 <div class="text-center">

 ...

Enter fullscreen mode

Exit fullscreen mode

The Privacy.cshtml page gets a fancy Bootstrap component. Let’s pick
a dropdown menu button!

 @{
     ViewData["Title"] = "Privacy Policy";
 }
+
+@section Scripts
+{
+    <script src="~/dist/bootstrap_js.entry.js" defer></script>
+}
+
 <h1>@ViewData["Title"]</h1>

 <p>Use this page to detail your site's privacy policy.</p>
+
+<div class="dropdown">
+    <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
+        Dropdown button
+    </button>
+    <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
+        <li><a class="dropdown-item" href="#">Action</a></li>
+        <li><a class="dropdown-item" href="#">Another action</a></li>
+        <li><a class="dropdown-item" href="#">Something else here</a></li>
+    </ul>
+</div>

Enter fullscreen mode

Exit fullscreen mode

… and then there’s the Contact page, a new page that we’ll build from scratch just to test form validation. We’ll need a view, a view-model, some new actions in the controller and a link on the site’s navigation bar.

Let’s start with the form itself, a new view created as Home/Contact.cshtml:

@model ContactViewModel

@{
    ViewBag.Title = "Contact";
    Layout = "_Layout";
}

@section Scripts
{
    <script src="~/dist/validation.entry.js" defer></script>
}

<h1>@ViewBag.Title</h1>

<form asp-controller="Home" asp-action="Contact">
    <div class="mb-3">
        <label asp-for="Subject" class="form-label"></label>
        <input asp-for="Subject" class="form-control"/>
        <span asp-validation-for="Subject" class="small text-danger"></span>
    </div>

    <div class="mb-3">
        <label asp-for="Message" class="form-label"></label>
        <textarea asp-for="Message" class="form-control"></textarea>
        <span asp-validation-for="Message" class="small text-danger"></span>
    </div>

    <button class="btn btn-primary" type="submit">Submit</button>
</form>

Enter fullscreen mode

Exit fullscreen mode

Subject, Message, ContactViewModel, … what are you on about!?
Let’s move out of the Views directory and into Models
ContactViewModel.cs:

using System.ComponentModel.DataAnnotations;

namespace net6npmwebpack.Models;

public class ContactViewModel
{
    [Required]
    [StringLength(30, MinimumLength = 3)]
    public string Subject { get; set; }

    [Required(ErrorMessage = "Please enter a message.")]
    public string Message { get; set; }
}

Enter fullscreen mode

Exit fullscreen mode

… but the form is GET-ing and POST-ing all over the place, how is that handled?
Time to edit HomeController:

         ...

+        [HttpGet]
+        public IActionResult Contact()
+        {
+            return View();
+        }
+
+        [HttpPost]
+        public IActionResult Contact(ContactViewModel contactVM)
+        {
+            if (ModelState.IsValid)
+            {
+                // Send an email or save the message in a table...
+                // Redirect to a page that says "Thanks for contacting us!"...
+
+                return RedirectToAction("Index");
+             }
+
+            return View();
+        }

        ...

Enter fullscreen mode

Exit fullscreen mode

… ok and the link?
Back to _Layout.cshtml:

 ...

                         <li class="nav-item">
                             <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                         </li>
+                        <li class="nav-item">
+                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Contact">Contact</a>
+                        </li>
                     </ul>
                 </div>

 ...

Enter fullscreen mode

Exit fullscreen mode

Done! (almost)
We’ve now got a full-blown webpack and NPM powered front-end with excellent performance and modern Javascript goodness.

We don’t need _ValidationscriptsPartial.cshtml anymore so be sure to remove that one from your repository:

$ rm Views/Shared/_ValidationScriptsPartial.cshtml

Enter fullscreen mode

Exit fullscreen mode

If you’re consistent about adding defer to your script tags (and you should be! :)) then you can go one step further and move the Scripts section inside _Layout to that page’s head section.

 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>@ViewData["Title"] - net6npmwebpack</title>
     <script src="~/dist/site.entry.js" defer></script>
+    @await RenderSectionAsync("Scripts", required: false)
     <link rel="stylesheet" href="~/dist/site.css">
 </head>
 <body>

     ...

     <footer class="border-top footer text-muted">
         <div class="container">
             &copy; 2022 - net6npmwebpack - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
         </div>
     </footer>
-    @await RenderSectionAsync("Scripts", required: false)
 </body>
 </html>

Enter fullscreen mode

Exit fullscreen mode

The project so far can be found on GitLab as version 3 of NetCoreNpmWebpack.

Give it a spin. You’ll notice that performance is good.

Note: We’re currently only including the bootstrap_js.entry.js file on the page that contains the dropdown. This will, unfortunately, mess with our responsiveness. When we’re on an extra small screen, a hamburger menu will be shown which requires Bootstrap. So if you care about responsiveness, you’ll be better off importing the Bootstrap JavaScript code from site.js and removing bootstrap_js.js altogether.

Building the project

Running the project is perhaps easier said than done. Let’s recap:

$ npm install          # only after a modification to package.json
$ npm run build
$ dotnet build
$ dotnet run

Enter fullscreen mode

Exit fullscreen mode

That’s too much typing for anyone, let’s automate that a bit.

The .csproj file can be extended with some extra build commands. Honestly, csproj-Hocus Pocus is a bit of uncharted territory for me (although it reminds me of the Ant build system), but this seems to work fine:

 <Project Sdk="Microsoft.NET.Sdk.Web">

     <PropertyGroup>
         <TargetFramework>net6.0</TargetFramework>
         <Nullable>disable</Nullable>
         <ImplicitUsings>enable</ImplicitUsings>
+        <IsPackable>false</IsPackable>
+        <MpaRoot>ClientApp</MpaRoot>
+        <WWWRoot>wwwroot</WWWRoot>
+        <DefaultItemExcludes>$(DefaultItemExcludes);$(MpaRoot)node_modules**</DefaultItemExcludes>
     </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.3"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <!-- Don't publish the MPA source files, but do show them in the project files list -->
+        <Content Remove="$(MpaRoot)**"/>
+        <None Remove="$(MpaRoot)**"/>
+        <None Include="$(MpaRoot)**" Exclude="$(MpaRoot)node_modules**"/>
+    </ItemGroup>
+
+    <Target Name="NpmInstall" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(MpaRoot)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="$(MpaRoot)" Command="npm install"/>
+    </Target>
+
+    <Target Name="NpmRunBuild" BeforeTargets="Build" DependsOnTargets="NpmInstall">
+        <Exec WorkingDirectory="$(MpaRoot)" Command="npm run build"/>
+    </Target>
+
+    <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
+        <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
+        <Exec WorkingDirectory="$(MpaRoot)" Command="npm install"/>
+        <Exec WorkingDirectory="$(MpaRoot)" Command="npm run build"/>
+
+        <!-- Include the newly-built files in the publish output -->
+        <ItemGroup>
+            <DistFiles Include="$(WWWRoot)dist**"/>
+            <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
+                <RelativePath>%(DistFiles.Identity)</RelativePath>
+                <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+                <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+            </ResolvedFileToPublish>
+        </ItemGroup>
+    </Target>
+
+    <Target Name="NpmClean" BeforeTargets="Clean">
+        <RemoveDir Directories="$(WWWRoot)dist"/>
+        <RemoveDir Directories="$(MpaRoot)node_modules"/>
+    </Target>

 </Project>

Enter fullscreen mode

Exit fullscreen mode

As you may have figured out from the diff above, the npm install command is only executed in case the node_modules directory is absent. That’s something to keep in mind in case you make modifications to package.json!

Now we can build the project in its entirety using the dotnet build command. Excellent! (pressing the run or compile button from your IDE works just as well)

Auto-building the bundle

To make life even easier, we want to automagically rebuild the bundle whenever the front-end code changes. At the same time we don’t want to restart ASP.NET Core’s HTTP server (Kestrel) when that happens.

To make this happen, we’ll add a webpack watcher for the front-end files that will trigger a rebuild. In package.json:

 ...
     },
     "scripts": {
-        "build": "webpack"
+        "build": "webpack",
+        "watch": "webpack --watch"
     }
 }

Enter fullscreen mode

Exit fullscreen mode

While editing front-end code our workflow will look like this:

  1. npm run watch (executed from within the ClientApp directory)
  2. dotnet run

(Note: I would advise against using dotnet watch since it seems to continuously detect changes to the bundle causing an endless rebuild loop)

Version 4 of the sample project can be found here.

Wrapping up

We now have a flexible and extensible project that is using modern front-end technologies and has excellent performance.

We’ve had to cover quite a bit of ground since many of these techniques are absent in most tutorials. Bower, Grunt and Gulp were dominant just a few years ago, but are now on their decline. Many sources on the internet still refer to these kings of yesteryear. However, on Bower’s website you can see that they are actively recommending alternatives.

I think that this guide may have filled a gap by bringing npm and webpack into MVC and MPA applications, more specifically .NET Core and .NET 5/6 apps.

What’s left?

There is no distinction yet between “Development” and “Production”. Minification of JavaScript code as well as CSS pruning are still to be added. I’m confident, though, that the flexibility of the build system won’t make that too challenging.

If you have any other suggestions, then please let me know in the comments below.

Good luck building your MVC application!

Когда я создаю простой React и Net Core 2.2. Проект с командой dotnet new react и добавляет файлы для сборки и развертывания в Docker приложения, но Docker не может собрать.

Мой файл Docker Compose содержит:

version: '3'
services:
  netreact:
    container_name: netreactapp
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
    restart: always

И мой Dockerfile содержит:

FROM microsoft/dotnet:sdk AS base
WORKDIR /app
FROM microsoft/dotnet:sdk AS build
WORKDIR /src
COPY *.sln ./
COPY MyReactApp/*.csproj ./MyReactApp/
RUN dotnet restore
WORKDIR /src/MyReactApp
RUN dotnet publish -c Release -o /app /property:PublishWithAspNetCoreTargetManifest=false
FROM build AS publish
RUN dotnet publish -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "MyReactApp.dll"]

Поэтому, когда я запускаю команду для сборки и docker-compose up -d моего докера, они не могут восстановить Npm со следующей ошибкой:

Step 16/22 : RUN dotnet publish -c Release -o /app /property:PublishWithAspNetCoreTargetManifest=false
 ---> Running in dc0532c03e90
Microsoft (R) Build Engine version 16.0.450+ga8dc7f1d34 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 276.74 ms for /src/MyReactApp/MyReactApp.csproj.
  StWebApp -> /src/MyReactApp/bin/Release/netcoreapp2.2/MyReactApp.dll
  StWebApp -> /src/MyReactApp/bin/Release/netcoreapp2.2/MyReactApp.Views.dll
  /bin/sh: 2: /tmp/tmp813c90672ba947489ab89ec17eb80722.exec.cmd: npm: not found
/src/MyReactApp/MyReactApp.csproj(48,5): error MSB3073: The command "npm install" exited with code 127.
ERROR: Service 'netreactapp' failed to build: The command '/bin/sh -c dotnet publish -c Release -o /app /property:PublishWithAspNetCoreTargetManifest=false' returned a non-zero code: 1

Я думаю, это потому, что конвейер csproj пытается собрать папку ClientApp с помощью npm. Есть ли обходной путь или способ исправить это?

  • Remove From My Forums
  • Question

  • Hello everyone. I use Visual Studio for Mac and having an error like this.

    error MSB3073: The command «xcopy «/Users/admin/Downloads/csharp-anonymous-pipes/AnonymousPipeExample/SomeOtherApp/bin/Debug/SomeOtherApp.exe» «/Users/admin/Downloads/csharp-anonymous-pipes/AnonymousPipeExample/SomeApp/bin/Debug/»
    /y» exited with code 127

    it happens when i clone from github and i try to search google but no answer. Anyone can help me? 

    • Moved by

      Tuesday, November 20, 2018 4:23 PM
      VS related

    • Edited by
      dummy_coder
      Tuesday, November 20, 2018 5:05 PM

Понравилась статья? Поделить с друзьями:
  • Error msb3073 cmake
  • Error msb3030 could not copy the file
  • Error msb3027 не удалось скопировать
  • Error msb3021 не удалось скопировать файл
  • Error msb1003 укажите проект или файл решения