BLOG

Angular App + Asp.Net Core API + Azure, Continuous deployment

Wednesday, February 7, 2018 Cedric Arnould

1/ Context

I wrote this article after spending 4 days to find how to automatically deploy an .NetCore API and a pure Angular-cli App. I didnt find the complete resource I was looking for. Of course, I could use VSTS to automatically deploy them. But I wanted someting easier to configure and to fast reproduce on each new projects. I hope this article will help you 😃
The complete code is available on Git: https://github.com/ranouf/AspNetCore-SignalR

A) I have :

  • 2 projects:
    • .NetCore API, (src/server/Asp.netCoreSignalR.API)
    • a website Angular App, (src/client)
  • an Azure Web App

B) I want to

  • have angular and .net core separated in Visual Studio,
  • run both on local,
  • be able to automatically deploy theses projects on each Pull Request on Azure,

2/ Run in Local

A) Create the Angular App

To create a new Angular App, we use Angular-cli:
cd src/
npm install -g @angular/cli
ng new client
To test the project:
ng serve

B) Create a new .NetCore Api project

Select new Project Asp.Net Core Web Application Select Web API

C) Configure the API to allow Html Page

In Startup.cs, add:
app.UseDefaultFiles();
app.UseStaticFiles();
Just before:
 app.UseMvc();

D) Configure local deployment

We want to be able to test the angular app in local. There is 2 ways to do that.

1) By post build command

So, when we are in debug, on each .Net Core API build a call to npm run build will be done. This command will build will automatically build the Angular and deploy it in src/server/AspNetCore-SignalR.Api/wwwroot/. Edit the .csproj Add the following section:
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
    <PreBuildEvent>
      cd "$(MSBuildProjectDirectory)/../../client"
      npm run build
    </PreBuildEvent>
  </PropertyGroup>
Edit the script section in src/client/packages.json, and add:
  "scripts": {
    [...]
    "build": "ng build --output-path=../server/AspNetCore-SignalR.Api/wwwroot/",
    [...]
  },
The big inconvenient of this way s on each build, the angular app is build again which takes few seconds each time. So the other way is to use a proxy on Angular App

2) By proxy

We will configure the Angular App to like this all the call to the API will be automatically redirected to IIS Express API instance.
a/ Get the localhost Url:
In Properties of the API project In Debug tab, copy the App URL
b/ Configure the proxy
Create a new file in the Angular App called proxy.config.json with the following code:
{
  "/api": {
    "target": "http://localhost:49402",
    "secure": false
  }
}
c/ Configure the Angular App
In packages.json edit the start command like this:
"start": "ng serve --proxy-config proxy.config.json",
d/ Launch the angular server
Execute the following command:
npm run start
e/ Configure the Client Start Options
We want to launch the client website on the ng server address (which is locahost:4200 by default) Open start options Configure the launch url
f/ Configure Visual Studio to launch both
Select Set Startup Projects Select Multiple projects, and Start without debugging for the client Then F5 or ctrl + F5

3/ Automatically Deploy on Azure

There is 2 ways to do it, we can use the Kudu deploy Script and like previously the BuildEvents.

A) Custom deployment

First I tried to use the BuildEvents, but I dont understand why (at the beginning, see B) BuildEvents for more details) all the post BuildEvents commands didn't execute. So I tried to make a custom deployment using Kudu Script.

1) Deployment file

Create a .deployment at the root of your branch with the following code:
[config]
command = deploy.cmd

1) Kudu script file

It s a not short script file, so I will separate it in different parts.
a/ Diagnostic
This will allow you to manage the trace level (more information here: Diagnostic related settings ).
@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off
b/ Setup
By enabling enabledelayedexpansion we tell the command processor to delay resolution of variables until it executes them. Then we declare the source and target folders, we will use them after.
setlocal enabledelayedexpansion

IF NOT DEFINED DEPLOYMENT_SOURCE (
  SET DEPLOYMENT_SOURCE=%ARTIFACTS%\repository
)

IF NOT DEFINED DEPLOYMENT_TARGET (
  SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot
)
c/ Deployment
First the script will set off the server by creating a App_Offline.htm file, will deploy the API, Angular App and will set on the server.
echo Source: %DEPLOYMENT_SOURCE%...
echo Deployment: in %DEPLOYMENT_TARGET%...

echo Set Server off
touch %DEPLOYMENT_TARGET%\App_Offline.htm

echo Deploy AspNetCore-SignalR.Api
call :ExecuteCmd dotnet publish src\server\AspNetCore-SignalR.Api\ -o %DEPLOYMENT_TARGET%
IF !ERRORLEVEL! NEQ 0 goto error

echo Move %DEPLOYMENT_SOURCE%\src\client
call :ExecuteCmd  pushd  %DEPLOYMENT_SOURCE%\src\client

echo Npm install
call :ExecuteCmd %DEPLOYMENT_SOURCE%\src\client\npm install --production
IF !ERRORLEVEL! NEQ 0 goto error

echo Deploy Angular App
call :ExecuteCmd %DEPLOYMENT_SOURCE%\src\client\ng build --env=prod --prod --output-path=%DEPLOYMENT_TARGET%
IF !ERRORLEVEL! NEQ 0 goto error
popd

echo Set Server on
rm %DEPLOYMENT_TARGET%\App_Offline.htm
c/ Functions and Goto
As you probably noticed previously, the script called ExecuteCmd function, this functions will execute the command and display the error message in case of failed. In case of error, the script exit.
IF !ERRORLEVEL! NEQ 0 goto error

goto end

:: Execute command routine that will echo out when error
:ExecuteCmd
setlocal
set _CMD_=%*
call %_CMD_%
if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
exit /b %ERRORLEVEL%

:error
endlocal
echo An error has occurred during web site deployment.
call :exitSetErrorLevel
call :exitFromFunction 2>nul

:exitSetErrorLevel
exit /b 1

:exitFromFunction
()

:end
endlocal
echo Finished successfully.
But I wasn't enough satisfied, on each new project, I will have to update the script, I wanted something more flexible. So I tried to understood why the BuildEvents didnt work correctly before.

B) BuildEvents

When we are in Release, on each new deploy on master, a new npm install will be done and then deploy to the D:/home/site/wwwroot/wwwroot/. Edit the .csproj Add the following section:
    <PropertyGroup Condition="'$(Configuration)' == 'Release'">
      <PreBuildEvent>
        cd "$(MSBuildProjectDirectory)/../../client"
        npm install
      </PreBuildEvent>
      <PostBuildEvent>
        cd "$(MSBuildProjectDirectory)/../../client"
        npm run build-azure
      </PostBuildEvent>
    </PropertyGroup>
Note: It tooks me a long time to discover than on Azure, after executing npm install, the instructions after are not [always] executed and there was not even an error message to explain why. So it's really important to have the npm install in the prebuildevent and the deploy in postbuild event. Edit the script section in src/client/packages.json, and add:
  "scripts": {
    [...]
    "build": "ng build-azure --env=prod --prod --aot --output-path=D:/home/site/wwwroot/wwwroot/",
    [...]
  },
Note: It s really important to deploy the Angular App in D:/home/site/wwwroot/wwwroot/ and not in D:/home/site/wwwroot/.

3/ Configure Azure

We will create a new WebApp and configure it to automatically deploy the Api and Angular App on each new PullRequest on Master branch. Go on Azure Portal  and create a new WebApp. Select Deployment options Choose the source, in our case VSTS Select the project

4/ Conclusion

We have seen how to manage a pure Angular-cli app with a .NetCore Api in local and how to automatically deploy it on Azure on each new pull request.