Unit test object with the static extension method in c#

I’m facing this issue on almost every new project in .net core. The most annoying for me are IServiceCollection and IConfigurationBuilder. These interfaces have many static extension methods. For example the IServiceCollection has AddScoped, AddDbContext, AddSingleton, AddLogging, AddOptions and so on. They are all static extensions and Moq will not work on them.

There are a few approaches to solve this. In this article I will discuss the combination of delegate and facade pattern to simplify the Moq for the objects with extensions.

The first step is to create a Facade file for example, ServiceCollectionFacade.cs. I wrapped all calls to ServiceCollection. First, derive the class from an interface: 

public class ServiceCollectionFacade : IServiceCollectionFacade

I will describe the interface later. Next, create private serviceCollection:

private readonly IServiceCollection serviceCollection;

Create a class constructor that will take parameter as IServiceCollection:

public ServiceCollectionFacade(IServiceCollection serviceCollection)
{
    this.serviceCollection = serviceCollection;
}

And finally add all your extension method as the following:

...
        public IServiceCollection AddDbContext<TContext>(Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
           where TContext : DbContext
        {
            return this.serviceCollection.AddDbContext<TContext>(optionsAction, contextLifetime, optionsLifetime);
        }

        public IServiceCollection AddScoped<TService, TImplementation>()
            where TService : class
            where TImplementation : class, TService
        {
            return this.serviceCollection.AddScoped<TService, TImplementation>();
        }

        public IServiceCollection AddScoped<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
            where TService : class
            where TImplementation : class, TService
        {
            return this.serviceCollection.AddScoped<TService, TImplementation>(implementationFactory);
        }

        public IServiceProvider BuildServiceProvider()
        {
            return this.serviceCollection.BuildServiceProvider();
        }

...

You may want to add the IServiceCollection interface method as well.

...
        public int Count => this.serviceCollection.Count;

        public bool IsReadOnly => this.serviceCollection.IsReadOnly;

        public ServiceDescriptor this[int index] { get => this.serviceCollection[index]; set => this.serviceCollection[index] = value; }

        public void Add(ServiceDescriptor item)
        {
            this.serviceCollection.Add(item);
        }

        public void Clear()
        {
            this.serviceCollection.Clear();
        }

        public bool Contains(ServiceDescriptor item)
        {
            return this.serviceCollection.Contains(item);
        }

        public void CopyTo(ServiceDescriptor[] array, int arrayIndex)
        {
            this.serviceCollection.CopyTo(array, arrayIndex);
        }
...

The  IServiceCollectionFacade I derived from the IServiceCollection and add extension methods:

    public interface IServiceCollectionFacade : IServiceCollection
    {
        IServiceCollection AddSingleton(Type serviceType, Type implementationType);

        IServiceCollection AddLogging(Action<ILoggingBuilder> configure);

        IServiceCollection AddOptions();

        IServiceCollection Configure<TOptions>(IConfiguration config)
            where TOptions : class;

        IServiceCollection AddDbContext<TContext>(Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
            where TContext : DbContext;

        IServiceCollection AddScoped<TService, TImplementation>()
            where TService : class
            where TImplementation : class, TService;

        IServiceCollection AddScoped<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
            where TService : class
            where TImplementation : class, TService;

        IServiceProvider BuildServiceProvider();
    }

Now, let go to the class where I will use my new Facade. First, I created static ServiceCollectionFunc:

internal static Func<IServiceCollectionFacade> ServiceCollectionFunc = () => new ServiceCollectionFacade(new ServiceCollection());

In my method I just call the delegate as:

public static IServiceProvider CreateServiceBuilder(string[] args)
{
    IServiceCollectionFacade services = ServiceCollectionFunc();
    services.AddSingleton(typeof(ILoggerAdapter<>), typeof(LoggerAdapter<>));
    services.AddOptions();
    services.AddScoped<IGlobalRepository, GlobalRepository>();
...
    IServiceProvider provider = services.BuildServiceProvider();
    return provider;
}

I do not use the method chaining. I found it more difficult for mocking and unit testing. If you want to have method chaining feature, change the facade method to return the  IServiceCollectionFacade (“return this;”).

Now, when we are done with the code, let create a unit test for the  CreateServiceBuilder method. I will use Moq and Xunit, but this approach compatible with other mocking and testing frameworks.

First, create the serviceCollectionFacadeMock and setup all it methods:

Mock<IServiceCollectionFacade> serviceCollectionFacadeMock = new Mock<IServiceCollectionFacade>();
serviceCollectionFacadeMock.Setup(s => s.AddSingleton(typeof(ILoggerAdapter<>), typeof(LoggerAdapter<>))).Returns(serviceCollectionFacadeMock.Object);
serviceCollectionFacadeMock.Setup(s => s.AddLogging(It.IsAny<Action<ILoggingBuilder>>())).Returns(serviceCollectionFacadeMock.Object);
serviceCollectionFacadeMock.Setup(s => s.AddOptions()).Returns(serviceCollectionFacadeMock.Object);
...
Mock<IServiceProvider> serviceProviderMock = new Mock<IServiceProvider>();
serviceCollectionFacadeMock.Setup(s => s.BuildServiceProvider()).Returns(serviceProviderMock.Object);
...

In the example above, I ignore parameters, however you may try to validate them. Now, I set up my delegate and call the method:

MyService.ServiceCollectionFunc = () => serviceCollectionFacadeMock.Object;
var result = MyService.CreateServiceBuilder(args);

Assert.NotNull(result);
Assert.Equal(serviceProviderMock.Object, result);
Mock.VerifyAll();

You may also want to create unit tests for facade as well. Here is an example how to test the Constructor

    public class ServiceCollectionFacadeFixture
    {
        [Fact]
        public void ServiceCollectionFacadeTest_ValidateConstructor()
        {
            const int count= 10;
            var mockServiceCollection = new Mock<IServiceCollection>();
            mockServiceCollection.Setup(s => s.Count).Returns(count);
            var actual = new ServiceCollectionFacade(mockServiceCollection.Object);

            Assert.NotNull(actual);
            Assert.Equal(count, actual.Count);
        }
    }

 

Advertisement

GitHub Automation Error: Az CLI Login failed. Please check the credentials.

Are you using the GitHub automation for Azure deployment? Suddenly, your build is stop working. The problem that the certificate that is used to authenticate the GitHub is expired. Here are steps to fix the issue:

  1. Login to Azure portal (https://portal.azure.com/)
  2. Navigate into Azure Active Directory. Type the word “directory” in the search box and click on the prompt.
  3. On the left panel select the “App registrations” and click on “View all applications in the directory”.
  4. Search the name for an application that you used for the GitHub integration. The certificate and secret column will show the word “Expired”.
  5. Click on the name and select the “Certificates & secrets” on the left panel.
  6. Click on the New client secret.
  7. Set the name and expiration period.
  8. Click Add.
  9. Re-run the build.

NuGet feed does not exist

If you get build error “Feed does not exist”, most likely you are using the “AspNetCore” feed in the NuGet.config.
Find this file in the root of your solution and replace the following:
https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json
with
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json

https://www.myget.org/F/aspnetcidev/api/v3/index.json
with
https://www.myget.org/F/aspnet/api/v3/index.json

NativeScript: Setup on MacOS Catalina

Recently, I have to create a test build environment for MacOS Catalina to troubleshoot the Nativescript mobile app for iPhone v14.

I decide to use a small machine from macstadium.com. Alternatively, you may use a VirtualBox.

My personal preference to turn off screen saver (System Preferences > Desktop and Screen Saver) and energy saver (System Preferences > Energy Saver), especially if you are using a remote desktop or VirtualBox.

 

My recommendation is to use the Advanced steps from the article: https://docs.nativescript.org/angular/start/ns-setup-os-x.

Complete the following steps to setup NativeScript on your MacOS development machine:

  1. Start with downloading Xcode from the store. As of today, version 12.2 released. This download very large (over 11Gb). So, start it ASAP.
  2. Open a Terminal window. Use Finder Menu: Go>Utilities
  3. Install Homebrew to simplify the installation process.
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. Install the latest Node.js (LTS) 12.x.
brew update
brew install node@12 
echo 'export PATH="/usr/local/opt/node@12/bin:$PATH"' >> ~/.zshrc
  1. Restart Terminal window. This is important, otherwise you will not see changes to environment variables.
  2. Verify that node and npm installed correctly:
node --version
npm --version

Install the Android components.

  1. Set up JDK 8
brew tap AdoptOpenJDK/openjdk
brew cask install adoptopenjdk8
echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)' >> ~/.zshrc
  1. Restart Terminal window.
  2. Install the Android SDK.
brew cask install android-sdk
echo 'export ANDROID_HOME=/usr/local/share/android-sdk' >> ~/.zshrc
  1. Restart Terminal window.
  2. Install all packages for the Android
$ANDROID_HOME/tools/bin/sdkmanager "tools" "emulator" "platform-tools" "platforms;android-28" "build-tools;29.0.3" "extras;android;m2repository" "extras;google;m2repository"

Install the dependencies for iOS development.

  1. At this point, Xcode should be downloaded.
  2. Start Xcode and accept the license agreement. Xcode will install its components.
  3. Download and install Command Line Tools for Xcode. Search for Command Line Tools for Xcode 12.2
  4. Double click on the icon (Command Line Tools.pkg). Complete the Install Command Line Tools wizard.
  5. Change the path if you installed Xcode somewhere else.
    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
  6. To verify that the installation is correct, please run
    xcodebuild -version 
  7. Install and setup CocoaPods 
    brew install cocoapods
    pod setup
  8. Install xcodeproj. Overwrite executable when it prompted.
    sudo gem install xcodeproj
  9. Install pip and six
    sudo easy_install pip
    pip install six
    
  10. Install the NativeScript CLI.
    npm i -g nativescript
  11. Restart the command prompt. To check if your system is configured properly, run the following command.
    tns doctor

 

Visual Studio “macro” search and replace

Have you ever try to edit/replace a large object? Recently, I need to add a method to a Typescript object with over 50 properties. This static method deals with ALL properties of the object. The class properties look like this:

activationDate: Date;
activationDateSpecified: boolean;
authorizations: any;
authorizedLocationOfCares: any;
currentLocation: number;
currentLocationDetail: any;
currentLocationSpecified: boolean;
data2000: any;
deaNumber: any;
errorDetails: any;
expirationDate: Date;
expirationDateSpecified: boolean;
firstName: string;
globalID: number;
globalIDSpecified: boolean;
groupList: any;
homeLocation: number;
homeLocationDetail: any;
homeLocationSpecified: boolean;
...

I create a method and copy a list of properties. However, I need to change every property to:
userDetailExternal.[propertyname] = _obj.[propertyname];
For example:

userDetailExternal.activationDateSpecified = _obj.activationDateSpecified;

I used Replace (Ctrl+H) with the following:
Use Regular Expressions (Alt+E)
In the Find field:

([a-zA-Z0-9]+):\s*([^\n\r]*)

In the Replace field:

userDetailExternal.$1 = _obj.$1;

Be careful not to replace the declaration of the properties.

The Azure pitfall with Let’s Encrypt

This month my Azure bill jumped high for using the Microsoft Azure App Services. I expected to pay around $54.75 for a B1 tear. I think it’s very high for 2 basic websites with minimum traffic. 4

I noticed a significant increase this month to the $82. I went into the bill details and found charges for using SSL!

Wait a minute. Did I already pay for the Custom domain/SSL? Apparently, (see the small print) Microsoft SSL is only SNI SSL. Azure charged extra for IP SSL.
$39 for using my own SSL certificate?!!! This is per certificate!!!
I have just two websites on an App Service and I’m charged more for the certificates.

3

If you get charged for it, you should request a refund.

Here is how you can fix it if you are using the Let’s Encrypt Extension:

1

You should Unclick this check-box.  You will receive the SNI SSL certificate.

That’s it. Good luck with the Azure refund. 😃

Migrate Angular SSR from .net core 2 to core 3.1 Angular 9 with SSR. Part 1

Asp.Net core 3.1 dropped the UseSpaPrerendering. So, no more server-side rendering (SSR). If you have Asp.Net core 2+ single page application (SPA) with SSR – no road map from Microsoft. Well, not exactly. Here and there you may find some ideas, but not a complete path with step-by-step migration instruction.

I want to give you a hint: iisnode. The iisnode exist for a long time. Azure supports it (https://github.com/Azure/iisnode). The iisnode with rewrite may do the job. However, I faced a small problem: iisnode does not play well with the IIS Express 10. Obviously, use IIS for development, the first that comes to my mind. But now I have to ask the entire team to install and manage IIS.

The default, out of the box, solution from Microsoft: UseProxyToSpaDevelopmentServer. This method just proxy all requests to an instance of the Node Express running in a separate process. Ok, the iisnode should not be used for development, but in a production environment only.

The web.config transformation can help here. Add a file called web.Release.config:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>

    <!-- Web.Debug.config adds attributes to this to enable remote debugging when publishing in Debug configuration. -->
    <iisnode watchedFiles="web.config;*.js" flushResponse="true" xdt:Transform="InsertIfMissing" />
    <!-- Remote debugging (Azure Website with git deploy): Comment out iisnode above, and uncomment iisnode below. -->
    <!--<iisnode watchedFiles="web.config;*.js"
      loggingEnabled="true"
      devErrorsEnabled="true"
      nodeProcessCommandLine="node.exe --debug"/>-->

    <handlers>
      <add name="aspNetCore" path="api" xdt:Locator="Match(name)" xdt:Transform="SetAttributes"/>

      <add name="iisnode" path="ClientApp/iisnode.js" verb="*" modules="iisnode" resourceType="Unspecified" responseBufferLimit="0" xdt:Locator="Match(name)" xdt:Transform="Insert"/>
      <!-- Remote debugging (Azure Website with git deploy): Uncomment NtvsDebugProxy handler below.
      Additionally copy Microsoft.NodejsTools.WebRole to 'bin' from the Remote Debug Proxy folder.-->
      <!-- The GUID in the following path is meant to protect the debugging endpoint against inadvertent access, and should be treated as a password. -->
      <!--<add name="NtvsDebugProxy" path="ntvs-debug-proxy/5c950099-1b79-430f-967d-fa523eb222b7" verb="*" resourceType="Unspecified"
        type="Microsoft.NodejsTools.Debugger.WebSocketProxy, Microsoft.NodejsTools.WebRole" xdt:Locator="Match(name)" xdt:Transform="Insert" />-->
    </handlers>
    <rewrite xdt:Transform="Insert">
      <rules>
        <clear />
        <!-- Remote debugging (Azure Website with git deploy): Uncomment the NtvsDebugProxy rule below. -->
        <!--<rule name="NtvsDebugProxy" enabled="true" stopProcessing="true">
          <match url="^ntvs-debug-proxy/.*"/>
        </rule>-->
        <rule name="Api" patternSyntax="ECMAScript" stopProcessing="true">
          <match url="api/(.*)" />
        </rule>
        <rule name="ClientApp" enabled="true" patternSyntax="ECMAScript" stopProcessing="true">
          <match url="iisnode.+" negate="true" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
          <action type="Rewrite" url="ClientApp/iisnode.js" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

 There are several points:

  1. All api/* routes are processing by aspNetCore.
  2. Everything else are processing by the iisnode.
  3. The handlers section changes the aspNetCore. The original path=”*” replaced with path=”api”
  4. The handlers section added the iisnode entry.
  5. Finally, the entire rewrite block added to manage routes.

Pay attention to the path=”ClientApp/iisnode.js” attribute in the iisnode element. You will need to add this iisnode.js file to the ClientApp. This file contains only one line:

require(__dirname + '\\dist-server\\main.js');

As this file will not be build by Angular, add this file to the project output directory. Set the “Build Action” to “None” and “Copy to Output Directory” to “Copy if newer”

Next, add another file: server.ts. I took this file from the Angular Universal sample. Navigate to https://angular.io/guide/universal and search for “Download the finished sample code”. I made several adjustments. Here is the entire file:

import 'zone.js/dist/zone-node';

import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';
import { BASE_URL } from './app/tokens/base-url.token';
import * as compression from 'compression';
import * as helmet from 'helmet';

// The Express app is exported so that it can be used by serverless Functions.
export function app() {
    const server = express();
    const distFolder = join(process.cwd(), 'dist');
    const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index.html';

    // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
    server.engine('html', ngExpressEngine({
        bootstrap: AppServerModule,
    }));

    // Security
    server.use(helmet());

    server.use(compression({ filter: shouldCompress }));
    server.set('view engine', 'html');
    server.set('views', distFolder);


    // TODO: implement data requests securely
    server.get('/api/**', (_req, res) => {
        res.status(404).send('data requests are not yet supported');
    });

    server.get('*.json', express.json({
        limit: '1000kb'
    }));

    // Serve static files from /browser
    server.get('*.*', express.static(distFolder, {
        immutable: true,
        maxAge: '1y'
    }));

    // All regular routes use the Universal engine
    server.get('*', (req, res) => {
        const baseUrl = req.protocol + '://' + ((req.headers.host !== undefined) ? req.headers.host : req.hostname);
        res.render(indexHtml, {
            req, providers: [
                { provide: APP_BASE_HREF, useValue: req.baseUrl },
                { provide: BASE_URL, useValue: baseUrl }
            ]
        });
    });

    return server;
}

function shouldCompress(req, res) {
    if (req.headers['x-no-compression']) {
        // don't compress responses with this request header
        return false
    }

    // fallback to standard filter function
    return compression.filter(req, res)
}

function run() {
    const port = process.env.PORT || 4000;

    // Start up the Node server
    const server = app();
    server.listen(port, () => {
        console.log(`Node Express server listening on http://localhost:${port}`);
    });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
    run();
}

export * from './main.server';

You may have to adjust the code to implement your own providers.

As you are no longer using the aspnet-prerendering, your main.server.ts can be simplified:

import '@angular/localize/init';
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

export { AppServerModule } from './app/app.server.module';
export { renderModule, renderModuleFactory } from '@angular/platform-server';

The angular.json needs some corrections as well. First, replace the main.server.ts with server.ts.
Second, in the projects:[your-project-name]:architect add “serve-ssr” and “prerender” below:

        "serve-ssr": {
          "builder": "@nguniversal/builders:ssr-dev-server",
          "options": {
            "browserTarget": "[your-project-name]:build",
            "serverTarget": "[your-project-name]:server"
          },
          "configurations": {
            "production": {
              "browserTarget": "[your-project-name]:build:production",
              "serverTarget": "[your-project-name]:server:production"
            }
          }
        },
        "prerender": {
          "builder": "@nguniversal/builders:prerender",
          "options": {
            "browserTarget": "[your-project-name]:build:production",
            "serverTarget": "[your-project-name]:server:production",
            "routes": [
              "/"
            ]
          },
          "configurations": {
            "production": {}
          }
        }
Make sure you replace the [your-project-name] with the actual name of your project in angular.json.

NativeScript: GitHub Action and Apple App store integration

Are you a windows developer and struggling with XCode build? Good news. GitHub Action will help you build and deploy straight to the Apple app store.

First step: Create a basic GitHub Action.

Step two: Download your Provisioning Profile from the Apple store and place it in some local directory. For  example, ./Mobile/publishing.requirements/iOS/my.mobileprovision

Step three: Download your p12 certificate and place it in the same directory. For example, ./Mobile/publishing.requirements/iOS/Certificates.p12

Step four: Create your “app-specific password”. See: “Tips for Updating NativeScript Apps on App Store and Google Play“, by Rob Lauer.

Step five: Create GitHub secrets. The first, called APPLE_APP_SPECIFIC_PASSWORD for the “app-specific password”, second: APPLE_APP_USER_NAME for Apple store user name and the third APPLE_CERT_P12_PASSWORD for the p12 certificate.

Step six: Find the Provisioning Profile Identifier. This is a GUID and it looks like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. I open it with the WordPad and searched for “UUID”

Step seven: Modify GitHub Action yml-file.

Finally, push updates to the master branch. The GitHub Action will put your new version into the Apple App Store.

name: Mobile-IOS-Master 

on:
  push:
    branches:
      - master 

env:
  NODE_VERSION: '12.x' # set this to the node version to use 

jobs:
  build:
    runs-on: macos-latest 

    steps:

    - uses: actions/checkout@v1 
    - name: Cache web node modules 
      uses: actions/cache@v1 
      with: 
        path: ~/.npm 
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 
        restore-keys: | ${{ runner.os }}-node- 

    - name: Use Node.js ${{ env.NODE_VERSION }} 
      uses: actions/setup-node@v1 
      with: 
        node-version: ${{ env.NODE_VERSION }} 
        registry-url: 'https://registry.npmjs.org' 

    - uses: apple-actions/import-codesign-certs@v1 
      with: 
        p12-filepath: './Mobile/publishing.requirements/iOS/Certificates.p12' 
        p12-password: ${{ secrets.APPLE_CERT_P12_PASSWORD }} 

    - name: Provisioning Profiles 
      run: | 
        mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles 
        echo "List profiles" 
        ls ~/Library/MobileDevice/Provisioning\ Profiles/ 
        echo "Move profiles" 
        cp ./Mobile/publishing.requirements/iOS/my.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/ 
        echo "List profiles" 
        ls ~/Library/MobileDevice/Provisioning\ Profiles/ 

    - name: Setup NativeScript 
      run: npm install -g nativescript 

    - name: Install Pip and Six 
      run: | 
        sudo pip install --upgrade pip 
        pip install --user --upgrade matplotlib 
        pip install six 

    - name: Verify the NativeScript setup 
      run: tns doctor 

    - name: Build IOS release
      run: | 
        cd ./Mobile tns build ios --release --for-device --env.production --provision xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 

    - name: Deploy to App store 
      run: | 
        xcrun altool --upload-app --type ios --file ./Mobile/platforms/ios/build/Release-iphoneos/Mobile.ipa --username ${{ secrets.APPLE_APP_USER_NAME }} --password ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}

Failed to proxy the request to http://localhost:4200/ on Azure App Service

We migrated our Angular 9 open-source SPA app from .NET Core 2.2 to .Net Core 3.1. Everything went well and the app ran fine in the Linux App Service on Azure. When we deployed to Windows App Service, we got an error:

HttpRequestException: Failed to proxy the request to http://localhost:4200/, because the request to the proxy target failed. Check that the proxy target server is running and accepting requests to http://localhost:4200/.

I validated the Azure environment and notice a message: 

Microsoft.Hosting.Lifetime: Hosting environment: Development

I tried to set ASPNETCORE_ENVIRONMENT to “Production“, but I keep getting the “Hosting environment: Development” message in the Log stream.

We are using a “Zip” deployment (WEBSITE_RUN_FROM_PACKAGE). Where the zip file is on Azure storage. After several hours of research, I noticed small changes in the web.config file:

<environmentVariables>
<environmentVariable name="ASPNETCORE_HTTPS_PORT" value="44395" />
<environmentVariable name="COMPLUS_ForceENC" value="1" />
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
</environmentVariables>

Apparently, during the conversion, these entries got into web.config file and they have priority over settings in the Configuration on the Azure portal. 

I changed setting to <environmentVariables /> and the site is running again.