<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jim Mc̮̑̑̑͒G</title>
    <description>The latest articles on Forem by Jim Mc̮̑̑̑͒G (@siliconorchid).</description>
    <link>https://forem.com/siliconorchid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F256204%2F80b5fe81-9b21-4d05-a340-6599bd541823.png</url>
      <title>Forem: Jim Mc̮̑̑̑͒G</title>
      <link>https://forem.com/siliconorchid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/siliconorchid"/>
    <language>en</language>
    <item>
      <title>How to save an audio file from Twilio Media streams to Azure storage.</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Mon, 25 May 2020 08:37:18 +0000</pubDate>
      <link>https://forem.com/twilio/how-to-save-an-audio-file-from-twilio-media-streams-to-azure-storage-m8d</link>
      <guid>https://forem.com/twilio/how-to-save-an-audio-file-from-twilio-media-streams-to-azure-storage-m8d</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault/"&gt;blogs.siliconorchid.com&lt;/a&gt; on 26-March-2020&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this article, we'll combine Twilio Media Streams and a .NET Core 3.1 Web App, to save a  copy of a telephone conversation as a .wav audio file to Azure Storage.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In July 2019, &lt;a href="https://www.twilio.com/blog/media-streams-public-beta"&gt;Twilio announced their new service "Media Streams"&lt;/a&gt; was available in public beta.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Twilio Media Streams&lt;/em&gt; are a really interesting service that further bring together the worlds of telephony and mainstream software development.  &lt;/p&gt;

&lt;p&gt;The Twilio service provides a way to stream the audio content of a live phone conversation into our own services.  &lt;/p&gt;

&lt;p&gt;What we then choose to do with this, is down to our own creativity - but suggested examples have been to integrate with voice-recognition and AI-related services such as sentiment-analysis etc.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're someone who just wants to see the code, skip straight over to my &lt;a href="https://github.com/SiliconOrchid/TwilioMediaStreams"&gt;GitHub : SiliconOrchid/TwilioMediaStreams&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;This article is not intended for beginners and assumes that you already have intermediate experience using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C#, .NET Core and experience working with .NET Core Web Applications.&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://www.twilio.com/try-twilio"&gt;account with Twilio &lt;/a&gt; and familiarity using the Twilio dashboard.&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://azure.microsoft.com/en-us/free/search/"&gt;account with MS Azure&lt;/a&gt; and experience with creating Azure resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are an existing .NET developer, but new to .NET Core, you may find resources such as the following useful:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tutorials/cli-create-console-app"&gt;Microsoft : Get started with .NET Core using the .NET Core CLI
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new"&gt;Microsoft : Dotnet New&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article has been produced on a Windows 10 system using VS2019 Community Edition.  It has not been written to provide alternative guidance for users of other OS or IDE.&lt;/p&gt;






&lt;h2&gt;
  
  
  What will we be doing?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a webhook (an HTTP "handshaking" endpoint)
&lt;/h3&gt;

&lt;p&gt;When we purchase a phone number from Twilio, their API platform allows us to define instructions to "do something" when that number is interacted with.   &lt;/p&gt;

&lt;p&gt;For this project, we want to instruct the Twilio service that when a user calls our number, we want to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read a brief message to the user &lt;/li&gt;
&lt;li&gt;wire-up the call to a service that can receive Twilio Media Streams.&lt;/li&gt;
&lt;li&gt;maintain the call for 60 seconds (unless the caller hangs up)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We achieve this by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Providing a "webhook" (an HTTP endpoint) that the Twilio service is configured to request when a call is made to the Twilio number.   This endpoint will return a set of instructions that are formatted in &lt;a href="https://www.twilio.com/docs/voice/twiml"&gt;Twilio's bespoke TWIML format&lt;/a&gt;.   That TWIML will look similar to the following:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Response&amp;gt;
    &amp;lt;Start&amp;gt;
        &amp;lt;Stream url="yourWebhookUrl"/&amp;gt;
    &amp;lt;/Start&amp;gt;
    &amp;lt;Say&amp;gt;Please record a message.&amp;lt;/Say&amp;gt;
    &amp;lt;Pause length="60"/&amp;gt;
&amp;lt;/Response&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;








&lt;h3&gt;
  
  
  Create a websocket service
&lt;/h3&gt;

&lt;p&gt;Our solution will require us to provide a websocket service that will receive a stream of encoded byte data from Twilio.   &lt;/p&gt;

&lt;p&gt;Twilio provides this stream as a sequence of websocket messages, which are wrapped as &lt;code&gt;JSON&lt;/code&gt; documents.  You should read the official documentation about this subject here at &lt;a href="https://www.twilio.com/docs/voice/twiml/stream#websocket-messages"&gt;Twilio : Voice Streams : Websocket Messages.&lt;/a&gt;   &lt;/p&gt;

&lt;p&gt;These documents contain various pieces of meta-data, but ultimately contain a payload of &lt;a href="https://en.wikipedia.org/wiki/Base64"&gt;binary data encoded as base64&lt;/a&gt; which represents part of the audio data.  We need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a websocket service for our ASP.NET website.  We implement this using &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1"&gt;ASP.NET Core Middleware&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip:  If you've not worked with websockets before, don't confuse the use of the word "streaming" with any experience you may have working with .NET streams (e.g. &lt;code&gt;MemoryStream&lt;/code&gt;).     Data transmitted using websockets is decomposed into discrete text-based packages called "messages" - it may help you to better picture what's going on, to think of these as a rapidly-received sequence of HTTP requests.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  Create a buffering system
&lt;/h3&gt;

&lt;p&gt;The need to have a buffer depends on our usage scenario.    For example, if we are building a service that relays audio data immediately onto another service (e.g. a speech-to-text service), we wouldn't need to buffer any data.&lt;/p&gt;

&lt;p&gt;However, in our demo scenario, we are writing a file to a storage medium.   To do this, we need to have the complete file available to us.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; We achieve this by buffering the received data in server memory until the transmission has ended. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A slight complication to this process is that &lt;strong&gt;unlike&lt;/strong&gt; a regular HTTP handler (whether that be MVC or WebAPI), where a &lt;code&gt;scoped&lt;/code&gt; instance of a &lt;code&gt;Controller&lt;/code&gt; class handles a single request, our WebSocket middleware will be a &lt;code&gt;singleton&lt;/code&gt; instance, dealing with multiple connections.&lt;/p&gt;

&lt;p&gt;Because of this, we need a way to separate data being simultaneously received from different connections.   &lt;/p&gt;

&lt;p&gt;If we didn't do this, a single buffer for the single service would receive the data from multiple streams.  Pretending for a moment that issues such as security and privacy aren't a concern … crudely, everyone's audio stream could be blended together in a jumbled mess and/or recordings become concatenated.&lt;/p&gt;



&lt;h3&gt;
  
  
  Create an audio file
&lt;/h3&gt;

&lt;p&gt;Again, if we were simply relaying a stream of data onward to another service, other than defining what the expected encoding of the data should be, we don't need to become involved.&lt;/p&gt;

&lt;p&gt;However, for this project, we have tasked ourselves with creating a &lt;a href="https://en.wikipedia.org/wiki/WAV"&gt;.wav audio file&lt;/a&gt;, so we need to do a little more work.   Fortunately, there are open-source libraries that do all the heavy-lifting for us.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need to produce code that calls a third-party library that will assemble a .wav file of the appropriate encoding format,  using the &lt;code&gt;bytearray&lt;/code&gt; data previously collected by our buffer.&lt;/li&gt;
&lt;/ul&gt;



&lt;h3&gt;
  
  
  Create a storage handler
&lt;/h3&gt;

&lt;p&gt;Finally, we want to write our newly-created audio file to cloud storage.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the purpose of this article, we'll be using an &lt;a href="https://azure.microsoft.com/en-us/services/storage/blobs/"&gt;Azure Storage&lt;/a&gt; account to save the &lt;a href="https://en.wikipedia.org/wiki/Binary_large_object"&gt;blob&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;






&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--12IrdZ7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-documents.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--12IrdZ7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-documents.jpg%23shadow" alt="clipart of documents "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary resources/references
&lt;/h2&gt;

&lt;p&gt;Primary resources that we will be building upon include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://radu-matei.com/blog/aspnet-core-websockets-middleware/"&gt;Radu Matei : Creating a WebSockets middleware for ASP .NET Core 3
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://markheath.net/post/introducing-naudio-net-audio-toolkit"&gt;Mark Heath : Introducing NAudio - .NET Audio Toolkit
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet"&gt;Microsoft : Quickstart: Azure Blob storage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Twilio have provided sample code for use with a number of tech-stacks and a selection of articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/docs/voice/twiml/stream"&gt;Twilio : TwiML Voice:  (Beta)
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/twilio/media-streams"&gt;Twilio's GitHub&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;(Example using Node.js) : &lt;a href="https://www.twilio.com/blog/live-transcribing-phone-calls-using-twilio-media-streams-and-google-speech-text"&gt;Twilio : Live transcribing phone calls using twilio media streams and google speech-to-text&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;(Example using Java) : &lt;a href="https://www.twilio.com/blog/transcribe-phone-calls-twilio-media-streams-java-websockets-spring-boot"&gt;Twilio : Transcribing phone calls using twilio media streams with Java, Websockets and Spring Boot&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;I would also recommend reading these articles by Jon McGuire, as aside from explaining many of the issues and problems we need to consider,  he also talks about some of the frustrations that he found, as relates to the availability of documentation and learning resources: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://mcguirev10.com/2019/08/18/minimal-full-feature-kestrel-websocket-server.html"&gt;Jon McGuire : A Minimal Full-Feature Kestrel WebSocket Server
&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://mcguirev10.com/2019/08/17/how-to-close-websocket-correctly.html"&gt;Jon McGuire : How to Close a WebSocket -Correctly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;






&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U7OhgKch--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-network-cables.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U7OhgKch--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-network-cables.jpg%23shadow" alt="clipart of computer network cables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Heads-up : Working with websockets in .NET isn't quite as straightforward as you might expect.
&lt;/h2&gt;

&lt;p&gt;If you're a seasoned .NET developer and you hear about the topic of "websockets", your instinct may likely steer you in the direction of &lt;a href="https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr"&gt;SignalR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;SignalR is terrific, but it solves a different problem.  SignalR can be thought of as a wrapper of several technologies - of which websockets represent a major component.  It primarily serves the purpose of connecting web-browser clients to a back-end service.   Other problems it solves include the maintenance of robust connections and the use of fallback techniques to enable browsers that don't natively support websockets, to still benefit from real-time connections.&lt;/p&gt;

&lt;p&gt;For Twilio Media Streams, we need to use websocket connections in a server-to-server configuration.   SignalR isn't the right tool for that job.&lt;/p&gt;

&lt;p&gt;What makes developing a websocket solution more difficult than it needs to be,  is that most resources related to using websockets in .NET either direct us towards SignalR or don't provide examples that would be appropriate for enterprise use.&lt;/p&gt;

&lt;p&gt;Regardless, the key to getting this working is that &lt;strong&gt;we need to create ASP.NET Core middleware&lt;/strong&gt; that manages the websocket connections and implements any specific functionality that we require.&lt;/p&gt;

&lt;p&gt;You can read more about middleware here at &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1"&gt;Microsoft : ASP.NET Core Middleware&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Use NGrok whilst developing external-facing web services.
&lt;/h2&gt;

&lt;p&gt;I recommend  (as does Twilio in their many articles) using the tunnelling/proxy utility &lt;a href="https://ngrok.com/"&gt;NGrok&lt;/a&gt; during development.   &lt;/p&gt;

&lt;p&gt;NGrok is super-useful as it lets us run our web server locally,  whilst exposing our endpoints publically to the internet, using a subdomain of ngrok.com.    &lt;/p&gt;

&lt;p&gt;For development and testing the integration of our service, with other services such as Twilio, this is a time-saving  godsend, as it means that we can quickly iterate and debug, without having to mess around with firewalls and port-forwarding on our router.&lt;/p&gt;

&lt;p&gt;My recommendation is to follow the instructions in this article  &lt;a href="https://www.twilio.com/docs/usage/tutorials/how-use-ngrok-windows-and-visual-studio-test-webhooks"&gt;Twilio : How to use ngrok with Windows and Visual Studio to test webhooks&lt;/a&gt;, which will take you through the steps needed to install and use it.&lt;/p&gt;

&lt;p&gt;Briefly, once NGrok is installed: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we will need to know both the exact local Url and Port number of our locally-hosted development service.&lt;/li&gt;
&lt;li&gt;use the &lt;code&gt;ngrok&lt;/code&gt; command as below.  Note that: 

&lt;ul&gt;
&lt;li&gt;we specify only the hostname and port&lt;/li&gt;
&lt;li&gt;we do not specify the  protocol (i.e. no "http://" etc) &lt;/li&gt;
&lt;li&gt;we do not specify any Url fragments.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ngrok http -host-header="localhost:5000" 5000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Gotcha : Update NGrok to latest version.&lt;/strong&gt;   I wasted hours on this project, assuming that my own code wasn't working, when in fact I was being caught out by a glitch in an older version of NGrok.  Briefly, the symptoms where :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the client could connect and send messages to the server successfully.
&lt;/li&gt;
&lt;li&gt;The server was unable to broadcast messages back to any of the connected clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was previously using NGrok v2.2.8 and the problem was resolved by updating the local NGrok client to v2.3.35 (latest at time of writing)&lt;/p&gt;
&lt;/blockquote&gt;






&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Azure Resources
&lt;/h2&gt;

&lt;p&gt;I recommend that we create a separate &lt;strong&gt;Resource Group&lt;/strong&gt; to organise the resources of our project.  You can read about that at &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal"&gt;Microsoft : Manage Azure Resource Manager resource groups by using the Azure portal&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To develop this project we only require an &lt;strong&gt;Azure Storage account&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will need the &lt;strong&gt;Azure Storage connection string&lt;/strong&gt; later, so for convenience, you should keep the portal open in the background, so you can easily cut+paste these settings later.   The storage connection string can be found in the "Access Keys" section.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you plan to deploy your project to an Azure App service, this may be a convenient time to also create that resource now - but we will only be testing using a local copy in this article.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Get a Twilio Phone number and configure the webhook
&lt;/h2&gt;

&lt;p&gt;If you don't already have a Twilio number that you can use for testing purposes, you should go ahead and obtain one now.   The following resources will help you here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/docs/phone-numbers"&gt;Twilio : Phone Numbers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.twilio.com/hc/en-us/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console"&gt;How to Search for and Buy a Twilio Phone Number from Console
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you have your phone number, you should set the webhook to match the domain of our Azure WebApp endpoint.   In this demo, we will call that route that GET method to the route &lt;code&gt;/handshake&lt;/code&gt;, so go ahead and enter a Uri that looks similar to this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://yourwebapp.azurewebsites.net/handshake&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--71k86zwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/twilio-mediastreams/twilio-set-webhook.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--71k86zwp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/twilio-mediastreams/twilio-set-webhook.png%23shadow" alt="screenshot showing complete solution files"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Create the project
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Template a new solution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use Microsoft templating to create a new &lt;strong&gt;API&lt;/strong&gt; ASP.NET Core 3.1 web application.  For the demo, we will name it &lt;code&gt;TwilioMediaStreams&lt;/code&gt;. We can leave the default options selected, such as SSL support.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add references to the following NuGet packages (versions indicated were those used at time of writing):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NAudio v1.10.0&lt;/code&gt; (Mark Heath &amp;amp; Contributors)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WindowsAzure.Storage v9.3.3&lt;/code&gt; (Microsoft)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Remove any default template-generated items, such as "weatherforecast":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/Controllers/WeatherForecastController.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/WeatherForcast.cs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For convenience, you may also prefer to change the default &lt;code&gt;launchUrl&lt;/code&gt; settings in &lt;code&gt;launchSettings.json&lt;/code&gt; from "weatherforecast" to "handshake".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is code in the  &lt;code&gt;startup.cs&lt;/code&gt; file that was added during templating, that we won't need to use.  Therefore edit the code so that it looks like the following: (for simplicity just cut+paste the following):&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;h3&gt;
  
  
  Set up configuration
&lt;/h3&gt;

&lt;p&gt;In this demo, we'll be using the commonly used &lt;code&gt;IOptions&lt;/code&gt; pattern to provide configuration.   You can read more about configuration in my article &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part1-intro-and-aspnetcore-configuration/"&gt; Introduction to .NET Core configuration&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the root of the project, create a new folder &lt;code&gt;Models&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In this new folder, create a new class &lt;code&gt;ProjectSettings.cs&lt;/code&gt; and populate it like this:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Models&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectSettings&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;TwilioMediaStreamWebhookUri&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AzureStorageAccountConnectionString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AzureStorageAccountContainerName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;Edit &lt;code&gt;appsettings.json&lt;/code&gt; so that it includes the following configuration section:-
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"ProjectSettings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"TwilioMediaStreamWebhookUri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wss://yourappname.azurewebsites.net/ws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureStorageAccountConnectionString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DefaultEndpointsProtocol=https;AccountName=yourStorageAccount;AccountKey=yourKey;EndpointSuffix=core.windows.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AzureStorageAccountContainerName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"filecontainer"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You should go ahead and copy in the configuration settings that correlate to the Azure resources you created earlier.   You can change the "ContainerName" to something else if you prefer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gotcha: Do not use capital letters in your &lt;code&gt;filecontainer&lt;/code&gt; name.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;ul&gt;
&lt;li&gt;Finally, modify the class &lt;code&gt;startup.cs&lt;/code&gt; so that the configuration model is registered, like this:&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;public void ConfigureServices(IServiceCollection services)
{
    services.Configure&amp;lt; TwilioSettings&amp;gt;(Configuration.GetSection("TwilioSettings"));
    services.AddControllers();
}&lt;/code&gt;&lt;/pre&gt;




&lt;h2&gt;
  
  
  Create webhook for Twilio to use
&lt;/h2&gt;

&lt;p&gt;When your Twilio phone number receives a call, Twilio will need a "webhook" to provide further instructions to their service.   &lt;/p&gt;

&lt;p&gt;These instructions take the form of "TWIML", which is an XML document that we need to supply.&lt;/p&gt;

&lt;p&gt;We do this by creating a simple REST method&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new empty API Controller class called &lt;code&gt;BasicController&lt;/code&gt; and add the following code:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Controllers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasicController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ProjectSettings&lt;/span&gt; &lt;span class="n"&gt;_projectSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;BasicController&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProjectSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;projectSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_projectSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/handshake"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;HandShake&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$@"&amp;lt;Response&amp;gt;&amp;lt;Start&amp;gt;&amp;lt;Stream url=""&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_projectSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TwilioMediaStreamWebhookUri&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;""/&amp;gt;&amp;lt;/Start&amp;gt;&amp;lt;Say&amp;gt;Please record a message.&amp;lt;/Say&amp;gt;&amp;lt;Pause length=""60""/&amp;gt;&amp;lt;/Response&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/xml"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Gotcha:  Make sure to define the content-type of &lt;code&gt;text/xml&lt;/code&gt; - Twilio doesn't like plain text, even if that text happens to be valid TWIML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, pay attention to the line &lt;code&gt;[Route("/handshake")]&lt;/code&gt; - this route needs to match the endpoint that we identified as the webhook in the Twilio control panel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recreate the WebsocketManager project
&lt;/h2&gt;

&lt;p&gt;For our project, we're going to use an implementation of a websocket manager by  &lt;a href="https://radu-matei.com/about/"&gt;Radu Matei&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can read about this project at &lt;a href="https://radu-matei.com/blog/aspnet-core-websockets-middleware/"&gt;Creating a WebSockets middleware for ASP .NET Core 3&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There technically exists a NuGet package for this project, but it hasn't been updated recently and doesn't play nicely with .NET Core 3.x.  Also, that package includes dependencies on &lt;code&gt;Newtonsoft.Json&lt;/code&gt;, that we don't want to bring into our project, because we're using the &lt;code&gt;System.Text.Json&lt;/code&gt; library instead.&lt;/p&gt;

&lt;p&gt;However, Radu has updated his project on GitHub to .NET Core 3.x, so we'll be using that version of the code instead.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Within the &lt;code&gt;TwilioMediaStreams&lt;/code&gt; &lt;strong&gt;solution&lt;/strong&gt; (not the web app project of the same name), create a new &lt;strong&gt;.NET Standard 2.0 Class Library&lt;/strong&gt; project, naming it &lt;code&gt;WebsocketManager&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create four classes named as follows.  Cut+Paste the entire code, for each of the linked GitHub pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WebSocketConnectionManager.cs&lt;/code&gt; ⭠ &lt;a href="https://raw.githubusercontent.com/radu-matei/websocket-manager/dotnetcore3/src/WebsocketManager/ConnectionManager.cs"&gt;Get the raw code from GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WebSocketHandler.cs&lt;/code&gt; ⭠ &lt;a href="https://raw.githubusercontent.com/radu-matei/websocket-manager/dotnetcore3/src/WebsocketManager/Handler.cs"&gt;Get the raw  code from GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WebSocketManagerExtensions.cs&lt;/code&gt; ⭠ &lt;a href="https://raw.githubusercontent.com/radu-matei/websocket-manager/dotnetcore3/src/WebsocketManager/Extensions.cs"&gt;Get the raw code from GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WebSocketManagerMiddleware.cs&lt;/code&gt; ⭠ &lt;a href="https://raw.githubusercontent.com/radu-matei/websocket-manager/dotnetcore3/src/WebsocketManager/Middleware.cs"&gt;Get the raw  code from GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, make a project reference to &lt;code&gt;WebsocketManager&lt;/code&gt;, from the &lt;code&gt;MediaStreamSandbox&lt;/code&gt; web app project by right-clicking on the project and going "add &amp;gt; reference".&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NBOLuBK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-scaffold.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NBOLuBK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-scaffold.jpg%23shadow" alt="clipart of scaffolding"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Scaffold the code of our project
&lt;/h2&gt;

&lt;p&gt;We'll start by creating the structural outline of our project.  Later in the article, we'll return to flesh-out the details, along with any necessary explanations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Within the &lt;code&gt;TwilioMediaStreams&lt;/code&gt; web project, create a new folder called &lt;code&gt;Services&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Within the &lt;code&gt;Services&lt;/code&gt; folder, create a new class called &lt;code&gt;MediaStreamHandler&lt;/code&gt; and populate it with the following code:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.WebSockets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.WindowsAzure.Storage.Blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WebSocketManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MediaStreamHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocketHandler&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ProjectSettings&lt;/span&gt; &lt;span class="n"&gt;_projectSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dictionaryByteList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;


        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MediaStreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebSocketConnectionManager&lt;/span&gt; &lt;span class="n"&gt;webSocketConnectionManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProjectSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;projectSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webSocketConnectionManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_projectSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnConnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebSocket&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ReceiveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebSocket&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WebSocketReceiveResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AddPayloadToBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;socketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnConnectionFinishedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WebSocket&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;socketId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessBufferAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;socketId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;CreateCompleteAudioByteArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;socketId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;Next, also in the folder &lt;code&gt;Services&lt;/code&gt;, create a new class file called &lt;code&gt;StorageHandler.cs&lt;/code&gt; and copy in the following code:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.WindowsAzure.Storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.WindowsAzure.Storage.Blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StorageHandler&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CloudBlockBlob&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SetupCloudStorageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProjectSettings&lt;/span&gt; &lt;span class="n"&gt;projectSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;Finally, again still in the &lt;code&gt;Services&lt;/code&gt; folder, create a class called &lt;code&gt;AudioHandler.cs&lt;/code&gt; and copy in this code:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NAudio.Utils&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NAudio.Wave&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TwilioMediaStreams.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AudioHandler&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;GenerateAudioStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MemoryStream&lt;/span&gt; &lt;span class="n"&gt;memoryStream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;




&lt;p&gt;The solution file structure should now look like this:-&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JuCmspKI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/twilio-mediastreams/solution-files.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JuCmspKI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/twilio-mediastreams/solution-files.png%23shadow" alt="screenshot showing complete solution files"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Er5vK40k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-layered-cake.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Er5vK40k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/clipart/clipart-layered-cake.jpg%23shadow" alt="clipart of layered cake"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer in the implementation code of our project
&lt;/h2&gt;

&lt;p&gt;Next, let's turn our attention to the detail of the code.&lt;/p&gt;




&lt;h3&gt;
  
  
  The buffer
&lt;/h3&gt;

&lt;p&gt;If you recall from earlier, we said that we need to buffer data for each websocket connection separately.  We address this requirement by using a &lt;code&gt;Dictionary&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dictionaryByteList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We should explain how this is going to be used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We'll be using the randomly created "websocket id" as the dictionary key.&lt;/li&gt;
&lt;li&gt;We'll be receiving small "payloads of data" from the websocket messages data, in the form of byte-arrays.&lt;/li&gt;
&lt;li&gt;Byte-arrays are immutable, meaning that we can't simply keep appending new bytes onto the end of a single byte-array (i.e. we could &lt;strong&gt;not&lt;/strong&gt; just use &lt;code&gt;Dictionary&amp;lt;string, byte[]&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;To work around this problem, we use a &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; to create a "list of byte-arrays".&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Starting websocket connections
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;MediaStreamHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;public override async Task OnConnected(WebSocket socket)
{
    await base.OnConnected(socket);
    string socketId = WebSocketConnectionManager.GetId(socket);
    dictionaryByteList.Add(socketId, new List&amp;lt; byte[]&amp;gt;());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What's happening here is that when we establish a new websocket connection, we get the id of the connection and create a new entry in the dictionary.&lt;/p&gt;






&lt;h3&gt;
  
  
  Receiving websocket messages
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;MediaStreamHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;public override async Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, byte[] buffer)
{
    string socketId = WebSocketConnectionManager.GetId(socket);

    using (JsonDocument jsonDocument = JsonDocument.Parse(Encoding.UTF8.GetString(buffer, 0, result.Count)))
    {
        string eventMessage = jsonDocument.RootElement.GetProperty("event").GetString();

        switch (eventMessage)
        {
            case "connected":
                break;
            case "start":
                break;
            case "media":
                string payload = jsonDocument.RootElement.GetProperty("media").GetProperty("payload").GetString();
                AddPayloadToBuffer(socketId, payload);
                break;
            case "stop":
                await OnConnectionFinishedAsync(socket, socketId);
                break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This code is run every time we receive a websocket message (so expect this to be triggering continuously when a stream is being received).&lt;/p&gt;

&lt;p&gt;You should refer to this part of the technical reference : &lt;a href="https://www.twilio.com/docs/voice/twiml/stream#websocket-messages"&gt;Twilio : Websocket messages&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Briefly, a message sent by Twilio is a JSON string, which contains various pieces of meta information and depending on the type, the payload itself.    The Twilio messages are grouped into four main types which can be identified by examining the root-level property &lt;code&gt;event&lt;/code&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;connected&lt;/code&gt; - &lt;a href="https://www.twilio.com/docs/voice/twiml/stream#message-connected"&gt;Twilio : Connected Message&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; - &lt;a href="https://www.twilio.com/docs/voice/twiml/stream#message-start"&gt;Twilio : Start Message&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;media&lt;/code&gt; - &lt;a href="https://www.twilio.com/docs/voice/twiml/stream#message-media"&gt;Twilio : Media Message&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stop&lt;/code&gt; - &lt;a href="https://www.twilio.com/docs/voice/twiml/stream#message-stop"&gt;Twilio : Stop Message &lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our demo code, we parse the JSON using the newer technique that uses &lt;code&gt;System.Text.Json&lt;/code&gt; and &lt;code&gt;JsonDocuments&lt;/code&gt;  (where previously we may have done almost the exact same thing using &lt;code&gt;NewtonSoft.Json&lt;/code&gt; and &lt;code&gt;JObject&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Our demo code has &lt;code&gt;switch&lt;/code&gt; placeholders for &lt;code&gt;connected&lt;/code&gt; and &lt;code&gt;start&lt;/code&gt;, but we have decided not to do anything with them.   In a more advanced version of the code, we could do things such as dynamically using the media format encoding-type and sample-rate dynamically, but in this demo we have left it hardcoded elsewhere.&lt;/p&gt;

&lt;p&gt;The key points to note in the &lt;code&gt;switch&lt;/code&gt; block is that :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for &lt;code&gt;media&lt;/code&gt; message types, we extract the stringified payload from the message and pass it to another method called &lt;code&gt;AddPayloadToBuffer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;for &lt;code&gt;stop&lt;/code&gt; message types, we call the  &lt;code&gt;OnConnectionFinishedAsync&lt;/code&gt; method which performs  processing on the buffer and attempts to clean up.&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Adding the payload to a buffer
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;MediaStreamHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;private void AddPayloadToBuffer(string socketId, string payload)
{
    //We convert the base64 encoded string into a byte array and append it to the appropriate buffer
    byte[] payloadByteArray = Convert.FromBase64String(payload);
    dictionaryByteList[socketId].Add(payloadByteArray);
}
&lt;/code&gt;&lt;/pre&gt;






&lt;h3&gt;
  
  
  Perform tasks when the stream closes
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;MediaStreamHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;private async Task OnConnectionFinishedAsync(WebSocket socket, string socketId)
{
    // extract buffer data, create audio file, upload to storage
    await ProcessBufferAsync(socketId);

    // instruct the server to actually close the socket connection
    await OnDisconnected(socket);

    // clean up buffer 
    dictionaryByteList.Remove(socketId);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This method is orchestration code, which triggers the onward processing of the buffer and attempts to clean up resources.&lt;/p&gt;






&lt;h3&gt;
  
  
  Orchestrate the creation of an audio file and uploading to storage
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;MediaStreamHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;private async Task ProcessBufferAsync(string socketId)
{
    byte[] completeAudioBuffer= CreateCompleteAudioByteArray(socketId);

    CloudBlockBlob blob = await StorageHandler.SetupCloudStorageAsync(_projectSettings);

    using (MemoryStream memoryStream = new MemoryStream())
    {
        AudioHandler.GenerateAudioStream(completeAudioBuffer, memoryStream);

        // make sure the memory stream is returned to its beginning, ready to stream to storage
        memoryStream.Seek(0, SeekOrigin.Begin);

        //upload memory stream to cloud storage
        await blob.UploadFromStreamAsync(memoryStream);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The code in this method is largely related to the orchestration of other tasks.  Main activities include :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating a single byteArray from the buffer of many smaller chunks, by invoking the &lt;code&gt;CreateCompleteAudioByteArray&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;setting up the connection to cloud storage.&lt;/li&gt;
&lt;li&gt;calling the static method &lt;code&gt;AudioHandler.GenerateAudioStream&lt;/code&gt; that generates the audio file.&lt;/li&gt;
&lt;li&gt;uploading the audio file, contained in a &lt;code&gt;MemoryStream&lt;/code&gt;, to cloud storage.&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Flatten the list of audio data chunks into a single bytearray
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;MediaStreamHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;private byte[] CreateCompleteAudioByteArray(string socketId)
{
    //get the relevant dictionary entry
    List&amp;lt; byte[]&amp;gt; byteList = dictionaryByteList[socketId];

    //create new byte array that will represent the "flattened" array
    List&amp;lt; byte&amp;gt; completeAudioByteArray = new List&amp;lt; byte&amp;gt;();

    foreach (byte[] byteArrayin byteList)
    {
        foreach (byte singleByte in byteArray)
        {
            completeAudioByteArray.Add(singleByte);
        }
    }

    //collate the List&amp;lt; T&amp;gt; of byte arrays into a single large byte array
    byte[] buffer = completeAudioByteArray.ToArray();
    return buffer;
}&lt;/code&gt;&lt;/pre&gt;






&lt;h3&gt;
  
  
  Implement audio file writer
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;AudioHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;public static void GenerateAudioStream(byte[] buffer, MemoryStream memoryStream)
{
    // define the audio file type
    var waveFormat = WaveFormat.CreateMuLawFormat(8000, 1);

    // use WaveFileWriter to convert the audio file buffer and write it into a memory stream
    using (var waveFileWriter = new WaveFileWriter(new IgnoreDisposeStream(memoryStream), waveFormat))
    {
        waveFileWriter.Write(buffer, 0, buffer.Length);
        waveFileWriter.Flush();
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This class is responsible for calling the &lt;code&gt;NAudio&lt;/code&gt; library to write an audio file into the &lt;code&gt;MemoryStream&lt;/code&gt;.     &lt;/p&gt;

&lt;p&gt;In this class, we hardcode the audio format selection to use  &lt;a href="https://cloud.google.com/speech-to-text/docs/encoding"&gt;MuLaw&lt;/a&gt; - this is a standard encoding format used in telephony.   Similarly, we hardcode that the audio sample rate to be 8000 (Khz) - which matches the quality provided by Twilio in the Media Stream.&lt;/p&gt;

&lt;p&gt;Finally, it's worth mentioning that at no point do we write temporary files to disk - everything is handled as an in-memory stream.&lt;/p&gt;






&lt;h3&gt;
  
  
  Implement Azure Storage handler
&lt;/h3&gt;

&lt;p&gt;You should modify the placeholder code in &lt;code&gt;StorageHandler&lt;/code&gt; to look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;public static async Task SetupCloudStorageAsync(ProjectSettings projectSettings)
{
    // new random filename
    string fileName = $"{Guid.NewGuid()}.wav";

    // set container name
    var containerName = projectSettings.AzureStorageAccountContainerName;

    // create storage account object
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(projectSettings.AzureStorageAccountConnectionString);

    // create storage account client
    CloudBlobClient client = storageAccount.CreateCloudBlobClient();

    // create reference of storage account container
    CloudBlobContainer container = client.GetContainerReference(containerName);

    // create container if it doesn't already exist
    var isCreated = await container.CreateIfNotExistsAsync();

    // set the permissions to blob
    await container.SetPermissionsAsync(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });

    CloudBlockBlob blob = container.GetBlockBlobReference(fileName);

    // MIME type used for MULAW wav files,
    blob.Properties.ContentType = "audio/wav";  
    return blob;
}&lt;/code&gt;&lt;/pre&gt;






&lt;h2&gt;
  
  
  Register the middleware and finish application configuration
&lt;/h2&gt;

&lt;p&gt;Finally, we need to wire everything together.   In the &lt;code&gt;startup.cs&lt;/code&gt; code, make the following changes:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;using TwilioMediaStreams.Models;
using TwilioMediaStreams.Services;
using WebSocketManager;


public void ConfigureServices(IServiceCollection services)
{
    services.Configure(Configuration.GetSection("ProjectSettings"));
    services.AddControllers();
    services.AddWebSocketManager();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    var serviceScopeFactory = app.ApplicationServices.GetRequiredService();
    var serviceProvider = serviceScopeFactory.CreateScope().ServiceProvider;

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseWebSockets();
    app.MapWebSocketManager("/ws", serviceProvider.GetService());
    app.UseEndpoints(endpoints =&amp;gt;
    {
        endpoints.MapControllers();
    });
}
&lt;/code&gt;&lt;/pre&gt;






&lt;h2&gt;
  
  
  Test the code
&lt;/h2&gt;

&lt;p&gt;With all the code in place and necessary configuration added, we can now go ahead and test our solution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you haven't done so already, you should start NGrok and run our web project locally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We can now grab our telephone and place a call to our Twilio number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If things are working correctly, we should hear the message defined in the TWIML that we listed in &lt;code&gt;BasicController.Handshake()&lt;/code&gt;  (so if you cut+paste the example code exactly, we should hear the message "Please record a message.")&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now that we have established that we have called the correct number, we can amuse ourselves by making a selection of farmyard animal impressions and then hanging-up.   &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the point that we hang up the call, our service will generate the audio file and save it to Azure Storage.&lt;/p&gt;

&lt;p&gt;We now want to download the file and make sure that everything worked.&lt;/p&gt;

&lt;p&gt;There are a couple of ways to view blobs in containers, but a simple way is to navigate to the Storage Account using the Azure portal.    &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having selected the storage account, locate and click the option "Storage Explorer (preview)" (which by default can be found near the top of the left-hand blade).&lt;/li&gt;
&lt;li&gt;Locate the item "Blob Containers", in the central blade,  and expand the option using the caret.&lt;/li&gt;
&lt;li&gt;We should see the container that we defined "filecontainer" - click on this.&lt;/li&gt;
&lt;li&gt;We should now see a list of any files that have been generated.   They will have GUID filenames, but should still be easily recognisable by the ".wav" file extension.&lt;/li&gt;
&lt;li&gt;Right-click the file to download.&lt;/li&gt;
&lt;li&gt;Play the file using a media player.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bjygbh-i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/twilio-mediastreams/storage-explorer.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bjygbh-i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/twilio-mediastreams/storage-explorer.png%23shadow" alt="screenshot showing azure storage explorer"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The code provided in this article is "demoware" and is not something to drop into a production system without further work.   There are a number of areas that I would suggest focussing on, to improve the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security and authentication&lt;/li&gt;
&lt;li&gt;Limiting access to handshake method (e.g. with a token)&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;li&gt;Using the caller ID&lt;/li&gt;
&lt;li&gt;Diligence to security and privacy related to data received from different callers.
&lt;/li&gt;
&lt;li&gt;Taking better care to ensure that connections are closed and that memory used by the buffer is cleaned up afterwards.&lt;/li&gt;
&lt;li&gt;Inspecting the metadata in the &lt;code&gt;start&lt;/code&gt; message and setting audio encoding dynamically, based on that information.&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://www.dialogic.com/webhelp/CSP1010/VXML1.1CI/WebHelp/standards_defaults%20-%20MIME%20Type%20Mapping.htm"&gt;Dialogic - MIME type mappings&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.codeguru.com/columns/dotnet/base64-encoding-from-c.html"&gt;Base64 Encoding from C#&lt;/a&gt;&lt;br&gt;
&lt;a href="https://stackoverflow.com/questions/31339708/object-de-serializing-from-base64-in-c-sharp"&gt;https://stackoverflow.com/questions/31339708/object-de-serializing-from-base64-in-c-sharp&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/4234665/using-naudio-to-decode-mu-law-audio"&gt;Using NAudio to decode mu-law audio&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.codeproject.com/Articles/1273613/Azure-Storage-Account-Part-2-Upload-Files-in-Blob"&gt;Code Project : Upload Files in Blob Storage using C#&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/"&gt;Microsoft : Try the new System.Text.Json APIs&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;No third party (i.e. Microsoft or Twilio) compensate me for my promotion of their services in this article.     However, I have been recognised by Twilio as someone who promotes their services and have been titled "Twilio Champion". Additionally, my partner Layla Porter is an employee of Twilio Inc,  in the capacity of a developer evangelist. Therefore I have a strong bias to recommend their services.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>netcore</category>
      <category>twilio</category>
    </item>
    <item>
      <title>[Configuration in Azure Functions Series - Part 4] Azure App Configuration and Azure Key Vault</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Mon, 09 Dec 2019 10:27:11 +0000</pubDate>
      <link>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-4-azure-app-configuration-and-azure-key-vault-41b6</link>
      <guid>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-4-azure-app-configuration-and-azure-key-vault-41b6</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault/"&gt;blogs.siliconorchid.com&lt;/a&gt; on 1-Nov-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part four of a series exploring &lt;em&gt;.NET Core&lt;/em&gt; configuration, with an emphasis on &lt;em&gt;Azure Functions&lt;/em&gt;.   In this article, we look at using other configuration providers in your &lt;em&gt;Azure Function&lt;/em&gt; project, specifically &lt;em&gt;Azure App Configuration&lt;/em&gt; and&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;Azure Key Vault&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part1-intro-and-aspnetcore-configuration/"&gt;part 1 of this series&lt;/a&gt;, we introduce the subject of configuration and review how &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration works.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part2-azure-functions-configuration/"&gt;part 2 of this series&lt;/a&gt;, we look at how configuration in &lt;em&gt;Azure Functions (v2)&lt;/em&gt; works and talk about some of the issues.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part3-aspnet-in-functions/"&gt;part 3 of this series&lt;/a&gt;, we show how you could include &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration into an &lt;em&gt;Azure Function&lt;/em&gt; project.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault/"&gt;part 4 of this series&lt;/a&gt;, we look at using other configuration services, specifically &lt;em&gt;Azure App Configuration&lt;/em&gt; and &lt;em&gt;Azure Key Vault&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Back in part one of this series, we learned that &lt;em&gt;.NET Core&lt;/em&gt; configuration is created from multiple sources called &lt;strong&gt;&lt;em&gt;Configuration Providers&lt;/em&gt;&lt;/strong&gt; and that these can be combined together in &lt;strong&gt;Layers&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Because of the flexibility that this model provides us with, we can take advantage of increasingly specialised services.  In this final article of the series, we'll explore two such Azure services that we can integrate into our architecture: &lt;em&gt;Azure App Configuration&lt;/em&gt; &amp;amp; &lt;em&gt;Azure Key Vault&lt;/em&gt;&lt;/p&gt;





&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Azure App Configuration
&lt;/h2&gt;

&lt;p&gt;You can read the official documentation here:  &lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/overview"&gt;Microsoft Documentation : What is Azure App Configuration?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Azure App Configuration&lt;/em&gt; is a cloud service that is used to store non-secret configuration settings in a centralised location.  These configuration settings can then be shared between multiple applications, including a global context.&lt;/p&gt;

&lt;p&gt;Usually, when researching how to use &lt;em&gt;ASP.NET Core&lt;/em&gt; and &lt;em&gt;Azure Functions&lt;/em&gt;, a vast majority of the documentation and examples will steer us into storing our configuration settings directly onto the host server (whether this is an Azure AppService or a traditional VM server).&lt;/p&gt;

&lt;p&gt;Whilst there is absolutely nothing wrong with this option, for certain systems that comprise of many related, but separate, services (&lt;a href="https://https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part3-frontdoor/"&gt;for example, a globally distributed solution of &lt;em&gt;Azure Functions&lt;/em&gt; spread across multiple Azure Regions&lt;/a&gt;, this can lead to us having to enter copies of exactly the same configuration settings into multiple different places.   Usually, where we find duplication, we find a risk of errors being introduced.  &lt;/p&gt;

&lt;p&gt;The official documentation for &lt;em&gt;Azure App Configuration&lt;/em&gt; is really comprehensive and provides tutorials specific to &lt;em&gt;Azure Functions&lt;/em&gt;, so  we should just go ahead and follow those guides: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-azure-function-csharp"&gt;Microsoft Documentation : Azure App Configuration quickstart&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-aspnet-core-app"&gt;Microsoft Documentation : Quickstart: Create an ASP.NET Core app with Azure App Configuration&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-azure-function-csharp"&gt;Microsoft Documentation : Quickstart: Create an Azure function with Azure App Configuration&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ABMB3KX6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/tomb-balin.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ABMB3KX6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/tomb-balin.jpg%23shadow" alt="tomb of balin from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Azure Key Vault
&lt;/h2&gt;

&lt;p&gt;You can read the official documentation here: &lt;a href="https://docs.microsoft.com/en-us/azure/key-vault/key-vault-overview"&gt;Microsoft Documentation : What is Azure Key Vault?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Azure Key Vault&lt;/em&gt; complements &lt;em&gt;Azure App Configuration&lt;/em&gt; by being the configurable and secure place that we should use for application secrets.&lt;/p&gt;

&lt;p&gt;This option, in particular, is an interesting proposition, as it uses an &lt;em&gt;Azure Active Directory&lt;/em&gt; to provide access control to secrets, from both users (i.e. developers) and resources (i.e. an App Service).  For example: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The built-in integration with Visual Studio means that if we are working on a project that requires significant security controls, we can perform tricks such as granting developers usage of credentials, without ever directly revealing them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We can explicitly grant access to a &lt;em&gt;Secret Setting&lt;/em&gt;, by a specific Azure resource.  Maybe this could be useful to minimize the risk of a credential intended for our production environment, accidentally being used in a development/testing system (e.g. because of a cut/paste mistakes) etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official documentation is good, but unlike with &lt;em&gt;Azure App Configuration&lt;/em&gt;, there are no guides that are specific to &lt;em&gt;Azure Functions&lt;/em&gt; - so we'll be stepping through some steps to demonstrate how we can integrate &lt;em&gt;Azure Key Vault&lt;/em&gt; into a demo project.&lt;/p&gt;

&lt;p&gt;You will most likely find these supporting documents helpful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/key-vault/quick-create-portal"&gt;Microsoft Documentation : Quickstart: Set and retrieve a secret from Azure Key Vault using the Azure portal&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/resources/samples/key-vault-dotnet-core-quickstart/"&gt;Microsoft Documentation : Getting Started with Azure Key Vault with .NET Core&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/key-vault/vs-key-vault-add-connected-service"&gt;Microsoft Documentation : Add Key Vault to your web application by using Visual Studio Connected Services&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Let's setup an Azure Key Vault!
&lt;/h2&gt;

&lt;p&gt;This tutorial assumes that we already know how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create new &lt;em&gt;Azure Functions&lt;/em&gt; projects using our IDE of choice&lt;/li&gt;
&lt;li&gt;create new &lt;em&gt;Azure Functions&lt;/em&gt; AppService in the Azure Portal&lt;/li&gt;
&lt;li&gt;publish our &lt;em&gt;Azure Functions&lt;/em&gt; project to Azure.&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  1) Create a new HTTP-Triggered Azure Function from the templates.
&lt;/h4&gt;

&lt;p&gt;First, create a new HTTP-Triggered &lt;em&gt;Function&lt;/em&gt; from the templates.   For the purpose of this demo, we can leave everything, including the project and function names, to their defaults (i.e. &lt;code&gt;Function1&lt;/code&gt; etc).&lt;/p&gt;


&lt;h4&gt;
  
  
  2) Add extension packages
&lt;/h4&gt;

&lt;p&gt;We need to add the following NuGet packages - we can use the Visual Studio package manager, or edit the &lt;code&gt;.csproj&lt;/code&gt; file manually like this:-&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;ItemGroup&amp;gt;
    &amp;lt;PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.0.0" /&amp;gt;
    &amp;lt;PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" /&amp;gt;
    &amp;lt;PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.29" /&amp;gt;
  &amp;lt;/ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;h4&gt;
  
  
  3) Create a new Resource Group in Azure
&lt;/h4&gt;

&lt;p&gt;Using the Azure Portal,  create a new &lt;strong&gt;&lt;em&gt;Resource Group&lt;/em&gt;&lt;/strong&gt; to keep all of the parts of our demonstration together.   I used the name &lt;code&gt;SiliconVaultDemoRG&lt;/code&gt;, but you can choose your own. &lt;/p&gt;


&lt;h4&gt;
  
  
  4) Create a new &lt;em&gt;Azure Functions&lt;/em&gt; AppService in Azure.
&lt;/h4&gt;

&lt;p&gt;Using the Azure Portal, create a new &lt;strong&gt;&lt;em&gt;Function App&lt;/em&gt;&lt;/strong&gt; resource.  I used the name &lt;code&gt;SiliconVaultDemoFunctionApp&lt;/code&gt;, but you can choose our own.   Select the &lt;em&gt;Runtime Stack&lt;/em&gt; to be &lt;code&gt;.NET Core&lt;/code&gt;, the &lt;em&gt;Region&lt;/em&gt; to be near to you, accept the automatically generated &lt;em&gt;StorageAccount&lt;/em&gt;, and leave the &lt;em&gt;Plan Type&lt;/em&gt; with the default of &lt;code&gt;Consumption Plan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oOX4ScXe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-create-appservice.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oOX4ScXe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-create-appservice.png%23shadow" alt="screenshot creating azure function appservice"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;
  
  
  5) Set AppService to have a System-Assigned Identity
&lt;/h4&gt;

&lt;p&gt;When the &lt;em&gt;AppService&lt;/em&gt; has been provisioned, select the &lt;code&gt;Identity&lt;/code&gt; option and enable the &lt;code&gt;System Assigned Identity&lt;/code&gt;.    Make sure that we click &lt;code&gt;Save&lt;/code&gt;.    This will register the AppService within an &lt;a href="https://azure.microsoft.com/en-gb/services/active-directory/"&gt;Azure Active Directory&lt;/a&gt;.  We'll be assigning this newly created identity to our &lt;em&gt;Azure KeyVault&lt;/em&gt; resource in a few moments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YjAaMf5v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-system-identity.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YjAaMf5v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-system-identity.png%23shadow" alt="screenshot appservice identity"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;
  
  
  6) Create a new &lt;em&gt;Azure Key Vault&lt;/em&gt; resource in Azure.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Using the Azure Portal, create a new &lt;em&gt;Azure Key Vault&lt;/em&gt;.   I chose to use the name &lt;code&gt;SiliconVaultDemoKeyVault&lt;/code&gt;, but you can choose your own.   Select a &lt;em&gt;Region&lt;/em&gt; near to you and leave the pricing tier as &lt;code&gt;Standard&lt;/code&gt;.   Note that this is a paid-for service, but depending on our usage, the costs are relatively negligible - we can check the costs &lt;a href="https://azure.microsoft.com/en-us/pricing/details/key-vault/"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't click the &lt;code&gt;create&lt;/code&gt; button just yet, and instead select the &lt;code&gt;Next: Access Policy&lt;/code&gt; button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DqvJGpQT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault1.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DqvJGpQT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault1.png%23shadow" alt="screenshot create key vault 1"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;
  
  
  7) Grant permission to the new AppService identity.
&lt;/h4&gt;

&lt;p&gt;Whilst displaying the "Access Policy" view, we should see our own user-identity appear in the &lt;code&gt;current access policy&lt;/code&gt; list (the account that you used to log into the Azure Portal).   We need to add in the identity of the &lt;em&gt;AppService&lt;/em&gt; now.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the &lt;code&gt;Add Access Policy&lt;/code&gt; option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XlBrIbkc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault2.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XlBrIbkc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault2.png%23shadow" alt="screenshot create key vault 2"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Completely ignore the options for &lt;code&gt;Configure from template&lt;/code&gt;, &lt;code&gt;Key Permissions&lt;/code&gt; and &lt;code&gt;Certificate Permissions&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the &lt;code&gt;Secrets Permission&lt;/code&gt; dropdown and choose just the sub-options &lt;code&gt;Get&lt;/code&gt; and &lt;code&gt;List&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;code&gt;Select Principle&lt;/code&gt; option and in the dialogue that appears, search for the name of the &lt;em&gt;ServiceApp&lt;/em&gt; that we created earlier.   For me, I added &lt;code&gt;SiliconVaultDemoFunctionApp&lt;/code&gt; here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;code&gt;Add&lt;/code&gt; to save the new policy record. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;code&gt;Review and Create&lt;/code&gt; to create the new Key Vault resource (along with the settings).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3jcRQOIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault3.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3jcRQOIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault3.png%23shadow" alt="screenshot create key vault 3"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h4&gt;
  
  
  8) Make a note of service address
&lt;/h4&gt;

&lt;p&gt;With the &lt;em&gt;Azure Key Vault&lt;/em&gt; resource now provisioned, go ahead and open the new resource.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select &lt;code&gt;Overview&lt;/code&gt; and look at the dialogue for a &lt;em&gt;DNS Name&lt;/em&gt;.   For me, this value is &lt;code&gt;https://siliconvaultdemokeyvault.vault.azure.net/&lt;/code&gt;.     Make a note of this, as we'll be using it in a few moments. &lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  9) Create a new secret setting
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Next, Select &lt;code&gt;Secrets&lt;/code&gt; option and click the &lt;code&gt;Generate/Import&lt;/code&gt; button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n8TWn-yS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault4.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n8TWn-yS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vault-createvault4.png%23shadow" alt="screenshot create key vault 4"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;We need to create a new secret setting, so leave &lt;em&gt;Upload Options&lt;/em&gt; set to &lt;code&gt;Manual&lt;/code&gt;,  provide a &lt;em&gt;Name&lt;/em&gt; of &lt;code&gt;our-secret&lt;/code&gt; and provide a value of: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;This is a secret from Key Vault&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leave all other settings at their default (i.e. don't set an expiration date), and go ahead and click the &lt;code&gt;Create&lt;/code&gt; button.&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;
  
  
  10) Add startup code
&lt;/h4&gt;

&lt;p&gt;In our &lt;em&gt;Azure Function&lt;/em&gt; project, we need to add startup code which tells our application how to connect to our new &lt;em&gt;Azure Key Vault&lt;/em&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new class in the project called &lt;code&gt;startup.cs&lt;/code&gt;, which contains the following code (we will need to substitute the &lt;em&gt;Azure Key Vault&lt;/em&gt; URL as necessary):
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.Functions.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;FunctionsStartup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FunctionApp1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FunctionsStartup&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFunctionsHostBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigurationBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureKeyVault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"https://siliconvaultdemokeyvault.vault.azure.net/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ourSecret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"our-secret"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ourSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It is worth noting that although our example DI code, above, is deliberately crude, it does still demonstrate how we can go about retrieving the secret-settings just once (at instance startup), rather than repeatedly hitting the &lt;em&gt;Azure Key Vault&lt;/em&gt; on each and every function operation (and unnecessarily driving up usage demand and costs, to that resource).&lt;/p&gt;


&lt;h4&gt;
  
  
  11) Update the http-triggered function code
&lt;/h4&gt;

&lt;p&gt;In the file &lt;code&gt;Function1.cs&lt;/code&gt; replace the entire code with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Logging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FunctionApp1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Function1&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_ourSecret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Function1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ourSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_ourSecret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ourSecret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Function1"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt;  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"We retrieved the following value from Azure Key Vault :  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_ourSecret&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;




&lt;h4&gt;
  
  
  12) Login locally to "Az Functions" CLI
&lt;/h4&gt;

&lt;p&gt;A cool feature of &lt;em&gt;Azure Key Vault&lt;/em&gt; is that &lt;em&gt;Active Directory&lt;/em&gt; users who have been granted permission to the &lt;em&gt;Key Vault&lt;/em&gt;, can develop locally using the secrets stored in the vault.   This means that we don't need to specify those values, at all, in the &lt;code&gt;local.secrets.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;We can read how to integrate Visual Studio at &lt;a href="https://docs.microsoft.com/en-us/azure/key-vault/vs-key-vault-add-connected-service"&gt;Microsoft Documentation : Add Key Vault to your web application by using Visual Studio Connected Services&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make this security integration work between &lt;em&gt;Azure Key Vault&lt;/em&gt; and the &lt;em&gt;Azure Functions&lt;/em&gt; CLI, we first need to make sure that we are logged in.  Use the following command and enter the same credentials that we used in the Azure Portal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az login
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You could add other users to the Azure Active Directory, and grant these different users (aka "principles") permissions to view the secrets.   That is beyond the scope of this article.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h4&gt;
  
  
  13) Test our local development version.
&lt;/h4&gt;

&lt;p&gt;Making a note of the appropriate HTTP-Triggered Url (for me, this was &lt;code&gt;http://localhost:7071/api/Function1&lt;/code&gt;), we should see the secret configuration information pulled from the &lt;em&gt;Azure Key Vault&lt;/em&gt; and displayed as an HTTP response, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We retrieved the following value from Azure Key Vault :  This is a secret from Key Vault
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;




&lt;h4&gt;
  
  
  14)  Deploy code to AppService and test cloud version.
&lt;/h4&gt;

&lt;p&gt;Use "right-click deployment" to publish the app to the &lt;em&gt;Azure Functions&lt;/em&gt; AppService that we created earlier.     Using the appropriate URL (for me, this was &lt;code&gt;https://siliconvaultdemofunctionapp.azurewebsites.net/&lt;/code&gt;), demonstrate that the deployed version also works.&lt;/p&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--47bZaPY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/aragorn.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--47bZaPY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/aragorn.jpg%23shadow" alt="aragorn from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Azure Key Vault References
&lt;/h2&gt;

&lt;p&gt;You can read the official documentation here : &lt;a href="https://docs.microsoft.com/en-gb/azure/app-service/app-service-key-vault-references"&gt;Microsoft Documentation : Use Key Vault references for App Service and Azure Functions&lt;br&gt;
&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With perfect timing, late in the writing process of these articles (Oct 2019), Microsoft announced that &lt;a href="https://azure.microsoft.com/en-us/updates/general-availability-of-key-vault-references-in-app-service-and-azure-functions/"&gt;&lt;em&gt;Key Vault References&lt;/em&gt; in App Service and Azure Functions are now generally available&lt;br&gt;
&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"References" are a convenient improvement over the previous approach (described in the previous section) as it means that we no longer have to explicitly add additional code into our application to read settings from &lt;em&gt;Azure Key Vault&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Instead, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;continue to use an &lt;em&gt;Azure Active Directory&lt;/em&gt; managed-identity for our application (as previously). &lt;/li&gt;
&lt;li&gt;where we need to access a secret stored in &lt;em&gt;Azure Key Vault&lt;/em&gt;, we can replace the AppService configuration item with a specially formed "Reference String".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should carefully read the official documentation for  "Reference Strings" &lt;a href="https://docs.microsoft.com/en-gb/azure/app-service/app-service-key-vault-references#reference-syntax"&gt;here&lt;/a&gt;, as you are presented with a couple of requirements and a choice of string-style.&lt;/p&gt;

&lt;p&gt;There are two ways to write a "Reference String", here are examples of both, based on the example earlier in this article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Microsoft.KeyVault(SecretUri=https://siliconvaultdemokeyvault.vault.azure.net/secrets/our-secret/ec96f020xxxxxxxxxxxxxxxxxxxxxxxx)


@Microsoft.KeyVault(VaultName=https://siliconvaultdemokeyvault.vault.azure.net;SecretName=our-secret;SecretVersion=ec96f020xxxxxxxxxxxxxxxxxxxxxxxx)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;p&gt;The Azure Portal provides a convenient "cut+paste" facility, which you may find a convenient option.   We can find this screen by clicking, first on the &lt;code&gt;name&lt;/code&gt; of the secret and then clicking a second time on the &lt;code&gt;version&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3zzTr_ps--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/azure-key-vault-secret-version.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3zzTr_ps--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/azure-key-vault-secret-version.png%23shadow" alt="azure-key-vault-secret-version"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;It doesn't appear that &lt;em&gt;Azure Key Vault References&lt;/em&gt; can be used for local development.  There is no obvious place where the reference string could be used, so we need to stick to using &lt;code&gt;local.settings.json&lt;/code&gt; as usual.   This exact &lt;a href="https://github.com/MicrosoftDocs/azure-docs/issues/26874"&gt;question has been asked on GitHub&lt;/a&gt; quite some time ago, during the beta phase, but at time of writing remains unanswered.  I'm sure this will be a useful feature that gets added in the future.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7N5RD6lb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/galladriel-farewell.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7N5RD6lb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/galladriel-farewell.jpg%23shadow" alt="galladriel from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That's all
&lt;/h2&gt;

&lt;p&gt;So, we're finally at the end of the series.  If you've managed to read all the way through - congratulations! This series has taken a surprising amount of time to put together, so I really hope that this information is helpful to someone!   If you have found it useful, please share this on social media, that way we can increase the chance of others in the developer community possibly finding the answers they've been looking for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;No third party (i.e. Microsoft) compensates me for my promotion of their services in any way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thumbs Up
&lt;/h2&gt;

&lt;p&gt;A big thank you to Microsoft &lt;a href="https://twitter.com/nthonyChu/status/1184204419593916417"&gt;&lt;del&gt;Cloud Advocate&lt;/del&gt; Azure Functions PM&lt;/a&gt;,     &lt;a href="https://anthonychu.ca/"&gt;Anthony Chu&lt;/a&gt; for volunteering his time and literally burning the midnight oil to provide a technical review.  Some of the stuff I waded into was out of my depth and I really appreciated a voice from within Microsoft to help me out.&lt;/p&gt;

&lt;p&gt;Thanks to Twilio Evangelist &lt;a href="https://twitter.com/laylacodesit?lang=en"&gt;Layla Porter&lt;/a&gt; for supporting me once again, with her document-reviewing talents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/index?view=aspnetcore-3.0"&gt;Microsoft Documentation : Configuration in ASP.NET Core&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/azure/azure-functions-host/issues/4517"&gt;Github issue &lt;em&gt;"When using a FunctionsStartup class, settings are not read from the correct directory"&lt;/em&gt;&lt;/a&gt; with fix, and mention from &lt;a class="comment-mentioned-user" href="https://dev.to/fabiocav"&gt;@fabiocav&lt;/a&gt;
 that application-level configuration is planned to be supported.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="//github.com/azure/azure-functions-dotnet-extensions/issues/17"&gt;GitHub issue &lt;em&gt;"Load custom configuration file at startup"&lt;/em&gt;&lt;/a&gt;, which presents a way to locate the path for loading a config file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Azure/azure-functions-host/issues/4597"&gt;GitHub Issue : Questions about local.settings.json / configuration files with Azure Functions&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Azure/azure-functions-core-tools/issues/122"&gt;GitHub Issue : Rename default appsettings.json file to local.settings.json&lt;/a&gt; - indicates that Azure Functions historically used "appsettings.json" but renamed to "local.settings.json"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Azure/azure-functions-core-tools/issues/925"&gt;GitHub Issue : local.settings.json in .gitignore&lt;br&gt;
&lt;/a&gt; -  a discussion about the various merits and confusions related to the local.settings.json file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Azure-Samples/function-image-upload-resize/issues/9"&gt;GitHub Issue : Perhaps we shouldn't include local.settings.example.json&lt;/a&gt; - a discussion about the merits of having a separate &lt;code&gt;local.settings.example.json&lt;/code&gt; file&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azurefunctions</category>
      <category>csharp</category>
      <category>netcore</category>
    </item>
    <item>
      <title>[Configuration in Azure Functions Series - Part 3] Add ASP.NET Core configuration into an Azure Function project</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Mon, 09 Dec 2019 10:26:58 +0000</pubDate>
      <link>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-3-add-asp-net-core-configuration-into-an-azure-function-project-29kn</link>
      <guid>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-3-add-asp-net-core-configuration-into-an-azure-function-project-29kn</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part3-aspnet-in-functions/"&gt;blogs.siliconorchid.com&lt;/a&gt; on 1-Nov-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part three of a series exploring &lt;em&gt;.NET Core&lt;/em&gt; configuration, with an emphasis on &lt;em&gt;Azure Functions&lt;/em&gt;.   In this article,  we look at how we can add &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration into an &lt;em&gt;Azure Function&lt;/em&gt; project and use &lt;code&gt;IOptions&amp;lt;&amp;gt;&lt;/code&gt; to inject strongly-typed configuration objects into our functions.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part1-intro-and-aspnetcore-configuration/"&gt;part 1 of this series&lt;/a&gt;, we introduce the subject of configuration and review how &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration works.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part2-azure-functions-configuration/"&gt;part 2 of this series&lt;/a&gt;, we look at how configuration in &lt;em&gt;Azure Functions (v2)&lt;/em&gt; works and talk about some of the issues.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part3-aspnet-in-functions/"&gt;part 3 of this series&lt;/a&gt;, we show how you could include &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration into an &lt;em&gt;Azure Function&lt;/em&gt; project.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault/"&gt;part 4 of this series&lt;/a&gt;, we look at using other configuration services, specifically &lt;em&gt;Azure App Configuration&lt;/em&gt; and &lt;em&gt;Azure Key Vault&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Should you even attempt to use &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration, using IOptions&amp;lt;&amp;gt;, in an &lt;em&gt;Azure Function&lt;/em&gt;?
&lt;/h2&gt;

&lt;p&gt;This may seem like a very odd question to bring up straight away, especially when this article seems to be presenting a working solution.   &lt;/p&gt;

&lt;p&gt;However, as a caveat before you rush to try this code out in your own solution, &lt;strong&gt;you really need to go into this with your eyes open&lt;/strong&gt;, as the situation can get muddy very quickly.    &lt;/p&gt;

&lt;p&gt;If you go ahead and use the code in this article, you'll end up with two somewhat disjointed configuration systems working in parallel.  This isn't ideal and will quite likely lead to confusion amongst your development team and conflict within your code.   &lt;/p&gt;

&lt;p&gt;However, an option that uses file-based configuration may be a solution that works well for some of you, particularly if you are working with a large number of separate configuration items, deployed in a number of different places.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a reminder from part 1, this series has been written from the perspective of the developer who may already be accustomed to developing with  &lt;em&gt;ASP.NET Core&lt;/em&gt;, and is wanting to learn about the slightly different way that &lt;em&gt;Azure Functions v2&lt;/em&gt; goes about handling application configuration. This is why we draw parallels to &lt;em&gt;ASP.NET Core&lt;/em&gt; throughout this article on &lt;em&gt;Azure Functions&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ASP.NET Core Configuration&lt;/strong&gt;. In &lt;a href="(https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part1-intro-and-aspnetcore-configuration/)"&gt;part 1&lt;/a&gt; of this series, we covered how &lt;em&gt;ASP.NET Core&lt;/em&gt; provides a relatively flexible system of configuration. Specifically good reasons include:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a baseline file-based configuration, in the form of the file &lt;code&gt;appsettings.json&lt;/code&gt;, that deploys to all environments.
&lt;/li&gt;
&lt;li&gt;an abstraction of key-value pairs, that allow us to easily define structured configuration settings using json.&lt;/li&gt;
&lt;li&gt;support for binding of configuration to strongly-typed objects, using the &lt;em&gt;Options Pattern&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;support for easy inclusion of those objects into our own code, using dependency injection of &lt;code&gt;IOptions&amp;lt;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Functions Configuration&lt;/strong&gt;. In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part2-azure-functions-configuration/"&gt;part 2&lt;/a&gt; of this series, we covered how &lt;em&gt;Azure Functions&lt;/em&gt; were created to address several different requirements; they needed to be simple to use, cross-language and self-contained.   They were not intended to require the additional complication of having to setup and configure the host (as we do with &lt;em&gt;ASP.NET Core&lt;/em&gt;).  We also talked about the fact that the runtime of &lt;em&gt;Azure Functions&lt;/em&gt; has baked-in behaviour which means that  some aspects of configuration need to be provided in a fairly prescribed way:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each &lt;em&gt;Function&lt;/em&gt; method is defined by an Attribute (that defines its type) that is &lt;strong&gt;hardcoded to accept certain parameters&lt;/strong&gt; which are used by the runtime/bindings.    These typically comprise of a &lt;strong&gt;setting name rather than the setting value&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;other configuration-settings, that are used within the body of the &lt;em&gt;Function&lt;/em&gt; code itself, can be requested as needed, typically using &lt;code&gt;Environment.GetEnvironmentVariable("SomeConfigurationSetting")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;specific to the use of &lt;code&gt;local.setting.json&lt;/code&gt;; configuration settings are expected to be located in the &lt;code&gt;Values&lt;/code&gt; node as flat key-value pairs.  We cannot introduce nested JSON structures within the &lt;code&gt;Values&lt;/code&gt; node, nor can we use certain characters to define hierarchy.  We can use a single-underscore to provide improved legibility.&lt;/li&gt;
&lt;li&gt;out of the box, because there is no file-based baseline, all configuration needs to be defined repeatedly in different environments.  i.e. all of the configuration-settings need to be listed in the &lt;code&gt;local.settings.json&lt;/code&gt; - and then repeated again in the Azure AppService configuration.  In a more complex distributed system,with duplicate instances of an AppService in multiple different &lt;a href="https://azure.microsoft.com/en-gb/global-infrastructure/regions/"&gt;Azure Regions&lt;/a&gt;, those configuration-settings need to be duplication for each instance.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Alternatively, if we wanted to share a common source of configuration, a solution that is readily supported, is to use &lt;em&gt;Azure App Configuration&lt;/em&gt; (we'll cover this in part 4).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Does this even work?
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;ASP.NET Core&lt;/em&gt; and &lt;em&gt;Azure Functions&lt;/em&gt; have approaches to configuration that are compatible - but they don't really coexist together:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We can't supply configuration to the &lt;em&gt;Azure Functions&lt;/em&gt; runtime without using the provided method Attributes.  As just mentioned, those &lt;em&gt;Function&lt;/em&gt; Attributes (usually) dictate that we supply &lt;a href="https://jonskeet.uk/csharp/strings.html#literals"&gt;string literal&lt;/a&gt; arguments for setting-names, not setting-values.   There is no opportunity to take advantage of strongly-typed &lt;code&gt;IOptions&amp;lt;&amp;gt;&lt;/code&gt; here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The baked-in behaviour means that &lt;strong&gt;the runtime will only be looking for configuration in the places that it supports&lt;/strong&gt;, such as &lt;code&gt;local.settings.json&lt;/code&gt;.     The &lt;em&gt;Azure Functions&lt;/em&gt; runtime &lt;strong&gt;cannot see configuration from file sources that we define ourselves&lt;/strong&gt; in the host startup.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;To illustrate how this becomes a problem, let's return to the sample code for &lt;code&gt;CosmosDBTrigger&lt;/code&gt;, that we looked at earlier in part2 of the series.   We're going to try to combine it with an unexpected configuration source.  In this code sample, if we replace the &lt;code&gt;CosmosDBTrigger&lt;/code&gt; &lt;strong&gt;attribute parameter&lt;/strong&gt; at line 7 with &lt;code&gt;ConnectionStringSetting = "cosmosConnectionString"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For reader convenience, here's that code again with the change highlighted:&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;public static class Function1
{
    [FunctionName("Function2")]
    public static void Run([CosmosDBTrigger(
        databaseName: "databaseName",
        collectionName: "collectionName",
        ConnectionStringSetting = "cosmosConnectionString",
        LeaseCollectionName = "leases")]
           IReadOnlyList input, ILogger log)
    {
        if (input != null &amp;amp;&amp;amp; input.Count &amp;gt; 0)
        {
            log.LogInformation("Documents modified " + input.Count);
            log.LogInformation("First document Id " + input[0].Id);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
  

&lt;p&gt;It would now be fair to assume that next, we should add a corresponding setting into the root node of our [&lt;em&gt;ASP.NET Core&lt;/em&gt; style]  &lt;code&gt;appsetting.json&lt;/code&gt; file, like this:-&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;{
  "cosmosConnectionString": "&amp;lt;an-actual-connectionstring&amp;gt;",
  "anotherUnrelatedConfigurationSetting" : "anotherValue"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For the sake of this example, let's assume that this &lt;code&gt;appsetting.json&lt;/code&gt; file has been correctly configured in the host startup, using the steps that we'll describe a little later in this article.   We should also assume that we haven't confused matters by defining a duplicate of the setting, in a location that is expected, such as &lt;code&gt;local.settings.json&lt;/code&gt;.  The purpose of this test is to attempt to use, solely, the &lt;code&gt;appsettings.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;If we now go ahead and try and run this, the &lt;em&gt;Azure Functions&lt;/em&gt; will &lt;a href="https://www.urbandictionary.com/define.php?term=throw%20a%20wobbly"&gt;throw a wobbly&lt;/a&gt;, as the runtime does not know about this configuration source, even though we defined it in the host startup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9KKp5WkR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/func-cannot-resolve.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9KKp5WkR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/func-cannot-resolve.png%23shadow" alt="screenshot showing unresolved configuration error"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;It's easy to sound flippant when referring to "the baked-in behaviour" of the &lt;em&gt;Azure Functions&lt;/em&gt; runtime, but we should be really mindful not to trivialise what &lt;em&gt;Azure Functions&lt;/em&gt;, as a wider platform, is actually doing behind the scenes.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://github.com/Azure/azure-functions-host/issues/4464"&gt;this GitHub issue&lt;/a&gt;, which discusses a scenario that is very similar to the one presented in this article,  &lt;a href="https://github.com/fabiocav"&gt;Microsoft Developer Fabio Cavalcante &lt;/a&gt; offered this advice which is totally worth quoting here, as it provides further insight into how the platform works:-&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"...some infrastructure pieces, particularly in the consumption model, need to monitor trigger sources and some configuration options that control the Function App behavior. One example of such component is the scale controller.&lt;br&gt;
In order to activate and scale your application, the scale controller monitors the event source to understand when work is available and when demand increases or decreases. Using Service Bus as an example; the scale controller will monitor topics or queues used by Service Bus triggers, inspecting the queue/topic lengths and reacting based on that information. To accomplish this task, that component (which runs as part of the App Service infrastructure) needs the connection string for each Service Bus trigger setup and, &lt;strong&gt;today, it knows how to get that information from the sources we support. Any configuration coming from other providers/sources is not visible to the infrastructure outside of the runtime.&lt;/strong&gt;"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  How could this approach using supplemental configuration sources be useful to me?
&lt;/h3&gt;

&lt;p&gt;As an example of where additional settings could be useful, let's refer back to the code sample for a &lt;code&gt;CosmosDbTriggered&lt;/code&gt; Function, back in part 2.    In that example, provided by the MS templates, we can see that there are baked-in settings for wiring everything up to the CosmosDb listener.&lt;/p&gt;

&lt;p&gt;It's likely that we'll want our function to perform a task such as adding a message to a completely separate &lt;a href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-get-started-with-queues"&gt;ServiceBus Queue&lt;/a&gt; service.     To make this work, we would need to supply configuration information relevant to that service.   Traditionally at this point, we would be adding multiple separate lines of code into our &lt;em&gt;Function&lt;/em&gt; that uses things such as &lt;code&gt;Environment.GetEnvironmentVariable("ServiceBusUrl");&lt;/code&gt; etc.&lt;/p&gt;

&lt;p&gt;By instead supplying our &lt;em&gt;Function&lt;/em&gt; code with a strongly-typed &lt;code&gt;IOptions&amp;lt;&amp;gt;&lt;/code&gt; class, we can benefit from the aforementioned advantages associated with using &lt;em&gt;ASP.NET Core&lt;/em&gt; style configuration.&lt;/p&gt;




&lt;h3&gt;
  
  
  Do I have to use a separate AppSettings.json file?
&lt;/h3&gt;

&lt;p&gt;We can choose any filename we like, it doesn't specifically need to be &lt;code&gt;appsettings.json&lt;/code&gt;.  If we wanted to, we could even identify &lt;code&gt;local.setting.json&lt;/code&gt; as the configuration source and shoehorn-in extra JSON nodes  - I really wouldn't recommend doing this though!  The key to making this work is to correctly identify the file in the &lt;code&gt;startup&lt;/code&gt; class, using &lt;code&gt;.AddJsonFile("appsettings.json", false)&lt;/code&gt;,  which is then read by the host.&lt;/p&gt;

&lt;p&gt;However, be mindful that a key reason for adopting this approach is so that we have the option to use &lt;code&gt;appsettings.json&lt;/code&gt; as a baseline configuration that can be deployed to different environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dependency Injection in Azure Functions
&lt;/h2&gt;

&lt;p&gt;You can read the official documentation here: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection"&gt;Use dependency injection in .NET Azure Functions&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://twitter.com/azurefunctions?lang=en"&gt;&lt;em&gt;Azure Functions&lt;/em&gt; team&lt;/a&gt; are always working super-hard on improving the platform.  It was announced at Build 2019 that &lt;em&gt;Azure Functions&lt;/em&gt; had been updated to support native dependency injection (DI).  &lt;/p&gt;

&lt;p&gt;In one fell swoop, many of the things possible in &lt;em&gt;ASP.NET Core&lt;/em&gt; became available for use in &lt;em&gt;Azure Functions&lt;/em&gt; also.&lt;/p&gt;

&lt;p&gt;Originally, &lt;em&gt;Azure Function&lt;/em&gt; classes were required to be static classes.  One of the notable recent changes made to support DI, is that &lt;strong&gt;&lt;em&gt;Azure Functions&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;can now be created as instance methods&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;This then means that, combined with the new native dependency injection framework, &lt;strong&gt;we can inject objects into the constructor&lt;/strong&gt; of our &lt;em&gt;Function&lt;/em&gt; class instances.  &lt;/p&gt;

&lt;p&gt;Of specific interest to us, is the ability to inject &lt;code&gt;IOption&amp;lt;&amp;gt;&lt;/code&gt; objects into out &lt;em&gt;Function&lt;/em&gt; classes and work with them, just like we do with &lt;em&gt;ASP.NET Core&lt;/em&gt; &lt;/p&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TP2pLy4q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/bilbo-scary-face.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TP2pLy4q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/bilbo-scary-face.jpg%23shadow" alt="image showing bilbo baggins with a scary face ring from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Let's do it!
&lt;/h2&gt;


&lt;h3&gt;
  
  
  1) Explicitly tell the compiler which version of &lt;em&gt;.NET Core&lt;/em&gt; SDK to use
&lt;/h3&gt;

&lt;p&gt;This demo was created using &lt;em&gt;.NET Core v2&lt;/em&gt; - specifically SDK 2.2.402&lt;br&gt;
/ Runtime 2.2.7, which was the last version released, prior to being superseded by the newer &lt;em&gt;.NET Core v3&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When testing this demo, I had recently installed the &lt;em&gt;.NET Core v3&lt;/em&gt; SDK - which had just been made GA (at the time of writing).   The default behaviour for the compiler is to use the most recent version of the SDK installed.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using the &lt;em&gt;.NET Core 3&lt;/em&gt; SDK, I experienced an incompatibility/bug which caused the demo to fail during the build&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Very briefly, it was "blowing up" with the error : &lt;code&gt;Could not load file or assembly 'Microsoft.AspNetCore.Mvc.Abstractions...&lt;/code&gt;.  I ran out of time attempting to fix this - but this may be a problem which is already resolved, by the time you read this article.  &lt;/p&gt;

&lt;p&gt;In the meanwhile, to work-around the problem, we need to install and target a specific version of the SDK.   &lt;/p&gt;

&lt;p&gt;To achieve this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;download and install the last release of the &lt;a href="https://dotnet.microsoft.com/download/dotnet-core/2.2"&gt;v2.2.x SDK from Microsoft&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;after installing, you can verify which SDK's you have installed by using the command &lt;code&gt;dotnet --info&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add a new file &lt;code&gt;global.json&lt;/code&gt; into the solution-level folder of the demo  (i.e. one above the project folder) and edit it with the following:-&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class="language-js"&gt;{
  "sdk": {
    "version": "2.2.402"
  }
}
&lt;/code&gt;&lt;/pre&gt;




&lt;h3&gt;
  
  
  2) Set Framework Version and Add Nuget Packages in the project
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Update the &lt;code&gt;TargetFramework&lt;/code&gt; to &lt;code&gt;netcoreapp2.2&lt;/code&gt; (edit the &lt;code&gt;.csproj&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use this complete list of packages - versions used as indicated:   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.NET.Sdk.Functions&lt;/code&gt;  (v1.0.29)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Azure.Functions.Extensions&lt;/code&gt;   (v1.0.0)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.AspNetCore.App&lt;/code&gt;  (v2.2.6)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not confuse &lt;code&gt;Microsoft.AspNetCore.App&lt;/code&gt; with &lt;code&gt;Microsoft.NetCore.App&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  3) Update &lt;code&gt;local.settings.json&lt;/code&gt; with a setting
&lt;/h3&gt;

&lt;p&gt;The goal here is not really to use &lt;code&gt;local.setting.json&lt;/code&gt;, but it will help to demonstrate that the configuration systems can work side-by-side in certain cases.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-js"&gt;{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet"
        "LocalSettingValue" : "Setting From local.settings.json"
    }
}
&lt;/code&gt;&lt;/pre&gt;




&lt;h3&gt;
  
  
  4) Create &lt;code&gt;appSettings.json&lt;/code&gt; and populate
&lt;/h3&gt;

&lt;p&gt;This will depend on your own project, but this is an example of what the configuration could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ConfigurationItems&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CommonValue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Setting From appsettings.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SecretValue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Do not save secret values in appsettings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You need to ensure that &lt;code&gt;appsetting.json&lt;/code&gt; will be included as a deployable artefact of the build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you are using Visual Studio, from &lt;em&gt;Solution Explorer&lt;/em&gt;, right-click and select &lt;em&gt;Properties&lt;/em&gt;.  Under "Copy to Output Directory", select "Copy always".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alternatively, for developers using other code editors, you can edit the &lt;code&gt;.csproj&lt;/code&gt; like this:-&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;None&lt;/span&gt; &lt;span class="na"&gt;Update=&lt;/span&gt;&lt;span class="s"&gt;"appsettings.json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;Always&lt;span class="nt"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;






&lt;h3&gt;
  
  
  5) Create user secrets
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;From the &lt;em&gt;Solution Explorer&lt;/em&gt;, go ahead and right-click on the project file, &lt;/li&gt;
&lt;li&gt;Select "Manage User Secrets".    A new &lt;code&gt;secrets.json&lt;/code&gt; file will open - populate it with the following code:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ConfigurationItems&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SecretValue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Secret Setting From User Secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As a reminder from part 1 of this series, behind the scenes, the "Manage User Secrets" option will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a new key within the &lt;code&gt;.csproj&lt;/code&gt; file, with the name &lt;code&gt;UserSecretsId&lt;/code&gt; and a new GUID value.&lt;/li&gt;
&lt;li&gt;create a new folder in your OS user-profile which mirrors the GUID.  Within this folder, is where you can find the actual &lt;code&gt;secrets.json&lt;/code&gt; file - although Visual Studio makes it easy to open this.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  6) Create a Model which will bind to configuration
&lt;/h3&gt;

&lt;p&gt;We're going to take our string-based configuration file and bind the values to a strongly-typed object.   We should make a model class for this purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigurationItems&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CommonValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SecretValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;








&lt;h3&gt;
  
  
  7) Create a startup class
&lt;/h3&gt;

&lt;p&gt;A project created from an &lt;em&gt;Azure Functions&lt;/em&gt; template is minimal and doesn't include a &lt;code&gt;Startup.cs&lt;/code&gt; class, so let's add that in next.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Reflection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.Functions.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FunctionApp1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FunctionApp1.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;FunctionsStartup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FunctionApp1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FunctionsStartup&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IFunctionsHostBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigurationBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                   &lt;span class="c1"&gt;//.SetBasePath(Environment.CurrentDirectory)  ← do not use&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBasePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCurrentDirectory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJsonFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"appsettings.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddUserSecrets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExecutingAssembly&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEnvironmentVariables&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;         

            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConfigurationItems&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ConfigurationItems"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Take care not to use &lt;code&gt;.SetBasePath(Environment.CurrentDirectory)&lt;/code&gt;.  This is provided in most examples you'll find on the web.   This works fine for development, but &lt;strong&gt;does not work when you publish to Azure&lt;/strong&gt;.  Instead, you should use this: &lt;code&gt;.SetBasePath(Directory.GetCurrentDirectory())&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Also take care not to omit the &lt;code&gt;[assembly: ...]&lt;/code&gt; attribute, found adjacent to the &lt;code&gt;namespace&lt;/code&gt; section in the example above, otherwise your startup class will not actually be invoked at startup.  The project will still build without it though, which could lead to confusion.&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;If you've been looking at other guides related to this subject on the web,, you may have found code examples which use &lt;code&gt;WebJobsStartup&lt;/code&gt; instead of &lt;code&gt;FunctionsStartup&lt;/code&gt; and &lt;code&gt;IWebJobsBuilder&lt;/code&gt; instead of &lt;code&gt;IFunctionsHostBuilder&lt;/code&gt; … these are constructs that come with &lt;em&gt;ASP.NET Core&lt;/em&gt;.  As far as I can tell,  the &lt;em&gt;Azure Functions&lt;/em&gt; team has added a &lt;code&gt;Functions&lt;/code&gt; variant in mid-April 2019, which looks to be a wrapper of &lt;code&gt;IWebJobsStartup&lt;/code&gt; etc - check out the &lt;a href="https://github.com/Azure/azure-functions-dotnet-extensions/tree/master/src/Extensions/DependencyInjection"&gt;source code for azure-functions-dotnet-extensions&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  8) In your Azure Function class
&lt;/h3&gt;

&lt;p&gt;Finally, in our &lt;em&gt;Function&lt;/em&gt; class, we can now inject your strongly-typed configuration options, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;FunctionApp1.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;FunctionApp1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt;  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Function1&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConfigurationItems&lt;/span&gt; &lt;span class="n"&gt;_configurationItems&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Function1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConfigurationItems&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;configurationItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_configurationItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configurationItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;


        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Function1"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt;  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;localSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LocalSettingValue"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Included so as to demo regular approach&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;commonValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_configurationItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommonValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;secretValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_configurationItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecretValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Local Value : '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;localSettings&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' | Common Value : '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;commonValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' | Secret Value : '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secretValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;When working with an &lt;code&gt;IOptions&amp;lt;T&amp;gt;&lt;/code&gt; type, don't forget to include &lt;code&gt;.Value&lt;/code&gt; to get to the actual configuration settings.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  9) Run the program
&lt;/h3&gt;

&lt;p&gt;We should see output like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Value : 'Setting From local.settings.json' | Common Value : 'Setting From appsettings.json' | Secret Value : 'Secret Setting From User Secrets'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;








&lt;p&gt;In the final part of this series, we look at using other configuration services, specifically &lt;em&gt;Azure App Configuration&lt;/em&gt; and &lt;em&gt;Azure Key Vault&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault/" class="button is-primary"&gt;&lt;br&gt;
 &lt;i class="fas fa-file-alt ml-1"&gt; &lt;/i&gt; NEXT:  Read Part 4&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azurefunctions</category>
      <category>csharp</category>
      <category>netcore</category>
    </item>
    <item>
      <title>[Configuration in Azure Functions Series - Part 2] A deeper dive into Azure Functions configuration</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Mon, 09 Dec 2019 10:26:39 +0000</pubDate>
      <link>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-2-a-deeper-dive-into-azure-functions-configuration-2ffi</link>
      <guid>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-2-a-deeper-dive-into-azure-functions-configuration-2ffi</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part2-azure-functions-configuration/"&gt;blogs.siliconorchid.com&lt;/a&gt; on 1-Nov-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part two of a series exploring .NET Core configuration, with an emphasis on Azure Functions.  In this article, we review how configuration in  &lt;em&gt;Azure Functions&lt;/em&gt; is recommended to be used, how it differs from &lt;em&gt;ASP.NET Core&lt;/em&gt; and some of the potential issues and confusions surrounding its use.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part1-intro-and-aspnetcore-configuration/"&gt;part 1 of this series&lt;/a&gt;, we introduce the subject of configuration and review how &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration works.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part2-azure-functions-configuration/"&gt;part 2 of this series&lt;/a&gt;, we look at how configuration in &lt;em&gt;Azure Functions (v2)&lt;/em&gt; works and talk about some of the issues.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part3-aspnet-in-functions/"&gt;part 3 of this series&lt;/a&gt;, we show how you could include &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration into an &lt;em&gt;Azure Function&lt;/em&gt; project.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault/"&gt;part 4 of this series&lt;/a&gt;, we look at using other configuration services, specifically &lt;em&gt;Azure App Configuration&lt;/em&gt; and &lt;em&gt;Azure Key Vault&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;blockquote&gt;
&lt;p&gt;As a reminder from part 1, this series has been written from the perspective of the developer who may already be accustomed to developing with  &lt;em&gt;ASP.NET Core&lt;/em&gt;, and is wanting to learn about the slightly different way that &lt;em&gt;Azure Functions v2&lt;/em&gt; goes about handling application configuration. This is why we draw parallels to &lt;em&gt;ASP.NET Core&lt;/em&gt; throughout this article on &lt;em&gt;Azure Functions&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h2&gt;
  
  
  Configuration, the Azure Functions v2 way
&lt;/h2&gt;

&lt;p&gt;Let's now focus our attention towards &lt;em&gt;Azure Functions&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;The starting point for the &lt;em&gt;Azure Function&lt;/em&gt; documentation can be found here: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/"&gt;Microsoft Documentation: Azure Functions documentation&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike the original &lt;em&gt;Azure Functions v1&lt;/em&gt;, with the newer &lt;em&gt;Azure Functions v2&lt;/em&gt;, we can develop our solution using &lt;em&gt;.NET Core&lt;/em&gt;.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Azure Functions v2&lt;/em&gt; can actually be written in a &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/supported-languages"&gt;number of different languages&lt;/a&gt;, which again illustrates that &lt;em&gt;ASP.NET Core&lt;/em&gt; and &lt;em&gt;Azure Functions&lt;/em&gt; have evolved over time as different products.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;If you are familiar with &lt;em&gt;ASP.NET Core&lt;/em&gt;, it may feel appropriate, given the common language and other similarities, that we should just be able to both define and consume configuration in exactly the same way. &lt;/p&gt;

&lt;p&gt;However, with &lt;em&gt;Azure Functions&lt;/em&gt; this is not the case - it's a totally different &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-versions"&gt;runtime&lt;/a&gt; - and it often goes about things differently.  &lt;/p&gt;

&lt;p&gt;For example, as &lt;em&gt;ASP.NET Core&lt;/em&gt; developers, our instinct will most likely be to develop the code locally (using an IDE such as Visual Studio or Rider) and ultimately, build and deploy our work someplace (let's assume an &lt;em&gt;Azure WebApp&lt;/em&gt;) as a separate process.  &lt;/p&gt;

&lt;p&gt;In contrast, with &lt;em&gt;Azure Functions&lt;/em&gt;, &lt;strong&gt;local development is not the defacto workflow&lt;/strong&gt;.  Microsoft's original intent was to create standalone units of code that could be edited directly within the Azure Portal, using a much wider range of programming languages, with the hosting and scaling aspects completely abstracted away.  &lt;/p&gt;

&lt;p&gt;It is only in the past year, with the release of  &lt;em&gt;Azure Function v2&lt;/em&gt;, that we now have the option to develop locally, using &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local"&gt;Azure Functions Core Tools&lt;/a&gt;  and deploy compiled code to the cloud.   &lt;strong&gt;The same runtime is used both locally for development and for deployment in the cloud.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are reminded of these origins, by the fact that we can still take our C# code and copy it into Azure, in the form of  C# script (.csx) files.&lt;/p&gt;




&lt;h3&gt;
  
  
  What does serverless really mean?
&lt;/h3&gt;

&lt;p&gt;If you've looked into &lt;em&gt;Serverless&lt;/em&gt; computing before, you'll most likely be familiar with the long-running joke, made by most presenters of Serverless Technology, that "of course there are servers".  &lt;/p&gt;

&lt;p&gt;From a marketing perspective, the &lt;em&gt;Serverless&lt;/em&gt; mantra suggests that self-contained units of code are untethered from our conventional concept of a host (especially when using the &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-scale"&gt;consumption model&lt;/a&gt;) and that &lt;em&gt;Functions&lt;/em&gt; exist in isolation, with everything they need, in terms of configuration, either hard-coded or made available through a simplified configuration system.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We need to clarify that in reality, multiple &lt;em&gt;Functions&lt;/em&gt; do in fact, still just run on individual AppService hosts&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Even without modification, it is the host that facilitates the various triggers.  If we peek under the hood of the &lt;a href="https://github.com/Azure/azure-functions-host/blob/dev/src/WebJobs.Script.WebHost/Program.cs"&gt;&lt;em&gt;Functions&lt;/em&gt; host code&lt;/a&gt; we can see that really, it's just an &lt;em&gt;ASP.NET Core&lt;/em&gt; app! &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is, of course, much more to &lt;em&gt;Azure Functions&lt;/em&gt; than just AppService hosts, as the runtime of the wider platform is aware of which triggers need to be monitored (e.g. monitoring a Service Bus Queue) and manages the appropriate provisioning and scaling of hosts) - but for the purpose of this article, we're only really interested in thinking of a single host in isolation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As with an MVC/WebAPI application, running on a more conventionally-provisioned and long-running &lt;em&gt;Azure WebApp&lt;/em&gt; host, this means that an individual &lt;em&gt;Function&lt;/em&gt; can in fact have access to any longer-running services and resources if needed.  &lt;/p&gt;

&lt;p&gt;We can readily demonstrate this by introducing an appropriate startup class to the project. We can then dive in and write code that does tasks such as setting up our own common services, which then exist for the lifecycle of that host.  &lt;/p&gt;

&lt;p&gt;By doing this, &lt;em&gt;Functions&lt;/em&gt; start to look much more like an &lt;em&gt;ASP.NET Core&lt;/em&gt; application running on comparatively short-lived server instances.  &lt;/p&gt;

&lt;p&gt;A distinction is that unlike &lt;em&gt;ASP.NET Core&lt;/em&gt;, &lt;em&gt;Azure Functions&lt;/em&gt; effectively have controllers that can be written in a wide range of different languages.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why do I need to know this?
&lt;/h3&gt;

&lt;p&gt;The relevance of knowing that we have the option to access the host (and that we can configure it, just like any other &lt;em&gt;.NET Core&lt;/em&gt; application) is that this means that in theory, &lt;strong&gt;we could define alternative configuration-providers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This opens the door to using the  &lt;em&gt;ASP.NET Core&lt;/em&gt; style of configuration.   We'll be looking at this subject properly, in part 3 of this series.&lt;/p&gt;

&lt;p&gt;However, before we start diving into that particular subject, let's back up and talk about how we are supposed to be using configuration, in &lt;em&gt;Azure Functions&lt;/em&gt;, under conventional circumstances...&lt;/p&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8g3qXwMQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/fellowship.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8g3qXwMQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/fellowship.jpg%23shadow" alt="fellowship of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Different types of Azure Function
&lt;/h3&gt;

&lt;p&gt;Let's start by looking into how &lt;em&gt;Azure Functions&lt;/em&gt; are used from a high-level.&lt;/p&gt;

&lt;p&gt;You can read the official documentation on this subject here: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings"&gt;Microsoft Documentation: Azure Functions triggers and bindings concepts&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The type of a &lt;em&gt;Function&lt;/em&gt; is determined by its &lt;strong&gt;Trigger&lt;/strong&gt; and &lt;strong&gt;Bindings&lt;/strong&gt;.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a trigger is what causes a function to run (e.g. an HTTP-trigger). &lt;/li&gt;
&lt;li&gt;the binding is a way to connect the input and/or output of the &lt;em&gt;Function&lt;/em&gt; to some other resource (e.g. outputting a result to a queue).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft provides the platform with &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings#supported-bindings"&gt;support for a wide variety of different Triggers and Bindings&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's possible to define your own &lt;em&gt;Function&lt;/em&gt; types, but that is out of scope for this article.  Instead, check out &lt;a href="http://dontcodetired.com/blog/post/Creating-Custom-Azure-Functions-Bindings"&gt;Jason Roberts: Creating Custom Azure Functions Bindings&lt;br&gt;
 &lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In code, the type of a particular &lt;em&gt;Function&lt;/em&gt; is declared by using a parameterised &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/"&gt;C# Attribute&lt;/a&gt;.     &lt;/p&gt;

&lt;p&gt;These parameters vary from type to type, but typically they are the place where information related to configuration can be identified.&lt;/p&gt;

&lt;p&gt;The following sample of code was produced directly by the template for the &lt;code&gt;CosmosDbTrigger&lt;/code&gt; function:-&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-csharp"&gt;public static class Function1
{
    [FunctionName("Function1")]
    public static void Run([CosmosDBTrigger(
        databaseName: "databaseName",
        collectionName: "collectionName",
        ConnectionStringSetting = "connectionstringsettingname",
        LeaseCollectionName = "leases")]
           IReadOnlyList input, ILogger log)
    {
        if (input != null &amp;amp;&amp;amp; input.Count &amp;gt; 0)
        {
            log.LogInformation("Documents modified " + input.Count);
            log.LogInformation("First document Id " + input[0].Id);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At first glance, we could be forgiven for thinking that the above-highlighted code is expecting us to directly hard-code all of the configuration information,  including the connection string to the &lt;em&gt;Cosmos Database&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Thankfully, this is not the case, as the values being requested are usually the key-name that correlates to an item in configuration.    &lt;/p&gt;

&lt;p&gt;It's a mildly ambiguous situation, so it helps to be warned that you need to pay attention to the naming of the parameters. In this example, it doesn't say &lt;code&gt;ConnectionString&lt;/code&gt;, it says &lt;code&gt;ConnectionStringSetting&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With that said, it's not uniformly the case that parameters can always be expected to relate to a configuration-key.    &lt;/p&gt;

&lt;p&gt;In the above example, some of the values (&lt;code&gt;databaseName&lt;/code&gt;, &lt;code&gt;collectionName&lt;/code&gt; and &lt;code&gt;leases&lt;/code&gt;) are expected to be supplied as hard-coded string literal values. We could say that this is not ideal, however, it should be possible to layer-in our own supplemental configuration and replace the &lt;a href="https://jonskeet.uk/csharp/strings.html#literals"&gt;string literals&lt;/a&gt; demonstrated in line-5, above, with a call to something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;databaseName&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;myDatabaseName&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;






&lt;h3&gt;
  
  
  What are the prescribed configuration sources?
&lt;/h3&gt;

&lt;p&gt;You should refer to  &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings#settings"&gt;Microsoft Documentation: Manage your function app: Application settings.&lt;br&gt;
&lt;/a&gt;  and &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file"&gt;Microsoft Documentation: Work with Azure Functions Core Tools : Local settings file&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;So what can we take away from the docs?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;In Azure Functions, there is no baseline configuration which is common to all environments&lt;/strong&gt; (i.e. no &lt;code&gt;appsettings.json&lt;/code&gt; or equivalent).  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you recall our earlier learning, that not all &lt;em&gt;Azure Function&lt;/em&gt; implementations are even deployed from a local development version, then we could say that this is understandable - because the focus has originally been about editing standalone solutions in the cloud, not about deploying multiple versions with different configurations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Because there is no baseline configuration to be deployed, &lt;strong&gt;when hosting in an &lt;em&gt;Azure App Service&lt;/em&gt;, we must use &lt;em&gt;App Service Configuration&lt;/em&gt; to specify each and every key&lt;/strong&gt;.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If we have a relatively large number of configuration items, entering via the Azure Portal is likely to be laborious and error-prone, so &lt;a href="https://docs.microsoft.com/en-us/azure/automation/"&gt;automated environment scripting&lt;/a&gt; may be an option to improve this issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The configuration file for local development is&lt;/strong&gt; &lt;code&gt;local.settings.json&lt;/code&gt;.  This is intended for all local settings, including any secrets that we need to use.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;ASP.NET Core&lt;/em&gt; normally uses a default builder in the application startup, to enable us to work with &lt;code&gt;appsettings.json&lt;/code&gt;.  In contrast, when using &lt;em&gt;Azure Functions&lt;/em&gt;, the ability to read settings from &lt;code&gt;local.settings.json&lt;/code&gt; is an example of behaviour that is baked-into the runtime.  This was exemplified in the previous code sample, where, simply by identifying the configuration-item name, this was enough to for the &lt;em&gt;Function&lt;/em&gt; to automatically locate and use the appropriate configuration source.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;We can still layer-in configuration from other&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;configuration providers&lt;/em&gt;&lt;/strong&gt;, such as &lt;em&gt;Azure App Configuration&lt;/em&gt; and &lt;em&gt;Azure Key Vault&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Opinion:&lt;/strong&gt; Compared to the exhaustive information found in &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/index?view=aspnetcore-3.0"&gt;Microsoft Documentation: ASP.NET Core Configuration&lt;/a&gt;, the equivalent documentation for &lt;em&gt;Azure Functions&lt;/em&gt;, as available at the time of writing, is not as insightful.   This is not to say that the documentation isn't useful - I just found it to be somewhat terse, short on links to other resources and doesn't provide a great deal of guidance for beginners.&lt;/p&gt;
&lt;/blockquote&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wb0M-KUC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gollum.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wb0M-KUC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gollum.jpg%23shadow" alt="gollum from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What do we use for local configuration?
&lt;/h3&gt;

&lt;p&gt;The official documentation &lt;a href="https://docs.microsoft.com/en-gb/azure/azure-functions/functions-run-local#local-settings-file"&gt;Microsoft Documentation:  Work with Azure Functions Core Tools: Local settings file&lt;br&gt;
&lt;/a&gt;  tells us that &lt;strong&gt;when developing locally, we should be using &lt;code&gt;local.settings.json&lt;/code&gt;&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;We are advised that the purpose of &lt;code&gt;local.settings.json&lt;/code&gt; is  (quote): &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;"stores app settings and connection strings that are used when running locally. This file contains secrets and isn't published to your function app in Azure."&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;Summarising from various documents, Microsoft has communicated that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We should be using the &lt;code&gt;Values&lt;/code&gt; section of &lt;code&gt;local.settings.json&lt;/code&gt; for our local application settings, secret or otherwise.   &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file"&gt;Here is an example in the docs&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The runtime supports a handful of expected nodes (e.g. &lt;code&gt;Host&lt;/code&gt; and &lt;code&gt;ConnectionStrings&lt;/code&gt; are indicated in the docs)  and ignore any custom nodes or structures that we include in the file. &lt;/li&gt;
&lt;li&gt;Settings found in this &lt;code&gt;Values&lt;/code&gt; node equate to counterparts located directly in the root of an Azure AppService Configuration collection. &lt;/li&gt;
&lt;li&gt;When hosted in Azure, there is no need to attempt to mirror the JSON structure found in &lt;code&gt;local.settings.json&lt;/code&gt; by specifying a parent node called "Values".  I.e. none of those key-names needs to include any hierarchy delimiters, such as &lt;code&gt;Values__MySetting&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rHny5VE8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/nasty-gollum.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rHny5VE8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/nasty-gollum.jpg%23shadow" alt="nasty gollum from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Great.  So now we are clear about where we should be saving local configuration settings, right?
&lt;/h3&gt;

&lt;p&gt;Not quite!   At this point, the documentation becomes a bit disjointed and suggests a different approach.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-azure-function-csharp"&gt;Microsoft Documentation: Quickstart: Create an Azure function with Azure App Configuration&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-gb/azure/azure-functions/functions-dotnet-class-library#environment-variables"&gt;Microsoft Documentation: Azure Functions C# developer reference&lt;/a&gt;,  the demonstrated way to store configuration during local development makes no mention of &lt;code&gt;local.settings.json&lt;/code&gt; and instead uses &lt;strong&gt;&lt;em&gt;Environment Variables.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Environment Variables&lt;/em&gt; aren't new to this story, because if you recall from part 1 of this series, they were identified as one of several possible &lt;em&gt;Configuration Providers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The suggestion to use &lt;em&gt;Environment Variables&lt;/em&gt; for local development, was likely intended as a way to maintain consistency with how apps are configured when deployed to the cloud.   &lt;/p&gt;





&lt;p&gt;&lt;em&gt;Environment Variables&lt;/em&gt; are used as a primary configuration choice for &lt;em&gt;Azure Functions&lt;/em&gt; because they are consistently available across the various languages and environments that &lt;em&gt;Functions&lt;/em&gt; support.  For example, you'll find them used in places &lt;a href="https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/"&gt;such as containers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-azure-function-csharp#test-the-function-locally"&gt;These instructions&lt;/a&gt; show you how to set these values using the CLI for different platforms.&lt;/p&gt;

&lt;p&gt;On Windows, you can also access these settings by visiting &lt;code&gt;System Properties&lt;/code&gt; → &lt;code&gt;Environment Variables&lt;/code&gt; and adding/editing values from there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i5sewofu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/windows-environment-settings.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i5sewofu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/windows-environment-settings.png%23shadow" alt="screenshot of windows environment setting dialogue"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SMuyo9g9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gollum-confused.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SMuyo9g9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gollum-confused.jpg%23shadow" alt="confused gollum from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  But wait, there are even more Environment Settings!
&lt;/h3&gt;

&lt;p&gt;Remember back in part 1, we said that it's easy to be &lt;em&gt;overwhelmed with too many options&lt;/em&gt;? &lt;/p&gt;

&lt;p&gt;In addition to OS Environment Settings, Visual Studio also provides even more "Environment Settings" for use when debugging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Visual Studio Solution Explorer, right-click on the &lt;em&gt;Functions&lt;/em&gt; project and select the &lt;code&gt;Properties&lt;/code&gt; option.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Debug&lt;/code&gt; - we are presented with a dialogue box with several options.  One of these options is &lt;code&gt;Environment Variables&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---5OYjSwE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vs-environment-settings.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---5OYjSwE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/vs-environment-settings.png%23shadow" alt="screenshot showing visual studio environment setting dialogue"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you fill out this section with configuration information, the settings get saved not to the environment-settings of the operating system, but as part of yet another type of configuration file:  &lt;code&gt;properties/launchsettings.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The file could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;profiles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTPFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commandName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Project&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;environmentVariables&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OurCustomSetting2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;settingsfromVS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Something to watch out for, is that:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unlike OS Environment Variables, this file will usually be included in version control - so &lt;strong&gt;don't use it to save secrets!&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;As far as configuration files go, this is probably not an obvious place to expect to find settings, so it's difficult to recommend using this option.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Enough already - what should I use?
&lt;/h3&gt;

&lt;p&gt;Microsoft presents us with several different options for local configuration, so which one should we use?   &lt;/p&gt;

&lt;p&gt;The answer to that question inevitably stumbles firmly into the usual territory of "it depends, there is no right answer".   &lt;/p&gt;

&lt;p&gt;I personally would recommend using &lt;code&gt;local.settings.json&lt;/code&gt; over Environment Settings, as this is generally more in keeping with documentation and examples.  However, this choice is not without other considerations, which we'll talk about next:&lt;/p&gt;





&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BvXl59Ae--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gates-of-mordor.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BvXl59Ae--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gates-of-mordor.jpg%23shadow" alt="image of black gate of mordor from lord of the rings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What's the issue with local.settings.json?
&lt;/h3&gt;

&lt;p&gt;It's widely accepted that you should not keep secrets in version control.   &lt;/p&gt;

&lt;p&gt;However, as we discussed earlier, in &lt;em&gt;Azure Functions&lt;/em&gt;, Microsoft advises us to use &lt;code&gt;local.settings.json&lt;/code&gt; for local secrets.  This is a file that is located within a folder that we can normally expect to be version-controlled.   &lt;/p&gt;

&lt;p&gt;In my opinion, this &lt;strong&gt;brings potential complications and compares poorly to alternative options&lt;/strong&gt;, specifically &lt;em&gt;User Secrets&lt;/em&gt;, which locate the file &lt;code&gt;secrets.json&lt;/code&gt; in the developers OS user-profile,  completely away from the rest of the project code.&lt;/p&gt;

&lt;p&gt;If you use a Microsoft template to create a new &lt;em&gt;Azure Function&lt;/em&gt; project, it's rather easy to overlook that there is a &lt;code&gt;.gitignore&lt;/code&gt; file included in the root of the &lt;strong&gt;project&lt;/strong&gt;.  To be clear, this will be &lt;strong&gt;in addition&lt;/strong&gt; to the &lt;code&gt;.gitignore&lt;/code&gt; that will quite likely already be present in the root of our &lt;strong&gt;solution&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Even more easy to overlook, is the fact that amongst the large list of common-looking exclusions, there is an entry near the top of that file, which is used to explicitly exclude &lt;code&gt;local.settings.json&lt;/code&gt; from version control.    &lt;/p&gt;

&lt;p&gt;In theory, this exclusion addresses the problem of not including the file in version control.&lt;/p&gt;

&lt;p&gt;I believe this poses some potential issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens when someone on your development team is not aware of this subtle exclusion, and then deletes this customised &lt;code&gt;.gitignore&lt;/code&gt; from the project (e.g. "why have we got a duplicate of the &lt;code&gt;.gitignore&lt;/code&gt;, when we've got the same one correctly at solution level?")?

&lt;ul&gt;
&lt;li&gt;Your secrets are at risk of being unintentionally introduced into version control.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the &lt;code&gt;local.settings.json&lt;/code&gt; is not included in version control, and a team member clones the repo to work afresh on the project - how are they easily supposed to know what configuration items are meant to be part of the project's configuration (without having to hunt around)?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I've seen the answer to this in several demonstration projects - which is to include the file &lt;code&gt;local.settings.sample.json&lt;/code&gt; in the project, as a way to convey the configuration schema.  &lt;/p&gt;

&lt;p&gt;This solution is a somewhat awkward workaround, which as far as I can tell, is undocumented as a recommended practice.  It also requires developers to purposefully update this "sample" file, as opposed to updating an actual working version (as we would, if working with an &lt;em&gt;ASP.NET Core&lt;/em&gt;  &lt;code&gt;appsettings.json)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This problem of "what are the baseline configuration-settings?" is not unique to &lt;code&gt;local.settings.json&lt;/code&gt; as it also rears its head, in just the same way, when using &lt;em&gt;Environment Settings&lt;/em&gt;.&lt;/strong&gt;  &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Opinion:&lt;/strong&gt; I should stress that the above is just an opinion and that there is nothing wrong with using &lt;code&gt;local.settings.json&lt;/code&gt; with its accompanying use of a version-control exclusion rule.   It is simply that everyone in our development team needs to be briefed about the nuances and workarounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Whilst researching these articles, I discovered that I am pretty late to the game with my learnings, observations and opinions. A concisely written article by &lt;a href="https://www.tomfaltesek.com/azure-functions-local-settings-json-and-source-control/"&gt;Tom Faltesek&lt;/a&gt;, covered many of the issues that I wanted to write about, a whole year beforehand (2018). &lt;/p&gt;
&lt;/blockquote&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jwWLXCf1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gandalf-reading.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jwWLXCf1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gandalf-reading.jpg%23shadow" alt="gandalf reading a scroll"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How do we manually read configuration settings?
&lt;/h3&gt;

&lt;p&gt;We've learned that &lt;em&gt;Function&lt;/em&gt; attributes are used as a mechanism to specify certain configuration-settings, which typically are required as binding parameters.   &lt;/p&gt;

&lt;p&gt;However, there are plenty of other scenarios where we need to request configuration-settings from within the body of our own code.    For example, we may want our function to add some information to a queue, so how do we go about retrieving a connection string (to that queue resource) in the body of our &lt;em&gt;Function&lt;/em&gt;?  &lt;/p&gt;

&lt;p&gt;Referring back to those same two documents; &lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-azure-function-csharp"&gt;Microsoft Documentation: Quickstart: Create an Azure function with Azure App Configuration&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-gb/azure/azure-functions/functions-dotnet-class-library#environment-variables"&gt;Microsoft Documentation: Azure Functions C# developer reference&lt;/a&gt;,  the demonstrated way to retrieve configuration during local development is to use this piece of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;configValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;app setting name&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EnvironmentVariableTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This does imply that we need to use a &lt;a href="https://jonskeet.uk/csharp/strings.html#literals"&gt;string literal&lt;/a&gt; value for the configuration-setting key, everywhere that it needs to be requested.  We could say that this is a somewhat brittle way to write code (we all make typos and this kind of bug can be hard to spot).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One way to slightly mitigate against this, is to use a static class with &lt;code&gt;const strings&lt;/code&gt; - as exemplified in this &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part2-look-at-code/"&gt;earlier article&lt;/a&gt; under the heading "GlobalConstants.cs".&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Although the method name &lt;code&gt;GetEnvironmentVariable()&lt;/code&gt; sounds like it may only work for retrieving Environment-Variables specifically, this in fact is not the case and this single command can be used to retrieve configuration from all of your sources, whether they be Azure AppService configuration, or the local &lt;code&gt;local.settings.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  How do we add structure to our configuration settings?
&lt;/h3&gt;

&lt;p&gt;When we talk about &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#hierarchical-configuration-data"&gt;configuration sections&lt;/a&gt; (aka "Hierarchical values"), we're referring to the ability to group similar pieces of configuration together in groups.&lt;/p&gt;

&lt;p&gt;When we are working with &lt;em&gt;ASP.NET Core&lt;/em&gt;, with its default use of an &lt;code&gt;appsettings.json&lt;/code&gt; file,  the structure of such "configuration sections" are clearly visible to both view and work with.  &lt;/p&gt;

&lt;p&gt;For example, an &lt;code&gt;appsetting.json&lt;/code&gt; file could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EmailSettings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ApiKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secretKeyValue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FromAddress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;do-not-reply@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;p&gt;However, because the use of a JSON file &lt;strong&gt;is an abstraction&lt;/strong&gt;, when we need to work with other configuration-providers, this structure needs to be mapped down into a single string (a basic key/value pair). &lt;/p&gt;

&lt;p&gt;This is where things can start to get a little confusing, because the various Microsoft services don't always work in quite the same way.  &lt;/p&gt;

&lt;p&gt;It's an issue which is further compounded by environment diversity (i.e. Windows, Linux, Containers, etc), so our options for what does and doesn't work will vary.  &lt;/p&gt;

&lt;p&gt;Usually, hierarchy can be described by using some combination of the delimiting characters &lt;code&gt;:&lt;/code&gt; (colon),  &lt;code&gt;__&lt;/code&gt; (double-underscore) and/or &lt;code&gt;--&lt;/code&gt; (double-dash).  &lt;/p&gt;

&lt;p&gt;In most cases, the &lt;code&gt;_&lt;/code&gt; (single-underscore) character can be used to punctuate long strings,  to make them more human-readable.  This could be used as a way to make related configuration-settings &lt;em&gt;appear&lt;/em&gt; to be grouped together (e.g. &lt;code&gt;EmailSettings_ReplyAddress&lt;/code&gt; and &lt;code&gt;EmailSettings_ApiKey&lt;/code&gt;), but it has no impact other than cosmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be aware that you may need to experiment, as the official guidance shows there are a mixture of approaches.&lt;/strong&gt;   Let's look at some examples:&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ASP.NET Core&lt;/strong&gt;.  In the guide &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#conventions"&gt;Configuration in ASP.NET Core : Conventions&lt;/a&gt;, the following advice seems to be the most encompassing and most relevant for a majority of scenarios (quote):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Within the Configuration API, a colon separator (&lt;code&gt;:&lt;/code&gt;) works on all platforms.  In environment variables, a colon separator may not work on all platforms. A double underscore (&lt;code&gt;__&lt;/code&gt;) is supported by all platforms and is automatically converted into a colon. In Azure Key Vault, hierarchical keys use &lt;code&gt;--&lt;/code&gt; (two dashes) as a separator. You must provide code to replace the dashes with a colon when the secrets are loaded into the app's configuration.&lt;br&gt;
"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Later in the same document, in the section &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#environment-variables-configuration-provider"&gt;Environment Variables Configuration Provider&lt;/a&gt;, the advice is refined, by noting that the Bash console may not support colons.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"When working with hierarchical keys in environment variables, a colon separator (:) may not work on all platforms (for example, Bash). A double underscore (__) is supported by all platforms and is automatically replaced by a colon."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;So, that means that the &lt;em&gt;Azure AppService Application settings&lt;/em&gt; that correlates to the JSON example we just looked at, would have keys that look like this  (the screenshot demonstrates both the use of a colon and double-underscore):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wC1eN6KE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/azure-portal-app-settings.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wC1eN6KE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/azure-portal-app-settings.png%23shadow" alt="azure portal app settings dialogue"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Functions&lt;/strong&gt; In the guide &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file"&gt;Work with Azure Functions Core Tools : Local settings file&lt;/a&gt; we are advised that when working with &lt;code&gt;local.settings.json&lt;/code&gt;, things don't quite work the same and that we can't describe hierarchy (quote):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;" Setting names can't include a colon (:) or a double underline (__). These characters are reserved by the runtime."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;It's worth underlining (ahem ;-) ), that in the case of &lt;em&gt;Azure Functions&lt;/em&gt;, we can still use the &lt;code&gt;_&lt;/code&gt; (single underscore) character to help to improve legibility.  You can see an example of this if we provision a new Azure AppService.  Out of the box,  it comes with a selection of settings already populated, including for example, &lt;code&gt;FUNCTIONS_WORKER_RUNTIME&lt;/code&gt;. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Key Vault&lt;/strong&gt;. We'll be looking at &lt;em&gt;Azure Key Vault&lt;/em&gt; more closely in part 4 of this series, but in the context of this particular topic, it's worth noting that &lt;em&gt;Azure Key Vault&lt;/em&gt; introduces its own unique set of restrictions.  The advice given in &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-3.0#secret-storage-in-the-production-environment-with-azure-key-vault"&gt;Azure Key Vault Configuration Provider in ASP.NET Core : Secret storage in the Production environment with Azure Key Vault&lt;br&gt;
&lt;/a&gt;, is (quote):-&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Azure Key Vault secret names are limited to alphanumeric characters and dashes. Hierarchical values (configuration sections) use -- (two dashes) as a separator. Colons, which are normally used to delimit a section from a subkey in ASP.NET Core configuration, aren't allowed in key vault secret names. Therefore, two dashes are used and swapped for a colon when the secrets are loaded into the app's configuration."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Wrapping up part two
&lt;/h3&gt;

&lt;p&gt;If you're adopting &lt;em&gt;Azure Functions&lt;/em&gt; having previously worked with &lt;em&gt;ASP.NET Core&lt;/em&gt;,  it pays to be aware of the subtle differences.   Hopefully, this article will have helped you to navigate this adjustment.&lt;/p&gt;

&lt;p&gt;To summarise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When it comes to configuration, &lt;em&gt;Azure Functions&lt;/em&gt; are a little bit opinionated because of baked-in behaviour in its runtime.   This applies both to where configuration-settings are expected to be found and also how they should be consumed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;em&gt;Azure Functions&lt;/em&gt; configuration system doesn't support hierarchical configuration-settings.  The various  configuration-providers have differing character restrictions for setting-names, so test that your choices can be used in the environments that you intend to use. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Azure Functions&lt;/em&gt; still have a host.  In the startup, we can define services just as we can like any other &lt;em&gt;.NET Core&lt;/em&gt; app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are nuances that come with using &lt;code&gt;local.settings.json&lt;/code&gt; that your team needs to be aware of.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This article is based upon &lt;em&gt;Azure Functions v2&lt;/em&gt;, so may be outdated in the near future as &lt;a href="https://github.com/Azure/app-service-announcements/issues/200"&gt;an announcement has been made&lt;/a&gt; that &lt;em&gt;Azure Functions v3&lt;/em&gt; is on the immediate horizon, with &lt;a href="https://github.com/Azure/app-service-announcements/issues/204"&gt;support for &lt;em&gt;.NET Core&lt;/em&gt; 3&lt;/a&gt; having already been rolled out.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;






&lt;p&gt;In part 3 of this series, we'll look in detail at the code needed to recreate &lt;em&gt;ASP.NET Core&lt;/em&gt; style configuration, by being able to read configuration JSON files and inject strongly-typed configuration models into your &lt;em&gt;Azure Functions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part3-aspnet-in-functions/" class="button is-primary"&gt;&lt;br&gt;
 &lt;i class="fas fa-file-alt ml-1"&gt; &lt;/i&gt; NEXT:  Read Part 3&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azurefunctions</category>
      <category>csharp</category>
      <category>netcore</category>
    </item>
    <item>
      <title>[Configuration in Azure Functions Series - Part 1] Introduction to .NET Core configuration</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Mon, 09 Dec 2019 10:24:54 +0000</pubDate>
      <link>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-1-introduction-to-net-core-configuration-1ecg</link>
      <guid>https://forem.com/siliconorchid/configuration-in-azure-functions-series-part-1-introduction-to-net-core-configuration-1ecg</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part1-intro-and-aspnetcore-configuration/"&gt;blogs.siliconorchid.com&lt;/a&gt; on 1-Nov-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part one of a series exploring .NET Core configuration, with an emphasis on Azure Functions.  In this article, we introduce the subject of configuration in general and look at how &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration is recommended to be used.&lt;/strong&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part1-intro-and-aspnetcore-configuration/"&gt;part 1 of this series&lt;/a&gt;, we introduce the subject of configuration and review how &lt;em&gt;ASP.NET Core&lt;/em&gt; configuration works.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part2-azure-functions-configuration/"&gt;part 2 of this series&lt;/a&gt;, we look at how configuration in &lt;em&gt;Azure Functions (v2)&lt;/em&gt; works and talk about some of the issues.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part3-aspnet-in-functions/"&gt;part 3 of this series&lt;/a&gt;, we show how you could include ASP.NET Core configuration into an Azure Function project.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault/"&gt;part 4 of this series&lt;/a&gt;, we look at using other configuration services, specifically &lt;em&gt;Azure App Configuration&lt;/em&gt; and &lt;em&gt;Azure Key Vault&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Welcome to this series of four articles where we'll be covering the topic of application configuration, looking at both &lt;strong&gt;&lt;em&gt;ASP.NET Core&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;Azure Functions v2&lt;/em&gt;&lt;/strong&gt;.   &lt;/p&gt;

&lt;p&gt;The series has been written from the perspective of the developer who may already be accustomed to developing with  &lt;em&gt;ASP.NET Core&lt;/em&gt;, and is wanting to learn about the slightly different way that &lt;em&gt;Azure Functions v2&lt;/em&gt; goes about handling application configuration. &lt;/p&gt;

&lt;p&gt;We'll cover how and why things are different, possible sources of confusion and provide some working examples.   In the final part, we'll look at how to integrate powerful Azure services that can help to improve your application configuration. &lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;Throughout this series, please be conscious that we will be referring to the following with &lt;strong&gt;deliberate distinction&lt;/strong&gt;:   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/.NET_Framework"&gt;&lt;strong&gt;&lt;em&gt;.NET&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; (AKA ".NET Framework") is a Windows-only software framework, dating back to 2002.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/ASP.NET"&gt;&lt;strong&gt;&lt;em&gt;ASP.NET&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; is a  server-side web application framework, that expands upon the original  &lt;em&gt;.NET&lt;/em&gt; framework.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/.NET_Core"&gt;&lt;strong&gt;&lt;em&gt;.NET Core&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; is a higher-performance, cross-platform and open-source software framework, introduced in 2016. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/ASP.NET_Core"&gt;&lt;strong&gt;&lt;em&gt;ASP.NET Core&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; is a server-side web application framework, that expands upon the newer &lt;em&gt;.NET Core&lt;/em&gt; software framework. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Azure Functions v1&lt;/em&gt;&lt;/strong&gt;  is Microsoft's initial offering of a "serverless" platform, &lt;a href="https://azure.microsoft.com/en-gb/blog/announcing-general-availability-of-azure-functions/"&gt;released in November 2016&lt;/a&gt;.  It supports a number of languages, including &lt;em&gt;.NET&lt;/em&gt;, but not &lt;em&gt;.NET Core&lt;/em&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Azure Functions v2&lt;/em&gt;&lt;/strong&gt;  is the second iteration of the "serverless" platform, &lt;a href="https://azure.microsoft.com/en-gb/blog/introducing-azure-functions-2-0/"&gt;released in September 2018&lt;/a&gt;.  It shifted the underpinning software framework from &lt;em&gt;.NET&lt;/em&gt; to &lt;em&gt;.NET Core&lt;/em&gt;.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Unless specified otherwise, assume that any mention of &lt;em&gt;Azure Functions&lt;/em&gt; in this series, relates specifically to v2&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Configuration - Where have we come from?
&lt;/h3&gt;

&lt;p&gt;If you've worked as a developer using Microsoft technologies for a number of years, it is most likely that you will have developed web applications using the &lt;em&gt;ASP.NET Framework&lt;/em&gt;.   In which case, you will be familiar with the venerable &lt;code&gt;web.config&lt;/code&gt; file.   &lt;/p&gt;

&lt;p&gt;For the benefit of new developers, it may be useful to know that this configuration system dates all the way back to the release of the original &lt;em&gt;ASP.NET&lt;/em&gt;, back in 2002.  For a very long time now, it has been the defacto, convention-based, way to manage configuration. &lt;/p&gt;

&lt;p&gt;Configuration in a &lt;code&gt;web.config&lt;/code&gt; file mixes server settings and application settings together in a file that looks partly like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;appSettings&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;add&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"connectionString"&lt;/span&gt;
   &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Server=localhost;UID=sa;PWD=secret;Database=Northwind"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/appSettings&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To access configuration settings in our application code, we would add something like the following, directly in the place where it was needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigurationSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"connectionString"&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;








&lt;h3&gt;
  
  
  Configuration - Where are we now?
&lt;/h3&gt;

&lt;p&gt;With the 2016 release of &lt;em&gt;.NET Core&lt;/em&gt; and &lt;em&gt;ASP.NET Core&lt;/em&gt;, the way application configuration was supported changed significantly and, as developers, the scope of what we needed to know about this subject broadened greatly.    &lt;/p&gt;

&lt;p&gt;This is particularly true when it comes to the topic of &lt;em&gt;"where should different types of configuration information be stored - and where should it not be stored?"&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;With the relatively recent &lt;a href="https://azure.microsoft.com/en-gb/blog/introducing-azure-functions-2-0/"&gt;release of &lt;em&gt;Azure Functions v2&lt;/em&gt;&lt;/a&gt; in September 2018, Microsoft shifted from using the &lt;em&gt;.NET Framework&lt;/em&gt;, to instead use &lt;em&gt;.NET Core&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;For the &lt;em&gt;ASP.NET Core&lt;/em&gt; developer in particular, there is now a synergy that makes the adoption of &lt;em&gt;Azure Functions&lt;/em&gt; a really appealing proposition.&lt;/p&gt;

&lt;p&gt;However, when it comes to the subject of configuration, the shift from &lt;em&gt;ASP.NET Core&lt;/em&gt; to &lt;em&gt;Azure Functions&lt;/em&gt; isn't entirely frictionless.   It is this main theme, that we will attempt to address in this series.&lt;/p&gt;




&lt;p&gt;Whilst researching these articles, two observations became apparent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Although Microsoft promotes a harmonised technology stack, there is a &lt;em&gt;slight&lt;/em&gt; lack of cohesion/consistency between their products.  This is noticeable when you are familiar with some of the conventions found in &lt;em&gt;ASP.NET Core&lt;/em&gt; and arrive at &lt;em&gt;Azure Functions&lt;/em&gt; expecting things to work in &lt;em&gt;exactly&lt;/em&gt; the same way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Because there are many options to tackle, what is essentially the same basic task, it's easy to feel overwhelmed with choice.   &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These observations are truly not intended as a criticism of Microsoft, but it is indicative that their products are created, maintained and supported by a large number of people across several different teams.  Furthermore, each product requires many people to create and maintain comprehensive documentation, with a consistent and cohesive message that dovetails with other documents - this alone is not a trivial task!  &lt;/p&gt;

&lt;p&gt;This series is the outcome of having collated various sources of documentation together.  Hopefully it will be a useful guide for developers shifting between, or integrating, the various technologies that we'll look at. &lt;/p&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2mCvdI5U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gandalf-entering-hobbiton.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2mCvdI5U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gandalf-entering-hobbiton.jpg%23shadow" alt="image gandalf entering hobbiton from the lord or rings movie"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Modern .NET Core configuration, in general
&lt;/h2&gt;

&lt;p&gt;If we distil the subject of configuration down to its most basic concept, the &lt;em&gt;.NET Core&lt;/em&gt; libraries provide us with an API that offers a collection of key/value pairs.  &lt;/p&gt;

&lt;p&gt;Configuration information is combined together from separate &lt;strong&gt;layers&lt;/strong&gt;, from one or more different sources known as &lt;strong&gt;&lt;em&gt;Configuration Providers&lt;/em&gt;&lt;/strong&gt;.    &lt;/p&gt;

&lt;p&gt;Where we require an &lt;strong&gt;&lt;em&gt;environment-specific&lt;/em&gt;&lt;/strong&gt; piece of configuration (e.g. a feature switch setting), or &lt;strong&gt;&lt;em&gt;secrets&lt;/em&gt;&lt;/strong&gt;  (e.g. a sensitive API key to another service), we can supplement or replace a configuration resource with information from additional other sources of configuration.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Configuration Providers&lt;/em&gt; are (usually) registered in the initialisation code of our project and locate the required information under the direction of the application and the specific environment within which our program is running.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/index?view=aspnetcore-3.0"&gt;official documentation&lt;/a&gt;, at the time of writing, the list of possible &lt;em&gt;configuration providers&lt;/em&gt; include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-gb/services/key-vault/"&gt;Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/overview"&gt;Azure App Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#command-line-configuration-provider"&gt;Command-line arguments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#custom-configuration-provider"&gt;Custom providers (installed or created)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#file-configuration-provider"&gt;Directory files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#environment-variables-configuration-provider"&gt;Environment variables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#memory-configuration-provider"&gt;In-memory .NET objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#file-configuration-provider"&gt;Settings files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Configuration providers (for example &lt;em&gt;Azure Key Vault&lt;/em&gt;) are integrated to our project by adding the appropriate Nuget Package and registering that provider into the WebHost Builder in &lt;code&gt;startup.cs&lt;/code&gt; class.   We'll explain exactly how to go about doing this, in part four of this series.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Configuration, the ASP.NET Core way
&lt;/h2&gt;

&lt;p&gt;You can read the official and in-depth documentation regarding &lt;em&gt;ASP.NET&lt;/em&gt; configuration here: &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/index?view=aspnetcore-3.0"&gt;Configuration in &lt;em&gt;ASP.NET Core&lt;/em&gt;&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;When we create a &lt;em&gt;.NET Core&lt;/em&gt; WebApp (either MVC or WebAPI) - and assuming that we use the default templates - we'll find ourselves working with a file-based configuration-source called &lt;code&gt;appsettings.json&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Broadly speaking, &lt;code&gt;appsettings.json&lt;/code&gt; should be considered as the baseline configuration resource for our web application.   &lt;/p&gt;

&lt;p&gt;Configuration-settings in &lt;code&gt;appsettings.json&lt;/code&gt; are read using the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.2#file-configuration-provider"&gt;File Configuration Provider&lt;/a&gt; - specifically the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.2#json-configuration-provider"&gt;JSON Configuration Provider&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Use of this file-based source is not a baked-in behaviour of the &lt;em&gt;ASP.NET Core&lt;/em&gt; runtime - it is facilitated by calling the method &lt;code&gt;CreateDefaultBuilder&lt;/code&gt; from within the application startup code &lt;code&gt;program.cs&lt;/code&gt;.  This "default builder" loads configuration from the various sources in a certain order - refer to the section&lt;br&gt;
&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/index?view=aspnetcore-3.0#default-configuration"&gt;Default configuration&lt;/a&gt; for more info.   &lt;/p&gt;

&lt;p&gt;&lt;code&gt;appsettings.json&lt;/code&gt; is structured JSON, so this allows us to freely add groupings to our configuration information.  For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"EmailSettings": {
     "ApiKey": "secretKeyValue",
     "FromAddress": "do-not-reply@test.com"
     },
"FeatureSwitches": {
    "EnableFeatureOne": true,
    "EnableFeatureTwo": false,
     },
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;p&gt;We can expect &lt;code&gt;appsettings.json&lt;/code&gt; to be deployed to all environments.  For example, when we build/publish (locally or to Azure), this file is literally copied as a deployment artefact.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N0IEvdaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/appsetting-copied-to-azure.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N0IEvdaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/appsetting-copied-to-azure.png%23shadow" alt="image of azure portal showing appsetting.json deployed as artefact"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Because the configuration is &lt;strong&gt;layered&lt;/strong&gt;, &lt;code&gt;appsettings.json&lt;/code&gt; is used first and is then overlaid with replacement values from other configuration providers, such as the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.0&amp;amp;tabs=windows"&gt;&lt;em&gt;User Secrets&lt;/em&gt;&lt;/a&gt; provider.   &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;N.B. To clarify: when we create those additional configuration resources, we &lt;strong&gt;only need to specify the individual keys to be overwritten&lt;/strong&gt; - we don't need to create an entire copy of each and every item in the baseline configuration.  &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Which options are commonly used with ASP.NET Core?
&lt;/h3&gt;

&lt;p&gt;We listed all of our choices just a moment ago, but let's look  a little more closely at some of the options that are commonly used:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Environment-specific appsetting variants&lt;/strong&gt; (file-based sources) such as &lt;code&gt;appsettings.development.json&lt;/code&gt; or &lt;code&gt;appsettings.production.json&lt;/code&gt;, are an overlay of the baseline file-based configuration.  They are used selectively at runtime, depending on the name of the Environment that has been set. &lt;/p&gt;

&lt;p&gt;As with the primary file, &lt;code&gt;appsetting.json&lt;/code&gt;, these remain intended for non-secret settings and   are included in version control. &lt;/p&gt;

&lt;p&gt;You can read about environments here at &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-3.0"&gt;Microsoft Documentation : Use multiple environments in ASP.NET Core.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Environment-specific appsetting variants&lt;/em&gt; should not be confused with the similar-sounding &lt;em&gt;Environment Variables&lt;/em&gt;.  We do however need to define the "environment name" using the &lt;em&gt;Environment Variable&lt;/em&gt; &lt;code&gt;ASPNETCORE_ENVIRONMENT&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;We can set any name that we like, but the &lt;em&gt;ASP.NET Core&lt;/em&gt; framework only natively supports three variants:   &lt;code&gt;Development&lt;/code&gt;, &lt;code&gt;Staging&lt;/code&gt; and &lt;code&gt;Production&lt;/code&gt;.   If no value is set, &lt;code&gt;Production&lt;/code&gt; is used as default.  &lt;/p&gt;

&lt;p&gt;In development, you will typically find &lt;code&gt;ASPNETCORE_ENVIRONMENT&lt;/code&gt; declared in the file &lt;code&gt;properties/launchsettings.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When we talk about "native framework support", we're referring to the fact that we have built-in features that allow us to react selectively to the stated environment.  As examples of how this is consumed, consider this C# code in the &lt;code&gt;startup.cs&lt;/code&gt; class and also an example of a Razor tag :-&lt;br&gt;
&lt;/p&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IHostingEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDeveloperExceptionPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;environment include="Development"&amp;gt;&amp;lt;p&amp;gt;Development mode&amp;lt;/p&amp;gt;&amp;lt;/environment&amp;gt;
&amp;lt;environment include="Staging"&amp;gt;&amp;lt;p&amp;gt;Staging mode&amp;lt;/p&amp;gt;&amp;lt;/environment&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To make use of these variants, we simply need to include &lt;code&gt;appsettings.development.json&lt;/code&gt; or &lt;code&gt;appsettings.staging.json&lt;/code&gt; files in our project.   We can include &lt;code&gt;appsettings.production.json&lt;/code&gt; in our project if we want to purposefully separate production settings from a default baseline configuration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip:  If you cannot see these files in Visual Studio, be aware that the &lt;em&gt;Solution Explorer&lt;/em&gt; collapses the variants - click the caret next to the file to expand the view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--37115-ht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/appsettings-variants-in-vs.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--37115-ht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/appsettings-variants-in-vs.png%23shadow" alt="image from visual studio showing nested appsettings"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.0"&gt;&lt;strong&gt;&lt;em&gt;User Secrets&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;for local development secrets&lt;/strong&gt;.   The &lt;code&gt;secrets.json&lt;/code&gt; file is stored outside of our project code, within our Operating System user profile.   This keeps it completely away from version control and provides &lt;a href="https://en.wikipedia.org/wiki/Access_control"&gt;access control&lt;/a&gt; via our operating system.  &lt;/p&gt;

&lt;p&gt;If you aren't familiar with &lt;em&gt;User Secrets&lt;/em&gt;, head over to &lt;a href="https://www.twilio.com/blog/2018/05/user-secrets-in-a-net-core-web-app.html"&gt;this beginner-friendly article&lt;/a&gt; for an introduction and step-by-step instructions.&lt;/p&gt;



&lt;p&gt;If you create a new MVC/WebAPI project from the MS templates and right-click on the project file, you will find the option "Manage User Secrets".   &lt;/p&gt;

&lt;p&gt;If you repeat this exercise, by creating an empty &lt;em&gt;Azure Function&lt;/em&gt; (or indeed, just a &lt;em&gt;Console App&lt;/em&gt;), you will find the "Manage User Secrets" option doesn't display.&lt;/p&gt;

&lt;p&gt;The key to enabling this menu option is the presence of the package &lt;code&gt;Microsoft.Extensions.Configuration.UserSecrets&lt;/code&gt;.   Adding either this package or the &lt;code&gt;Microsoft.NETCore.App&lt;/code&gt; meta-package, to your project, will enable the option in the menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z1s75fxf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/user-secrets-dialogue.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z1s75fxf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/user-secrets-dialogue.png%23shadow" alt="image showing User Secrets menu option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Behind the scenes, when you use this menu item, VS will have added a new &lt;code&gt;&amp;lt;UserSecretsId&amp;gt;&lt;/code&gt; node and Guid value into your &lt;code&gt;.csproj&lt;/code&gt;.  At the same time, a new folder that matches the Guid, will have been created inside your user profile. Inside this folder we can expect to find the file &lt;code&gt;secrets.json&lt;/code&gt; which will partly mirror the structure of the &lt;code&gt;appsettings.json&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class="language-xml"&gt;&amp;lt;Project Sdk="Microsoft.NET.Sdk"&amp;gt;
&amp;lt;PropertyGroup&amp;gt;
&amp;lt;TargetFramework&amp;gt;netcoreapp2.2&amp;lt;/TargetFramework&amp;gt;
&amp;lt;AzureFunctionsVersion&amp;gt;v2&amp;lt;/AzureFunctionsVersion&amp;gt;
&amp;lt;UserSecretsId&amp;gt;550b1379-cb4e-42ec-aa0c-72a5ac8f3700&amp;lt;/UserSecretsId&amp;gt;
&amp;lt;/PropertyGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\&amp;lt;your username&amp;gt;\AppData\Roaming\Microsoft\UserSecrets\&amp;lt;UserSecretsId&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;It is this separation of files, well away from the rest of your source code, that gives us a clear delineation between files that should be secret and those that should be in version control.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/app-service/configure-common"&gt;&lt;strong&gt;&lt;em&gt;Azure AppService Application settings&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;for &lt;a href="https://azure.microsoft.com/en-gb/overview/what-is-paas/"&gt;PaaS&lt;/a&gt; cloud deployment.&lt;/strong&gt;    For a majority of scenarios, this is where all of our environment-specific configuration is defined when deployed to an Azure AppService.  &lt;/p&gt;

&lt;p&gt;If you aren't familiar with &lt;em&gt;Azure AppService Application settings&lt;/em&gt;, check out &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/configure-common"&gt;Microsoft Documentation: Configure an App Service app in the Azure portal&lt;/a&gt;.   &lt;/p&gt;

&lt;p&gt;Azure AppService support the concept of separate &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots"&gt;"deployment slots"&lt;/a&gt;, which offer us the option of multiple similar environments (e.g. production, UAT and a test versions) - each with slot-specific configuration if required.&lt;/p&gt;

&lt;p&gt;Take care not to confuse &lt;em&gt;"Azure AppService Application settings"&lt;/em&gt; with the similarly-named &lt;em&gt;"Azure App Configuration"&lt;/em&gt;, as this is another Azure service.  We'll look at this service in &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault#azureappconfiguration"&gt;part 4 of this series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With regards to &lt;em&gt;secrets&lt;/em&gt; specifically, we could safely add these configuration items into &lt;em&gt;Azure AppService Application settings&lt;/em&gt;.  There is nothing inherently insecure about doing this (from an external-intrusion perspective) as values are encrypted at rest.  &lt;/p&gt;

&lt;p&gt;However, a potential concern is that anyone with legitimate permission to access the Azure AppService, will also be able to view these secrets.  This may be an issue depending on your organisation (a problem solved by using &lt;em&gt;Azure Key Vault&lt;/em&gt;, which we'll look at in  &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part4-keyvault#azurekeyvault"&gt;part 4 of this series&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;blockquote&gt;
&lt;p&gt;Tip: As an alternative to editing values directly into the Azure Portal, &lt;a href="https://microsoft.github.io/AzureTipsAndTricks/blog/tip130.html"&gt;this AzureTipsAndTricks article&lt;/a&gt; shows us how to update Azure settings, in Visual Studio, when publishing.&lt;/p&gt;
&lt;/blockquote&gt;





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SHYDYma9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gandalf-keep-it-safe.gif%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SHYDYma9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/gandalf-keep-it-safe.gif%23shadow" alt="animated gif of gandalf telling frodo to keep ring secret"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Keep it secret.  Keep it safe!
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;appsettings.json&lt;/code&gt; file can be expected to be included in version control, so it must not include secrets (e.g. things such as a database connection string that includes a login credential). &lt;/p&gt;

&lt;p&gt;Although the actual &lt;em&gt;secret value&lt;/em&gt; should itself not be included in version control, it is recommended that you still retain the &lt;em&gt;configuration-item key&lt;/em&gt; in the &lt;code&gt;appsettings.json&lt;/code&gt;. Instead of providing the actual secret value, we can add a message with some variant of  "do not include here").  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By doing this, we can use &lt;code&gt;appsetting.json&lt;/code&gt; as a working manifest of sorts, listing all expected configuration items for the project, along with the expected structure. Although we provide unusable key-values, we can trust that this will still work fine, as the actual working values will be substituted at runtime.
&lt;/li&gt;
&lt;li&gt;This provides a way to clearly communicate amongst your development team, which configuration items are &lt;strong&gt;supposed&lt;/strong&gt; to be present.  It also could be said that regularly seeing this message serves to reinforce the need for the development team to think about where they store configuration items.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To illustrate this, an &lt;code&gt;appconfig.json&lt;/code&gt; and &lt;code&gt;secrets.json&lt;/code&gt; pair could look like this:-&lt;/p&gt;

&lt;p&gt;Firstly, the &lt;code&gt;appconfig.json&lt;/code&gt;:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"Logging": {
    "IncludeScopes": false,
    "LogLevel": {
        "Default": "Debug",
        "System": "Information",
        "Microsoft": "Information"
    }
},
"ConnectionStrings": {
    "DefaultSql": "Do not specify here. Set in Azure AppConfiguration or User Secrets",
    "AzureStorage": "UseDevelopmentStorage=true"
},
"EmailSettingsConfig": {
    "ApiKey": "Do not specify here. Set in Azure AppConfiguration or User Secrets",
    "FromAddress": "do-not-reply@test.com"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
Combined with a &lt;code&gt;secrets.json&lt;/code&gt;:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"ConnectionStrings": {
    "DefaultSql": "Server=tcp:[serverName].database.windows.net;Database=myDataBase;
User ID=[secretDbLogin]@[serverName];Password=secretPassword;Trusted_Connection=False;
Encrypt=True"
},
"EmailSettingsConfig": {
    "ApiKey": "secretAccountKey"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;








&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_AbPzkBc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/hobbiton.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_AbPzkBc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blogs.siliconorchid.com/images/coding-inspiration/azure-function-configuration/hobbiton.jpg%23shadow" alt="image of hobbit hole"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Strongly-typed Configuration models
&lt;/h3&gt;

&lt;p&gt;A really nice feature that &lt;em&gt;ASP.NET Core&lt;/em&gt; introduced, alongside the use of &lt;code&gt;appsettings.json&lt;/code&gt;, was support for the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.0"&gt;Options Pattern&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This lets us &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#bind-to-a-class"&gt;bind nodes within configuration files to strongly-typed C# objects (models)&lt;/a&gt; and then make them available for consumption by our application using the &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.options.ioptions-1?view=aspnetcore-3.0"&gt;&lt;code&gt;IOptions&lt;/code&gt;&lt;/a&gt; interface.   &lt;/p&gt;

&lt;p&gt;By using &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection"&gt;dependency injection&lt;/a&gt;, we can conveniently insert these populated models into the constructor of classes as required.   If you are familiar with the subject of unit testing, you will appreciate that this is a very good thing.&lt;/p&gt;

&lt;p&gt;Benefits of this way of working include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's super easy to organise/group related configuration items together, by using &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.0#hierarchical-configuration-data"&gt;hierarchy&lt;/a&gt; to nest them under a parent item in the config file.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The configuration binding code can typically be confined to a single place in our project (e.g. the startup class), avoiding code sprawl.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jonskeet.uk/csharp/strings.html#literals"&gt;String literal&lt;/a&gt; bugs (e.g. the silly mistakes that we all make, such as typo's in a key name) are reduced or even avoided.  Where they are a problem, they tend to show up sooner in the development process.  This is because an error in a centralised part of the codebase tends to be noticed quickly, because it usually impacts many parts of our application.  This is when compared to having our wider codebase littered with references to configuration that rely on string literal values.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When we start working with strongly-typed configuration objects, rather than crude string values, we can then start to do useful things such as using dependency injection to insert populated configuration models into our consuming code.    &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;We'll examine the actual code for all of this, in &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part3-aspnet-in-functions/"&gt;part 3 of this series&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update Nov2019&lt;/strong&gt; : Shortly after publishing this article, &lt;a href="https://twitter.com/stevetalkscode"&gt;Steve Collins&lt;/a&gt; of &lt;a href="http://stevetalkscode.co.uk/"&gt;stevetalkscode.co.uk&lt;/a&gt; reached out, as he has recently been speaking and blogging about the subject of configuration himself.   You can find his &lt;a href="https://t.co/HM0J3voVVS"&gt;slide deck here&lt;/a&gt;.   You may find it useful to explore his blog about &lt;a href="http://stevetalkscode.co.uk/configuration-bridging-part-1"&gt;configuration-bridging&lt;/a&gt;, as it covers the subject of using &lt;code&gt;IOptions&amp;lt;&amp;gt;&lt;/code&gt; compared to directly binding an object to configuration. &lt;/p&gt;
&lt;/blockquote&gt;






&lt;p&gt;In part 2 of this series, we look at how configuration in &lt;em&gt;Azure Function&lt;/em&gt; works and talk about some of the issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/azure-function-configuration/part2-azure-functions-configuration/" class="button is-primary"&gt;&lt;br&gt;
 &lt;i class="fas fa-file-alt ml-1"&gt; &lt;/i&gt; NEXT:  Read Part 2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azurefunctions</category>
      <category>csharp</category>
      <category>netcore</category>
    </item>
    <item>
      <title>Draw sketches from SVG, using  Line-us robot and .NET Core Console application</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Mon, 28 Oct 2019 10:04:01 +0000</pubDate>
      <link>https://forem.com/siliconorchid/draw-sketches-from-svg-using-line-us-robot-and-net-core-console-application-39h4</link>
      <guid>https://forem.com/siliconorchid/draw-sketches-from-svg-using-line-us-robot-and-net-core-console-application-39h4</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/sketch-with-line-us/" rel="noopener noreferrer"&gt;blogs.siliconorchid.com&lt;/a&gt; on 26-Jul-2019&lt;/p&gt;

&lt;p&gt;Over the past few months, I've largely been blogging about how to use various cloud services. This is typically the stuff that you could be using in a commercial setting.&lt;/p&gt;

&lt;p&gt;Programming is something that can be a hobby for some people, not just a career skill.  For this article, I thought we should do something a little different and just play with code for fun.  I've chosen to look at something that is  &lt;a href="https://en.wikipedia.org/wiki/Internet_of_things" rel="noopener noreferrer"&gt;IoT&lt;/a&gt; related and produces "physically visible" results.&lt;/p&gt;

&lt;p&gt;We're going to experiment with a small piece of kit called the &lt;a href="https://www.line-us.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Line-us&lt;/strong&gt;&lt;/a&gt; which originated from a &lt;a href="https://www.kickstarter.com/projects/line-us/line-us-the-little-robot-drawing-arm" rel="noopener noreferrer"&gt;2017 Kickstarter campaign&lt;/a&gt;.    &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Line-us&lt;/em&gt; is a device that holds a pen on the end of a small robotic arm and draws doodle-style sketches.  The device has built in Wifi and internet-connectivity, making it easy to connect to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fline-us-in-motion.gif%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fline-us-in-motion.gif%23shadow" alt="animated image showing line-us drawing robot in action"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  What will we be doing?
&lt;/h2&gt;

&lt;p&gt;For this project, we're going to write a &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio" rel="noopener noreferrer"&gt;.NET Core Console App&lt;/a&gt; that converts an &lt;a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics" rel="noopener noreferrer"&gt;SVG vector image&lt;/a&gt; into &lt;a href="https://en.wikipedia.org/wiki/G-code" rel="noopener noreferrer"&gt;G-Code&lt;/a&gt;.   &lt;/p&gt;

&lt;p&gt;G-Code is a simple instruction language typically used by industrial &lt;a href="https://en.wikipedia.org/wiki/Numerical_control" rel="noopener noreferrer"&gt;CNC machines&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;We'll transmit the G-Code instructions, using &lt;a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol" rel="noopener noreferrer"&gt;TCP&lt;/a&gt; networking, to the &lt;em&gt;Line-us&lt;/em&gt; and sit back as a drawing appears on a piece of paper!&lt;/p&gt;






&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A &lt;em&gt;Line-us&lt;/em&gt; robot&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There will be an assumption that you have familiarity working with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/getting-started/introduction-to-the-csharp-language-and-the-net-framework" rel="noopener noreferrer"&gt;C#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio" rel="noopener noreferrer"&gt;.NET Core Console projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Dependency_injection" rel="noopener noreferrer"&gt;Dependency Injection&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;This project uses .NET Core 2.x, so you'll need to have installed the &lt;a href="https://dotnet.microsoft.com/download" rel="noopener noreferrer"&gt;.NET Core 2.x  SDK&lt;/a&gt;, as appropriate to your platform.   .NET Core is cross-platform, which means this project will work on Windows, Mac and Linux.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;You'll need something to edit and build your project.  This demo was primarily created using .NET Core 2.2 on a Windows 10 system using &lt;a href="https://visualstudio.microsoft.com/vs/" rel="noopener noreferrer"&gt;Visual Studio 2019&lt;/a&gt; Community edition. There is no reason why you cannot use other platforms, code editors and &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tutorials/using-with-xplat-cli" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; tools - my recommendation would be to use &lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;You'll need to clone the sample code that accompanies this article from here : &lt;a href="https://github.com/SiliconOrchid/DrawWithLineUs" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://github.com/SiliconOrchid/DrawWithLineUs" rel="noopener noreferrer"&gt;https://github.com/SiliconOrchid/DrawWithLineUs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;






&lt;h2&gt;
  
  
  The road from photo to robot-drawn sketch
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Line-us&lt;/em&gt; is not a printer.  It is a line-drawing machine, more akin to a &lt;a href="https://en.wikipedia.org/wiki/Plotter" rel="noopener noreferrer"&gt;plotter&lt;/a&gt;.   Everything will be monochromatic and based purely on strokes of a pen on a page.&lt;/p&gt;

&lt;p&gt;Your source material will still most likely be a bitmapped image, but there are a number of transformations that this image will need to undergo, before it can be sketched on paper by the &lt;em&gt;Line-us&lt;/em&gt;.   &lt;/p&gt;

&lt;p&gt;Let's overview the steps we need to take:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;First, an image needs to be processed into an approximation of a black-and-white line drawing&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;You can use an online utility of a photo-editing program for this step.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A quick search for "online photo to line-art" will return a wide selection of free online conversion tools.   I tried both &lt;a href="https://online.rapidresizer.com/photograph-to-pattern.php" rel="noopener noreferrer"&gt;RapidResizer Stencil Maker&lt;/a&gt;  and  &lt;a href="http://www.snapstouch.com/Sketch.aspx" rel="noopener noreferrer"&gt;Snaptouch Sketch&lt;/a&gt; with good results.&lt;/li&gt;
&lt;li&gt;I also used Adobe Photoshop to process a photo.  If using a photo-editor, I suggest: 

&lt;ul&gt;
&lt;li&gt;start by adjusting the image contrast heavily&lt;/li&gt;
&lt;li&gt;then apply an "edge-detection" filter.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Flovebird-photo-to-edges.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Flovebird-photo-to-edges.jpg" alt="photo of a lovebird alongside an edge-detected version"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Next, the processed bitmap needs to be converted into an &lt;a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics" rel="noopener noreferrer"&gt;SVG vector image&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I used the open-source utility &lt;a href="http://potrace.sourceforge.net/" rel="noopener noreferrer"&gt;&lt;em&gt;Potrace&lt;/em&gt;&lt;/a&gt;.  Be mindful that &lt;em&gt;Potrace&lt;/em&gt; only accepts images in the .BMP file format, so you will need to export your modified artwork into this format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;em&gt;Potrace&lt;/em&gt;, I used the command &lt;code&gt;potrace.exe -s lovebird_processed.bmp&lt;/code&gt;, where the &lt;code&gt;-s&lt;/code&gt; switch is used to declare that you want to output an .SVG file.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Flovebird-edges-to-vector.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Flovebird-edges-to-vector.jpg" alt="iedge-detected image of a lovebird alongside an SVG version"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Finally, the SVG needs to be converted into G-Code.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Our project will not create a solution that addresses the first two items in the above list - but we will write code to read an SVG file.&lt;/p&gt;

&lt;p&gt;The following image is a photo of the actual hardcopy that is produced by the &lt;em&gt;Line-us&lt;/em&gt;!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Flovebird-actual-sketch.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Flovebird-actual-sketch.jpg" alt="lovebird sketch drawn by line-us robot"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The  Line-us coordinate system
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Line-us&lt;/em&gt; has a small robotic arm, which holds a pen at its end.   It can draw on a sheet of paper which is clamped immediately adjacent to the body of the device.    &lt;/p&gt;

&lt;p&gt;The robotic arm can position the pen in 2-axes of movement (X and Y coordinates).  The pen can also be raised or lowered (the Z coordinate) by way of a cam mechanism that moves the body of the unit up and down.&lt;/p&gt;

&lt;p&gt;The arm only has a limited range of reach, meaning that although the device can draw in a sweeping arc around the body, the available "rectangular" area is limited to something about the size of a postcard.&lt;/p&gt;

&lt;p&gt;The coordinate system originates at the fulcrum of the robotic arm and covers the following range of values :-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;X-axis:  0 to 2000 (with only 650 to 1775 in the main drawable area)&lt;/li&gt;
&lt;li&gt;Y-axis: -1600 to 1600 (with only -1000 to 1000 in the main drawable area)&lt;/li&gt;
&lt;li&gt;Z-axis 0 to 1000 (with 0 being pen-fully-down and 1000 being pen-fully-up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;According to the documentation, each 100 units corresponds to 5mm of travel.&lt;/p&gt;

&lt;p&gt;The following diagram has been duplicated from the &lt;a href="https://github.com/%20Line-us/%20Line-us-Programming/blob/master/Documentation/LineUsDrawingArea.pdf" rel="noopener noreferrer"&gt;&lt;em&gt;Line-us&lt;/em&gt; documentation on GitHub&lt;/a&gt; and clearly illustrates the range of motion and the drawable area:-&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fline-us-drawable-area.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fline-us-drawable-area.png%23shadow" alt="image showing line-us coordinate system and drawable area"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What is G-Code?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/G-code" rel="noopener noreferrer"&gt;G-Code&lt;/a&gt; is typically used to instruct &lt;a href="https://en.wikipedia.org/wiki/Numerical_control" rel="noopener noreferrer"&gt;CNC machines&lt;/a&gt; (computer-controlled lathes and cutters, etc).&lt;/p&gt;

&lt;p&gt;G-Code started life in the 1950's, became standardised in the 1970's and has more recently evolved to support more complex logic, that is required by increasingly advanced industrial machines.  &lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Line-us&lt;/em&gt; however, is a &lt;em&gt;relatively&lt;/em&gt; straightforward device and does not have a complex set of drawing features or commands.     &lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Line-us&lt;/em&gt; firmware only supports a small set of G-Code commands, which you can find here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/%20Line-us/%20Line-us-Programming/blob/master/Documentation/GCodeSpec.pdf" rel="noopener noreferrer"&gt;&lt;em&gt;Line-us&lt;/em&gt; G-Code Spec on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "language" of the commands is straightforward - for example, if we wanted to reset the pen to a "home" position, we need to send the device nothing other than the command "G28".&lt;/p&gt;

&lt;p&gt;In this project, we're going to use a subset of just two commands!  These are:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;G00&lt;/code&gt; - &lt;strong&gt;Rapid Repositioning&lt;/strong&gt; - Moves the pen as quickly as possible to new coordinates.   In our code, we use this to reposition the pen after a line has been drawn, ready to start drawing the next one.   Example &lt;code&gt;G00 X1000 Y1000 Z1000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;G01&lt;/code&gt; - &lt;strong&gt;Linear Interpolation&lt;/strong&gt; - This is what we use to draw a line between two points.   Example &lt;code&gt;G00 X1200 Y1200 Z0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A limitation to be aware of, is that the &lt;em&gt;Line-us&lt;/em&gt; provides no native support for drawing true curves (usually described as &lt;a href="https://en.wikipedia.org/wiki/B%C3%A9zier_curve" rel="noopener noreferrer"&gt;beziers&lt;/a&gt;).    The &lt;em&gt;Line-us&lt;/em&gt; can only draw straight lines, directly from one point to another.   &lt;/p&gt;

&lt;p&gt;In practice however, this doesn't seem to pose a problem for our project, as many SVG source-images will be created from relatively small shapes, meaning that the lack of true curves isn't really apparent.   Also, remember that this is a fun toy for drawing "wonky" pictures - it's not meant to be perfect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Manually test Line-us
&lt;/h2&gt;

&lt;p&gt;Before we get stuck into any coding, we should ensure that our &lt;em&gt;Line-us&lt;/em&gt; is working correctly and that we can communicate with it from your computer.   &lt;/p&gt;

&lt;p&gt;We'll check this by performing a manual test, which will verify that you have a working network connection and that our &lt;em&gt;Line-us&lt;/em&gt; is responding to commands as expected.&lt;/p&gt;

&lt;p&gt;We're not going to cover the setup and configuration of the &lt;em&gt;Line-us&lt;/em&gt; device itself in this article, as there is already ample support provided by the &lt;a href="https://www.line-us.com/help.html" rel="noopener noreferrer"&gt;manufacturers documentation&lt;/a&gt; and a  &lt;a href="https://forum.line-us.com/" rel="noopener noreferrer"&gt;community forum&lt;/a&gt;.  For this article, we will assume that we have already successfully added the device to our WiFi network.     &lt;/p&gt;

&lt;p&gt;We &lt;strong&gt;need to know what the local IP address of the &lt;em&gt;Line-us&lt;/em&gt; is&lt;/strong&gt;, so if you haven't already, connect to your network router and make a note of the IP that has been assigned.&lt;/p&gt;




&lt;h3&gt;
  
  
  Introducing and setting-up Telnet
&lt;/h3&gt;

&lt;p&gt;We're going to use &lt;a href="https://en.wikipedia.org/wiki/Telnet" rel="noopener noreferrer"&gt;&lt;strong&gt;Telnet&lt;/strong&gt;&lt;/a&gt; to communicate with the &lt;em&gt;Line-us&lt;/em&gt; during our manual test.   &lt;/p&gt;

&lt;p&gt;Younger readers, in particular, may not be familiar with &lt;em&gt;Telnet&lt;/em&gt;, so briefly, &lt;em&gt;Telnet&lt;/em&gt; is one of the oldest protocols on the Internet.  It is used to connect to a remote system, over TCP, and provides a bi-directional text-based interface.   You tend not to find it used much these days, as it is insecure and has been replaced with &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell" rel="noopener noreferrer"&gt;SSH&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;To use &lt;em&gt;Telnet&lt;/em&gt;, you will need a &lt;em&gt;Telnet client&lt;/em&gt; installed on your system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telnet for Windows users&lt;/strong&gt;
A Telnet client is included with Windows, but nowadays is disabled by default.   To use it, we'll need to re-enable it.

&lt;ul&gt;
&lt;li&gt;In the Windows Start search bar, type "turn win" and select the "Turn Windows Features On or Off" option.&lt;/li&gt;
&lt;li&gt;In the "Windows Features" window that opens, scroll down the list and enable the "Telnet Client" option.&lt;/li&gt;
&lt;li&gt;Windows will install the feature and Telnet will now be available on the command prompt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telnet for Mac users&lt;/strong&gt;
The &lt;em&gt;Telnet&lt;/em&gt; Client has been removed from recent versions of MacOs.

&lt;ul&gt;
&lt;li&gt;Follow the instructions in &lt;a href="http://osxdaily.com/2018/07/18/get-telnet-macos/" rel="noopener noreferrer"&gt;this guide to install Telnet on a Mac&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;When I first encountered &lt;em&gt;Telnet&lt;/em&gt; as a teenager, I would use it to connect to "MUDs" (multi-user dungeons) which were online multi-user text-based adventure games!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Connect to the  Line-us
&lt;/h3&gt;

&lt;p&gt;With the &lt;em&gt;Telnet&lt;/em&gt; client installed, open a command prompt and enter the following command.  You will need to substitute the local IP address in the example, with the one you identified from your own router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telnet 192.168.1.200 1337
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number &lt;code&gt;1337&lt;/code&gt; is the port number that &lt;em&gt;Line-us&lt;/em&gt; uses by default and must be included.&lt;/p&gt;

&lt;p&gt;If the connection is successful, you will hear/see the robot twitch its arm and a message similar to the following will be returned to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hello VERSION:"3.0.0 Feb 10 2019 10:25:48" NAME:line-us SERIAL:155xxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we are successfully connected to the &lt;em&gt;Line-us&lt;/em&gt; in a two-way conversation.  The &lt;em&gt;Line-us&lt;/em&gt; has accepted our connection, responded with a "hello" and is now awaiting for further instructions.&lt;/p&gt;

&lt;p&gt;Start by sending some test commands.  Simply enter the following, ensuring that you use capital letters were shown (and press enter):-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;G28&lt;/code&gt; - you should see the arm recenter to the home position.  The pen will also be raised.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With the &lt;em&gt;Line-us&lt;/em&gt; raised, this is a perfect opportunity to correctly set the pen height.   The &lt;em&gt;Line-us&lt;/em&gt; &lt;a href="https://www.line-us.com/help.html" rel="noopener noreferrer"&gt;instructions&lt;/a&gt; recommend placing a UK £1 coin or two stacked US Dimes (which for the benefit of readers in other countries, is approx 3mm thick) underneath the tip of the pen and then tightening the clamp in this position.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;G01 X1600 Y-800 Z1000&lt;/code&gt; - this should move the arm noticeably.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If you can see an obvious result, then we're all set to progress.&lt;/p&gt;






&lt;h2&gt;
  
  
  Quick start to using the code
&lt;/h2&gt;

&lt;p&gt;Aside from a small configuration edit, you should be able to clone the project from GitHub, add the  Line-us to your network and just run the code.   &lt;/p&gt;

&lt;p&gt;Before running the project,  open the file &lt;code&gt;....\DrawWithLineUs\Config\ProgramConfig.cs&lt;/code&gt; and update the configuration values.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You will need to change the path to a sample SVG image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will need to change the IP address, to whatever your router has assigned to your own  Line-us.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you want to actually send commands to the  Line-us, you should set &lt;code&gt;Testmode&lt;/code&gt; to be &lt;code&gt;false&lt;/code&gt;.  Alternatively, if you just want to check that the code works and emits results to the console window,  leave it set to &lt;code&gt;true&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Overview of the solution
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You should clone the solution from &lt;a href="https://github.com/SiliconOrchid/DrawWithLineUs" rel="noopener noreferrer"&gt;https://github.com/SiliconOrchid/DrawWithLineUs&lt;/a&gt; and refer to the code as we go along.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our C# .NET Core solution is primarily a console app called &lt;code&gt;DrawWithLineUs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The app can be broadly categorised into the following areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration/Enumerations&lt;/strong&gt; - &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members" rel="noopener noreferrer"&gt;&lt;code&gt;static&lt;/code&gt;&lt;/a&gt; classes that contain &lt;code&gt;const&lt;/code&gt; fields, for items such as the "drawable canvas area".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Models&lt;/strong&gt; - classes that are used to model things such as the list of coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Services&lt;/strong&gt; - classes that contain the main program logic.   Exploring this area further, there are four services that provide the following functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SVGService&lt;/strong&gt; - has the job of extracting data from an SVG file and turning it into a list of coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GeometryService&lt;/strong&gt; - is concerned with activities such as determining the extremities of points within an image and calculating an appropriate scaling, so that our image can be resized into the drawable area. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCodeService&lt;/strong&gt; - responsible for converting a list of C# types into appropriate G-Code strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CommunicationService&lt;/strong&gt; - this class is responsible for handling the network communication and transmitting bytes back and forth between your app and the &lt;em&gt;Line-us&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;






&lt;h3&gt;
  
  
  Designed to use Dependency Injection
&lt;/h3&gt;

&lt;p&gt;The application uses  .NET Core's built-in  dependency injection.   &lt;/p&gt;

&lt;p&gt;Services are written so that they implement &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/" rel="noopener noreferrer"&gt;interfaces&lt;/a&gt;.  Using the DI framework, the services can easily be added to a class via its constructor.  If you are unfamiliar with this type of code, you can &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2" rel="noopener noreferrer"&gt;learn about Dependency Injection here&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A majority of the code in &lt;code&gt;Program.cs&lt;/code&gt; is related to the configuration of the DI system.&lt;/li&gt;
&lt;li&gt;Most of the code that relates to the app logic, can be found in the file &lt;code&gt;ConsoleApplication.cs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code written in this way, lends itself better to unit testing.  The source code comes supplied with a small set of unit tests that can be found in &lt;code&gt;....\DrawWithLineUs\DrawWithLineUs.Console.Test\&lt;/code&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Reading SVG data
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics" rel="noopener noreferrer"&gt;Scalable Vector Graphics (SVG)&lt;/a&gt; are an open standard which was created by the &lt;a href="https://en.wikipedia.org/wiki/World_Wide_Web_Consortium" rel="noopener noreferrer"&gt;W3C&lt;/a&gt; and dates back to 1999.   Unlike other image formats which are &lt;a href="https://en.wikipedia.org/wiki/Binary_file" rel="noopener noreferrer"&gt;binary files&lt;/a&gt;, an SVG is nothing more than a text file containing &lt;a href="https://en.wikipedia.org/wiki/XML" rel="noopener noreferrer"&gt;XML&lt;/a&gt;.    This means they can be readily viewed and edited in any text editor and can easily be read and parsed using .NET libraries.&lt;/p&gt;

&lt;p&gt;The SVG description language is extensive and beyond the scope of this article.  However, for our purposes, we can focus on a relatively small subset.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a huge caveat, regarding the code in this solution, that should be made clear.    &lt;/p&gt;

&lt;p&gt;This article is intended to be an exploration of the &lt;em&gt;Line-us&lt;/em&gt;, so we are not attempting to write a fully-featured SVG parser.&lt;/p&gt;

&lt;p&gt;Therefore, we are &lt;strong&gt;assuming that we will only be using SVG images that have been created using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths" rel="noopener noreferrer"&gt;Path elements&lt;/a&gt;&lt;/strong&gt;. I have only tested using SVG files that are the output from a &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Potrace" rel="noopener noreferrer"&gt;Potrace&lt;/a&gt;&lt;/em&gt; conversion (which seems to generate images using only Path elements).      &lt;/p&gt;

&lt;p&gt;This means that you cannot expect to get results from all SVG files.   This is because an SVG file can be created using many different elements, including &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Basic_Shapes" rel="noopener noreferrer"&gt;basic shapes&lt;/a&gt; such as  Rectangles, Circles, Ellipses, etc.&lt;/p&gt;

&lt;p&gt;However, there is absolutely no reason why this project couldn't be &lt;a href="https://help.github.com/en/articles/fork-a-repo" rel="noopener noreferrer"&gt;forked&lt;/a&gt; and expanded upon to support SVG more fully! You could even submit a PR to my repo!&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;The following code snippet shows the first couple of lines of an example SVG file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" standalone="no"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;
&lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"850.000000pt"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"650.000000pt"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 850.000000 650.000000"&lt;/span&gt;
&lt;span class="na"&gt;preserveAspectRatio=&lt;/span&gt;&lt;span class="s"&gt;"xMidYMid meet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;transform=&lt;/span&gt;&lt;span class="s"&gt;"translate(0.000000,650.000000) scale(0.100000,-0.100000)"&lt;/span&gt;
&lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"#000000"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M6627 5334 c-13 -13 -7 -23 23 -39 40 -21 50 -19 21 4 l-25 19 30 -5
c20 -4 26 -2 19 5 -13 14 -59 25 -68 16z"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5360 5695 c0 -9 -4 -13 -10 -10 -5 3 -10 1 -10 -4 0 -6 6 -11 14
-11 17 0 29 27 16 35 -6 4 -10 -1 -10 -10z"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5440 5665 c-6 -8 -10 -20 -7 -27 4 -8 8 -4 13 10 5 18 9 20 15 10 7
-10 9 -9 9 5 0 21 -13 22 -30 2z"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M6478 5673 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M5571 5653 c0 -12 2 -13 6 -5 4 10 8 10 19 1 18 -15 18 -29 -1 -29
-23 0 -18 -16 10 -28 20 -9 23 -9 18 4 -4 10 0 12 11 8 9 -3 2 10 -16 30 -35
40 -48 45 -47 19z"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

[… truncated for brevity …]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For our purposes, we can disregard entirely  the first few lines and concentrate purely on the &lt;code&gt;&amp;lt;path/&amp;gt;&lt;/code&gt; nodes.   Specifically, we are only interested in the &lt;code&gt;d&lt;/code&gt; (data) attribute, so our code will need to be able to parse and extract this information.&lt;/p&gt;






&lt;h3&gt;
  
  
  Understanding an SVG Path
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths" rel="noopener noreferrer"&gt;Mozilla provides an excellent guide to reading SVG Paths&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fsvg-breakdown.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fsvg-breakdown.png%23shadow" alt="limage show breakdown of SVG path element"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's concentrate on the structure of the actual data in the &lt;code&gt;&amp;lt;path/&amp;gt;&lt;/code&gt;.    On first inspection, it looks to be nothing more than a long list of numbers.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These numbers are always found in pairs representing X and Y values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you look more closely, you may spot that these numbers are interspersed with the occasional letter.   These letters mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;M&lt;/strong&gt; - Start of the path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;l&lt;/strong&gt; - Start a sequence of &lt;strong&gt;lines&lt;/strong&gt; : Unless changed, all subsequent number-pairs will relate to lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;c&lt;/strong&gt; - Start a sequence of &lt;strong&gt;curves&lt;/strong&gt; : Unless changed, all subsequent number-pairs will relate to curves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;z&lt;/strong&gt; - End of a path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first pair of numbers immediately following an "M" are the &lt;strong&gt;absolute&lt;/strong&gt; X/Y starting coordinates of a path.  &lt;/p&gt;

&lt;p&gt;All subsequent number-pairs are the &lt;strong&gt;delta&lt;/strong&gt; (the change) from the previous position and not an &lt;em&gt;absolute&lt;/em&gt; coordinate.   In order to derive &lt;em&gt;absolute&lt;/em&gt; coordinates, we would need to track the change(s) all the way back to the starting pair.   &lt;/p&gt;

&lt;p&gt;Recalculating these &lt;em&gt;delta&lt;/em&gt; points as &lt;em&gt;absolute&lt;/em&gt; coordinates will be one of the requirements of our code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lines&lt;/strong&gt; comprise of a single pair of numbers.  The number-pair being inspected is the ending-point of the line.   The starting-point will be the ending-point of the previous step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Curves&lt;/strong&gt; comprise of three pairs of numbers, which describe the shape of a &lt;a href="https://en.wikipedia.org/wiki/B%C3%A9zier_curve" rel="noopener noreferrer"&gt;bezier curve&lt;/a&gt;.  The first two pairs relate to control-points that influence the shape of the curve, whilst the third pair is the ending-point.&lt;/p&gt;

&lt;p&gt;In the image below, point A represents the starting point of the curve (the previous step), point B represents the first pair of digits, point C the second pair of digits and finally, point D represents the third and final pair (the end point).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fbezier-curve.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fbezier-curve.jpg%23shadow" alt="image showing simple bezier curve"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  Line-us cannot draw curves
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Importantly&lt;/strong&gt;,  Line-us does not understand curves and only works with straight lines.&lt;/p&gt;

&lt;p&gt;This means that when we encounter a path element that describes a curve, we simply have to ignore the first two pairs of values and simply extract the end-point.   For our solution, a curve will effectively be the same thing as a straight-line.&lt;/p&gt;

&lt;p&gt;This inevitably means that our image will not be the same as the source SVG and may appear jagged.  However, in practice, this wasn't really noticeable because most of the paths in the test images were so small that even straight-lines held up acceptably well to scrutiny.&lt;/p&gt;






&lt;h2&gt;
  
  
  Let's look at the code!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Program entry point
&lt;/h3&gt;

&lt;p&gt;As previously mentioned, the file &lt;code&gt;Program.cs&lt;/code&gt; mostly contains the setup for Dependency Injection, so instead you should look to the file &lt;code&gt;ConsoleApplication.cs&lt;/code&gt; for the entry point into the program.&lt;/p&gt;

&lt;p&gt;Within this file, you will find a clearly named set of method calls.  These address each of the four main steps that our program needs to perform:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;GetCoordinatesFromSVG&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// extracts a list of coordinates from the source file&lt;/span&gt;
    &lt;span class="nf"&gt;ApplyGeometry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// rescales the coordinates, to fit within the drawable area&lt;/span&gt;
    &lt;span class="nf"&gt;GenerateGCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// produces a list of G-Code from the list of coordinates&lt;/span&gt;
    &lt;span class="nf"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Sends the G-Code to the  Line-us&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  SvgService
&lt;/h3&gt;

&lt;p&gt;The SvGService is probably the most complex part of the project, so don't worry if you find it a bit confusing!&lt;/p&gt;

&lt;p&gt;The code is located here:  &lt;code&gt;....\DrawWithLineUs\DrawWithLineUs\Service\SvgService.cs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The SvgService is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opening the SVG file specified in configuration and reading the XML content, by using &lt;code&gt;System.Xml.XmlReader&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Locating only the &lt;code&gt;path&lt;/code&gt; XML nodes from within the document and returning a string - we ignore anything else in the XML.&lt;/li&gt;
&lt;li&gt;For each path, tracking and extracting the appropriate coordinates that comprise the path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extracting XML data is a relatively straightforward problem to address, as the .NET framework has a library that does all of the heavy-lifting.   The code looks like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;XmlReader&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XmlReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PathToSourceSVG&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;listPathNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"d"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Extracting the appropriate coordinates that comprise a path is not entirely straightforward, so let's look at this code closely.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our program is initially presented with a long, single string that represents the "raw" data that has been extracted from the XML source.
&lt;/li&gt;
&lt;li&gt;The first thing we need to do, is to chop this up into a &lt;a href="https://www.dotnetperls.com/list" rel="noopener noreferrer"&gt;List&lt;/a&gt; of individual words, using the &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/how-to/parse-strings-using-split" rel="noopener noreferrer"&gt;.NET String.Split()&lt;/a&gt; method. &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;As a reminder, in an earlier section, we looked at how each SVG Path is made up of lines and curves.  We looked at how the data description uses the characters &lt;code&gt;l&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt; as delimiters.  We learned that &lt;em&gt;lines&lt;/em&gt; comprise of a single pair of numbers, whilst a &lt;em&gt;curve&lt;/em&gt; comprises three pairs of numbers.  Finally we learned that we can't actually take advantage of curves, so we are only interested in extracting the third pair of digits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The XML data is not usable to us in its original form. We need to model the data by using C# objects that store a list of paths, each of which contains a nested-list of &lt;em&gt;absolute&lt;/em&gt; coordinates (not just the &lt;em&gt;delta&lt;/em&gt;, as found in the XML).   These coordinates will eventually be used to drive the &lt;em&gt;Line-us&lt;/em&gt; robot arm.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use the model &lt;code&gt;...\DrawWithLineUs\Model\CoordinateStructure.cs&lt;/code&gt; as a container for this information.     Rather than store the X/Y coordinates into separate &lt;code&gt;int&lt;/code&gt; fields, we can use .NET's &lt;code&gt;System.Drawing.Point&lt;/code&gt; struct, as it is intended for this kind of use.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoordinateStructure&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ListPoints&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PointF&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ListRescaledPoints&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;//using PointF for better precision, having been scaled&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CoordinateStructure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ListPoints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;ListRescaledPoints&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PointF&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;We then need to parse through the list of &lt;em&gt;words&lt;/em&gt;.    We use the term &lt;em&gt;words&lt;/em&gt; here deliberately, because they are not necessarily just numbers.  For example, a word may include a letter like this:  &lt;code&gt;l-25&lt;/code&gt;, which conveys the double meaning that  we are starting a new sequence of &lt;em&gt;lines&lt;/em&gt; and that the first X value is "negative 25".&lt;/p&gt;

&lt;p&gt;Your first idea may be that, because numbers always appear as pairs of X/Y values, that we just need to iterate over the list of words, incrementing our index value of 2 on each pass of the loop.   However, this doesn't work, because &lt;em&gt;curves&lt;/em&gt; require us to skip the index forward by 6.    &lt;/p&gt;

&lt;p&gt;The increasing complication is that a &lt;em&gt;path&lt;/em&gt; can contain a mixture of both &lt;em&gt;lines&lt;/em&gt; and &lt;em&gt;curves&lt;/em&gt; (we'll call this a "variant"), but it doesn't tell us which &lt;em&gt;variant&lt;/em&gt; we are looking at each time - only when a change happens.  This means that we also need to track what the current &lt;em&gt;variant&lt;/em&gt; is, as we iterate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use the variable &lt;code&gt;currentSvgPathVariantEnum&lt;/code&gt; to persist the &lt;em&gt;variant&lt;/em&gt; that is currently in effect.

&lt;ul&gt;
&lt;li&gt;We have used the &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum" rel="noopener noreferrer"&gt;enumeration&lt;/a&gt; &lt;code&gt;SvgPathVariantEnum&lt;/code&gt; to make it clearer, in the code, which type of &lt;em&gt;variant&lt;/em&gt; we are working with.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We use a &lt;code&gt;while&lt;/code&gt; loop, which ultimately keeps iterating forward over the words, until we reach the end of the list.&lt;/li&gt;
&lt;li&gt;Within the &lt;code&gt;while&lt;/code&gt; loop, we use the method &lt;code&gt;GetCurrentPathVariant(...)&lt;/code&gt; to test the current word to see if it contains the characters &lt;code&gt;i&lt;/code&gt; or &lt;code&gt;c&lt;/code&gt;.   If either of these are found, we update the variable  &lt;code&gt;currentSvgPathVariantEnum&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Still within the &lt;code&gt;while&lt;/code&gt; loop, we use the method &lt;code&gt;ExtractCoordinate(...)&lt;/code&gt; to extract the appropriate set of delta numbers.

&lt;ul&gt;
&lt;li&gt;The index used to extract the number will vary, depending on whether we are looking at a line or a curve.  We use a &lt;a href="https://en.wikipedia.org/wiki/Regular_expression" rel="noopener noreferrer"&gt;Regular Expression&lt;/a&gt; to strip out any letters.  This code looks like the snippet just below:-&lt;/li&gt;
&lt;li&gt;In addition to extracting the delta numbers, we pass in the previous &lt;em&gt;absolute&lt;/em&gt; coordinates, combine this with the new &lt;em&gt;delta&lt;/em&gt; values, and ultimately return new &lt;em&gt;absolute&lt;/em&gt; coordinates as a &lt;code&gt;Point&lt;/code&gt; struct.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;coordX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;coordY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentSvgPathVariantEnum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;SvgPathVariantEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;coordX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"[^0-9.+-]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;coordY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"[^0-9.+-]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;SvgPathVariantEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Curve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;coordX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"[^0-9.+-]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;coordY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"[^0-9.+-]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;Finally within the &lt;code&gt;while&lt;/code&gt; loop, we use the method &lt;code&gt;IncrementCurrentPathIndex(...)&lt;/code&gt; to determine the correct number by which we should increment the loop's index (i.e. either 2 or 6).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The service ultimately returns a list of the container objects : &lt;code&gt;List&amp;lt;CoordinateStructure&amp;gt;&lt;/code&gt;.&lt;/p&gt;



&lt;p&gt;The partial code listing of the main method looks like this (&lt;a href="https://github.com/SiliconOrchid/DrawWithLineUs/blob/master/DrawWithLineUs/Service/SvgService.cs" rel="noopener noreferrer"&gt;check the source on GitHub&lt;/a&gt; for the class in its entirety):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CoordinateStructure&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExtractCoordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listPathNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// an array of arrays (which contain point coordinates, which are constructed from the list of offsets in the SVG&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CoordinateStructure&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listCoordinateStructures&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CoordinateStructure&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Parsing nodes..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pathNode&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listPathNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;coordinateStructure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CoordinateStructure&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;listWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// extract start coordinates&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;coordStartX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"[^0-9.+-]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;coordStartY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"[^0-9.+-]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;coordinateStructure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListPoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coordStartX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coordStartY&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;


        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// don't start at very beginning, skip over the first pair of values as these are always the "starting position"&lt;/span&gt;
        &lt;span class="n"&gt;SvgPathVariantEnum&lt;/span&gt; &lt;span class="n"&gt;currentSvgPathVariantEnum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SvgPathVariantEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//check to see if the "variant" has changed (is this a line or curve, or are we carrying on from the last previously set variant?)&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newPathVariant&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetCurrentPathVariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newPathVariant&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;SvgPathVariantEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;currentSvgPathVariantEnum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newPathVariant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// depending on the "variant" extract the appropriate coordinates and add this to "coordinateStructure.ListPoints" collection. &lt;/span&gt;
            &lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;previousPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coordinateStructure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListPoints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;coordinateStructure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListPoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;nextPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ExtractCoordinate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentSvgPathVariantEnum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextPoint&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// skip over, as point was a "MoveTo" command.&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;coordinateStructure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListPoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;nextPoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;//depending on the current variant, increment the current index appropriately (either by 2 for a line, or 6 for a curve)&lt;/span&gt;
            &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;IncrementCurrentPathIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentSvgPathVariantEnum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;listCoordinateStructures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coordinateStructure&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;listCoordinateStructures&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  GeometryService
&lt;/h3&gt;

&lt;p&gt;The  Line-us only accept coordinates within a certain range of values (refer to the diagram earlier in the article).  &lt;/p&gt;

&lt;p&gt;The SvgService however, may return coordinates with large values that exceed the bounds of the drawable area.&lt;/p&gt;

&lt;p&gt;We need to be able to rescale the image so that everything fits correctly - this is where the GeometryService comes in.&lt;/p&gt;

&lt;p&gt;This service is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Iterating through each and every single point returned from the SvgService and determining the coordinate extremities (in your mind, you can picture this as a rectangle which goes around the very edge of your image).

&lt;ul&gt;
&lt;li&gt;We use the class &lt;code&gt;BoundingBox&lt;/code&gt; to model this result.   This class has methods to return the height and width of this box.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;We compare the width and height of this box against the height and width of the  Line-us available drawing area.   From this, we can determine an appropriate scaling ratio.&lt;/li&gt;

&lt;li&gt;We then reiterate through all points a second time, apply the scaling factor to resize/reposition them.

&lt;ul&gt;
&lt;li&gt;The recalculated coordinates are stored back into the instance of the model &lt;code&gt;CoordinateStructure&lt;/code&gt;, except that this time they are store into &lt;code&gt;ListRescaledPoints&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The fields inside &lt;code&gt;ListRescaledPoints&lt;/code&gt; are of type &lt;code&gt;PointF&lt;/code&gt; (for a floating-point point, rather than integer point).    This is so that we don't accidentally lose precision, should we end up scaling a very differently sized image.   We want to preserve the accuracy of the new points as much as possible, up until the point that we export GCode. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;






&lt;h3&gt;
  
  
  GCodeService
&lt;/h3&gt;

&lt;p&gt;This service is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Iterating the list of rescaled coordinates (having just been rescaled by the GeometryService).  The data is passed to this service via a populated instance of the &lt;code&gt;CoordinateStructure&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;Producing a list of appropriate GCode commands that will be later sent to the device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the time we come to use the GCodeService, we've done all the hard work of preparing lists of data that represent &lt;strong&gt;multiple paths&lt;/strong&gt; each containing &lt;strong&gt;multiple points&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All that is left for us to do, is pay attention to what the pen of the robot is doing, by lifting it off the page and moving it to the start of each new path.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When we start drawing a new path, we reposition the pen to the new starting coordinates.
&lt;/li&gt;
&lt;li&gt;Lower the pen ready to start drawing. &lt;/li&gt;
&lt;li&gt;Move it to each point in sequence.&lt;/li&gt;
&lt;li&gt;We then raise the pen at the end of the sequence, ready for the move to the start of the next path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because our rescaled coordinates have been stored as a &lt;code&gt;PointF&lt;/code&gt; (a floating-point precision point), we need to convert back to an &lt;code&gt;int&lt;/code&gt; when producing our GCode.&lt;/p&gt;



&lt;h4&gt;
  
  
  Tip:  try and use enums or static classes to describe fixed values
&lt;/h4&gt;

&lt;p&gt;As a developer, something you should avoid the &lt;a href="https://en.wikipedia.org/wiki/Anti-pattern" rel="noopener noreferrer"&gt;anti-pattern&lt;/a&gt; of using &lt;a href="https://en.wikipedia.org/wiki/Magic_number_(programming)" rel="noopener noreferrer"&gt;magic numbers&lt;/a&gt; or hardcoding of strings that,  although make sense at the time you write the code, are not apparent to other people (or indeed yourself in the future, when you forget the details!).   &lt;/p&gt;

&lt;p&gt;As such, you should aim to make the meaning of your code as clear as possible by using easily-understood words in code.   You can usually use &lt;a href="https://www.dotnetperls.com/enum" rel="noopener noreferrer"&gt;enumerations&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members" rel="noopener noreferrer"&gt;static classes&lt;/a&gt; to help you here.&lt;/p&gt;

&lt;p&gt;So in the case of our GCode, we &lt;em&gt;could&lt;/em&gt; easily have written the snippet above so it looked like this...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;listGCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"G01 x&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; y&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; z0\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… but how would someone who is new to the project, who is reading your code, know that "G01" means "Linear Interpolation" - or that the Z axis of zero means "pen down".   To get meaning, they would have to stop what they're doing and cross-reference with the documentation.&lt;/p&gt;

&lt;p&gt;Instead, we can improve the meaning in our code like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GCodes&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;RapidReposition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"g00"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LinearInterpolation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"g01"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pen&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PenUpIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;PenDownIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With awareness of the above, we can instead write our code that produces GCode for the path like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SequencePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listGCodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CoordinateStructure&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListRescaledPoints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;listGCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;GCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LinearInterpolation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; x&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; y&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; z&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Pen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PenDownIndex&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you are not entirely familiar with some of the syntax in the example above, we are using C#6 &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated" rel="noopener noreferrer"&gt;String Interpolation&lt;/a&gt; to build our string (where previously, you may have used &lt;code&gt;String.Format()&lt;/code&gt; to achieve the same outcome).&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  CommunicationService
&lt;/h3&gt;

&lt;p&gt;This service is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establishing a &lt;a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol" rel="noopener noreferrer"&gt;TCP&lt;/a&gt; connection between your computer and the  Line-us, by using the .NET library &lt;code&gt;System.Net.Sockets&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Receiving the initial "hello" back from the  Line-us, signalling the device is ready to start a two-way communication.&lt;/li&gt;
&lt;li&gt;Converting GCode into a &lt;a href="https://stackoverflow.com/questions/4019837/what-do-we-mean-by-byte-array" rel="noopener noreferrer"&gt;byte-array&lt;/a&gt; and sending the command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The class in its entirety looks like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommunicationService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommunicationService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConnectToLineUs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;TcpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;NetworkStream&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;lineusIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;lineusport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IPAddress&lt;/span&gt; &lt;span class="n"&gt;lineusipaddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IPAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lineusIP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TcpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IPEndPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lineusipaddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lineusport&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;SayHello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SayHello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NetworkStream&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;responseData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Received: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NetworkStream&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Console.WriteLine($"Sent: {instruction}");&lt;/span&gt;
        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;responseData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Received: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;responseData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Line-us is a nice little machine, thoughtfully designed and easy to use.   It sidesteps much of the complications that come with "rolling your own" hardware systems.&lt;/p&gt;

&lt;p&gt;It's very much something I could envisage being used as a way to get kids interested in programming and for me, mildly echoes the &lt;a href="https://en.wikipedia.org/wiki/Logo_(programming_language)" rel="noopener noreferrer"&gt;LOGO&lt;/a&gt; programming that I was shown in school, a generation ago.&lt;/p&gt;

&lt;p&gt;I'm hoping this article can re-assert that programming can be just for fun and hopefully inspire folks to try something different.&lt;/p&gt;

&lt;p&gt;If you've discovered this article through a search, coming as someone wanting to learn about Line-us, then hopefully this has been useful to you as an example of C# code.&lt;/p&gt;






&lt;h2&gt;
  
  
  Where to next?
&lt;/h2&gt;

&lt;p&gt;Check out this &lt;a href="https://www.line-us.com/things-to-do" rel="noopener noreferrer"&gt;things-to-do&lt;/a&gt; link by line-us.com  for inspiration.&lt;/p&gt;

&lt;p&gt;Here are some ideas for you to improve this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extend the SVG parser to support more shapes - not just &lt;em&gt;paths&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Extend the code to support multi-colour painting - check out the following image (from line-us press images):&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fline-us-colour-painting.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fsketch-with-line-us%2Fline-us-colour-painting.jpg%23shadow" alt="animated image showing line-us drawing robot in action"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/%20Line-us/%20Line-us-Programming/tree/master/Documentation" rel="noopener noreferrer"&gt; Line-us Documentation &lt;/a&gt;(including GCode Spec and drawable area diagram)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A useful guide to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths" rel="noopener noreferrer"&gt;syntax of a 'Path' element&lt;/a&gt;  of an SVG file by Mozilla&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/@lschoeneman/how-to-take-advantage-of-dependency-injection-in-net-core-2-2-console-applications-274e50a6c350" rel="noopener noreferrer"&gt;DI in a console application&lt;/a&gt;  by Larry Schoeneman&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>csharp</category>
      <category>netcore</category>
      <category>iot</category>
    </item>
    <item>
      <title>How to extract a phone number from an image using Azure cognitive services and Twilio number lookup</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Thu, 24 Oct 2019 10:53:11 +0000</pubDate>
      <link>https://forem.com/twilio/how-to-extract-a-phone-number-from-an-image-using-azure-cognitive-services-and-twilio-number-lookup-34k3</link>
      <guid>https://forem.com/twilio/how-to-extract-a-phone-number-from-an-image-using-azure-cognitive-services-and-twilio-number-lookup-34k3</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/extract-phone-number-from-image-using-azure-and-twilio/" rel="noopener noreferrer"&gt;blogs.siliconorchid.com&lt;/a&gt; on 6-Mar-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this article, we’re going to create a solution that extracts any potential phone numbers that appear in an image file.  Having extracted potential numbers we'll then attempt to verify that they are valid phone numbers.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The past couple of years have turned out to be really interesting times for developers.&lt;/p&gt;

&lt;p&gt;We've witnessed the rise of powerful Software-as-a-Service (SaaS), that can be accessed using nothing more than a simple HTTP API call.  SaaS has been transformative in the way that we work and can result in the creation of some very potent software.  The big tech companies, like Microsoft and Google,  have been  giving us cheap access to amazing computing resources to solve problems that, only a decade ago, would have been a serious technical hurdle for regular developers to overcome.&lt;/p&gt;

&lt;p&gt;In this article, we're going to explore some of these awesome services and have a bit of fun. This will involve connecting together some of these APIs together in a way that could solve the sort of business-requirement that a real-world application could need solving.&lt;/p&gt;

&lt;p&gt;Imagine you want to take a photograph of a business card, or maybe a poster you see at a convention, using your cellphone.  You then want to be able to automatically extract a list of phone numbers from that image.  That would be pretty useful right?&lt;/p&gt;

&lt;p&gt;We'll achieve this using the following technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.Net Core 2.x WebApi project&lt;/li&gt;
&lt;li&gt;Azure Cognitive Services&lt;/li&gt;
&lt;li&gt;Twilio Lookup API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From a high-level, we're going to perform these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We'll use the Azure Text Recognition API to process an image and return a collection of words.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We'll then review code that attempts to extract "candidate" phone numbers from this list of words, discarding non-numbers and attempting to combine number fragments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, we'll use a Twilio API to perform a check on the candidate phone number to verify if it is actually a real number.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As these blogs are partly intended to be an exploration into C# coding, we'll also touch on something called &lt;strong&gt;method-chaining&lt;/strong&gt; which, depending on the context of use,  could be a useful way to improve your code.   We'll also make use of asynchronous programming for more efficient use of time when calling API services.&lt;/p&gt;






&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-cognitive.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-cognitive.jpg" alt="image showing cognition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Cognitive Services
&lt;/h2&gt;

&lt;p&gt;Microsoft offers a powerful set of AI-driven services that they call &lt;a href="https://azure.microsoft.com/en-gb/services/cognitive-services/" rel="noopener noreferrer"&gt;Cognitive Services&lt;/a&gt;.  These fall into the categories of &lt;a href="https://azure.microsoft.com/en-gb/services/cognitive-services/directory/speech/" rel="noopener noreferrer"&gt;speech&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-gb/services/cognitive-services/directory/vision/" rel="noopener noreferrer"&gt;vision&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-gb/services/cognitive-services/directory/lang/" rel="noopener noreferrer"&gt;language&lt;/a&gt; understanding and search.&lt;/p&gt;

&lt;p&gt;In this article we'll be interested in consuming the &lt;strong&gt;Computer Vision&lt;/strong&gt;&lt;br&gt;
service and specifically the Text Recognition API(s).&lt;/p&gt;

&lt;p&gt;There are actually two separate flavours of text-recognition APIs available. At the time of writing, they are still currently in preview:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; A service for extracting &lt;strong&gt;typed text&lt;/strong&gt; from an image.&lt;/li&gt;
&lt;li&gt; A service for extracting &lt;strong&gt;handwritten text&lt;/strong&gt; from an image.  This API is slightly more complicated to consume, because the service takes marginally longer to process and does not yield an immediate response to the API request.  Therefore we need to code a polling mechanism to receive a result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;According to the documentation, a limitation of the handwritten recognition, is that currently it only supports English language.   However, given that our goal is only to extract phone numbers, this shouldn't pose any problem for us (later in the article, we'll be writing code that will discard anything that doesn't look like a phone number anyway).&lt;/p&gt;




&lt;h2&gt;
  
  
  Twilio Phone Number Lookup.
&lt;/h2&gt;

&lt;p&gt;We'll also be calling an API provided by Twilio to lookup and verify phone numbers.    &lt;/p&gt;

&lt;p&gt;This is extremely useful, as it means that we'll be able to go beyond the usual guess-work that is typical when working with phone numbers and be able to return a list of genuinely validated numbers.&lt;/p&gt;





&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-hard-problem.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-hard-problem.jpg%23shadow" alt="image showing man struggling with hard problem"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem - Validating Phone Numbers Is Hard.
&lt;/h2&gt;

&lt;p&gt;Validating  phone numbers is a deceptively tricky problem to address.  The complexity of phone number validation can change depending upon the audience/localisation that your software will be serving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If you are writing a solution for a domestic audience, you may be able to apply a relatively strict set of validation rules because you can reasonably expect a number to be in a particular format.   In this scenario, use of something like a regular expression, could be a suitable way to validate a number. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If your application will require you to accommodate and validate international numbers, each with their own combination of valid numbers - and potentially alphanumeric text, then a strict pattern match solution just isn't going to cut it. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you attempt to get creative with a set of complicated logic rules, you run the risk of not properly accounting for regional variations (and thereby risk rejecting potentially valid numbers).&lt;/p&gt;

&lt;p&gt;At this point I would strongly refer you to read the Twilio Voices Blog &lt;a href="https://www.twilio.com/blog/validating-phone-numbers-effectively-with-c-and-the-net-frameworks" rel="noopener noreferrer"&gt;Validating phone numbers effectively with C# and the .NET frameworks&lt;/a&gt;, which covers many of the issues you'll need to consider when capturing phone number data, along with potential solutions to the validation problem.&lt;/p&gt;

&lt;p&gt;We'll be taking advantage of &lt;a href="https://www.twilio.com/docs/lookup/api" rel="noopener noreferrer"&gt;Twilio's Lookup API&lt;/a&gt; for number validation.   The Lookup API accepts a phone number, attempts to match it against the phone network databases, and responds with appropriate information.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The solution presented in this article is far from perfect and is not intended to be a solution that will work well for everyone.  However, what I have put together here is intended to be inspirational and I am certain that there will be elements that you will be able to take away and adapt into your own code.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Introducing the E.164 convention
&lt;/h2&gt;

&lt;p&gt;You should be mindful that the Twilio Lookup API cannot magically figure out what a partial, or unusually formatted, phone number should be.   &lt;/p&gt;

&lt;p&gt;Part of our challenge will be that we need to supply a number to their API in a correctly formatted international convention known as  &lt;a href="https://en.wikipedia.org/wiki/E.164" rel="noopener noreferrer"&gt;E.164&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Whilst the E.164 format itself is straightforward, a difficulty we face is that the string of text extracted from the image is unlikely to be in this format. For example, it is highly likely that it will not contain the international dialling code.  &lt;/p&gt;

&lt;p&gt;So a hurdle with our code, is that we may need to get creative with how we go about "filling in the blanks"!&lt;/p&gt;

&lt;p&gt;The problem of internationalisation is a tricky one;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If your software is serving only a domestic audience, you could potentially cut corners and make a sweeping presumption that you should add a fixed international prefix code (assuming the prefix is not already present).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If your software is serving an international audience, you may need to provide a mechanism to obtain this information from the user - for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in a UI where a user uploads an image, perhaps they will be required to select a country from something like a dropdown.&lt;/li&gt;
&lt;li&gt;you could possibly determine the user's geographic location (e.g. using a geolocation API from a device, or perhaps a more crude IP based lookup).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;There will be an assumption that you have familiarity working with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.2" rel="noopener noreferrer"&gt;.Net Core WebApi projects&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/" rel="noopener noreferrer"&gt;LINQ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2" rel="noopener noreferrer"&gt;Dependency injection&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://www.twilio.com/blog/2018/05/user-secrets-in-a-net-core-web-app.html" rel="noopener noreferrer"&gt;AppSettings and User Secrets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/" rel="noopener noreferrer"&gt;Async/Await&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You'll need to have installed the &lt;a href="https://dotnet.microsoft.com/download" rel="noopener noreferrer"&gt;.Net Core 2.x  SDK&lt;/a&gt;, as appropriate to your platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You'll need something to edit and build your project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This demo was primarily created using .Net Core 2.1 on a Windows 10 system using Visual Studio 2017 Community edition. There is no reason why you cannot use other platforms, code editors and CLI tools - my recommendation would be to use &lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You'll need to download the sample code that accompanies this article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project source can be downloaded from here : &lt;a href="https://github.com/SiliconOrchid/PhoneExtractVerify" rel="noopener noreferrer"&gt;https://github.com/SiliconOrchid/PhoneExtractVerify&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You will need an &lt;a href="https://azure.microsoft.com/" rel="noopener noreferrer"&gt;Azure account&lt;/a&gt; with an active subscription.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specifically, you'll need to create Computer Vision services.  If you are a new subscriber, you can get access to this service for free (at time of writing).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You will need a &lt;a href="https://www.twilio.com/try-twilio" rel="noopener noreferrer"&gt;Twilio account&lt;/a&gt; with an active subscription.  You'll need this to access the Lookup API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you don't have a Twilio account or are completely new to their service, then &lt;a href="https://www.twilio.com/docs/sms/quickstart/csharp-dotnet-core" rel="noopener noreferrer"&gt;check out their .NET Core quickstart tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Setup Azure Computer Vision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Log onto your Azure Portal&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new Resource Group for this project and name it "PhoneNumberRecognitionDemo":&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-create-resourcegroup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-create-resourcegroup.png" alt="image showing Creating Resource Group in Azure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To add a Computer Vision resource to your subscription simply by search for "computer vision" in the Azure marketplace:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-create-computer-vision.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-create-computer-vision.png%23shadow" alt="image showing Creating Resource Group in Azure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Configure the Computer Vision resource:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give your resource a name that will make it easy to recognise (I chose to &lt;code&gt;PhoneNumberRecognitionDemo_CV&lt;/code&gt;, using the suffix "_CV" for "Computer Vision").&lt;/li&gt;
&lt;li&gt;Choose a data centre nearest to your location that supports Computer Vision (Noting that this service is not available in all Azure data-centres).&lt;/li&gt;
&lt;li&gt;Choose the 'F0' Pricing tier.  This is the free version, but our limited usage in the demo will be fine at that level.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-create-computer-vision-2.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-create-computer-vision-2.png%23shadow" alt="image showing the selection of the free tier in Azure Portal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Wait a few moments while the resource deploys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open up the Computer Vision resource and navigate to the "Keys" section.   We'll need to copy these credentials into our project's configuration a bit later, so keep this window open.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-cv-keys.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fazure-cv-keys.png%23shadow" alt="image showing Creating Resource Group in Azure Portal"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup Twilio
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log onto your Twilio Portal (or create a free trial if you don't already have one).&lt;/li&gt;
&lt;li&gt;We need to locate the &lt;code&gt;ACCOUNT SID&lt;/code&gt; and &lt;code&gt;AUTH TOKEN&lt;/code&gt; values, which are prominently located on the home page. 

&lt;ul&gt;
&lt;li&gt;The only slightly non-obvious thing that I encountered, as a brand-new Twilio user, was that in order to see any of your api keys, you must first have created at least one project, for these values to show.   When I first started, I bypassed Twilio's "My New Learn &amp;amp; Explore Project" introduction, meaning that I could not find the credentials.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When you have found the credentials, keep the webpage open so that we can copy the keys into our application configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftwilio-credentials.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftwilio-credentials.png" alt="image showing Credentials in Twilio Portal"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Clone The Sample Repository
&lt;/h2&gt;

&lt;p&gt;In order to more easily follow-along with this tutorial, you should clone the source code from here : &lt;a href="https://github.com/SiliconOrchid/PhoneExtractVerify" rel="noopener noreferrer"&gt;https://github.com/SiliconOrchid/PhoneExtractVerify&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Talk About App Configuration
&lt;/h2&gt;

&lt;p&gt;Both the Azure and Twilio APIs require you to provide credentials in order to use their respective services. &lt;/p&gt;

&lt;p&gt;Note that we don't actually save any credentials directly in our code (otherwise this sensitive information will end up in version-control).  Instead we will be using &lt;code&gt;appsettings.json&lt;/code&gt; combined with User Secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the &lt;code&gt;....\src\PhoneExtractVerify.Api\appsettings.json&lt;/code&gt; file is used to provide structure. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User Secrets&lt;/strong&gt; and most likely  &lt;strong&gt;Azure Appsettings&lt;/strong&gt; (if you're planning to host this solution in Azure).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're not familiar with the concept of User Secrets, I could recommend that you read &lt;a href="https://www.twilio.com/blog/2018/05/user-secrets-in-a-net-core-web-app.html" rel="noopener noreferrer"&gt;this blog&lt;/a&gt; as an introduction.&lt;/p&gt;

&lt;p&gt;Let's look at the &lt;code&gt;appsettings.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"Logging": {
  "LogLevel": {
    "Default": "Warning"
  }
},
"AllowedHosts": "*",

"TwilioCredentials": {
  "AccountSid": "Set In Secrets or Environment",
  "AuthToken": "Set In Secrets or Environment"
},

"AzureComputerVisionCredentials": {
  "UriBase": "https://westeurope.api.cognitive.microsoft.com/vision/v2.0/ocr",
  "SubscriptionKey": "Set In Secrets or Environment"
}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can ignore the first few lines of this file that are related to logging.   The parts we're interested in are the entries for &lt;code&gt;TwilioCredentials&lt;/code&gt; and &lt;code&gt;AzureComputerVisionCredentials&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;appsettings.json&lt;/code&gt; file makes it clear that you shouldn't be saving any sensitive information directly into this file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;UriBase&lt;/code&gt; for the Azure credentials is dependent on your own Azure subscription.   In the sample code, this is &lt;code&gt;westeurope&lt;/code&gt;, but you will need to change this if you are using a different region.    The URI to use is available to cut+paste out of the Azure Console, along with your other credentials.    &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The appSettings correlate to two strongly typed models within our code.  These models are populated by binding to the configuration source and are then consumed by injecting them into the service class:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;....\src\PhoneExtractVerify.Api\Models\AzureComputerVisionCredentials.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;....\src\PhoneExtractVerify.Api\Models\TwilioCredentials.cs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code for binding data to the configuration models can be found up in the solution startup.   If you look at &lt;code&gt;...\PhoneExtractVerify\src\PhoneExtractVerify.Api\Startup.cs&lt;/code&gt;,  the two lines with &lt;code&gt;service.Configure&lt;/code&gt; do all the binding magic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TwilioCredentials&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TwilioAccount"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AzureComputerVisionCredentials&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureComputerVisionCredentials"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h2&gt;
  
  
  Run the application
&lt;/h2&gt;

&lt;p&gt;We'll examine the code itself shortly, but first, let's run the program and see an actual result!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The system is accessed using an HTTP endpoint, so assuming your code is now cloned and configured, all you need to do now is build and run the project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Here are a pair of sample images that you could use to perform the tests:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-typed.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-typed.jpg" alt="image showing typed phone number testing text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-handwritten.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-handwritten.jpg" alt="image showing handwritten phone number testing text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We need to &lt;code&gt;POST&lt;/code&gt; an image to our API, so I recommend that you use a tool such as &lt;a href="https://www.getpostman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; to make the request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Set the HTTP action to &lt;code&gt;POST&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Provide a URL to the correct endpoint (taking care to provide the right port number) - e.g. &lt;code&gt;https://localhost:44367/api/PhoneReader&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Set the body of the post to be a &lt;code&gt;binary&lt;/code&gt; type (i.e. not form-data, etc).&lt;/li&gt;
&lt;li&gt; Choose an image file to send (use the "typed" sample provided above if you like).&lt;/li&gt;
&lt;li&gt; If everything has worked, you should get an HTTP200 (ok) response from the API, along with any verified phone numbers:&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fusing-postman.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Fusing-postman.png%23shadow" alt="image showing postman sending http request"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The screenshot above shows that our API correctly returns two phone numbers &lt;code&gt;+441234567890&lt;/code&gt; and &lt;code&gt;+447700123456&lt;/code&gt;.   These have been correctly extracted from the image and subsequently verified as real telephone numbers by the Twilio Api lookup.&lt;/p&gt;






&lt;h2&gt;
  
  
  Integrate With Azure Computer Vision
&lt;/h2&gt;

&lt;p&gt;Next, let's start looking at the code by examining the service class responsible for communicating with the Azure Text Recognition API.    In your code editor, open up this file:-&lt;/p&gt;

&lt;p&gt;&lt;code&gt;....\PhoneExtractVerify.Api\Services\AzureComputerVisionHelperService.cs&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AzureComputerVisionHelperService Constructor&lt;/strong&gt; of the class is concerned with the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2" rel="noopener noreferrer"&gt;IoC injection of configuration settings&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AzureComputerVisionHelperService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAzureComputerVisionHelperService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;AzureComputerVisionCredentials&lt;/span&gt; &lt;span class="n"&gt;_azureComputerVisionCredentials&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AzureComputerVisionHelperService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AzureComputerVisionCredentials&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;azureComputerVisionCredentialsConfiguration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_azureComputerVisionCredentials&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;azureComputerVisionCredentialsConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;azureComputerVisionCredentialsConfiguration&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  Reading Printed Text
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;AzureComputerVisionHelperService ExtractPrintedText(...) method&lt;/strong&gt; performs the work of talking to the Azure API for recognising printed text in an image.&lt;/p&gt;

&lt;p&gt;The complete documentation for how to use this API can be found at &lt;a href="https://docs.microsoft.com/ga-ie/azure/cognitive-services/Computer-vision/quickstarts/csharp-print-text" rel="noopener noreferrer"&gt;Microsoft Computer Vision Quickstart (For Printed Text)&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We provide credentials to use the service in the API request header.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We supply the image (as a byte array) as content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The method asynchronously returns the JSON response from the API call.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll talk about that JSON response in a moment.&lt;/p&gt;

&lt;p&gt;Below is the code that talks to the image-recognition API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExtractPrintedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ocp-Apim-Subscription-Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_azureComputerVisionCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubscriptionKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="n"&gt;HttpResponseMessage&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ByteArrayContent&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ByteArrayContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MediaTypeHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/octet-stream"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_azureComputerVisionCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UriBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  Reading Handwritten Text
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;AzureComputerVisionHelperService ReadHandwrittenText(...) method&lt;/strong&gt; performs the work of talking to the Azure API for recognising handwritten text in an image.&lt;/p&gt;

&lt;p&gt;The complete documentation for how to use this API can be found at &lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/computer-vision/quickstarts/csharp-hand-text" rel="noopener noreferrer"&gt;Microsoft Computer Vision Quickstart (For Handwritten Text)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code for talking to the "Handwriting Recognition" API has a fair bit more code than the equivalent "Typed Text" version.   The principle reason is that the "Typed Text" version of the API returns data along with the API response.  In contrast, the "Handwritten Version" of the API requires more processing effort by the service, which could take a relatively long time to return a result.   As such, this API &lt;strong&gt;does not&lt;/strong&gt; return an immediate result with the API request.&lt;/p&gt;

&lt;p&gt;Instead, this API returns a dynamically generated URL as the response.  This URL then needs to be polled repeatedly, until the service eventually provides you with a result.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We provide credentials to use the service in the API request header.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We supply the image (as a byte array) as content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We check the initial API response for a valid response - if all is OK, we look at the response header as it will contain a URL value (in the code below, this is stored in the variable &lt;code&gt;operationLocation&lt;/code&gt; )&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We create a loop that iterates once every second, up to a maximum of 10 attempts.   Each iteration will poll the &lt;code&gt;operationLocation&lt;/code&gt; for a result.   We assume that if there is no result after 10 seconds, that there has been a problem with the API service and log an error message to the console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If we receive a result during the poll, the json data is returned from the method.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is the code that talks to the image-recognition API (it is more or less cut &amp;amp; paste directly from the MS quickstart guide):-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ReadHandwrittenText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ocp-Apim-Subscription-Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_azureComputerVisionCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubscriptionKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;requestParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mode=Handwritten"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_azureComputerVisionCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UriBase&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"?"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;requestParameters&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="n"&gt;HttpResponseMessage&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;operationLocation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ByteArrayContent&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ByteArrayContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MediaTypeHeaderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/octet-stream"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;operationLocation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Operation-Location"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;errorString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"ReadHandwrittenText : Response:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;JToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errorString&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;contentString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operationLocation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="n"&gt;contentString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;++&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;contentString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\"status\":\"Succeeded\""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;contentString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\"status\":\"Succeeded\""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ReadHandwrittenText : Timeout error."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contentString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  Extracting words from the response
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;AzureComputerVisionHelperService ExtractPrintedWords(...) method&lt;/strong&gt; performs the work of extracting a collection of words from the JSON data returned from the API request.&lt;/p&gt;

&lt;p&gt;The data returned by either of the text-recognition APIs is a relatively complex json string (partial example below).  The string contains a great deal of information including the actual words, the location of those words within the image and how the words may be grouped together (as part of a sentence, etc).&lt;/p&gt;

&lt;p&gt;In many scenarios, all of this information could be very useful - but for this project, we really don't need anything other than a list of the actual words.   &lt;/p&gt;

&lt;p&gt;So, with this understanding, we can write a piece of code that simply iterates over the data structures, and discards pretty much everything other than the data nodes containing the actual words themselves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The variable &lt;code&gt;listDistinctWords&lt;/code&gt; is used to store a list of extracted words.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We convert the JSON string into a &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic" rel="noopener noreferrer"&gt;C# &lt;code&gt;dynamic&lt;/code&gt; object &lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using a &lt;code&gt;dynamic&lt;/code&gt; type &lt;a href="https://softwareengineering.stackexchange.com/questions/135160/shortcomings-of-using-dynamic-types-in-c" rel="noopener noreferrer"&gt;is not without issues&lt;/a&gt; (for example, you lose compile-time type-safety), but for our demonstration code, it is a convenient way to address the problem of mapping and working with the complex object that is provide in the JSON response.  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The JSON response has 3 basic structures : &lt;code&gt;regions&lt;/code&gt;, &lt;code&gt;lines&lt;/code&gt; and &lt;code&gt;words&lt;/code&gt;.   We need to iterate through each of these structures and return only the &lt;code&gt;word.text&lt;/code&gt; value.  The following is a short snippet of the json response, creating using the sample images provided earlier:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "language": "en",
  "orientation": "Up",
  "textAngle": 0.0,
  "regions": [
      {
          "boundingBox": "81,229,1020,455",
          "lines": [
              {
                  "boundingBox": "84,229,989,53",
                  "words": [
                      {
                          "boundingBox": "84,231,72,51",
                          "text": "My"
                      },
                      {
                          "boundingBox": "182,240,106,32",
                          "text": "new"
                      },
                      {
                          "boundingBox": "314,229,162,53",
                          "text": "phone"
                      },
  ... (clipped for brevity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The method then simple returns a collection of words:-
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExtractPrintedWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;jsonResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listDistinctWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;jsonObj&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Newtonsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;jsonObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;listDistinctWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;listDistinctWords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h2&gt;
  
  
  Integrate With Twilio Lookup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dependency on Twilio API Wrapper&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interaction with the Twilio API is simplified because it provides helper libraries that can easily be added to your project.&lt;/p&gt;

&lt;p&gt;Using NuGet, add dependencies to these packages:  &lt;code&gt;Twilio&lt;/code&gt; and &lt;code&gt;Twilio.AspNet.Core&lt;/code&gt;.  &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; If you prefer, you can add these references directly to the following file:-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;....\PhoneExtractVerify\src\PhoneExtractVerify.Api\PhoneExtractVerify.Api.csproj&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ItemGroup&amp;gt;
  &amp;lt;PackageReference Include="Microsoft.AspNetCore.App" /&amp;gt;
  &amp;lt;PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.1" /&amp;gt;
  &amp;lt;PackageReference Include="Twilio" Version="5.14.0" /&amp;gt;
  &amp;lt;PackageReference Include="Twilio.AspNet.Core" Version="5.9.7" /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  The Twilio helper service
&lt;/h3&gt;

&lt;p&gt;Next, we'll look at the service class responsible for communicating with the Twilio API.    &lt;/p&gt;

&lt;p&gt;In your code editor, open up this file:-&lt;/p&gt;

&lt;p&gt;&lt;code&gt;....\src\PhoneExtractVerify.Api\Services\TwilioHelperService.cs&lt;/code&gt;&lt;/p&gt;






&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TwilioHelperService Constructor&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The constructor for this class is really straightforward, so let's just quickly note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;we are injecting a populated model containing the credentials to be used with the Twilio API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;we are initialising an instance of the TwilioClient with the credentials.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;PhoneExtractVerify.Api.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwilioHelperService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITwilioHelperService&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;TwilioCredentials&lt;/span&gt; &lt;span class="n"&gt;_twilioCredentials&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TwilioHelperService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TwilioCredentials&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;_twilioCredentials&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

          &lt;span class="n"&gt;TwilioClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_twilioCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountSid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_twilioCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthToken&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TwilioHelperService VerifyWithTwilioAsync(...) method&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method is responsible for transmitting a single "candidate phone number" (a string) to the Twilio Lookup API.&lt;/p&gt;

&lt;p&gt;Our interaction with the Twilio API has been simplified, because we're using that Twilio API helper library. &lt;/p&gt;

&lt;p&gt;The majority of the code in this class is testing the response to the API and performing logging to help us diagnose any problems that may arise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;VerifyWithTwilioAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;numberToTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;twilioPhoneNumberResource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;PhoneNumberResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FetchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;pathPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Twilio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numberToTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;twilioPhoneNumberResource&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;twilioPhoneNumberResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PhoneNumber&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"TwilioHelperService : Using number '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numberToTest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;', Twilio Api response was null"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"TwilioHelperService : Using number '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numberToTest&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;', Twilio Api returned valid object:  National Format : '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;twilioPhoneNumberResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NationalFormat&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' . Carrier : '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;twilioPhoneNumberResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Carrier&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' . Phone Number :  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;twilioPhoneNumberResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;twilioPhoneNumberResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"TwilioHelperService : Exception : &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TwilioHelperService ProcessListCandidateNumbersAsync(...) method&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method is responsible for iterating a collection of "candidate phone numbers" and calling the previously discussed &lt;code&gt;VerifyWithTwilioAsync&lt;/code&gt; method repeatedly.&lt;/p&gt;

&lt;p&gt;Notice that this method wraps each separate API call in an &lt;em&gt;asynchronous task&lt;/em&gt; and then &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/" rel="noopener noreferrer"&gt;awaits&lt;/a&gt;, using  &lt;code&gt;Task.WhenAll(...)&lt;/code&gt;, for all of the API calls to complete - before progressing.   This allows us to fire of a number of API calls in parallel, which makes more efficient use of time.&lt;/p&gt;

&lt;p&gt;The resultant collection of strings contains a list of phone numbers that have been validated by the Twilio API.  The method then returns the collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessListCandidateNumbersAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listCandidatePhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listVerifiedNumbers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;listTasks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

  &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;candidatePhoneNumber&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listCandidatePhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;VerifyWithTwilioAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidatePhoneNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;listTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;taskResult&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listTasks&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskResult&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;listVerifiedNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;listVerifiedNumbers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-mediator.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-mediator.jpg" alt="image showing cg characters mediating"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mediator Service
&lt;/h2&gt;

&lt;p&gt;The mediator service is the glue of the project and is responsible for orchestrating the multiple steps we need to take.&lt;/p&gt;

&lt;p&gt;In your code editor, open up this file:-&lt;/p&gt;

&lt;p&gt;&lt;code&gt;....\src\PhoneExtractVerify.Api\Services\MediatorService.cs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It could be very tempting to put the kind of code in this class directly into the WebAPI controller method.  However, by locating this orchestration code into its own class, we greatly improve the separation-of-concerns and make our code much clearer.  This means that the WebAPI is responsible only for things which it should be concerned with (e.g. validating input parameters and returning http responses).&lt;/p&gt;






&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MediatorService Constructor&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The constructor of the class &lt;code&gt;MediatorService&lt;/code&gt; injects the components that we will be using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;PhoneExtractVerify.Api.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MediatorService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IMediatorService&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IWordProcessingService&lt;/span&gt; &lt;span class="n"&gt;_wordProcessingService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITwilioHelperService&lt;/span&gt; &lt;span class="n"&gt;_twilioHelperService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IAzureComputerVisionHelperService&lt;/span&gt; &lt;span class="n"&gt;_azureComputerVisionHelperService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MediatorService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IWordProcessingService&lt;/span&gt; &lt;span class="n"&gt;wordProcessingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ITwilioHelperService&lt;/span&gt; &lt;span class="n"&gt;twilioHelperService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAzureComputerVisionHelperService&lt;/span&gt; &lt;span class="n"&gt;azureComputerVisionHelperService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;_wordProcessingService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wordProcessingService&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="n"&gt;_twilioHelperService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;twilioHelperService&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="n"&gt;_azureComputerVisionHelperService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;azureComputerVisionHelperService&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MediatorService ProcessPhoneNumber(...) method&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method is responsible for calling all of the various activities needed to process data from an image and ultimately into a filtered list of words.:-&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this demo code, we've provided versions for either "printed" text or "handwritten" text.   Depending on which one you want to use, you should comment/uncomment the appropriate code.&lt;/p&gt;

&lt;p&gt;As a suggestion, you could modify the code, so you provide additional switching to select the appropriate usage (e.g. a Url parameter in the calling Api, etc)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Calls the &lt;code&gt;RecognisePrintedText&lt;/code&gt; method of the "Azure Computer Vision Helper" service, first providing an image (which will be provided by the WebAPIcontroller method) and storing the JSON response in the variable &lt;code&gt;jsonresponse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Calls either the &lt;code&gt;ExtractWordsFromPrintedResult&lt;/code&gt; or &lt;code&gt;ExtractWordsFromHandwrittenResult&lt;/code&gt; from the "Azure Computer Vision Helper" service - this processes the JSON object to return a simple list of words, which gets saved in the variable &lt;code&gt;listAllWords&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Calls the "Word Processing" service to extract a list of "candidate phone numbers" from the complete list of words returned from the image OCR.   There are a number of chained methods in this activity, each performing a step in the overall processing. We'll examine the "Word Processing Service" in detail later in this article, but for the moment, let's just press onwards so that we get to see a result.&lt;/li&gt;
&lt;li&gt;Calls the "Twilio Helper" service to validate the candidate phone numbers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code listing looks like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ProcessPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Call the Azure Computer Vision service (for Printed Text) and extract words from response.&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;jsonResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_azureComputerVisionHelperService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RecognisePrintedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listAllWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_azureComputerVisionHelperService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExtractWordsFromPrintedResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsonResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// -or-  if you want to use a handwritten image, swap out the above lines for the following:&lt;/span&gt;

  &lt;span class="c1"&gt;// Call the Azure Computer Vision service (for Handwritten Text) and extract words from response.&lt;/span&gt;
      &lt;span class="c1"&gt;//string jsonResponse = await _azureComputerVisionHelperService.RecogniseHandwrittenText(imageBytes);&lt;/span&gt;
      &lt;span class="c1"&gt;//List&amp;lt;string&amp;gt; listAllWords = _azureComputerVisionHelperService.ExtractWordsFromHandwrittenResult(jsonResponse);&lt;/span&gt;


  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listCandidatePhoneNumbers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_wordProcessingService&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listAllWords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCandidatePhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExtractWordsWithNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReformatAsUKInternational&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetMinLengthNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListProcessedWords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listVerifiedPhoneNumbers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_twilioHelperService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessListCandidateNumbersAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listCandidatePhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;listVerifiedPhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h2&gt;
  
  
  The WebAPI Controller
&lt;/h2&gt;

&lt;p&gt;The WebAPI class is our entry point to this project.    It provides a tidy way to interact with our code using an HTTP endpoint.&lt;/p&gt;

&lt;p&gt;In your code editor, open up this file:-&lt;/p&gt;

&lt;p&gt;&lt;code&gt;....\src\PhoneExtractVerify.Api\Controllers\PhoneReaderController.cs&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We inject the mediator class, which has responsibility to call the various processes that we need to run.&lt;/li&gt;
&lt;li&gt;The API method itself, &lt;code&gt;Post()&lt;/code&gt;, is responsible for:-

&lt;ul&gt;
&lt;li&gt;receiving an HTTP post containing an image.&lt;/li&gt;
&lt;li&gt;confirming whether the rest of our code has run successfully and returning an appropriate HTTP response.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PhoneReaderController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IMediatorService&lt;/span&gt; &lt;span class="n"&gt;_mediatorService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PhoneReaderController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMediatorService&lt;/span&gt; &lt;span class="n"&gt;mediatorService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_mediatorService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mediatorService&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Read the image from the POSTed data stream, into a byte array&lt;/span&gt;
      &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyToAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="n"&gt;imageBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;try&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listVerifiedPhoneNumbers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_mediatorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" - "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listVerifiedPhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;catch&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h2&gt;
  
  
  Processing Scanned Numbers Into E.164 Format
&lt;/h2&gt;

&lt;p&gt;By now you have hopefully either run the project and seen a result, or at least have a good feel for the various moving parts.&lt;/p&gt;

&lt;p&gt;This section addressed two key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We're going to be using something called &lt;strong&gt;method chaining&lt;/strong&gt;.   You can read more about this in another of my articles here : &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/c-sharp-method-chaining/" rel="noopener noreferrer"&gt;C# Method Chaining&lt;/a&gt;.   Very briefly, the idea behind this approach is that you can pass the output from one method, directly to another, using an easy-to-use syntax.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This article is going to touch on a specific-regional phone-number format.  Because I'm based in the UK, my demonstration is going to explain how UK phone number formatting works and will include demo code that addresses this.  You should view this as "a business logic problem" and adapt your own code to suit your localisation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I've deliberately left the explanation of this "WordProcessing" service until later in this article, because I wanted you to learn about the overall program structure and the APIs first, without getting you overly bogged-down in detail which is more "business logic" orientated. &lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  A Brief Introduction To UK Phone Number Formatting
&lt;/h3&gt;

&lt;p&gt;In the UK, the phone number system has a few variations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The basic phone number is a six-digit numeric-only number - e.g. 123456. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You could telephone this "short" number and (assuming it is connected) you would get through to the geographically local landline (someone in your town or city).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;You might be calling someone elsewhere in the country or on a mobile phone network.  To achieve this, you need to include what we call the "area code" (or "prefix code").  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An example landline number would now look like this:  01234 123456. &lt;/li&gt;
&lt;li&gt;Mobile (cellphone) numbers typically start with "07" rather than "01", so a mobile number could be: 07700 123456.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In the UK, we also have the concept of non-geographic and premium numbers.  These have a slightly shorter prefix - e.g. 0800 numbers are typically toll-free,  0845 are non-geographic but charged at local call rates.  E.g.  0845 123456&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;The UK international dialling code is +44.    If you are calling a UK number from abroad - or more relevantly to our scenario, wanting to use the international E.164 format - you must drop the leading zero from the national version of the number. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This means that the national number 01234 123456  becomes  +44 1234 123456 ... or more correctly (without spaces) +441234123456.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;The above covers the basic number system.  However, when you start to venture into printed media (such as adverts, posters or business cards etc), phone numbers can become more arcane as they rely on human interpretation to decode.&lt;/p&gt;

&lt;p&gt;For example, it is very common in the UK to include parenthesis and spacing to make it easier for a human to read - for example "(01234) 567890".   To adapt this style of number into E.164 format, we would need to do a couple of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need to remove the non-numeric characters.   In the example above, the parentheses and spaces needs to be removed.&lt;/li&gt;
&lt;li&gt;The leading zero is used when dialling domestically, but not internationally, so we also need to drop that.&lt;/li&gt;
&lt;li&gt;Assuming that we know for a fact that this number is from the UK (and truthfully, we are making a big assumption, as there is nothing in the example itself that tells us that this number is from the UK), we need to add the international dialling code.  So we prefix with +44. &lt;/li&gt;
&lt;li&gt;We should end up with the number  +441234567890&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  The WordProcessingService
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;WordProcessingService&lt;/code&gt; class is responsible for processing a string. The class has various methods that refine the results.    The overall goal of the service is to take a collection of any possible words that originate in a scanned image, and return a list of strings that could be a candidate phone number.&lt;/p&gt;

&lt;p&gt;In your code editor, open up this file:-&lt;/p&gt;

&lt;p&gt;&lt;code&gt;....\src\PhoneExtractVerify.Api\Controllers\PhoneReaderController.cs&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WordProcessingService Class variables&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The various methods in this class chain results together, so rather than passing values around to each method as an argument, we use private member variables that each method modifies.&lt;/p&gt;

&lt;p&gt;The first couple of items are configuration items (such as international dialing prefix).   Arguably this sort of thing could be extracted out into configuration, but given the relatively specific purpose of the code in this class, it's doing no harm to leave it here.&lt;/p&gt;

&lt;p&gt;We publicly expose &lt;code&gt;ListProcessedWords&lt;/code&gt; as a collection of refined values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;PhoneExtractVerify.Api.Services&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WordProcessingService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IWordProcessingService&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;_countryPrefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"+44"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_minWordLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


      &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ListProcessedWords&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WordProcessingService AddWords(...) method&lt;/strong&gt;
Most of the methods in this class read and write from the private member variable &lt;code&gt;_listProcessedWords&lt;/code&gt;.   We need a way to pass an initial value into the chain, which is the purpose that &lt;code&gt;AddWords(...)&lt;/code&gt; serves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see an example of use, described earlier, in the &lt;code&gt;MediatorService ProcessPhoneNumber(...)&lt;/code&gt; method, but to briefly recap, we call the &lt;code&gt;AddWords(...)&lt;/code&gt; method first providing a collection of words as an argument and then append the other method calls.&lt;/p&gt;

&lt;p&gt;Superficially, &lt;code&gt;AddWords(...)&lt;/code&gt; does nothing more than take the argument and assigns it to the private member variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;WordProcessingService&lt;/span&gt; &lt;span class="nf"&gt;AddWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listWords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WordProcessingService GetCandidatePhoneNumbers(...) method&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem we are trying to solve with this method, is that when the Azure OCR service is run on an image, it returns a list of words that are separated by numbers.&lt;/p&gt;

&lt;p&gt;It has no way of recognising that a number separated by spaces is actually intended to be a single phone number, so the result is that it returns multiple separate words.  In other words, our phone number has become fragmented.&lt;/p&gt;

&lt;p&gt;A single number fragment on its own is of no use to us, so we need a way to re-assemble those fragments back together.&lt;/p&gt;

&lt;p&gt;The code in this method achieves this, by iterating over all the words in the collection in sequence.  It works by making a note of whether the previous word it looked at in the sequence, was a number (or contains the "+" symbol).   If the previous word &lt;strong&gt;and&lt;/strong&gt; the current word both contain a number within the word,  the code will combine the two into a new string and insert this new value into the collection. &lt;/p&gt;

&lt;p&gt;The inclusion of the "+" symbol when checking for words containing digits, was something I added when testing.  I noticed that some OCR passes separate the "+"  as a distinct word - separated from the the international code - which caused problems.&lt;/p&gt;

&lt;p&gt;This code will combine new words containing numbers together, until it encounters a word-only word, or reaches the end of the list.&lt;/p&gt;

&lt;p&gt;The code trails on (and includes) the newly-created compound number, meaning we can build up longer strings of adjacent numbers&lt;/p&gt;

&lt;p&gt;So, this is probably best explained by showing you an example.  The following words are extracted from the Azure OCR process :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"call", "me", "on", "+44", "1234", "567", "890", "or"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the above list through our method, creates the following list as an output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"call", "me", "on", "+44", "1234", "+441234", "567", "+441234567", "890", "+441234567890", "or"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a later step, we'll discard most of the unwanted number fragments, but the key thing is that we have reassembled the four separate numbers fragments ("+44", "1234", "567" and "890") into a new word "+441234567890" - which will be useful to us.&lt;/p&gt;

&lt;p&gt;Also worth noting, is that we need to be accepting of numbers that aren't purely integers.  For example the international dialling code fragment with typically contain a plus symbol (e.g. "+44"), so we need to allow this to be included.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;WordProcessingService&lt;/span&gt; &lt;span class="nf"&gt;GetCandidatePhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsTrailingNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listCombinedNumberWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

  &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;listCombinedNumberWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsDigit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"+"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;

          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsTrailingNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="n"&gt;listCombinedNumberWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;listCombinedNumberWords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;listCombinedNumberWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]}{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="n"&gt;IsTrailingNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;IsTrailingNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listCombinedNumberWords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WordProcessingService ExtractWordsWithNumbers(...) method&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose of this method is to discard words that do not contain a numeric digit.&lt;/p&gt;

&lt;p&gt;To explain with an example, when filtered, the following list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"call", "me", "on", "+44", "1234", "+441234", "567", "+441234567", "890", "+441234567890", "or"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... will be filtered to become this list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"+44", "1234", "+441234", "567", "+441234567", "890", "+441234567890"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;WordProcessingService&lt;/span&gt; &lt;span class="nf"&gt;ExtractWordsWithNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsDigit&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WordProcessingService GetMinLengthNumbers(...) method&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose of this method is to discard words that do not meet a value that we deem to be the minimum threshold in length.&lt;/p&gt;

&lt;p&gt;The idea here, is that while we may not know whether a number is valid or not, we can be fairly certain that are too short to possibly be a E.164 code, can be discarded without further testing.&lt;/p&gt;

&lt;p&gt;To explain with an example, when filtered, the following list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"+44", "1234", "+441234", "567", "+441234567", "890", "+441234567890"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... will be filtered to become this list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"+441234567", "+441234567890"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;WordProcessingService&lt;/span&gt; &lt;span class="nf"&gt;GetMinLengthNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_minWordLength&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WordProcessingService ReformatAsUKInternational(...) method&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose of this method is to ensure that any recombined phone number fragments correctly include an international dialling code.&lt;/p&gt;

&lt;p&gt;Earlier in the article, we already established that determining which country code to apply would be difficult.   For the purpose of this demo code, we will assume that we need to be applying a UK code.&lt;/p&gt;

&lt;p&gt;The code inspects the number, performing a few checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non numeric characters are removed, with exception of the "+" symbol.&lt;/li&gt;
&lt;li&gt;The leading zero of the national area code is removed.&lt;/li&gt;
&lt;li&gt;If not already present, the international prefix is added.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;WordProcessingService&lt;/span&gt; &lt;span class="nf"&gt;ReformatAsUKInternational&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listReformattedNumberWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

  &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;numbersOnlyWord&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetOnlyNumbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"+"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;listReformattedNumberWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"+&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;numbersOnlyWord&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbersOnlyWord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;listReformattedNumberWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_countryPrefix&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;numbersOnlyWord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numbersOnlyWord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;listReformattedNumberWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_countryPrefix&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;numbersOnlyWord&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;_listProcessedWords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listReformattedNumberWords&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-ocr.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-ocr.jpg%23shadow" alt="image showing reading glasses on laptop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems With OCR
&lt;/h3&gt;

&lt;p&gt;I've already said that this is a hard problem to solve and that the solution presented in this article is far from robust.  Similarly, however clever the OCR process is, it is far from foolproof. &lt;/p&gt;

&lt;p&gt;To close this article, we'll have a brief look at some of the problems that have been thrown up with our own test material:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OCR incorrectly detecting lines of text&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've followed along with the example, you may have already spotted that the phone number "(07700) 1234567" was not detected correctly in the handwritten-text version of the test. &lt;/p&gt;

&lt;p&gt;The reason for this is not because the OCR process fails to recognise the words, nor is it because our own code here fails to re-assemble the word.  &lt;/p&gt;

&lt;p&gt;The problem is that the OCR process incorrectly identifies which line that the text should appear.  In this example, the OCR incorrectly identifies the second word to appear sooner in the sequence of words (i.e. "123456" appears before "(07700)"), therefore causing our own process to attempt to assemble the phone number in the incorrect order:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-handwritten-line-difference.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-handwritten-line-difference.jpg" alt="image showing OCR incorrectly detecting lines of text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the following code block, I have extracted out the raw data as returned from the OCR API, as this shows you the fragmented nature of the process along with how the data can be returned out of the expected sequence.   What is more reassuring though, is that we can see our loop for reassembling phone numbers seems to work fine, despite being broken up into many smaller words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
call
me
on
+
4
4
1234
567
890
or
123
45
6
(
0
7
700)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OCR incorrectly identifying characters&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The OCR process seems to work pretty well with printed text, but handwritten text is still a "big ask" to assume that results will be returned reliably.   One must remember that a human reading a number can apply "the context" of what they see, not just the literal guess of "what they see", as the computer does.&lt;/p&gt;

&lt;p&gt;To demonstrate an example of a problem with the OCR process, we can see that the handwritten example contains a number zero marked with a cross-line (as you see shown in many fonts).   When drawing the handwritten-text sample, I had deliberately added the cross-line, as I had hoped that this would have made the character easier for the OCR process to interpret, so as to avoid confusion with the "o" or uppercase "O".  As it turned out, this was not the case, as that "0" (zero) character was incorrectly recognised as a "$" (dollar character)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-handwritten-incorrect-ocr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fextract-phone-number-from-image-using-azure-and-twilio%2Ftest-image-handwritten-incorrect-ocr.jpg" alt="image showing OCR incorrectly detecting lines of text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
my
new
phone
number
is
535
$
199
please
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/cognitive-services/computer-vision/" rel="noopener noreferrer"&gt;Azure Computer Vision QuickStart&lt;/a&gt; (linking to other documentation)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;No third party (i.e. Microsoft or Twilio) compensate me for my promotion of their services.     However, my partner Layla Porter is an employee of Twilio Inc,  in the capacity of a developer evangelist (and I very occasionally hang out with other Twilio evangelist) so I have an association and bias to recommend their services.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>twilio</category>
      <category>cognitiveservices</category>
    </item>
    <item>
      <title>Scale your application globally with Azure Series - Part 3 - Azure Front Door</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Thu, 24 Oct 2019 10:14:44 +0000</pubDate>
      <link>https://forem.com/siliconorchid/scale-your-application-globally-with-azure-series-part-3-azure-front-door-2lnd</link>
      <guid>https://forem.com/siliconorchid/scale-your-application-globally-with-azure-series-part-3-azure-front-door-2lnd</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part3-frontdoor/" rel="noopener noreferrer"&gt;blogs.siliconorchid.com&lt;/a&gt; on 30-May-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part three of a three-part series that shows you how to use Azure to distribute your application globally and ensure that users get the best experience.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part1-introduction/" rel="noopener noreferrer"&gt;part 1 of this series&lt;/a&gt;, we talk about how to scale an application and look at a demonstration application.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part2-cdn/" rel="noopener noreferrer"&gt;part 2 of this series&lt;/a&gt;, we look at using &lt;em&gt;Azure CDN&lt;/em&gt; to distribute static content.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part3-frontdoor/" rel="noopener noreferrer"&gt;part 3 of this series&lt;/a&gt;, we look at using &lt;em&gt;Azure Front Door&lt;/em&gt; to globally distribute an application back end hosted in dual Azure Regions.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h2&gt;
  
  
  Use Azure Front Door for, well, everything!
&lt;/h2&gt;

&lt;p&gt;If you have an application that comprises a mix of static and active content (e.g. a JavaScript SPA client consuming a backend that you provide), &lt;em&gt;Azure Front Door&lt;/em&gt; is a service that can benefit all aspects, simply by making use of it.&lt;/p&gt;

&lt;p&gt;Azure Front Door is a newly-available "Network Entrypoint" service from Azure, which became  &lt;a href="https://azure.microsoft.com/en-us/blog/azure-front-door-service-is-now-generally-available/" rel="noopener noreferrer"&gt;generally available in April 2019&lt;/a&gt;.   You should not interpret "newly-available" as meaning that this is a completely new service because it isn't.  This is a tried and tested technology that Microsoft has been using with their own services (e.g. Office365 and Xbox Live) for about 5 years. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Front Door&lt;/em&gt; is &lt;a href="https://docs.microsoft.com/en-us/azure/frontdoor/front-door-overview" rel="noopener noreferrer"&gt;pretty awesome and offers tonnes of features&lt;/a&gt; that can improve most applications by offering a layer of caching, acceleration, global distribution, and failover.&lt;/p&gt;

&lt;p&gt;An interesting feature to be aware of is that you don't necessarily have to have a backend powered by Azure to use and benefit from the service. By simply overlaying &lt;em&gt;Front Door&lt;/em&gt;, over your existing service, wherever they happen to be hosted (they don't have to be in Azure), you could potentially see some performance improvements for users.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this demo we separated static content to use &lt;em&gt;Azure CDN&lt;/em&gt;, so as to demonstrate the use of the CDN in isolation.  &lt;/p&gt;

&lt;p&gt;If you are choosing to use &lt;em&gt;Azure Front Door&lt;/em&gt;, you don't separately need to use the CDN because Front Door provides global distribution for static content also.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  The Microsoft network
&lt;/h3&gt;

&lt;p&gt;To begin to explain what &lt;em&gt;Azure Front Door&lt;/em&gt; is, we need to have a little background understanding of the infrastructure that underpins Azure.     &lt;/p&gt;

&lt;p&gt;Microsoft owns and operates its own global network, which is used to connect its various data centre locations together.  &lt;/p&gt;

&lt;p&gt;This network utilises high capacity dedicated connections, which are not considered to be part of the public internet.  This means any traffic that is within the Azure network will benefit from increased performance.  As a paying Azure client, network interactions that you make within the service will be using this high-performance dedicated network.  &lt;/p&gt;

&lt;p&gt;For example, imagine you had a web API application hosted in the East US Region, that connected to a SQL database hosted in the West US Region.   The traffic between your two servers (i.e. queries and data results) would traverse the private Microsoft network.  However, onward traffic between your users and the web API server  (e.g. JSON) would use public networks.&lt;/p&gt;

&lt;p&gt;This global network represents not only the large &lt;em&gt;Azure Region&lt;/em&gt; data centres (&lt;a href="https://azure.microsoft.com/en-gb/global-infrastructure/regions/" rel="noopener noreferrer"&gt;reported by Microsoft&lt;/a&gt; to be 54 in number, at time of writing)  but also a huge number of satellite locations which form part of their wider network.&lt;/p&gt;

&lt;p&gt;Microsoft is a little coy about detailing exactly the makeup of their network (no doubt because of competitive and national secrecy reasons), but &lt;a href="https://azure.microsoft.com/en-us/global-infrastructure/global-network/" rel="noopener noreferrer"&gt;according to this document&lt;/a&gt; which markets their network to enterprise users,  at time of writing, there are:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"more than 130" &lt;a href="https://whatis.techtarget.com/definition/edge-node" rel="noopener noreferrer"&gt;&lt;strong&gt;Edge Node&lt;/strong&gt;&lt;/a&gt; locations , and&lt;/li&gt;
&lt;li&gt;"more than 60" &lt;a href="https://azure.microsoft.com/en-us/services/cdn/" rel="noopener noreferrer"&gt;&lt;strong&gt;CDN&lt;/strong&gt;&lt;/a&gt; points. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Edge Nodes", in particular, are of interest to us because an "Edge Node" is a way to create additional well-connected Points-Of-Presence for the Azure network.    &lt;/p&gt;

&lt;p&gt;In other words, from your own location (at home, in the office, etc), rather than having to make a network connection across the public internet to a major &lt;em&gt;Azure Region&lt;/em&gt;, you can instead make the shorter connection across the public network to nearby "Edge Node".   From that point, you maintain your contact with that node, which in turn makes requests to your underpinning application, across Microsoft's high-performance dedicated connection.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An analogy that may help you visualise this, is to imagine that you live in a rural village and that you need to travel to a major city to collect a parcel.    &lt;/p&gt;

&lt;p&gt;Normally, you would use the public road network to drive there by car.   This would typically comprise of a journey on local roads and major highways, that are probably congested and across multiple, different junctions.   &lt;/p&gt;

&lt;p&gt;Imagine instead, that you just needed to drive to a local town where you would meet a courier.   This courier would in turn use a private &lt;a href="https://en.wikipedia.org/wiki/Autobahn" rel="noopener noreferrer"&gt;&lt;em&gt;autobahn&lt;/em&gt;&lt;/a&gt;, with little congestion and potentially higher speed limits, that connects directly from the town to the city.  The courier could quickly zip back and forth to the city to get your parcel for you, taking much less time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following graphic, duplicated from &lt;a href="https://azure.microsoft.com/en-us/global-infrastructure/global-network/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, gives fascinating insight not only into the location of &lt;em&gt;Regions&lt;/em&gt; and &lt;em&gt;Nodes&lt;/em&gt;, but also an approximation of the physical routes that comprise the infrastructure links:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fglobal-network-map-desktop.svg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fglobal-network-map-desktop.svg%23shadow" alt="image showing microsoft network"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  What is Azure Front Door?
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Azure Front Door&lt;/em&gt; is a premium service which gives your application access to this network of Edge Nodes.    &lt;/p&gt;

&lt;p&gt;Without making any modifications to your existing application, just by adding &lt;em&gt;Front Door&lt;/em&gt; as a service layer, you can potentially improve network access performance for your users.  &lt;/p&gt;

&lt;p&gt;Instead of having to traverse the public internet to connect directly to your application, hosted in an Azure Region,  your users can now take advantage of the Microsoft network, as previously described.  &lt;/p&gt;

&lt;p&gt;The service offers several features including geographic load-balancing.   This is the feature that we'll demonstrate in this article, as we'll dynamically connect our users to a geographically closer service.&lt;/p&gt;

&lt;p&gt;For other features, check out the &lt;a href="https://azure.microsoft.com/en-us/services/frontdoor/" rel="noopener noreferrer"&gt;Microsoft Front Door: Product summary page&lt;/a&gt;.   It's worth noting that the overhead of SSL encryption can be offloaded from your application server and onto the Edge Node.  Also worth noting is that &lt;em&gt;Front Door&lt;/em&gt; provides firewall and &lt;a href="https://en.wikipedia.org/wiki/DDoS_mitigation" rel="noopener noreferrer"&gt;DDOS protection&lt;/a&gt;.&lt;/p&gt;






&lt;h3&gt;
  
  
  Create Azure Function App Service Plans in multiple Azure Regions
&lt;/h3&gt;

&lt;p&gt;To demonstrate a global infrastructure, we're going to need to create App Service Plans in at least two separate Azure Regions.    &lt;/p&gt;

&lt;p&gt;For the purpose of our demo, we're going to provision one in &lt;strong&gt;UK South&lt;/strong&gt; (London) and the other in &lt;strong&gt;West US&lt;/strong&gt; (California).  We'll use the following settings and resourcename(s).  When you come to do your own version, you'll need to use your own unique names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Name(s)&lt;/strong&gt; :  &lt;code&gt;GlobalScalingDemoFuncAppLondon&lt;/code&gt; and   &lt;code&gt;GlobalScalingDemoFuncAppCalifornia&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Group Name&lt;/strong&gt; : &lt;code&gt;GlobalScalingDemo&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting Plan&lt;/strong&gt; : "Consumption Plan"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt; : "UK South"  and "West US"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt; : &lt;code&gt;globalscalefuncukstorage&lt;/code&gt;  and &lt;code&gt;globalscalefuncusstorage&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also need to create a pair of corresponding &lt;em&gt;Application Insight&lt;/em&gt; Resources.   These will become important to have available later in the demo, as they will show us how the services are being utilised.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fcreate-azure-function-appservice.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fcreate-azure-function-appservice.png%23shadow" alt="image showing how azure function app service is created"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;Earlier, we discussed that our &lt;em&gt;Azure Function&lt;/em&gt; requires a small piece of configuration to be included, which will be used to display the name of the &lt;em&gt;Azure Region&lt;/em&gt; in effect.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Go ahead and add the following configuration into both of your Functions.  You should provide a value which is appropriate for the Azure Region you are using.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration Key : &lt;code&gt;CurrentHostingLocation&lt;/code&gt;.    Value : &lt;code&gt;London&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are unfamilar with Azure Function configuration, please refer to my earlier article where I  &lt;a href="(https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud/#configureazurefunction)"&gt;demonstrate how to create configuration settings&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;&lt;strong&gt;Both sets of Azure Functions&lt;/strong&gt;  also need to have a &lt;a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" rel="noopener noreferrer"&gt;CORS&lt;/a&gt; entry, set up independently on each resource, in order for our web client to be able to successfully access these API resources.&lt;/p&gt;

&lt;p&gt;In this example, we will need to add a CORS entry for the website &lt;code&gt;https://globalscalingdemocdnendpoint.azureedge.net&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't know how to update the CORS entry in Azure Functions, I have detailed the steps in a &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud/#configurecors" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  Publish the Function to both Azure Regions
&lt;/h3&gt;

&lt;p&gt;This article is not intended to be a tutorial of how to publish Azure Functions to the cloud.  If you don't already know how to do this, please refer to my &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud/#deployazurefunction" rel="noopener noreferrer"&gt;previous article&lt;/a&gt; where we walk through one way to publish, directly from Visual Studio.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;From Visual Studio 2019, we'll now publish our Function application directly to Azure.   We need to do this twice - to both Azure Regions.  This means that you'll end up creating two &lt;em&gt;Deployment Profiles&lt;/em&gt;.      &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the Functions deployed, the easiest way to test that they are working correctly and returning the expected result, is simply to use our web browser to check for a response.    For me, the URL(s) to test are:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://globalscalingdemofuncapplondon.azurewebsites.net/api/CalculatePrimeNumber&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://globalscalingdemofuncappcalifornia.azurewebsites.net/api/CalculatePrimeNumber&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If everything is working correctly, you'll see the following message displayed in your browser:- &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Hello, there are 41539 prime numbers in the range 0 - 500000.   This result was calculated in London&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note:  By the time I publish this article, I will have disabled my Azure Function App Service, as I don't want to run up an unnecessary bill - so please don't expect the above links to be live and working - they are purely to provide a realistic and consistent context to talk about.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  Create the Azure Front Door service
&lt;/h3&gt;

&lt;p&gt;Now we're finally getting to the exciting part.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the Azure Portal, &lt;strong&gt;Create a resource&lt;/strong&gt; and search for "Front Door".   Select &lt;strong&gt;Microsoft Front Door&lt;/strong&gt; from the list and click &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your existing &lt;em&gt;Resource Group&lt;/em&gt; - for me, this is "GlobalScalingDemo"&lt;/li&gt;
&lt;li&gt;Select the option &lt;strong&gt;Next : Configuration&lt;/strong&gt; (don't choose "Review + Create" just yet)&lt;/li&gt;
&lt;li&gt;In the panel "Front End Host", click the &lt;strong&gt;+&lt;/strong&gt; symbol in the top-right corner.

&lt;ul&gt;
&lt;li&gt;A blade will slide out from the right, prompting you to enter a Host Name.
&lt;/li&gt;
&lt;li&gt;I entered &lt;code&gt;GlobalScalingDemoFrontendHost&lt;/code&gt;, but you will need to provide your own unique name.
&lt;/li&gt;
&lt;li&gt;I left the "Session Affinity" option in its default "off" setting, as this isn't relevant to this demo (session affinity is a way to "glue" users to a particular service on subsequent visits, which could be relevant if your application has user persistence of some description).

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Add&lt;/strong&gt; to create the front end host&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffrontdoor-frontendpool.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffrontdoor-frontendpool.png%23shadow" alt="image showing how azure front door frontend is created"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;ul&gt;
&lt;li&gt;In the second panel, "Backend Pools", click the &lt;strong&gt;+&lt;/strong&gt; symbol in the top-right corner.

&lt;ul&gt;
&lt;li&gt;Another blade will slide out from the right, prompting you to enter a "Name".&lt;/li&gt;
&lt;li&gt;I entered &lt;code&gt;GlobalScalingDemoBackendPool&lt;/code&gt;, but you will need to provide your own unique name.&lt;/li&gt;
&lt;li&gt;You now need to register backend services.   For us, this will be the pair of Function App Services that we created earlier.&lt;/li&gt;
&lt;li&gt;Click the "Add Backend" option.

&lt;ul&gt;
&lt;li&gt;When prompted for "Backend Host Type", choose "App Service"&lt;/li&gt;
&lt;li&gt;Under the option for "Backend Host Name" choose one of our Function Apps&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Repeat the process of "Adding Backend" a second time for our other App Service.&lt;/li&gt;

&lt;li&gt;Under the option "HEALTH PROBES", leave the setting "interval" at its default "30sec".  We'll return to talk about this subject in a moment.&lt;/li&gt;

&lt;li&gt;Leave the "Load Balancing" options at their default setting.&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffrontdoor-backendpool.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffrontdoor-backendpool.png%23shadow" alt="image showing how azure front door backend is created"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;ul&gt;
&lt;li&gt;In the third panel, "Routing Rules", click the &lt;strong&gt;+&lt;/strong&gt; symbol in the top-right corner.

&lt;ul&gt;
&lt;li&gt;Another blade will slide out from the right, prompting you to enter a "Name".&lt;/li&gt;
&lt;li&gt;I entered &lt;code&gt;GlobalScalingDemoRouting&lt;/code&gt;, but you will need to provide your own unique name.&lt;/li&gt;
&lt;li&gt;Leave all other option at their default setting.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;Finally, click &lt;strong&gt;Create&lt;/strong&gt; button to validate, review and create the new resource.&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Azure Front Door Health Probe
&lt;/h3&gt;

&lt;p&gt;Before we move on to testing the new solution, let's talk about the topic of "Health Probes" (one of the settings we just looked at).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Azure Front Door&lt;/em&gt; polls your resources to ensure that they are still in a healthy condition.    &lt;/p&gt;

&lt;p&gt;Should a backend resource become unresponsive, then the &lt;em&gt;Front Door&lt;/em&gt; service can detect this and "&lt;a href="https://searchstorage.techtarget.com/definition/failover" rel="noopener noreferrer"&gt;fail over&lt;/a&gt;", meaning that users will be directed to an alternative, healthy, resource, albeit, with increased latency.  &lt;/p&gt;

&lt;p&gt;Something to consider about this constant health-checking is that your underlying &lt;em&gt;Azure Function&lt;/em&gt; will be triggered each time a check is made.      &lt;/p&gt;

&lt;p&gt;For a production system, this is not an undesirable thing, as failures can be rapidly detected and responded to swiftly.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are just experimenting with the technology, be conscious that this will generate a low-level amount of activity.   In the case of Azure Functions, the &lt;a href="https://azure.microsoft.com/en-us/pricing/details/functions/" rel="noopener noreferrer"&gt;current pricing model&lt;/a&gt; provides you with a surprisingly generous free quota of usage, so it &lt;em&gt;shouldn't&lt;/em&gt; cost you anything extra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Article Update 12 June 2019]&lt;/strong&gt; : I was speaking with my friend MVP &lt;a href="https://twitter.com/shahiddev?lang=en" rel="noopener noreferrer"&gt;Shahid Iqbal&lt;/a&gt; last night at a community meetup.  He raised an interesting point about the traffic generated by AFD's health-check probe.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When I originally wrote this article, I did consider that this may be an issue, but then dismissed it on the basis that calling something like an Azure Function API, even a few thousand times a day, would be an inexpensive operation.
&lt;/li&gt;
&lt;li&gt;What Shahid has highlighted, to make me reconsider this opinion,  is that if you call other types of default resource (specifically, a default &lt;strong&gt;webpage&lt;/strong&gt;), this can actually consume a meaningfully large amount of bandwidth (that you will be charged for).
&lt;/li&gt;
&lt;li&gt;Researching this topic does not currently bring up much information, but I did find this &lt;a href="https://stackoverflow.com/questions/55572932/azure-front-door-generates-a-large-amount-of-bandwidth" rel="noopener noreferrer"&gt;stack-overflow article&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;Therefore, &lt;strong&gt;I would recommend creating a simple REST API that does nothing more than return an HTTP-200 OK response&lt;/strong&gt;.   This can be used as a lightweight dedicated-responder to the health-check probe.   You will need to explicitly register the URL to this API in the "path" field of the "backend pool, health probe" configuration in Azure.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;





&lt;p&gt;Normally, if an Azure function is not used for a while, the platform unloads the service from memory and the function goes dormant.  This is called "going cold".    The impact of this is that the next person to request the resource will experience a "&lt;a href="https://azure.microsoft.com/en-us/blog/understanding-serverless-cold-start/" rel="noopener noreferrer"&gt;cold start&lt;/a&gt;" meaning they could face a slight delay, whilst the function is started back up.   &lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://mikhail.io/serverless/coldstarts/azure/" rel="noopener noreferrer"&gt;this recent article by Mikhail Shilkov&lt;/a&gt;, benchmarks indicate that a typical cold-state for a C# Azure Function 2.0, is ~3 seconds. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A useful side-effect of using &lt;em&gt;Front Door&lt;/em&gt; is that &lt;strong&gt;because your App Service is constantly being polled, it will never go cold, meaning that it will always be in a state ready to immediately serve the next user.&lt;/strong&gt;   &lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  Check that all the Azure resources have been created
&lt;/h3&gt;

&lt;p&gt;Before we move further, let's just take an inventory to make sure that we really have set up all the Azure Resources that this demonstration requires.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the Azure Portal, navigate to the homepage and select your &lt;em&gt;Resource Group&lt;/em&gt;.  For me, this is called &lt;code&gt;GlobalScalingDemo&lt;/code&gt;.   Click on that &lt;em&gt;Resource Group&lt;/em&gt; to view a list of resources contained within.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Having followed all the steps in the article, we should have a total of 12 separate resources.  Please refer to the following screenshot as a reference.   As a reminder, the resource names and regions that I have used are intended for this demo and, in many cases, you will have had to use your own unique names:-&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fazure-resourcegroup-complete.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fazure-resourcegroup-complete.png%23shadow" alt="image showing how azure resource group with all required resources"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Testing the System
&lt;/h2&gt;

&lt;p&gt;With the &lt;em&gt;Azure Front Door&lt;/em&gt; resource created and configured, the easiest way to check that it is working correctly is to use your browser to request a result directly from the "Frontend Host Name", the service API endpoint.   For me, I used this URL:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://globalscalingdemofrontendhost.azurefd.net/api/CalculatePrimeNumber&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A successful result should respond with a message that indicates it was generated in the &lt;em&gt;Azure Region&lt;/em&gt; nearest to you.   For me, using a native internet connection, I would expect London.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At this point, we should now update our original JavaScript client with this endpoint. In the file &lt;code&gt;....\GlobalScalingDemo\src\GlobalScalingDemo.Web\wwwroot\js\client.js&lt;/code&gt; you need to update the variable &lt;code&gt;serviceEndpoint&lt;/code&gt; like this:-
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://globalscalingdemofrontendhost.azurefd.net/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having updated this setting, you should redeploy the JavaScript file, by copying it to the Azure Storage Container, used by the CDN.&lt;/p&gt;






&lt;h3&gt;
  
  
  How to test that the location-based routing is working correctly?
&lt;/h3&gt;

&lt;p&gt;There are a couple of ways we could check that the service is routing to an appropriate Azure Region (for example, a low-tech way might be to ask a friend in another country to check for you).&lt;/p&gt;

&lt;p&gt;A more scientific way is to alter your &lt;a href="https://en.wikipedia.org/wiki/Point_of_presence" rel="noopener noreferrer"&gt;Internet Point-of-Presence&lt;/a&gt; (POP).    There are a few ways you might do this:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;a href="https://www.howtogeek.com/133680/htg-explains-what-is-a-vpn/" rel="noopener noreferrer"&gt;VPN&lt;/a&gt; (if you work for an international company - or - use a VPN service).&lt;/li&gt;
&lt;li&gt;Remotely connect to a distant machine you have access to - e.g. temporarily &lt;a href="https://azure.microsoft.com/en-gb/services/virtual-machines/" rel="noopener noreferrer"&gt;provision an Azure VM&lt;/a&gt; - and operate this using a &lt;a href="https://support.microsoft.com/en-gb/help/17463/windows-7-connect-to-another-computer-remote-desktop-connection" rel="noopener noreferrer"&gt;Remote Desktop Connection&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the purpose of testing, I used a private VPN service to change my &lt;em&gt;POP&lt;/em&gt;.  You can watch my test in the following animation, but briefly, the steps I made were:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use VPN to connect to a POP in Frankfurt, Germany&lt;/li&gt;
&lt;li&gt;Navigate to the CDN-hosted client app in the browser.&lt;/li&gt;
&lt;li&gt;Wait for the application to return a result from the backend.&lt;/li&gt;
&lt;li&gt;Verify that the result identifies as having originated from London (which is the nearest service to Germany).&lt;/li&gt;
&lt;li&gt;Disconnect VPN and reconnect to a different POP, in Denver, USA&lt;/li&gt;
&lt;li&gt;Refresh the browser window;  the client app will make a fresh request to the backend.&lt;/li&gt;
&lt;li&gt;Verify that the result identifies as having originated in California. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ftesting-application.gif%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ftesting-application.gif%23shadow" alt="image showing how service was tested using a VPN"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  Use Application Insights to understand what's happening with your service
&lt;/h3&gt;

&lt;p&gt;Firstly, let's make sure we understand how to see what's actually going on in Azure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decide which of the two App Service resources we are going to inspect.  Initially, you should choose the Region which you know is geographically closest to you.   For me, this is my "London" version.&lt;/li&gt;
&lt;li&gt;From the Azure Portal, locate and open the &lt;em&gt;Application Insight&lt;/em&gt; resource that corresponds with this &lt;em&gt;App service&lt;/em&gt;.   For me, this is called "GlobalScalingDemoFuncAppLondonAppInsight"&lt;/li&gt;
&lt;li&gt;With the &lt;em&gt;Application Insight&lt;/em&gt; blade open, locate the option "Live Metrics Stream" and select it.&lt;/li&gt;
&lt;li&gt;In the panel that appears, scroll down towards the bottom so that it displays the section "Servers" &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This report is really interesting to watch, as it shows the incoming requests placing a load on the service.   It also shows the number of actual Azure Function host instances that have been [automatically] provisioned for you.   If a service is worked hard, we can expect to see extra instances appear.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fazure-functionapp-livemetrics.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fazure-functionapp-livemetrics.png%23shadow" alt="image showing how azure function app live metrics"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;p&gt;A low-tech way to test your service could be to refresh your browser window really quickly, so as to trigger a number of HTTP calls.   However, you'll probably break your mouse button before making much of an impact on Azure.   &lt;/p&gt;

&lt;p&gt;Instead, we're going to use automation tools to create an artificial workload. There are a couple of ways that we could do this.   &lt;/p&gt;

&lt;p&gt;One of my favourite tools, &lt;a href="https://www.getpostman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt;, has a feature which allows you to trigger a queue of API requests.  You can read about the amusingly named "Postman Collection Runs" in this article : &lt;a href="https://learning.getpostman.com/docs/postman/collection_runs/starting_a_collection_run/" rel="noopener noreferrer"&gt;Postman Learning Centre : Intro to collection runs&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this demo, however, we're going to use a &lt;a href="https://www.guru99.com/load-testing-tutorial.html" rel="noopener noreferrer"&gt;load-testing&lt;/a&gt; tool called &lt;a href="https://artillery.io/" rel="noopener noreferrer"&gt;&lt;em&gt;Artillery&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Download and install &lt;em&gt;Artillery&lt;/em&gt;.  Installation is as simple as running the following command ( but you can &lt;a href="https://artillery.io/docs/getting-started/" rel="noopener noreferrer"&gt;follow the official instructions on the website: Getting Started With Artillery&lt;/a&gt;):-&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -g artillery&lt;/code&gt;  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next we want to create a load on our server.  The following command will connect to our &lt;em&gt;Front Door&lt;/em&gt; global endpoint and create 20 virtual users, each of which call the API 50 times.  The process is set to run over a period of 30 seconds. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;artillery quick --count 20 -n 50  --duration 30 https://globalscalingdemofrontendhost.azurefd.net/api/CalculatePrimeNumber/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When you have started the test, you should watch &lt;em&gt;Application Insights&lt;/em&gt; to see how your service reacts.   You should see results similar to the following animation:-&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffunction-load-test.gif%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffunction-load-test.gif%23shadow" alt="image showing how azure function app live metrics"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When not to use a CDN&lt;/strong&gt; - if you have an application with actively-generated server content, e.g. MVC), which has only a very small amount of static content such as a  tiny amount of CSS or JavaScript) the overhead of an additional request to another network resource may not add up.   Consider in-lining this content into your page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/static-content-hosting" rel="noopener noreferrer"&gt;Microsoft Documentation : Static Content Hosting pattern
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infoq.com/news/2019/04/Azure-Front-Door/" rel="noopener noreferrer"&gt;INFOQ : Microsoft Introduces Azure Front Door, a Scalable Service for Protecting Web Applications
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/global-infrastructure/global-network/" rel="noopener noreferrer"&gt;Azure global network
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=3Di9H1V0zuc" rel="noopener noreferrer"&gt;Azure Friday : AFD Preview
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-us/blog/how-microsoft-builds-its-fast-and-reliable-global-network/" rel="noopener noreferrer"&gt;How Microsoft builds its fast and reliable global network
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;No third party (i.e. Microsoft) compensate me for my promotion of their services.    I have no bias to recommend their services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thumbs Up
&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.linkedin.com/in/layla-porter-12433b3b/" rel="noopener noreferrer"&gt;Layla Porter&lt;/a&gt; for being my document reviewer.  I don't have a team to back me up and sometimes you need another set of eyes!&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://twitter.com/GerardoLijs" rel="noopener noreferrer"&gt;Gerardo Lijs&lt;/a&gt; for &lt;a href="https://twitter.com/GerardoLijs/status/1133971741934587904" rel="noopener noreferrer"&gt;suggesting an alternative take&lt;/a&gt; on the prime-number calculation,  which was both more concise and better saturated a CPU.  This originated in the book &lt;a href="https://twitter.com/GerardoLijs/status/1133971741934587904" rel="noopener noreferrer"&gt;C# 7.0 in a Nutshell&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://twitter.com/shahiddev?lang=en" rel="noopener noreferrer"&gt;Shahid Iqbal&lt;/a&gt;  for highlighting a potential issue related to the health-check polling mechanism.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>azure</category>
      <category>azurefunctions</category>
    </item>
    <item>
      <title>Scale your application globally with Azure Series - Part 2 - Azure CDN</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Thu, 24 Oct 2019 10:14:31 +0000</pubDate>
      <link>https://forem.com/siliconorchid/scale-your-application-globally-with-azure-series-part-2-azure-cdn-232m</link>
      <guid>https://forem.com/siliconorchid/scale-your-application-globally-with-azure-series-part-2-azure-cdn-232m</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part2-cdn/" rel="noopener noreferrer"&gt;blogs.siliconorchid.com&lt;/a&gt; on 29-May-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part two of a three-part series that shows you how to use Azure to distribute your application globally and ensure that users get the best experience.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part1-introduction/" rel="noopener noreferrer"&gt;part 1 of this series&lt;/a&gt;, we talk about how to scale an application and look at a demonstration application.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part2-cdn/" rel="noopener noreferrer"&gt;part 2 of this series&lt;/a&gt;, we look at using &lt;em&gt;Azure CDN&lt;/em&gt; to distribute static content.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part3-frontdoor/" rel="noopener noreferrer"&gt;part 3 of this series&lt;/a&gt;, we look at using &lt;em&gt;Azure Front Door&lt;/em&gt; to globally distribute an application back end hosted in dual Azure Regions.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h2&gt;
  
  
  Use Azure Content Delivery Network (CDN) for static content
&lt;/h2&gt;

&lt;p&gt;If your application has only static content, such as a blogsite like mine, or a JavaScript application that only consumes APIs from third-parties, then using a CDN is a perfect way to distribute your files.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a CDN?
&lt;/h3&gt;

&lt;p&gt;A CDN is a globally distributed network of servers.  Data is replicated in a large number of different geographic locations, so as to bring copies physically nearer to users around the planet.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Each location of the network is called a "node" (or "edge server").  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CDNs work by seeding from a single source of data and replicating copies out to the various nodes on the network.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Something to be aware of is that when you update a file at the source,  you cannot expect the content to update across the network instantly - it can sometimes take a few minutes to propagate globally. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Often, a single country may have multiple nodes, located in different cities, to further increase the chance of geographic proximity to a user.   &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When requesting a piece of data, the network determines which node will be fastest for the user and serves from that node accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you've never set up your own CDN, there's a very good chance that you are already familiar with consuming content from one, because it's very common to put a reference to an open-source JavaScript library/resource (e.g. Bootstrap)  as a link on a webpage.   There are several benefits to doing so:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files can be requested and downloaded more quickly (because of physical proximity). &lt;/li&gt;
&lt;li&gt;If you are referencing a popular shared resource (e.g. many sites reference the same copy of bootstrap from a Google CDN), there is a good chance that your user may already have a locally cached copy of the resource.
&lt;/li&gt;
&lt;li&gt;It may save bandwidth charges if another organisation is responsible for hosting shared content.&lt;/li&gt;
&lt;/ul&gt;






&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffile-barrage.gif%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Ffile-barrage.gif%23shadow" alt="image showing a lady being hit by a barrage of paper files"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How does a CDN apply to you?
&lt;/h3&gt;

&lt;p&gt;For many developers, the de facto place to publish their &lt;strong&gt;entire&lt;/strong&gt; website content will be the tried and trusted web server (or perhaps a derivative, such as an &lt;a href="https://azure.microsoft.com/en-gb/services/app-service/web/" rel="noopener noreferrer"&gt;&lt;em&gt;Azure WebApp Service&lt;/em&gt;&lt;/a&gt;).  &lt;/p&gt;

&lt;p&gt;Such a website will likely be used to serve up both active content, e.g. MVC pages and RESTful APIs, and static content such as stylesheets, images, SPA payloads, etc.  &lt;/p&gt;

&lt;p&gt;The static content can be a barrage of many smaller files, each of which your server needs to process.  This incurs a load on resources such as CPU, memory, network bandwidth, etc.&lt;/p&gt;

&lt;p&gt;If this is how you currently work, I would &lt;strong&gt;strongly recommend that you review which parts of your application are purely static content and consider publishing these separately to a CDN&lt;/strong&gt;.  Your users could benefit immediately from improved download times and your servers could benefit from a reduced overall traffic load. &lt;/p&gt;

&lt;p&gt;Static content doesn't just represent obvious items such as .css and images, it could also represent the compiled payload of your JavaScript SPA application which even when minified could easily be a large download.&lt;/p&gt;

&lt;p&gt;You should note that despite having huge capacity and global reach, CDNs are relatively cheap to provision.  You can read about &lt;a href="https://azure.microsoft.com/en-gb/pricing/details/cdn/" rel="noopener noreferrer"&gt;&lt;em&gt;Azure CDN&lt;/em&gt; charges here&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;The Azure Portal provides an element of choice and convenience, directly offering alternative services from both Akamai and Verizon.&lt;/p&gt;

&lt;p&gt;Something that you may find interesting, is the list of physical CDN node locations.  Check out this link for a &lt;a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-pop-locations" rel="noopener noreferrer"&gt;list of &lt;em&gt;Azure CDN&lt;/em&gt; locations&lt;/a&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unrelated to Azure, but definitely worth mentioning, if you are an individual with a personal website e.g. a budding blogger like me, &lt;a href="https://www.cloudflare.com/en-gb/plans/" rel="noopener noreferrer"&gt;Cloudflare offers a free pricing tier&lt;/a&gt; which could be perfect for you! &lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  Create an Azure Resource Group
&lt;/h3&gt;

&lt;p&gt;We've been covering a lot of theory so far, it's about time we made a start on our demonstration project.&lt;/p&gt;

&lt;p&gt;Firstly, we need a way to keep our Azure resources grouped together logically, so we need to make a &lt;em&gt;Resource Group&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you don't know how to create a &lt;em&gt;Resource Group&lt;/em&gt;, refer to this &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/manage-resource-groups-portal" rel="noopener noreferrer"&gt;Microsoft Documentation Guide : Manage Azure Resource Manager resource groups by using the Azure portal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;In this demo I have created a new group called "GlobalScalingDemo"&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Create and configure an Azure Storage Static Website
&lt;/h3&gt;

&lt;p&gt;A CDN requires a source of data to work from.   In our demo, we'll be using an &lt;em&gt;Azure Storage Static Website&lt;/em&gt; for this purpose.&lt;/p&gt;

&lt;p&gt;It's worth clarifying that an &lt;em&gt;Azure CDN&lt;/em&gt;  &lt;strong&gt;does not need a website as it's source&lt;/strong&gt; - for a couple of years now, it has been possible to connect a CDN &lt;strong&gt;directly&lt;/strong&gt; to files in a storage container.   &lt;/p&gt;

&lt;p&gt;There is an easy-to-overlook reason as to why we specifically want to use the recently new &lt;em&gt;Azure Storage Static Site&lt;/em&gt; option - this relates to the default webpage (and default "not found" page).      Without a web server, &lt;em&gt;Azure Storage&lt;/em&gt; simply serves up files - it doesn't understand the concept of a default web page, meaning that &lt;code&gt;index.html&lt;/code&gt; has no significance.    The only way you could load the homepage would be to explicitly ask for it in the URL i.e. something like &lt;code&gt;www.mywebsite.com/index.html&lt;/code&gt;.    However, with the &lt;em&gt;Static Site&lt;/em&gt; feature enabled and configured, we can use a naked URL and correctly serve up the default page as expected i.e. just &lt;code&gt;www.mywebsite.com&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If your application has no requirement to serve a homepage, you can bypass the advice to create a &lt;em&gt;Static Website&lt;/em&gt; and simply connect the CDN directly to the exposed URL of the storage container (making sure that the container has public visibility turned on!)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We now need to create an &lt;em&gt;Azure Storage Static Website&lt;/em&gt;.   If you don't already know how to do this, you should refer to my &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud/#createazurestoragestaticwebsite" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;, which details exactly the steps you need to take. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In this demo, I've given my resource the name &lt;code&gt;globalscalingdemostorage&lt;/code&gt; which exposes an endpoint called &lt;code&gt;https://globalscalingdemostorage.z6.web.core.windows.net/&lt;/code&gt; - but you will need to provide your own unique name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the new resource has been provisioned, you will need to copy the content for the demo website across to the &lt;code&gt;$web&lt;/code&gt; container.  There are a couple of ways you could do this, but we &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud/#uploadfilestoazurestoragestaticwebsite" rel="noopener noreferrer"&gt;discussed using the &lt;em&gt;Azure Storage Explorer&lt;/em&gt; in the previous article&lt;/a&gt;.     &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are uncertain what content needs to be copied, it should be everything in the folder &lt;code&gt;....\GlobalScalingDemo\src\GlobalScalingDemo.Web\wwwroot&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Please note that at this point in time, we have not set up a backend system, so we don't know what the endpoint will be.   As such, we cannot set this value in JavaScript,  so the client app will be expected to error.&lt;/p&gt;






&lt;h3&gt;
  
  
  Create and configure the Azure CDN
&lt;/h3&gt;

&lt;p&gt;Now that we've created a website as a source, we can now go ahead and create the actual Azure CDN.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From your resource group, click the &lt;strong&gt;Add&lt;/strong&gt; new resource button and search for "CDN"&lt;/li&gt;
&lt;li&gt;From the "Microsoft CDN" blade, click &lt;strong&gt;Create&lt;/strong&gt; to start the process. &lt;/li&gt;
&lt;li&gt;Populate the fields.  For some of these fields, you will need to provide your own unique name.   For the demo I used these settings:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;:  "globalscalingdemocdn" - noting that I suffixed this resource name with "cdn" to make it clear what this was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Group&lt;/strong&gt;:  select the resource group that you created earlier.  For me, I used "GlobalScalingDemo".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource Group Location&lt;/strong&gt;:  This setting is greyed-out and cannot be edited - it inherits from the location associated with the parent resource group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing Tier&lt;/strong&gt;: You have a number of options here, with different providers offering different types of service at different price points.  You can &lt;a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-features" rel="noopener noreferrer"&gt;review a matrix of features here&lt;/a&gt;.   For the sake of this demo, we're going to choose the default "Standard Microsoft". &lt;/li&gt;
&lt;li&gt;We want to be able to define a custom URL Endpoint for our CDN, so go ahead and check the tickbox for "Create a new CDN Endpoint now".     Doing so will reveal further options:-&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN Endpoint Name&lt;/strong&gt; : "globalscalingdemocdnendpoint" - noting that I suffixed this resource name with "cdnendpoint" to make it clear what this was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin&lt;/strong&gt; : Now this is important!  You should resist any instinct to select the option called "Storage" and instead choose &lt;strong&gt;"Custom Origin"&lt;/strong&gt;.    The difference is that the "storage" option is used to connect the CDN to a regular storage container, whereas when we use a static website, we need to connect our CDN to the endpoint of that static website instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin Hostname&lt;/strong&gt; : You need to use the primary endpoint of the Azure Storage Static Website.    Be careful cutting+pasting the value though, as you need to remove both the leading "https://" and the trailing "/".    For my demo, this means I would enter &lt;code&gt;globalscalingdemostorage.z6.web.core.windows.net&lt;/code&gt; into the field - but you will have to provide your own unique version.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fcreate-azure-cdn.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Fcreate-azure-cdn.png%23shadow" alt="image showing azure CDN creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;  There is a bit of an "unpolished user experience" in the Azure Portal UI, that you should keep an eye out for, related to the option "Resource Group Location".   &lt;/p&gt;

&lt;p&gt;An example of this manifested whilst researching this article.   I had originally selected "UK South" to be the location of my resource group.   When I came to create the CDN, the option showed "Australia",  which was disabled (greyed out) and prevented me from changing it.   When I tried to create the CDN resource, the portal threw an error and wouldn't let me proceed.   &lt;/p&gt;

&lt;p&gt;Not all Azure Regions (datacentres) are equal, with some providing more services than others.  Although a CDN is inherently a global service, it still needs to be allocated to an Azure region (the resource metadata needs to be associated with something).   &lt;/p&gt;

&lt;p&gt;The problem was that only certain Azure Regions support a CDN, but this isn't necessarily apparent up-front.   &lt;/p&gt;

&lt;p&gt;Evidently, my underlying "UK" selection couldn't be matched to any of those valid regions, in the drop-down list, so "Australia" was being selected as it was alphabetically the first.&lt;/p&gt;

&lt;p&gt;As such, the fix was to create a replacement resource group, selecting a major Azure Region (I happened to use Europe West instead).  &lt;/p&gt;
&lt;/blockquote&gt;





&lt;p&gt;That's pretty much all you need to do to get a basic working result!&lt;/p&gt;

&lt;p&gt;If you navigate to the CDN-Endpoint resource in the portal and view the "overview", it will confirm to you what the Endpoint Hostname is.   If you like, you can open this link in a browser window and view your globally-distributed website right now!&lt;br&gt;
As a reminder, in this example, that CDN-serviced endpoint is as follows: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://globalscalingdemocdnendpoint.azureedge.net&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Yours will look similar to the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://&amp;lt;your CDN Endpoint Name &amp;gt;.azureedge.net&lt;/code&gt;&lt;/p&gt;






&lt;p&gt;There are a few other things worth talking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can, of course, assign your own custom domain to the CDN.   This is beyond the intended scope of this article, but you can follow this &lt;a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-map-content-to-custom-domain" rel="noopener noreferrer"&gt;Microsoft Tutorial : Add a custom domain to your Azure CDN endpoint
&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A feature I really, really like about Azure CDN&lt;/strong&gt; is that there is an option for "complete certificate management".

&lt;ul&gt;
&lt;li&gt;This means that HTTPS is handled for you and that Azure automatically provisions an SSL  certificate, for your custom domain,  &lt;strong&gt;for free&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;If, like me, you're more accustomed to having to purchase an SSL certificate, which can be really expensive!)You'll also be used to generating the actual certificate file, uploading it to a provider and remembering to renew it, etc - then you'll be delighted to hear that this expensive chore just goes away.
&lt;/li&gt;
&lt;li&gt;You can read more about this in the &lt;a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-custom-ssl?tabs=option-1-default-enable-https-with-a-cdn-managed-certificate" rel="noopener noreferrer"&gt;Tutorial: Configure HTTPS on an Azure CDN custom domain
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The only limitation I have discovered with using a CDN, is that &lt;strong&gt;you cannot assign the CDN to your Apex domain&lt;/strong&gt; (i.e. a "naked domain" without any subdomain).   For example, you cannot assign "&lt;a href="https://mysite.com" rel="noopener noreferrer"&gt;https://mysite.com&lt;/a&gt;" - it has to include a subdomain, such as "www" or "blog" etc.   &lt;/p&gt;

&lt;p&gt;If you're familiar with this subject, behind the scenes, you will be using &lt;a href="https://www.pickaweb.co.uk/kb/cname-can-use-domain/" rel="noopener noreferrer"&gt;CNAME records&lt;/a&gt; (which are domain-name-based) in your DNS control panel and not &lt;a href="https://support.dnsimple.com/articles/a-record/" rel="noopener noreferrer"&gt;A records&lt;/a&gt; (which are IP-based).   I had briefly hoped that using a &lt;a href="https://support.dnsimple.com/articles/alias-record/" rel="noopener noreferrer"&gt;DNS ALIAS record&lt;/a&gt; (a DNS workaround offered by some &lt;a href="https://www.cloudflare.com/learning/dns/glossary/what-is-a-domain-name-registrar/" rel="noopener noreferrer"&gt;registrars&lt;/a&gt;) could have been a solution, but no, it simply isn't supported.  Because of the dynamic nature of the CDN service, this does actually make sense if you think it through!   Just in case I was providing incorrect advice, &lt;a href="https://twitter.com/jimbobbennett/status/1097551343814955008" rel="noopener noreferrer"&gt;I checked with Azure Advocate Jim Bennett&lt;/a&gt; who confirmed this for me.  &lt;/p&gt;
&lt;/blockquote&gt;






&lt;p&gt;Next, in part three, we'll be looking at how you can use &lt;em&gt;Azure Front Door&lt;/em&gt; to globally distribute all parts of our application, along with one way how we can scale by using multiple instances hosted in different data centres.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part3-frontdoor/" rel="noopener noreferrer"&gt;&lt;br&gt;
   NEXT:  Read part 3&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>azure</category>
      <category>azurefunctions</category>
    </item>
    <item>
      <title>Scale your application globally with Azure Series - Part 1 - Introduction</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Thu, 24 Oct 2019 10:14:16 +0000</pubDate>
      <link>https://forem.com/siliconorchid/scale-your-application-globally-with-azure-series-part-1-introduction-1l80</link>
      <guid>https://forem.com/siliconorchid/scale-your-application-globally-with-azure-series-part-1-introduction-1l80</guid>
      <description>&lt;p&gt;This article was originally published at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part1-introduction/" rel="noopener noreferrer"&gt;blogs.siliconorchid.com&lt;/a&gt; on 25-May-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part one of a three-part series that shows you how to use Azure to distribute your application globally and ensure that your users get the best experience.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part1-introduction/" rel="noopener noreferrer"&gt;part 1 of this series&lt;/a&gt;, we talk about how to scale an application and look at a demonstration application.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part2-cdn/" rel="noopener noreferrer"&gt;part 2 of this series&lt;/a&gt;, we look at using &lt;em&gt;Azure CDN&lt;/em&gt; to distribute static content.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part3-frontdoor/" rel="noopener noreferrer"&gt;part 3 of this series&lt;/a&gt;, we look at using &lt;em&gt;Azure Front Door&lt;/em&gt; to globally distribute an application back end hosted in dual Azure Regions.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;p&gt;If your application has users located around the globe, you need to take extra architectural steps to maintain great performance, whilst presenting a consistent and seamless presence for all.&lt;/p&gt;

&lt;p&gt;In this article, we're going to discuss some of the issues that you need to consider.  &lt;/p&gt;

&lt;p&gt;We'll also introduce you to two key Azure services that may help you to address those global distribution challenges:- &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-gb/services/cdn/" rel="noopener noreferrer"&gt;Azure CDN&lt;/a&gt; - for distributing static content&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com/en-gb/services/frontdoor/" rel="noopener noreferrer"&gt;Azure Front Door&lt;/a&gt; - for distributing and accelerating all aspects of your application/service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To demonstrate these services in action, we're going to create a small demo application that comprises:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a basic web client, created using static files and published to an &lt;a href="https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website" rel="noopener noreferrer"&gt;Azure Storage Static Websites&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;a simple backend, with a single API method, which uses &lt;a href="https://azure.microsoft.com/en-gb/services/functions/" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll then:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configure services that scale the application globally &lt;/li&gt;
&lt;li&gt;show one way to create a test that demonstrates how the system reacts under a simulated load.&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Local Scaling
&lt;/h3&gt;

&lt;p&gt;Before we dive into global-scale considerations, let's just review how our computing resources can scale at a single location - a single datacentre or "Azure Region".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One requirement is that we need to be able to minimize our hosting commitments, in order to keep costs down for those times when we only have a small number of users. &lt;/li&gt;
&lt;li&gt;We also need to be able to quickly and dynamically increase our computing resources in order to serve &lt;strong&gt;significantly&lt;/strong&gt; more users, should demand change.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smartphone users, in particular, don't adhere to predictable hours of access.  When combined with unpredictable and spiky consumption patterns that often stem from social media trends and other viral behaviours, resource demands could potentially shift dramatically in a very short period of time.&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vertical scaling&lt;/strong&gt; - Back in the early days of the internet, the provisioning of more powerful servers used to be our first port-of-call to address the issue of increased computing demand. It is &lt;em&gt;still&lt;/em&gt; something we can do,  but in a majority of cases, this shouldn't be seen as your primary solution.  This is because:-

&lt;ul&gt;
&lt;li&gt;It cannot be instantly and seamlessly provisioned.  It potentially requires a period of downtime as we move from one server to a more powerful one.  Solutions such as the &lt;a href="https://azure.microsoft.com/en-gb/services/app-service/web/" rel="noopener noreferrer"&gt;&lt;em&gt;Azure WebApp Service&lt;/em&gt;&lt;/a&gt; have improved matters by making this relatively quick and easy, but there is still a change-over.
&lt;/li&gt;
&lt;li&gt;Vertical scaling has diminishing returns - an increasingly more powerful server does not yield proportional returns in performance, relative to its cost.&lt;/li&gt;
&lt;li&gt;The scope of improvement is ultimately finite if you are using a single instance.  It doesn't matter how powerful a single machine can be, the internet can and will overwhelm it.
&lt;/li&gt;
&lt;li&gt;Vertical scaling should only really be considered if your service is of some specialist type or resource, that can't distribute across multiple systems (e.g. an API that processes an extremely complicated or large set of data, such as a simulation etc, which absolutely requires access to a large pool of CPU and memory resources).    Even then, it's likely that you will require a pool of machines to service a large audience.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal Scaling&lt;/strong&gt; -  For a vast majority of scenarios the best solution is to provision multiple separate servers, so as to distribute processing in a parallel, load-balanced, configuration.

&lt;ul&gt;
&lt;li&gt;This solution offers both the greatest outright performance potential, along with the best "bang for buck".
&lt;/li&gt;
&lt;li&gt;Typically, by using a greater number of lower-specification, and therefore relatively less expensive servers, costs can usually be kept even further down (when compared to using fewer, but higher-specification servers).&lt;/li&gt;
&lt;li&gt;When combined with &lt;a href="https://en.wikipedia.org/wiki/Autoscaling" rel="noopener noreferrer"&gt;elastic/automatic scaling&lt;/a&gt;, additional servers can be brought online automatically, as demand dictates.  When server load is reduced, we can automatically de-provision the extra servers, allowing money to be saved.
&lt;/li&gt;
&lt;li&gt;Options such as &lt;em&gt;Azure WebApp Service&lt;/em&gt;, a PaaS (&lt;a href="https://en.wikipedia.org/wiki/Platform_as_a_service" rel="noopener noreferrer"&gt;Platform as a Service&lt;/a&gt;), easily provide you with these options.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Serverless architectures&lt;/strong&gt; are an evolution of horizontal scaling.

&lt;ul&gt;
&lt;li&gt;Instead of invoicing resources in terms of "instances of a server", when choosing the "consumption" pricing model, computing resources are provisioned completely dynamically, based upon a calculation of the actual time and resources used.
&lt;/li&gt;
&lt;li&gt;This architecture can be incredibly appealing as it offers the potential to make significant cost-savings, as we are no longer paying for dormant or underutilised servers.
&lt;/li&gt;
&lt;li&gt;Just like elastic-scaling, when demand rises, the cloud provider can keep allocating additional computing resource as needed.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;n.b. You have the option to provision Azure Functions on a "traditional" service plan if your circumstances require this.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  Global distribution
&lt;/h3&gt;

&lt;p&gt;We've just talked about how, with modern cloud services, we can easily scale a system, in one location, to meet demand.  But how does this work with respect to serving to a global audience?    &lt;/p&gt;

&lt;p&gt;As the physical distance between the data centre and your user(s) increases, there is an unavoidable rise in &lt;a href="https://www.keycdn.com/blog/network-bandwidth" rel="noopener noreferrer"&gt;"latency" (not to be confused with "bandwidth")&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;It doesn't matter if you have unlimited computing resources in one location, basic physics (the speed of light for signalling over very long distances) and the limitations of our technology (the fact that traffic needs to route across multiple networks to reach its destination) will combine to slow down performance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To give context to this discussion, I conducted a crude experiment to explore the impact upon latency as distance increased.  I ran dozens of &lt;a href="https://searchnetworking.techtarget.com/definition/ping" rel="noopener noreferrer"&gt;pings&lt;/a&gt; to a selection of different &lt;a href="https://www.itpro.co.uk/domain-name-system-dns/30232/what-is-a-dns-server" rel="noopener noreferrer"&gt;DNS servers&lt;/a&gt; around the world.  DNS servers were chosen, as they should be inherently fast with low latency.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I'm based in the UK, so for my first test, I found a list of servers in &lt;strong&gt;London&lt;/strong&gt;.      My average ping across the sample was &lt;strong&gt;~10ms&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pinging a selection of servers in &lt;strong&gt;New York City&lt;/strong&gt;, I averaged &lt;strong&gt;~75ms&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To demonstrate communicating with somewhere on the opposite side of the globe, I then repeated the process down under, in &lt;strong&gt;Sydney&lt;/strong&gt;, averaging a ping of &lt;strong&gt;~260ms&lt;/strong&gt;.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shouldn't be considered as an authoritative result.  Network performance is a transient thing and this was a brief one-off test.  For example, I may have been crossing a part of the internet that had a largely sleeping population at the time!.&lt;/p&gt;

&lt;p&gt;Even so, this quick experiment does indicate that a global round-trip could incur an increase in latency in the region of &lt;strong&gt;26x&lt;/strong&gt;!&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;By this point, there's a good chance that you've already guessed that &lt;strong&gt;part of the solution is to bring your application servers physically closer to your users&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;You can address this by hosting multiple instances of your service in different, geographically distributed, data centres.  Cloud providers have amazing global coverage and make it easy for you to do this. &lt;/p&gt;

&lt;p&gt;So the proposition becomes:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt;     &lt;em&gt;"how do we set up multiple geographic regions, with users automatically connecting to the fastest host, whilst still presenting a common set of endpoints?"&lt;/em&gt;      &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A:&lt;/strong&gt;    we require a service that offers &lt;a href="https://avinetworks.com/glossary/geographic-load-balancing/" rel="noopener noreferrer"&gt;"Geographic Load Balancing"&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;… and this is where  &lt;em&gt;Azure Front Door&lt;/em&gt; comes in.&lt;/p&gt;






&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Foptimus-prime.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fcoding-inspiration%2Fglobal-scaling%2Foptimus-prime.jpg%23shadow" alt="optimus prime"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's our demonstration going to be?
&lt;/h3&gt;

&lt;p&gt;For our demonstration application, we're going to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a basic web client, using static content.&lt;/li&gt;
&lt;li&gt;use &lt;em&gt;Azure Functions&lt;/em&gt; to create an &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook" rel="noopener noreferrer"&gt;HTTP-triggered&lt;/a&gt; endpoint.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;From my exhaustive research, I have discovered that there is an international shortage of APIs that calculate prime numbers, so we're going to create an &lt;em&gt;Azure Function&lt;/em&gt; that fills this shortfall.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ok, I may have completely made up the requirement in that last sentence.   &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The real reason we're going to be calculating prime numbers, is because it creates a computational load and can typically take a few moments to complete.   &lt;/p&gt;

&lt;p&gt;Later in this article, we'll be looking at one way to visually demonstrate how Azure Functions scale with demand, so we want to do something that will make the platform work a little bit harder!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You should clone the demonstration source code from the GitHub repository, here : &lt;a href="https://github.com/SiliconOrchid/GlobalScalingDemo.git" rel="noopener noreferrer"&gt;https://github.com/SiliconOrchid/GlobalScalingDemo.git&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Create an Azure Function to calculate prime numbers
&lt;/h3&gt;

&lt;p&gt;This article is not intended to be an introduction or tutorial on how to use Azure Functions.  If you are not already familiar with this subject (either how to write the code, or how to create and publish them to Azure),  please refer to my previous series &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part1-setup/" rel="noopener noreferrer"&gt;Chat using Twilio WhatsApp API, SignalR Service &amp;amp; Azure Functions&lt;br&gt;
&lt;/a&gt;.  You should also read the  &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview" rel="noopener noreferrer"&gt;Microsoft Documentation: An introduction to Azure Functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the demonstration source code, we have a Visual Studio 2019 project called &lt;code&gt;GlobalScalingDemo.Function&lt;/code&gt; and specifically we are interested in the code for the Function itself which is &lt;code&gt;....\GlobalScalingDemo\src\GlobalScalingDemo.Function\CalculatePrimeNumberFunction.cs&lt;/code&gt;: &lt;/p&gt;

&lt;p&gt;The complete source code for this file looks like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;GlobalScalingDemo.Function&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CalculatePrimeNumberFunction&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CalculatePrimeNumber"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;primeNumberRangeSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CountPrimeNumbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primeNumberRangeSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;currentHostingLocation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CurrentHostingLocation"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkObjectResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Hello, there are &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; prime numbers in the range 0 - &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;primeNumberRangeSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.   This result was calculated in &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;currentHostingLocation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="nf"&gt;CountPrimeNumbers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rangeSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ParallelEnumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rangeSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, I would bring your attention to a couple of things:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The variable &lt;code&gt;primeNumberRangeSize&lt;/code&gt; is used to define a range of numbers to check for prime numbers.    This effectively controls the amount of processing work that the Function needs to undertake.

&lt;ul&gt;
&lt;li&gt;If you want to increase the amount of time the Function needs to run for, put a larger number in here.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The variable &lt;code&gt;currentHostingLocation&lt;/code&gt; is used to obtain a label declaring the location that the code is running from (e.g. "Local",  "London", or "California").

&lt;ul&gt;
&lt;li&gt;This is added to the textual output of the Function.
&lt;/li&gt;
&lt;li&gt;Later in the article, when we come to test the system, this will help us to easily recognise from which data centre the function being executed.
&lt;/li&gt;
&lt;li&gt;This configuration information will need to be provided either locally in the &lt;code&gt;local.settings.json&lt;/code&gt; or defined in Azure for each instance of the Function App.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Don't worry too much about the sample code itself.  The point of this exercise is to create an arbitrary and artificial CPU load, so slow code is fine!&lt;/li&gt;

&lt;/ul&gt;






&lt;h3&gt;
  
  
  Create a basic web client
&lt;/h3&gt;

&lt;p&gt;Our web client is intended to be as simple as possible, so comprises:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a single html page :  &lt;code&gt;...\GlobalScalingDemo\src\GlobalScalingDemo.Web\wwwroot\index.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a simple JavaScript file :  &lt;code&gt;...\GlobalScalingDemo\src\GlobalScalingDemo.Web\wwwroot\js\client.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;minor cosmetic components, such as a &lt;em&gt;.css&lt;/em&gt; file and use of &lt;em&gt;Bootstrap&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose of the client app is simply to trigger an HTTP request to our backend, when the page is first loaded.   &lt;/p&gt;

&lt;p&gt;When a response is received, a default message that reads &lt;code&gt;Waiting for server...&lt;/code&gt; is replaced with the message returned from the back end.&lt;/p&gt;

&lt;p&gt;The JavaScript client code looks like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:7071/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// include trailing slash&lt;/span&gt;
&lt;span class="c1"&gt;//const serviceEndpoint = 'https://{your function app hostname}.azurewebsites.net/api/';&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverApiMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CalculatePrimeNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// do not include any slashes&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;messageElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;messageElement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceEndpoint&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;serverApiMethod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;messageElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="nx"&gt;messageElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failure connecting to server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;p&gt;Next, in part two, we'll be looking at how to use &lt;em&gt;Azure CDN&lt;/em&gt; to globally distribute static content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/global-scaling/part2-cdn/" rel="noopener noreferrer"&gt;&lt;br&gt;
   NEXT:  Read part 2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>azure</category>
      <category>azurefunctions</category>
    </item>
    <item>
      <title>Chat using Twilio WhatsApp API, SignalR Service &amp; Azure Functions Series - Part 3  - Cloud Deployment</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Wed, 23 Oct 2019 18:36:25 +0000</pubDate>
      <link>https://forem.com/twilio/chat-using-twilio-whatsapp-api-signalr-service-azure-functions-series-part-3-cloud-deployment-5n1</link>
      <guid>https://forem.com/twilio/chat-using-twilio-whatsapp-api-signalr-service-azure-functions-series-part-3-cloud-deployment-5n1</guid>
      <description>&lt;p&gt;This article was originally published  at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud/" rel="noopener noreferrer"&gt;blogs.siliconorchid.com&lt;/a&gt; on 19-May-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part three of a three-part series that shows you a way to combine several serverless technologies, creating an online chat system that sends messages between users of WhatsApp and a real-time web app.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com//post/coding-inspiration/whatsapp-signalrservice-azurefunction/part1-setup" rel="noopener noreferrer"&gt;part 1 of this series&lt;/a&gt;, we introduce you to the scenario and walk through the configuration of an entire solution, ready for local testing.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com//post/coding-inspiration/whatsapp-signalrservice-azurefunction/part2-look-at-code" rel="noopener noreferrer"&gt;part 2 of this series&lt;/a&gt;, we look at the various pieces of code that make up our solution in detail.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com//post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud" rel="noopener noreferrer"&gt;part 3 of this series&lt;/a&gt;, we walk through the additional steps needed to setup and deploy the solution to the cloud.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fclipart%2Fclipart-plane-clouds.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fclipart%2Fclipart-plane-clouds.jpg%23shadow" alt="image showing broadcast studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to the cloud
&lt;/h2&gt;

&lt;p&gt;In the previous articles of this series, we've tested the system locally and reviewed the code.   In this final part of the series, we're now going to deploy the system to the cloud.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the first article of this series, we created and configured the &lt;em&gt;SignalR Service&lt;/em&gt; in Azure.  That's already set to go, so there is nothing further we need to do with this.&lt;/li&gt;
&lt;li&gt;We need to create and publish our &lt;em&gt;Azure Functions&lt;/em&gt; to an &lt;em&gt;App Service&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;We need to create an &lt;em&gt;Azure Storage&lt;/em&gt; account and publish the front-end web app to a &lt;em&gt;Static Website&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;






&lt;h3&gt;
  
  
  Create the Azure Function App
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Return to the Azure Portal and navigate to the &lt;em&gt;Resource Group&lt;/em&gt; that we created in Part One.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Add&lt;/strong&gt; button (at the top) to add a new resource.&lt;/li&gt;
&lt;li&gt;Type "Function App" into the search bar and select "Function App" from the list (it should be at the top).&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Create&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;A "Function App Create" &lt;em&gt;blade&lt;/em&gt; will now show.  We need to provide some information:-

&lt;ul&gt;
&lt;li&gt;Provide an app name - I used "WhatsAppChatFunction.azurewebsites.net" for this demo, but the name must be unique - so you will need to use your own name.&lt;/li&gt;
&lt;li&gt;Select your existing resource group from the dropdown.&lt;/li&gt;
&lt;li&gt;The form will prompt you to create a new &lt;em&gt;Storage Account&lt;/em&gt;.   This will be used to store the &lt;em&gt;Build Artefacts&lt;/em&gt; (compiled code, etc.) that we will publish.   These files will be used when provisioning instances of our Function.   The automatically generated name is fine.&lt;/li&gt;
&lt;li&gt;Click the Create button - Azure will now provision the &lt;em&gt;Function App&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fsetup-azurefunction.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fsetup-azurefunction.png%23shadow" alt="image showing azure function creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  Configure the Azure Function App
&lt;/h3&gt;

&lt;p&gt;With the Azure Function resource provisioned, select it so we can edit its values.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the &lt;strong&gt;Platform Features&lt;/strong&gt; (a tab along the top of the screen) to view an extended view of all the features we have access to.&lt;/li&gt;
&lt;li&gt;Under the "General Settings" heading,  click the &lt;strong&gt;Configuration&lt;/strong&gt; option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Ffunction-navigate-to-configure.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Ffunction-navigate-to-configure.png%23shadow" alt="image showing azure function creation"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;We need to add some &lt;strong&gt;Application Settings&lt;/strong&gt;.   You'll see that the list of settings already contains some values - we can completely ignore these.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Click the &lt;strong&gt;New Application Setting&lt;/strong&gt; button to add a new key.   You will need to repeat this process for each of the four keys that we need to add.   To make things easier, you can copy both the keys and their values directly from the  &lt;code&gt;local.settings.json&lt;/code&gt; configuration file in the solution. These are the four we need:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AzureSignalRConnectionString&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TwilioAccountSid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TwilioAuthToken&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TwilioWhatsAppPhoneNumber&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note, that it's ok to enter these keys directly into the Azure application setting list.  There is a potentially confusing inconsistency, in that the  &lt;code&gt;local.settings.json&lt;/code&gt; config file has these nested under a key called &lt;code&gt;Values&lt;/code&gt; - but there is no need to include this when copying the values into Azure (i.e. you do not need to make the key &lt;code&gt;Values_TwilioAccountSid&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Ffunction-configuration-settings.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Ffunction-configuration-settings.png%23shadow" alt="image showing azure function creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Deploy Azure Functions to Azure
&lt;/h3&gt;

&lt;p&gt;Although it's possible to write functions directly using the Azure Portal, we've already written all of our code in Visual Studio, so we'll be deploying our code from there. &lt;/p&gt;

&lt;p&gt;With the Azure Function resource provisioned, configured and ready to go  - let's go to Visual Studio so we can publish from there:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Right-click on the &lt;code&gt;WhatsAppSignalRDemo.Function&lt;/code&gt; project file.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Publish&lt;/strong&gt; from the menu (usually near the top).&lt;/li&gt;
&lt;li&gt;In the "Pick a publish target" dialogue, choose "Select Existing" [Azure App Service] and then click the &lt;strong&gt;Publish&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;A new dialogue will appear, allowing us to choose the &lt;em&gt;Resource Group&lt;/em&gt; and then the &lt;em&gt;Function Resource&lt;/em&gt; that you created earlier.  I named mine &lt;code&gt;WhatsAppChatFunction&lt;/code&gt; but you will have to choose your own.

&lt;ul&gt;
&lt;li&gt;Ignore the "deployment slot" item and just keep the "Function Resource" highlighted.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;OK&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Visual Studio will now go about deploying your code.&lt;/p&gt;

&lt;p&gt;If you need to make a change to your code and re-publish,  your "publish profile" will be retained on subsequent visits here, meaning you simply need to click the &lt;strong&gt;Publish&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  Create Azure Storage Static Website
&lt;/h3&gt;

&lt;p&gt;If you have purely static content, such as the website in this demo  (or my blogging website), then there is a way to radically slash your hosting costs using Azure.&lt;/p&gt;

&lt;p&gt;Your instinct may lead you down the path of creating resources such as "Web Apps", but there is a new feature in Azure which means you don't need to do this!&lt;/p&gt;

&lt;p&gt;As of Dec 2018, &lt;a href="https://azure.microsoft.com/en-us/blog/static-websites-on-azure-storage-now-generally-available/" rel="noopener noreferrer"&gt;Azure Storage now offers static website hosting&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From your resource group, click the &lt;strong&gt;Add&lt;/strong&gt; to create a new resource.&lt;/li&gt;
&lt;li&gt;Search for "storage" and select "Storage Account".&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create&lt;/strong&gt; to start creating a new *Storage Account *resource.&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;Create Storage account&lt;/em&gt; screen:-

&lt;ul&gt;
&lt;li&gt;Select your existing resource group.&lt;/li&gt;
&lt;li&gt;Provide a unique name for the storage account.  I chose "whatsappwebsitestorage", but you will need to provide your own.&lt;/li&gt;
&lt;li&gt;Choose a nearby location.&lt;/li&gt;
&lt;li&gt;Leave "Performance" as "Standard".&lt;/li&gt;
&lt;li&gt;Leave "Access Tier" as "Hot"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Click &lt;strong&gt;Review and Create&lt;/strong&gt; to proceed.&lt;/li&gt;

&lt;li&gt;You will be prompted to review your selections, but assuming there is no problem, carry on and create the new Storage Account and wait a few moments for the resource to be provisioned.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Ffunction-configuration-settings.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Ffunction-configuration-settings.png%23shadow" alt="image showing azure function creation"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;With the Storage Account resource created, find the option "Static Website" under the heading "Settings".   The Azure portal presents a slightly overwhelming number of options, so check the screenshot below if you can't spot the option easily:-&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fstorage-select-staticwebsite.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fstorage-select-staticwebsite.png%23shadow" alt="image showing azure function creation"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;In the dialogue that appears:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch the mode "static website" to "enable".&lt;/li&gt;
&lt;li&gt;In the text-entry for "Index document name" enter "index.html".&lt;/li&gt;
&lt;li&gt;You can also enter "index.html" into the "Error Document Path" option, simply as a default redirect back to the homepage, because we haven't created an actual default error page.   If you were to improve the system, you could make a page such as "404.html" to contain an error message.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fstorage-enable-website.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fstorage-enable-website.png%23shadow" alt="image showing azure function creation"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Inside the &lt;em&gt;Blobs&lt;/em&gt; section of this &lt;em&gt;Storage Resources&lt;/em&gt;, the Azure Portal will have created a specially named &lt;em&gt;Container&lt;/em&gt; called "&lt;strong&gt;Web$&lt;/strong&gt;".    You can look at this yourself by:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clicking "Overview" to take you back to the main dashboard page of the &lt;em&gt;Storage Resource&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Click the prominent "Blobs" tile which will be displayed on the main dashboard (alongside other tiles "Files", "Tables" and "Queues".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can click on this new "$Web" container, which will list any blobs that are stored inside.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the terminology is not familiar to you, think of a "Storage Account" as a kind of disk drive, a "Container" as a kind of "folder", and "Blobs" as the actual files.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You could at this point, use the Azure Portal to upload files individually, but this will be slow and frustrating.  So instead, we'll use a different tool for the job, which we'll talk about in the next section.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before we leave this Azure Portal page, make a note of the "Primary Endpoint" setting which will be displayed.   This is the URL we need to visit in our browser.

&lt;ul&gt;
&lt;li&gt;The Url will be different in your own project, but in my case, the Url is &lt;code&gt;https://whatsappwebsitestorage.z6.web.core.windows.net/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  Upload the static site files into Azure Storage
&lt;/h3&gt;

&lt;p&gt;When it comes to deploying files to an &lt;em&gt;Azure Storage&lt;/em&gt; account, there are a number of ways we could tackle the problem.  For example, in a more complicated &lt;a href="https://en.wikipedia.org/wiki/Continuous_delivery" rel="noopener noreferrer"&gt;&lt;em&gt;Continuous Deployment&lt;/em&gt;&lt;/a&gt; system, we could get our build process to automatically drop files into the &lt;em&gt;Storage Container&lt;/em&gt; for us.&lt;/p&gt;

&lt;p&gt;For the purpose of this demonstration, we want to keep it simple and convenient to use.   &lt;/p&gt;

&lt;p&gt;We'll be using a Windows utility created by Microsoft called the "Azure Storage Explorer".   Please follow the instruction in the following links:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can &lt;a href="https://azure.microsoft.com/en-gb/features/storage-explorer/" rel="noopener noreferrer"&gt;download "Azure Storage Explorer" from here&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;You can find an &lt;a href="https://docs.microsoft.com/en-us/azure/vs-azure-tools-storage-manage-with-storage-explorer?tabs=windows" rel="noopener noreferrer"&gt;Azure Storage Explorer : Getting Started guide here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have followed the instructions, have installed "Azure Storage Explorer" and have provided your Azure credentials, you should be able to view your Azure Storage resources.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the tree structure, locate and click on the &lt;code&gt;$web&lt;/code&gt; &lt;em&gt;Blob Container&lt;/em&gt;.

&lt;ul&gt;
&lt;li&gt;Note, you will also see a &lt;em&gt;Storage Account&lt;/em&gt; that we created early in this demo, which is used to store the contents of our &lt;em&gt;Azure Function&lt;/em&gt; code … make sure you don't get mixed up as to which &lt;em&gt;Storage Account&lt;/em&gt; you should be looking at!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;



&lt;p&gt;We're just about to copy the files to the cloud, but before we do that, we need to update our JavaScript configuration to link to the endpoints of our published Azure Functions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return to the file &lt;code&gt;...\WhatsAppSignalRDemo\src\WhatsAppSignalRDemo.Web\wwwroot\js\chat.js&lt;/code&gt; and open it for editing.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change the value of the constant &lt;code&gt;serviceEndpoint&lt;/code&gt;, from &lt;code&gt;http://localhost:7071/api/&lt;/code&gt; to be the Url of your Azure Function Endpoint.    You will have to use your own, but for me, this value would be &lt;code&gt;https://whatsappchatfunction.azurewebsites.net/api/&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using a regular &lt;em&gt;Windows File Explorer&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Navigate to the folder. &lt;code&gt;....\WhatsAppSignalRDemo\src\WhatsAppSignalRDemo.Web\wwwroot&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;copy or drag-and-drop all of the files in the &lt;code&gt;wwwroot&lt;/code&gt; folder into the main panel of the &lt;em&gt;Container&lt;/em&gt; within the "Azure Storage Explorer".&lt;/li&gt;
&lt;li&gt;Note, that the contents of &lt;code&gt;wwwroot&lt;/code&gt; is just static content, there is no complication or cleverness required - just a simple file copy.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fazure-explorer-locate-container.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fazure-explorer-locate-container.png%23shadow" alt="image showing azure storage explorer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are alternative ways to deploy a static website - for example, &lt;a href="https://code.visualstudio.com/tutorials/static-website/getting-started" rel="noopener noreferrer"&gt;this tutorial shows you how to deploy directly from Visual Studio Code&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;






&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fclipart%2Fclipart-finishline.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fclipart%2Fclipart-finishline.jpg%23shadow" alt="image showing racing track finish line"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The finish line is in sight
&lt;/h2&gt;

&lt;p&gt;We've already done a lot of work by this point, but there are still a final few configuration changes that we need to make, in order for our published system to be able to work.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  Update CORS on the Azure Function
&lt;/h3&gt;

&lt;p&gt;The Azure Function APIs have &lt;a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" rel="noopener noreferrer"&gt;CORS&lt;/a&gt; restrictions that will stop our published Web App from being able to use them.   We need to add our new Web App to a whitelist.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the Azure Portal, navigate through your resource group to you Azure Function App.   In this demo, I called mine &lt;code&gt;WhatsAppChatFunction&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Locate and click the "CORS" option  which is found under the heading "API"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fazure-function-cors.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fazure-function-cors.png%23shadow" alt="image showing azure function cors selector"&gt;&lt;/a&gt;&lt;/p&gt;





&lt;ul&gt;
&lt;li&gt;Add a new entry to the list of allowed hosts, to include the URL to your static website.   For my demo, this host would be &lt;code&gt;https://whatsappwebsitestorage.z6.web.core.windows.net&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fazure-function-cors-addentry.png%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2F%2Fimages%2Fcoding-inspiration%2Fwhatsapp-signalrservice-azurefunction%2Fazure-function-cors-addentry.png%23shadow" alt="image showing azure function cors add new entry"&gt;&lt;/a&gt;&lt;/p&gt;






&lt;h3&gt;
  
  
  Update Twilio WhatsApp API callback
&lt;/h3&gt;

&lt;p&gt;We're almost there now.   The final task we need to perform is to update the API endpoint stored in the Twilio dashboard and replace the callback with the URL of our published Azure Function.   &lt;/p&gt;

&lt;p&gt;You will need to use your own address, but for me, this was &lt;code&gt;https://whatsappchatfunction.azurewebsites.net/api/ReceiveWhatsAppMessage&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your system will now be good to go!&lt;/p&gt;






&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Messaging API's.&lt;/strong&gt; The decision to use one messaging service provider over another is largely a market decision.   There are many companies who can provide solutions, so you will ultimately be comparing service costs against support and reputation.   Twilio have emerged as a market leader.  Their ease of use and great documentation makes them a compelling option.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Serverless Computing.&lt;/strong&gt;  We've presented the use of Azure in this series, but disregarding your actual preference of cloud provider (i.e.  putting Google Cloud, AWS, Azure etc on a level playing field), the case to use serverless functions instead of more conventional hosting platforms (such as regular VMs, or an Azure WebApp etc), is not necessarily a clear-cut decision.    That dreaded cliche of "it depends" still comes into play.&lt;/p&gt;

&lt;p&gt;In scenarios where your service is not fully utilising your host platform, the cost savings of using serverless compute can be hugely significant.  &lt;/p&gt;

&lt;p&gt;An example of such a solution [that could make significant cost savings] could be an application that serves a domestic audience predominantly during business hours.  That system would have a host machine that is effectively only being used for a third of the day and is sat largely dormant at other times.   This is a waste of money.&lt;/p&gt;

&lt;p&gt;In contrast, with a heavily-used system with a global 24 / 7 usage pattern, you'll need to pay very close attention to the costs.   Similarly, if stable and predictable costs are important, serverless computing may not be for you if choosing the &lt;em&gt;consumption model&lt;/em&gt; (instead choose the more familiar &lt;em&gt;hosted model&lt;/em&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Where next?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;These articles are intended for someone taking their first steps with these technologies&lt;/strong&gt;.  They are not attempting to present an architecture that you should use in a commercial product.   There are aspects that we have not covered, such as security and resilience, that you should seek to explore further.&lt;/p&gt;

&lt;p&gt;There are some standout issues that I would highlight, which could be the subject of your next steps to improve this project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The UI and API's have no authentication.   As presented they are wide-open to potential abuse (for example, there is nothing to stop you sending messages to the &lt;code&gt;SendWhatsAppMessageFunction&lt;/code&gt;). &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The individual functions communicate by calling HTTP endpoints.    If there is a transient problem - e.g. the Function has not been used for a while and has "gone cold" (non-responsive) - that communication attempt could be lost.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A far better way&lt;/strong&gt; to process tasks could be to use services such as Azure &lt;a href="https://docs.microsoft.com/en-us/azure/event-hubs/" rel="noopener noreferrer"&gt;Event Hubs&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/azure/storage/queues/storage-queues-introduction" rel="noopener noreferrer"&gt;Queues&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview" rel="noopener noreferrer"&gt;Service Bus&lt;/a&gt;, or &lt;a href="https://azure.microsoft.com/en-gb/blog/introducing-azure-event-grid-an-event-service-for-modern-applications/" rel="noopener noreferrer"&gt;Event Grid&lt;/a&gt;.  Also consider investigating the recently introduced &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview" rel="noopener noreferrer"&gt;&lt;em&gt;Azure Durable Functions&lt;/em&gt;&lt;/a&gt; which accommodate the concept of chaining-together workflows of separate functions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For example, in this demo the &lt;code&gt;BroadcastSignalRMessageFunction&lt;/code&gt; messages &lt;code&gt;SendWhatsAppMessageFunction&lt;/code&gt; by HTTP.   A more resilient improvement would be to recode the &lt;code&gt;BroadcastSignalRMessageFunction&lt;/code&gt; so that it adds a message to a queue.  Subsequently, we could change  &lt;code&gt;SendWhatsAppMessageFunction&lt;/code&gt; so that it is a queue-triggered function.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;



&lt;p&gt;For now, I'll leave you with some other ideas of where you could take this project next:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SignalR Authentication, to prevent completely open and insecure access.&lt;/li&gt;
&lt;li&gt;JavaScript automatic reconnect to SignalR if dropped (using &lt;code&gt;connection.onclose()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Authenticated pages, where "username" can be automatically picked up from logged-in user instead of manually typing it.&lt;/li&gt;
&lt;li&gt;Define the service endpoints used by Client into WebApp configuration (i.e. don't just hardcode them into JavaScript file, pass the endpoint through from server).&lt;/li&gt;
&lt;li&gt;Explore using &lt;a href="https://azure.microsoft.com/en-gb/services/cdn/" rel="noopener noreferrer"&gt;Azure CDN&lt;/a&gt; or &lt;a href="https://azure.microsoft.com/en-gb/services/frontdoor/" rel="noopener noreferrer"&gt;Azure FrontDoor&lt;/a&gt; for ways to improve your static website (e.g. with your own custom domain, using free SSL).&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-quickstart-azure-functions-csharp" rel="noopener noreferrer"&gt;Microsoft: SignalR QuickStart Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;Microsoft:  Tutorial: Get started with ASP.NET Core SignalR
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/aspnet/azure-signalr-service-now-supports-asp-net/" rel="noopener noreferrer"&gt;Azure SignalR Service now supports ASP.NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Azure/azure-functions-host/issues/3736" rel="noopener noreferrer"&gt;GitHub Issue : Dependency Injection support for Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection" rel="noopener noreferrer"&gt;Microsoft : Use dependency injection in .NET Azure Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/azure/using-entity-framework-with-azure-functions-50aa"&gt;DEV.to : Using Entity Framework with Azure Functions
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tomfaltesek.com/azure-functions-local-settings-json-and-source-control/" rel="noopener noreferrer"&gt;Azure Functions local.settings.json Secrets and Source Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-app-settings" rel="noopener noreferrer"&gt;App settings reference for Azure Functions
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#local-settings-file" rel="noopener noreferrer"&gt;Work with Azure Functions Core Tools - Local Settings File&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tomfaltesek.com/azure-functions-local-settings-json-and-source-control/" rel="noopener noreferrer"&gt;Tom Faltesek : Azure Functions local.settings.json Secrets and Source Control
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;






&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;No third party (i.e. Microsoft or Twilio) compensate me for my promotion of their services.     However, my partner Layla Porter is an employee of Twilio Inc,  in the capacity of a developer evangelist (and I very occasionally hang out with other Twilio evangelists) so I have a strong bias to recommend their services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thumbs Up
&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.linkedin.com/in/coreylweathers/" rel="noopener noreferrer"&gt;Corey Weathers&lt;/a&gt; for hopping onto a video call with me to discuss parts of this project.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://www.linkedin.com/in/layla-porter-12433b3b/" rel="noopener noreferrer"&gt;Layla Porter&lt;/a&gt; for being my document reviewer.  I don't have a team to back me up and sometimes you need another set of eyes!&lt;/p&gt;

</description>
      <category>signalr</category>
      <category>twilio</category>
      <category>azurefunctions</category>
    </item>
    <item>
      <title>Chat using Twilio WhatsApp API, SignalR Service &amp; Azure Functions Series - Part 2  -  The Code</title>
      <dc:creator>Jim Mc̮̑̑̑͒G</dc:creator>
      <pubDate>Wed, 23 Oct 2019 18:36:01 +0000</pubDate>
      <link>https://forem.com/twilio/chat-using-twilio-whatsapp-api-signalr-service-azure-functions-series-part-2-the-code-32km</link>
      <guid>https://forem.com/twilio/chat-using-twilio-whatsapp-api-signalr-service-azure-functions-series-part-2-the-code-32km</guid>
      <description>&lt;p&gt;This article was originally published  at &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part2-look-at-code/" rel="noopener noreferrer"&gt;blogs.siliconorchid.com&lt;/a&gt; on 19-May-2019&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is part two of a three-part series that shows you a way to combine several Serverless technologies, creating an online chat system that sends messages between users of WhatsApp and a real-time web app.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part1-setup" rel="noopener noreferrer"&gt;part 1 of this series&lt;/a&gt;, we introduce you to the scenario and walk through the configuration of an entire solution, ready for local testing.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part2-look-at-code" rel="noopener noreferrer"&gt;part 2 of this series&lt;/a&gt;, we look at the various pieces of code that make up our solution in detail.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud" rel="noopener noreferrer"&gt;part 3 of this series&lt;/a&gt;, we walk through the additional steps needed to set up and deploy the solution to the cloud.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h2&gt;
  
  
  Let's look at the code
&lt;/h2&gt;

&lt;p&gt;In the previous article, we spent a while looking at how to set up and run the project. Let's look at some code next!&lt;/p&gt;

&lt;p&gt;If you haven't done so already, you should &lt;a href="https://github.com/SiliconOrchid/WhatsAppSignalRDemo" rel="noopener noreferrer"&gt;clone this repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our Visual Studio project is comprised of two projects - an Azure Function project (&lt;code&gt;WhatsAppSignalRDemo.Function&lt;/code&gt;) and an ASP.NET Core Web Application (&lt;code&gt;WhatsAppSignalRDemo.Web&lt;/code&gt;).  &lt;/p&gt;

&lt;p&gt;Parts of the code in this demo originated from the example provided in the &lt;a href="https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-quickstart-azure-functions-csharp" rel="noopener noreferrer"&gt;Microsoft SignalR Quickstart guide&lt;/a&gt;, so this would be a recommended read.&lt;/p&gt;

&lt;p&gt;Another recommended resource from Microsoft is the tutorial  &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;Get started with ASP.NET Core SignalR&lt;br&gt;
&lt;/a&gt; - whilst it doesn't cover &lt;em&gt;SignalR Service&lt;/em&gt; specifically, it does show you how to write a chat client using "Hosted SignalR" and introduces most of the concepts you need to know.&lt;/p&gt;


&lt;h2&gt;
  
  
  The web client project
&lt;/h2&gt;

&lt;p&gt;In this demo, we've gone out of our way to make the Web Client as simple as possible.   &lt;/p&gt;

&lt;p&gt;The Web App consists purely of static web content.  There is no MVC, Razor, Controllers nor any other server-side logic.  Instead, we have a single &lt;code&gt;index.html&lt;/code&gt; and a small amount of accompanying static content (such as JavaScript and CSS).&lt;/p&gt;

&lt;p&gt;The purpose of doing this is twofold:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We don't want to distract you with any implementation details that are specific to a UI framework (whether that be ASP.NET MVC or one of the many JavaScript frameworks/libraries such as Angular or React).    We want to focus upon the essentials, meaning that we have a very basic UI and minimal code (which includes any code needed to interact with SignalR).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We specifically wanted to have purely static web content (i.e. just &lt;code&gt;.html&lt;/code&gt;, &lt;code&gt;.css&lt;/code&gt; and &lt;code&gt;.js&lt;/code&gt; files) so that, later in the article when we come to publish the Web App to the cloud, we can show you how to use &lt;em&gt;Azure Storage Static Websites&lt;/em&gt;.  One considerable benefit to this approach is that the hosting cost is a fraction of what it could otherwise be. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For development and testing purposes, we will still need a development web server, so we have opted to use an "ASP.NET Core Web Application" project as a starting point.    This is still essentially the same as you would find for a conventional &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-2.2" rel="noopener noreferrer"&gt;MVC&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-2.2&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;RazorPages&lt;/a&gt; project, except it has been pared back to the absolute minimum.    &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For example, there is no longer a &lt;code&gt;Pages&lt;/code&gt; folder (for &lt;code&gt;.cshtml&lt;/code&gt; views, in general) and none of the usual MVC constructs (such as &lt;code&gt;_Layout.cshtml&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;Static content has no need for configuration, so &lt;code&gt;appsettings.json&lt;/code&gt; has been discarded.&lt;/li&gt;
&lt;li&gt;The template includes the &lt;em&gt;JQuery&lt;/em&gt; library, but that is not being used so has been removed from the &lt;code&gt;wwwroot/lib&lt;/code&gt; folder.
&lt;/li&gt;
&lt;li&gt;We have retained the &lt;code&gt;site.css&lt;/code&gt; stylesheet and the &lt;em&gt;Bootstrap&lt;/em&gt; library.&lt;/li&gt;
&lt;li&gt;We needed to include the  &lt;em&gt;&lt;a href="https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-2.2" rel="noopener noreferrer"&gt;SignalR Javascript Client&lt;/a&gt;&lt;/em&gt; library.  You can find that in &lt;code&gt;wwwroot/lib/signalr&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Moving on to the small amount of C# code in this project:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Program.cs&lt;/code&gt; remains untouched&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Startup.cs&lt;/code&gt; has significant changes:-

&lt;ul&gt;
&lt;li&gt;Pretty much the entire content that was provided by the new-project Template has been discarded.&lt;/li&gt;
&lt;li&gt;We need to explicitly tell ASP.NET core to use the &lt;code&gt;index.html&lt;/code&gt; and we need to explicitly tell it to serve up static content.    The revised code looks like this:-
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Web&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IHostingEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;DefaultFilesOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultFilesOptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultFileNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDefaultFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h3&gt;
  
  
  Javascript code on the client
&lt;/h3&gt;

&lt;p&gt;The script that runs in the browser can be located here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;….\WhatsAppSignalRDemo\src\WhatsAppSignalRDemo.Web\wwwroot\js\chat.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We briefly looked at this piece of code earlier in the article, when we checked that the API endpoint &lt;code&gt;serviceEndpoint&lt;/code&gt; was correctly defined.&lt;/p&gt;

&lt;p&gt;This particular piece of code started off life as being a copy of the sample code presented in the aforementioned Microsoft chat-application Tutorial &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;Get started with ASP.NET Core SignalR&lt;br&gt;
&lt;/a&gt;, so any similarities are due to this.   That original sample was intended to work with &lt;em&gt;Hosted SignalR&lt;/em&gt;,  so along with other customisations, we've adapted the code to work with our &lt;em&gt;SignalR Service&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As an overview, the code uses the Microsoft SignalR JavaScript library to create a “connection” object (named &lt;code&gt;signalRconnection&lt;/code&gt; in this demo). The MS library does all the heavy-lifting work for us.  &lt;/p&gt;



&lt;p&gt;The code found in &lt;code&gt;....\WhatsAppSignalRDemo\src\WhatsAppSignalRDemo.Web\wwwroot\js\chat.js&lt;/code&gt; does the following:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines a handful of configuration values, identifying the API endpoint of the service and a specific API name to be used for broadcasting messages.&lt;/li&gt;
&lt;li&gt;Calls the &lt;code&gt;signalR.HubConnectionBuilder()&lt;/code&gt; function to establish a connection to either a SignalR Hub or SignalR Service (we use the same JavaScript client, regardless of how the backend is implemented).

&lt;ul&gt;
&lt;li&gt;The "HubConnectionBuilder" code &lt;strong&gt;is hardcoded to look for an API which is called &lt;code&gt;negotiate&lt;/code&gt; by convention.  We need to provide this in our Azure Function backend.&lt;/strong&gt;  This part is quite an important point to be aware of.   You can read about it on &lt;a href="https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-concept-serverless-development-config" rel="noopener noreferrer"&gt;Microsoft: Azure Functions development and configuration with Azure SignalR Service
&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;signalRconnection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;signalR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HubConnectionBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceEndpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;We have an event listener, which reacts to messages being broadcast from the SignalR Service.   When they arrive, the message object is separated into component parts (a username and user message) and simply appended to a UI list:-
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;signalRconnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signalRTargetGroupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;broadcastMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;broadcastMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;encodedMsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;broadcastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt; : &amp;amp;quot;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;broadcastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;quot;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;li&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encodedMsg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;messagesList&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;We have a startup handler, that is responsible for updating the UI when the client established a connection to the SignalR Service (it enables the "send" button and removes the "please wait" message):-
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;signalRconnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendButton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connectingMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h3&gt;
  
  
  With SignalR Service, instead of "invoking" Hub methods, we send HTTP messages to a restful API endpoint.
&lt;/h3&gt;

&lt;p&gt;The way we interact with &lt;em&gt;SignalR Service&lt;/em&gt; is different from traditional &lt;em&gt;Hosted SignalR&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;Previously, with &lt;em&gt;Hosted SignalR&lt;/em&gt; we would have a server-side "Hub" that contained [C# proxy] methods.     In our client-side [JavaScript] code, we would "invoke" these methods using the &lt;code&gt;SignalR&lt;/code&gt; connection object itself.    &lt;/p&gt;

&lt;p&gt;Such a piece of code would have looked like this (where "SendMessage" would be a corresponding  C# method of the Hub class):-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;signalRconnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SendMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast, when using the "serverless mode" of &lt;em&gt;SignalR Service&lt;/em&gt;, we still have the concept of Hubs (e.g. will still need to identify a "HubName" to group connections together)  - but those Hubs are generic and no longer contain our [C# proxy] code.  This means that there is no longer anything to "invoke".  &lt;/p&gt;

&lt;p&gt;Instead, we need our [web] client to POST separate HTTP messages to an API that we provide ourselves.   In our serverless architecture, this would be another HTTP-triggered Azure Function.     &lt;/p&gt;

&lt;p&gt;Any APIs we create that provide SignalR functionality will have a connection string tying that Azure Function to our SignalR Service.  We'll talk about the C# API code itself, later in the series.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This change is worth underlining because, at the time of writing, I felt that this was slightly ambiguous in the MS documentation.   When researching this article, I wasn't confident that I should have been abandoning the use of the &lt;code&gt;invoke&lt;/code&gt; function of the &lt;code&gt;signalRConnection&lt;/code&gt; object - having to make separate REST calls felt like I awkwardly working around their solution.&lt;/p&gt;

&lt;p&gt;To be sure that I have presented the correct solution in this article, &lt;a href="https://twitter.com/bradygaster/status/1119288198125903872" rel="noopener noreferrer"&gt;I asked Microsoft Cloud Advocates directly&lt;/a&gt;. Thank you &lt;a href="http://www.bradygaster.com/" rel="noopener noreferrer"&gt;Brady Gaster&lt;/a&gt; and &lt;a href="https://anthonychu.ca/" rel="noopener noreferrer"&gt;Anthony Chu&lt;/a&gt; for clarifying that I was indeed, doing it right!!     &lt;/p&gt;

&lt;p&gt;Something interesting that they pointed out, was that they are currently looking for a way to harmonise the experience, so there is a good chance that this current approach may change again in the near future.  &lt;/p&gt;
&lt;/blockquote&gt;





&lt;p&gt;Returning to the code in this demo, the need to make separate HTTP call(s) represents the most significant shift away from the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt; Microsoft sample code&lt;/a&gt; that this file was originally based upon.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The following encapsulates a vanilla JavaScript &lt;code&gt;fetch&lt;/code&gt; request into the function &lt;code&gt;postMessage&lt;/code&gt;.   In the future, if our code expands to require other HTTP calls, we could then easily reuse this function.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendButton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userInput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;messageInput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceEndpoint&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;signalRBroadcastApiMethod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Default options are marked with *&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// *GET, POST, PUT, DELETE, etc.&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// no-cors, cors, *same-origin&lt;/span&gt;
        &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// *default, no-cache, reload, force-cache, only-if-cached&lt;/span&gt;
        &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;same-origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// include, *same-origin, omit&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// "Content-Type": "application/x-www-form-urlencoded",&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;follow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// manual, *follow, error&lt;/span&gt;
        &lt;span class="na"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-referrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// no-referrer, *client&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// body data type must match "Content-Type" header&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// parses JSON response into native Javascript objects &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h2&gt;
  
  
  The Azure Function project
&lt;/h2&gt;

&lt;p&gt;Broadly speaking this project is comprised of just three main elements:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Models&lt;/strong&gt; - there are only two models in the project, a container for some configuration items &lt;code&gt;GenericConfig.cs&lt;/code&gt; and a very simple container for user messages &lt;code&gt;UserMessage.cs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Common Code&lt;/strong&gt; - this represents a handful of classes that are shared across all functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functions&lt;/strong&gt; - there are four HTTP-triggered Azure Functions .&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have worked with Azure Functions in the past, you may be expecting to declare each function as a &lt;code&gt;static&lt;/code&gt;.       A very recent change to the SDK means that this is no longer a requirement and you can create function as an instance class.    This is all due to scoping changes being made in order to accommodate the &lt;a href="https://github.com/Azure/azure-functions-host/issues/3736" rel="noopener noreferrer"&gt;upcoming introduction of Dependency Injection&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;






&lt;h3&gt;
  
  
  Project Dependencies
&lt;/h3&gt;

&lt;p&gt;There are only a handful of dependencies to other libraries.    It would be simplest to add these dependencies using the NuGet Package Manager, but here is the library listing as seen in the &lt;code&gt;....\src\WhatsAppSignalRDemo.Function\WhatsAppSignalRDemo.Function.csproj&lt;/code&gt; file:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.Azure.WebJobs.Extensions.SignalRService"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk.Functions"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.0.24"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"twilio"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"5.28.2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  GlobalConstants.cs
&lt;/h3&gt;

&lt;p&gt;One of the things that struck me when looking at much of the sample code available for both Azure Functions and SignalR, is that there seems to be a heavy reliance on string literals for identifying things in the code.&lt;/p&gt;

&lt;p&gt;For example, functions typically have a &lt;code&gt;[FunctionName="xyz"]&lt;/code&gt; attribute to define their exposed API name.  &lt;/p&gt;

&lt;p&gt;Similarly, if we wanted to pass messages between functions (over HTTP), we find ourselves identifying the function we want by the URL - again, another form of string.&lt;/p&gt;

&lt;p&gt;I feel that these various examples make for &lt;a href="https://en.wikipedia.org/wiki/Software_brittleness" rel="noopener noreferrer"&gt;brittle code&lt;/a&gt;  - it would only take a typo for things to break.   &lt;/p&gt;

&lt;p&gt;Additionally, by using string-literals, we lose things such as compile-time checks and intellisense [to hint at mistakes].  If there is a problem, it will only manifest at runtime.   &lt;/p&gt;

&lt;p&gt;Therefore I have implemented a simple solution to address this issue. We create the static class &lt;code&gt;GlobalConstants&lt;/code&gt; and within this, define a small selection of string &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/const" rel="noopener noreferrer"&gt;constants&lt;/a&gt;.  The &lt;code&gt;globalConstants.cs&lt;/code&gt; class looks like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Common&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalConstants&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SignalR_HubName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Chat"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SignalR_TargetGroup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"AllUsers"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FunctionName_Negotiate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"negotiate"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FunctionName_BroadcastSignalRMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"BroadcastSignalRMessage"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FunctionName_SendWhatsAppMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SendWhatsAppMessage"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FunctionName_ReceiveWhatsAppMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ReceiveWhatsAppMessage"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Config_TwilioAccountSid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TwilioAccountSid"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Config_TwilioAuthToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TwilioAuthToken"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Config_TwilioWhatsAppPhoneNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TwilioWhatsAppPhoneNumber"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating and using this class means that we can define string values in just one single place and then we can use those strongly-typed values across the rest of the project.  &lt;/p&gt;

&lt;p&gt;Aside from greatly reducing the possibility of making a silly mistake caused by a typo, it means that if you want to do something such as rename a keyname in your configuration, you don't have to then make this change all over your codebase.&lt;/p&gt;

&lt;p&gt;To use these values, we can substitute them anywhere where a string literal would otherwise be used.  So where previously we would have code that looks like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendWhatsAppMessageFunction&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SendWhatsAppMessage"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can instead replace it with the following:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendWhatsAppMessageFunction&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_SendWhatsAppMessage&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  HttpHelper.cs
&lt;/h3&gt;

&lt;p&gt;This class is a component that helps our functions make HTTP requests.  It is a common, reusable component that can be shared by several separate Azure Functions. &lt;/p&gt;

&lt;p&gt;Making RESTful calls is one mechanism for communicating between different functions (there are other, more reliable, ways such as using a queuing mechanism, but that is beyond the scope of this article).     For example, in this demo, when a message is received from WhatsApp to the &lt;code&gt;ReceiveWhatsAppMessageFunction&lt;/code&gt;, the message is relayed onward to the &lt;code&gt;BroadcastSignalRMessageFunction&lt;/code&gt; by way of an HTTP call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Newtonsoft.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Common&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpHelper&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PostMessage&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//send request&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpWebRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpWebRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;WebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;httpWebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;httpWebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;streamWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StreamWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpWebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRequestStream&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;streamWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="n"&gt;streamWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;streamWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;//get response&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpWebResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;httpWebRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetResponse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;streamReader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetResponseStream&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;streamReader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadToEnd&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-ignition.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-ignition.jpg%23shadow" alt="image showing ignition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  StartupHelper.cs
&lt;/h3&gt;

&lt;p&gt;Azure Functions are self-contained units that are instanced as demand requires.  This is why they can scale without issue.   &lt;/p&gt;

&lt;p&gt;In contrast,  ASP.NET APIs exist on a common platform - a constantly provisioned App Server.  &lt;/p&gt;

&lt;p&gt;One benefit of using an ASP.NET Web Application is that you only need a single application startup (with that code literally being in the &lt;code&gt;Startup.cs&lt;/code&gt; file).  This means that any setup and configuration needs happen only once and the results can then be made available until the application is recycled.  &lt;/p&gt;

&lt;p&gt;In contrast, an Azure Function requires you to run configuration on each and every use.   If you look at many of the code examples for Azure Functions, you tend to see often those examples repeating boilerplate code for things like configuration.&lt;/p&gt;

&lt;p&gt;Looking at an Azure Function in isolation, this probably doesn't appear to be much of an issue.  However, as soon as you have a suite of different functions, you'll start to see a pattern of repetition, which isn't ideal.&lt;/p&gt;

&lt;p&gt;As developers we aspire to produce, what we hope to be well-considered code, therefore, we should try and follow software design principles where possible.   Amongst these recommendations is the &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;DRY Principle&lt;/a&gt; - which basically says "don't repeat the same code everywhere - try and reuse it".&lt;/p&gt;

&lt;p&gt;So, the code in &lt;code&gt;....\src\WhatsAppSignalRDemo.Function\Common\StartupHelper.cs&lt;/code&gt; is an effort to try and encapsulate some of that code which is common between each of the four Functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Common&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StartupHelper&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;GenericConfig&lt;/span&gt; &lt;span class="nf"&gt;GetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExecutionContext&lt;/span&gt; &lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;functionHostName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WEBSITE_HOSTNAME"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;GenericConfig&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;TwilioAccountSid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config_TwilioAccountSid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                &lt;span class="n"&gt;TwilioAuthToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config_TwilioAuthToken&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                &lt;span class="n"&gt;TwilioWhatsAppPhoneNumber&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config_TwilioWhatsAppPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;ApiEndpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"http://&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;functionHostName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/"&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;Within the Function class (such as &lt;code&gt;ReceiveWhatsAppMessageFunction&lt;/code&gt;), the &lt;code&gt;StartupHelper&lt;/code&gt; is made available a little bit like an ASP.NET Core Configuration Item.  It would look like this (partial code, for brevity):-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt; 

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;GenericConfig&lt;/span&gt; &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt; 

&lt;span class="n"&gt;_genericConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StartupHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt; 

&lt;span class="n"&gt;HttpHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiEndpoint&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_BroadcastSignalRMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;intent&lt;/em&gt; of the &lt;code&gt;StartupHelper&lt;/code&gt; class is to demonstrate a way to encapsulate all of the requests for configuration information, into one place (which can then be re-used by multiple different functions).  The mindset behind this idea is to emulate the effect of having all of your configuration code in a startup class.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;practical&lt;/em&gt; use of the above class is to define and populate the field &lt;code&gt;ApiEndpoint&lt;/code&gt;, which is genuinely re-used in three of the four functions.&lt;/p&gt;

&lt;p&gt;The inclusion of the three Twilio configuration items into &lt;code&gt;StartupHelper&lt;/code&gt;, is a little bit more tenuous to justify.   Those particular fields are only actually used in &lt;code&gt;SendWhatsAppMessageFunction&lt;/code&gt;, so you could argue that those configuration items may have been better placed directly into just &lt;code&gt;SendWhatsAppMessageFunction&lt;/code&gt;, using &lt;code&gt;Environment.GetEnvironmentVariables&lt;/code&gt; as appropriate.&lt;/p&gt;

&lt;p&gt;The point of this article is to talk about and demonstrate some ideas, so take all of this into consideration and choose a solution that suits your purposes best.&lt;/p&gt;






&lt;h3&gt;
  
  
  NegotiateFunction.cs
&lt;/h3&gt;

&lt;p&gt;This function is fundamental to the operation of the &lt;em&gt;SignalR Service&lt;/em&gt;, so you need to know that it's both required and how works.&lt;/p&gt;

&lt;p&gt;Previously, with &lt;em&gt;Hosted SignalR&lt;/em&gt;, the [web] client would negotiate directly with the Hub.   This would all be managed transparently  by the SignalR JavaScript client code - we only needed to provide the hostname of the service.&lt;/p&gt;

&lt;p&gt;With &lt;em&gt;SignalR Service&lt;/em&gt;, the concept of a Hub still exists, but it is all dynamically provisioned and managed by Azure.   The upshot is that our [web] client can't make the first connection directly.    To make this work, we need to provide a static HTTP API, which will negotiate with the SignalR Service, and return connection details and a user-access token.&lt;/p&gt;

&lt;p&gt;The good news is that the SignalR JavaScript client software, provided by Microsoft, understands the differences between working with &lt;em&gt;Hosted&lt;/em&gt; and &lt;em&gt;Service&lt;/em&gt; modes,  so will explicitly be expecting you to provide an API called "negotiate".   You can read about that in their own documentation &lt;a href="https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-concept-serverless-development-config" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When it comes to writing the C# for the "negotiate" API, fortunately, the code itself is really straightforward and we didn't even need to write it ourselves, as it comes directly cut+paste from &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service" rel="noopener noreferrer"&gt;Microsoft's documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In our demo, we've tweaked it slightly, so as to provide consistency and make use of our  &lt;code&gt;GlobalConstants&lt;/code&gt;:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.SignalRService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Common&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NegotiateFunction&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_Negotiate&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;SignalRConnectionInfo&lt;/span&gt; &lt;span class="nf"&gt;Negotiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;SignalRConnectionInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HubName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignalR_HubName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="n"&gt;SignalRConnectionInfo&lt;/span&gt; &lt;span class="n"&gt;connectionInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;connectionInfo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One part of the code to draw your attention to is the need to be consistent with your use of the SignalR &lt;code&gt;HubName&lt;/code&gt;.     This needs to be shared across any of the references that you make - so in the case of this demo, be conscious that this HubName is also used in the &lt;code&gt;BroadcastSignalRMessageFunction&lt;/code&gt; (which is another good reason to use string constants, rather than literals)&lt;/p&gt;






&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-broadcast.jpg%23shadow" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.siliconorchid.com%2Fimages%2Fclipart%2Fclipart-broadcast.jpg%23shadow" alt="image showing broadcast studio"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  BroadcastSignalRMessageFunction.cs
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;BroadcastSignalRMessageFunction&lt;/code&gt; class is an HTTP-triggered Azure Function, which receives a message from the web client and relays it onto both:- &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The SignalR Service, which In turn, broadcasts the message to every connected client.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;SendWhatsAppMessageFunction&lt;/code&gt; API, which in turn, broadcasts the message to every subscribed WhatsApp user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in this article, we've explained that although &lt;em&gt;SignalR Service&lt;/em&gt; has Hubs, they no longer accommodate having methods written against them.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;BroadcastSignalRMessageFunction&lt;/code&gt; is the stand-alone HTTP API, that effectively replaces code that would previously have been a method within the Hub class.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;SignalR Service&lt;/em&gt; extension is added as an argument to our Function and the object is named &lt;code&gt;signalRMessages&lt;/code&gt;.    This handles all the operational details, such as locating a connection string, automatically for us.&lt;/p&gt;

&lt;p&gt;We send messages into the Service by calling &lt;code&gt;signalRMessages.AddAsync(...)&lt;/code&gt; like this:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.SignalRService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Common&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BroadcastSignalRMessageFunction&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;GenericConfig&lt;/span&gt; &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_BroadcastSignalRMessage&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;SignalR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HubName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignalR_HubName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="n"&gt;IAsyncCollector&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SignalRMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;signalRMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ExecutionContext&lt;/span&gt; &lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_genericConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StartupHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// relay the message to the WhatsApp API&lt;/span&gt;
            &lt;span class="n"&gt;HttpHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiEndpoint&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_SendWhatsAppMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;//relay the message to the SignalR Service&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;signalRMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SignalRMessage&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignalR_TargetGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  SendWhatsAppMessageFunction.cs
&lt;/h3&gt;

&lt;p&gt;This Function is not intended to be called directly from the [web] client, but instead triggered by the &lt;code&gt;BroadcastSignalRMessageFunction&lt;/code&gt; Function.&lt;/p&gt;

&lt;p&gt;This particular Function is probably the most complex in terms of different tasks that it performs:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The incoming JSON message is deserialised and ultimately end ups in the C# &lt;code&gt;dynamic&lt;/code&gt; type (for simplicity) named &lt;code&gt;requestObject&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;The Twilio client is created and initialised using credentials originating in our configuration.&lt;/li&gt;
&lt;li&gt;Using the Twilio library method &lt;code&gt;MessageResource.ReadAsync(...)&lt;/code&gt; we obtain a list of all WhatsApp messages that have been added to our sandbox in this session.   This will contain a list of every single message, including each phone number of the user involved.  This collection is named &lt;code&gt;messageCollection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We use a LINQ query to filter the list of every phone number in &lt;code&gt;messageCollection&lt;/code&gt;,  down to a list of  unique phone numbers.  This resultant collection is named &lt;code&gt;distinctPhoneNumbers&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We iterate over the list of unique phone numbers, using the Twilio library method &lt;code&gt;MessageResource.Create(...)&lt;/code&gt; to send a message to each.

&lt;ul&gt;
&lt;li&gt;We test that the phone number of the recipient is not the same as the phone number in the message (i.e. the sender) - we don't want to send a WhatsApp message straight back to the user who sent it!
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Newtonsoft.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Twilio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Twilio.Base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Twilio.Types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Twilio.Rest.Api.V2010.Account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Common&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendWhatsAppMessageFunction&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;GenericConfig&lt;/span&gt; &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_SendWhatsAppMessage&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;httpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ExecutionContext&lt;/span&gt; &lt;span class="n"&gt;executionContext&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;_genericConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StartupHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;requestBody&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ReadToEndAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;requestObject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonConvert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeserializeObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// connect to Twilio&lt;/span&gt;
            &lt;span class="n"&gt;TwilioClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TwilioAccountSid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TwilioAuthToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// query twilio api ... and get distinct phone numbers of whatsapp users subscribed to chat&lt;/span&gt;
            &lt;span class="n"&gt;ResourceSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MessageResource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messageCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;MessageResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TwilioWhatsAppPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;distinctPhoneNumbers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messageCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;phoneNumber&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;distinctPhoneNumbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;requestObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// if the message originated from whatsapp, don't send a copy straight back to sender&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;// send message to whatsapp user&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MessageResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Twilio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TwilioWhatsAppPhoneNumber&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;requestObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; : &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;requestObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;phoneNumber&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;








&lt;h3&gt;
  
  
  ReceiveWhatsAppMessageFunction.cs
&lt;/h3&gt;

&lt;p&gt;This Function is the endpoint we identified in the Twilio Dashboard.   Its purpose is to simply receive a message sent from Twilio and extract some of the fields that we receive.   &lt;/p&gt;

&lt;p&gt;That extracted information is then used to populate our Model Class  &lt;code&gt;UserMessage&lt;/code&gt;.   The fields are simply a username (the WhatsApp phone number) and message.&lt;/p&gt;

&lt;p&gt;The processed message is then sent onward, via an HTTP POST, to &lt;code&gt;BroadcastSignalRMessage&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Web&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.WebJobs.Extensions.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function.Common&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;WhatsAppSignalRDemo.Function&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReceiveWhatsAppMessageFunction&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;GenericConfig&lt;/span&gt; &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_ReceiveWhatsAppMessage&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorizationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anonymous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;           
            &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;httpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ExecutionContext&lt;/span&gt; &lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_genericConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StartupHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;requestBody&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ReadToEndAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;UserMessage&lt;/span&gt; &lt;span class="n"&gt;userMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;UserMessage&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpUtility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseQueryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"From"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpUtility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseQueryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Body"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="n"&gt;HttpHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;_genericConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiEndpoint&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;GlobalConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionName_BroadcastSignalRMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We've now had an in-depth look at the code and you should be in a good place with your understanding of what's needed in the system.&lt;/p&gt;

&lt;p&gt;In the final article of this series, we'll look into the additional steps needed to get our development system published to the cloud.&lt;/p&gt;

&lt;p&gt;See you there!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blogs.siliconorchid.com/post/coding-inspiration/whatsapp-signalrservice-azurefunction/part3-deploy-to-cloud" rel="noopener noreferrer"&gt;&lt;br&gt;
    NEXT:  Read part 3&lt;/a&gt;&lt;/p&gt;

</description>
      <category>signalr</category>
      <category>twilio</category>
      <category>azurefunctions</category>
    </item>
  </channel>
</rss>
