<?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>DEV Community: Luigi Bianchi</title>
    <description>The latest articles on DEV Community by Luigi Bianchi (@luigbi).</description>
    <link>https://dev.to/luigbi</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%2F3004246%2Fc94c0472-9f7b-408e-9b0c-ba58ffc3b586.jpg</url>
      <title>DEV Community: Luigi Bianchi</title>
      <link>https://dev.to/luigbi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luigbi"/>
    <language>en</language>
    <item>
      <title>Building Seamless Communication: Integrating VirtualFront with Microsoft Teams via Azure Communication Services (ACS)</title>
      <dc:creator>Luigi Bianchi</dc:creator>
      <pubDate>Mon, 07 Apr 2025 06:51:31 +0000</pubDate>
      <link>https://dev.to/luigbi/building-seamless-communication-integrating-virtualfront-with-microsoft-teams-via-azure-1dnl</link>
      <guid>https://dev.to/luigbi/building-seamless-communication-integrating-virtualfront-with-microsoft-teams-via-azure-1dnl</guid>
      <description>&lt;p&gt;The &lt;strong&gt;VirtualFront&lt;/strong&gt; project is a production-grade Single Page Application (SPA) that integrates Microsoft Teams and Azure Communication Services (ACS) to enable video calling, PSTN integration, and secure authentication via Azure Active Directory (AAD). This article provides a comprehensive analysis of the source code, including architecture, project structure, integration steps, and deployment considerations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;High-Level Architecture&lt;/li&gt;
&lt;li&gt;Detailed Project Structure&lt;/li&gt;
&lt;li&gt;Key Components&lt;/li&gt;
&lt;li&gt;Integration Steps&lt;/li&gt;
&lt;li&gt;Code Snippets and Token Flow&lt;/li&gt;
&lt;li&gt;API Endpoints&lt;/li&gt;
&lt;li&gt;Technologies Used&lt;/li&gt;
&lt;li&gt;Future Enhancements&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;&lt;strong&gt;VirtualFront&lt;/strong&gt; is a modern web application designed to act as a virtual receptionist system. It connects a browser-based SPA with Microsoft Teams users via ACS, making it ideal for kiosk-based solutions, front-desk automation, and remote concierge systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One-click video calls to Microsoft Teams users.&lt;/li&gt;
&lt;li&gt;Token exchange using Azure AD and ACS.&lt;/li&gt;
&lt;li&gt;PSTN calling support (dialing out to phone numbers).&lt;/li&gt;
&lt;li&gt;Scalable backend built with Node.js and Express.&lt;/li&gt;
&lt;li&gt;Frontend powered by Vanilla JavaScript and ACS SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n1g2zd2mz1wq3fbcy4i.png" alt=" "&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  High-Level Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture of VirtualFront is modular, with a clear separation between the frontend, backend, and Azure services. Below is a high-level diagram:&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;Frontend&lt;/strong&gt; communicates with the &lt;strong&gt;Backend&lt;/strong&gt; to request ACS tokens.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Backend&lt;/strong&gt; authenticates users via Azure AD, exchanges tokens, and provides the frontend with call credentials.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Azure Services&lt;/strong&gt; (ACS, AAD, Graph API) handle authentication, token management, and communication.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0r1cfrmbdfoqtoj3o2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0r1cfrmbdfoqtoj3o2v.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Detailed Project Structure
&lt;/h2&gt;

&lt;p&gt;The project is organized into the following directories and files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── client/                      # Frontend SPA (HTML, JS, CSS)
│   ├── assets/                  # Static assets (images, styles)
│   ├── app.js                   # Main application logic
│   └── call.js                  # ACS call logic
│
├── constants/                   # Common constants (enums, labels)
│   └── roles.js
│
├── controllers/                 # Express route controllers
│   ├── token.controller.js      # Token management (ACS, AAD)
│   ├── user.controller.js       # Teams users &amp;amp; endpoint APIs
│   └── pstn.controller.js       # PSTN token and phone services
│
├── public/                      # Static public directory
│   └── index.html
│
├── routes/                      # API route definitions
│   ├── token.routes.js
│   ├── user.routes.js
│   └── pstn.routes.js
│
├── services/                    # Service layer for Azure SDK calls
│   ├── acs.service.js
│   ├── auth.service.js
│   └── teams.service.js
│
├── utils/                       # Utility helpers
│   ├── logger.js
│   └── environment.js
│
├── .env                         # Environment variables
├── index.js                     # Express server entry
├── package.json                 # Node dependencies
├── webpack.config.js            # Frontend bundling
└── README.md                    # Project documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Components
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. &lt;strong&gt;Frontend&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The frontend is implemented in Vanilla JavaScript and interacts with the Azure Communication Services SDK to manage video calls and UI interactions.&lt;/p&gt;
&lt;h4&gt;
  
  
  Code Snippet: Initializing the Teams Call Agent
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CallClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@azure/communication-calling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callClient&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;CallClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;callClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCallAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;user-id&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;callAgent&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;incomingCall&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="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;)&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Incoming call:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&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;
  
  
  2. &lt;strong&gt;Backend&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The backend is built using &lt;strong&gt;Node.js&lt;/strong&gt; and &lt;strong&gt;Express&lt;/strong&gt;. It provides APIs for fetching access tokens, validating tokens, and serving the SPA.&lt;/p&gt;
&lt;h4&gt;
  
  
  Code Snippet: Fetching Access Tokens
&lt;/h4&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAccessToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../services/auth.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAccessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&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="nx"&gt;accessToken&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;
  
  
  3. &lt;strong&gt;Services&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The services directory contains the core business logic for interacting with Azure SDKs.&lt;/p&gt;
&lt;h4&gt;
  
  
  Code Snippet: Fetching ACS Token for Teams User
&lt;/h4&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommunicationIdentityClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@azure/communication-identity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAcsToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aadToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;CommunicationIdentityClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ACS_CONNECTION_STRING&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTokenForTeamsUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;teamsUserAadToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aadToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userObjectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;tenantId&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;tokenResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&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;
  
  
  Integration Steps
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Provision Azure Communication Services (ACS)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a Communication Services resource in Azure.&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;connection string&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enable PSTN (optional).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Register an Azure AD Application
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create App Registration in Azure AD.&lt;/li&gt;
&lt;li&gt;Configure:

&lt;ul&gt;
&lt;li&gt;Redirect URIs (if needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client ID&lt;/strong&gt;, &lt;strong&gt;Tenant ID&lt;/strong&gt;, &lt;strong&gt;Client Secret&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add Microsoft Graph API permissions:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;User.Read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Directory.Read.All&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Enable ACS Federation in Microsoft Teams
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MicrosoftTeams&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Connect-MicrosoftTeams&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$resourceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;ACS_RESOURCE_ID&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-CsTeamsAcsFederationConfiguration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-EnableAcsUsers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AllowedAcsResources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resourceId&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Set-CsExternalAccessPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-EnableAcsFederationAccess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Configure Environment Variables
&lt;/h3&gt;

&lt;p&gt;Create a .env file in the root directory:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ACS_CONNECTION_STRING&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;endpoint=https://&amp;lt;your-acs-resource&amp;gt;.communication.azure.com/;accesskey=&amp;lt;your-access-key&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;AAD_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-client-id&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;AAD_TENANT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-tenant-id&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;AAD_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-client-secret&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;TEAMS_USER_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-teams-user-id&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbc621nnvmkn6e5uwmox.png" alt=" "&gt;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Code Snippets and Token Flow
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Backend: Exchange AAD Token for ACS Token
&lt;/h3&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CommunicationIdentityClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@azure/communication-identity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;CommunicationIdentityClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ACS_CONNECTION_STRING&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTokenForTeamsUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;teamsUserAadToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aadToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AAD_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userObjectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AAD_TENANT_ID&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;tokenResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Frontend: Initiate Call to Teams
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CallClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@azure/communication-calling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callClient&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;CallClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenCredential&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;AzureCommunicationTokenCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;acs-token&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;callClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCallAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenCredential&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;callAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startCall&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;communicationUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;8:acs:...&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="na"&gt;videoOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;localVideoStreams&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;
  
  
  API Endpoints
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;POST /get-access-token&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Fetches an ACS token for Teams users.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;POST /get-pstn-token&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Fetches a PSTN token for phone calls.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;GET /endpoint&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Fetches the REST API endpoint for the current environment.&lt;/p&gt;


&lt;h2&gt;
  
  
  Technologies Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Vanilla JavaScript, Webpack, ACS Calling SDK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js, Express, Azure SDK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity&lt;/strong&gt;: Azure AD, Microsoft Graph&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud&lt;/strong&gt;: Azure Communication Services&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add support for group calls and chat features.&lt;/li&gt;
&lt;li&gt;Implement a CI/CD pipeline for automated deployments.&lt;/li&gt;
&lt;li&gt;Enhance error handling and logging mechanisms.&lt;/li&gt;
&lt;li&gt;Add an admin panel for managing Teams users.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;VirtualFront demonstrates how to build a secure and scalable communication platform by integrating Microsoft Teams and Azure Communication Services. By leveraging Azure's robust APIs and SDKs, developers can create seamless communication experiences for various use cases, such as virtual receptionists, kiosks, and remote concierge systems.&lt;/p&gt;

&lt;p&gt;For more information, refer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/communication-services/" rel="noopener noreferrer"&gt;Azure Communication Services Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/microsoftteams/platform/" rel="noopener noreferrer"&gt;Microsoft Teams Developer Platform&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curious to see it in action? Explore the complete implementation and code on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/luigbi" rel="noopener noreferrer"&gt;
        luigbi
      &lt;/a&gt; / &lt;a href="https://github.com/luigbi/MSTeams-CommunicationService-SPA-Integration" rel="noopener noreferrer"&gt;
        MSTeams-CommunicationService-SPA-Integration
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;VirtualFront: Seamless Communication with Microsoft Teams via Azure Communication Services (ACS)&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;VirtualFront is a production-grade Single Page Application (SPA) that integrates Microsoft Teams and Azure Communication Services (ACS) to enable video calling, PSTN integration, and secure authentication via Azure Active Directory (AAD). This repository contains the source code and setup instructions to get started.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;One-click video calls to Microsoft Teams users.&lt;/li&gt;
&lt;li&gt;Token exchange using Azure AD and ACS.&lt;/li&gt;
&lt;li&gt;PSTN calling support (dialing out to phone numbers).&lt;/li&gt;
&lt;li&gt;Scalable backend built with Node.js and Express.&lt;/li&gt;
&lt;li&gt;Frontend powered by Vanilla JavaScript and ACS SDK.&lt;/li&gt;
&lt;/ul&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📂 Project Structure&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;
├── client/                      # Frontend SPA (HTML, JS, CSS)
│   ├── assets/                  # Static assets (images, styles)
│   ├── app.js                   # Main application logic
│   └── call.js                  # ACS call logic
│
├── constants/                   # Common constants (enums, labels)
│   └── roles.js
│
├── controllers/                 # Express route controllers
│   ├── token.controller.js      # Token&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/luigbi/MSTeams-CommunicationService-SPA-Integration" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Job Scheduler with Quartz.NET and Azure WebJobs</title>
      <dc:creator>Luigi Bianchi</dc:creator>
      <pubDate>Wed, 02 Apr 2025 00:25:57 +0000</pubDate>
      <link>https://dev.to/luigbi/job-scheduler-with-quartznet-and-azure-webjobs-2dne</link>
      <guid>https://dev.to/luigbi/job-scheduler-with-quartznet-and-azure-webjobs-2dne</guid>
      <description>&lt;p&gt;This article explores a .NET-based job scheduler built using the Quartz.NET library and hosted as a continuous Azure WebJob. The scheduler dynamically retrieves job definitions from a database via a REST API, loads them into a shared Quartz scheduler, and executes each schedule independently in its own thread. Schedules can be triggered daily, weekly, or monthly using CRON expressions, making it a flexible and scalable solution for automated task management in the cloud.&lt;/p&gt;




&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In modern cloud-based applications, scheduling recurring tasks is a common requirement. Whether it's running daily reports, weekly data syncs, or monthly maintenance jobs, a reliable scheduler is essential. This implementation leverages Quartz.NET, a powerful open-source scheduling library, and integrates it with Azure WebJobs for seamless deployment and execution in the Azure cloud. The scheduler fetches job definitions from a database through a REST API, allowing for dynamic configuration without redeploying the application. Each schedule runs in its own thread, ensuring independent execution, and can be triggered based on daily, weekly, or monthly CRON patterns.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1pqlah5z1jzrig676zy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1pqlah5z1jzrig676zy.png" alt="Orchestrating Cloud Tasks"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Code Structure and Key Components
&lt;/h1&gt;

&lt;p&gt;The application is composed of several key components, each responsible for a specific aspect of the scheduling process. Below is a breakdown of the main parts:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h27vxcc745rz5il0sei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h27vxcc745rz5il0sei.png" alt="Job Scheduler Components"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Main Entry Point (Program.cs)
&lt;/h2&gt;

&lt;p&gt;The application starts in Program.cs, where the host is configured, and the Quartz scheduler is initialized.&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Methods:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SetConfiguration(HostBuilder builder)&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Configures the hosting environment, WebJobs extensions, logging, and dependency injection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Environment: Sets the environment (e.g., "development") based on the YOUR_ENVIRONMENT variable.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WebJobs Configuration: Adds support for EventHubs, Timers, Azure Storage Queues, and Blobs.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.ConfigureWebJobs((context, b) =&amp;gt;
{
    b.AddEventHubs();
    b.AddTimers();
    b.AddAzureStorageQueues(a =&amp;gt; { a.BatchSize = 1; /* ... */ });
    b.AddAzureStorageBlobs();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logging: Configures logging with a minimum level of Debug and console output, with optional Application Insights integration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Services: Registers services like IRestAPIClient, ISchedulerListener, and IBackgroundJob using .NET Core’s dependency injection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Quartz Setup: Configures Quartz with an in-memory store, a thread pool (max concurrency of 10), and a scheduler name ("MyScheduler").&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services.AddQuartz(q =&amp;gt;
{
    q.SchedulerName = "MyScheduler";
    q.UseInMemoryStore();
    q.UseDefaultThreadPool(tp =&amp;gt; { tp.MaxConcurrency = 10; });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;InitializeScheduler(HostBuilder builder)&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Builds the host, retrieves services, and starts the scheduler.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Fetches schedules from the REST API using IRestAPIClient.&lt;/li&gt;
&lt;li&gt;  Adds a scheduler listener (ISchedulerListener) and starts the Quartz scheduler.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Iterates through the schedules and calls AddOrUpdateSchedule to load them into the scheduler.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var schedules = await restAPI.FetchSchedulesAsync();
foreach (var schedule in schedules)
{
    await myBackgroundJob.AddOrUpdateSchedule(schedule);
}
await host.RunAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2. REST API Client (RestAPIClient.cs)
&lt;/h2&gt;

&lt;p&gt;The RestAPIClient class handles communication with the REST API to fetch job schedules and execute jobs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Methods:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Authenticate()&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Authenticates with the API using a username and password from the configuration, returning a bearer token.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string jsonRequestBody = JsonConvert.SerializeObject(new Dictionary&amp;lt;string, string&amp;gt;
{
    { "UserName", _config["RESTAPI_USERNAME"] },
    { "Password", _config["RESTAPI_PASSWORD"] }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;FetchSchedulesAsync()&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Retrieves a list of JobScheduleDTO objects from the API using the bearer token.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HttpResponseMessage getResponse = await client.GetAsync(endpoint);
List&amp;lt;JobScheduleDTO&amp;gt; response = JsonConvert.DeserializeObject&amp;lt;List&amp;lt;JobScheduleDTO&amp;gt;&amp;gt;(responseBody);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;RunJobs(long scheduleId)&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Triggers the execution of jobs for a specific schedule via a POST request to the API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  3. Scheduler Listener (MySchedulerListener.cs)
&lt;/h2&gt;

&lt;p&gt;The MySchedulerListener class implements Quartz’s ISchedulerListener interface to monitor and log scheduler events (e.g., job added, paused, or errors).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Each method logs the event using the injected ILogger.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Task JobAdded(IJobDetail jobDetail, CancellationToken cancellationToken = default)
{
    _logger.LogInformation($"JobAdded:{jobDetail.Key.Name} \n ID: {jobDetail.Key.Group}");
    return Task.CompletedTask;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  4. Background Job (BackgroundJob.cs)
&lt;/h2&gt;

&lt;p&gt;The BackgroundJob class implements IBackgroundJob and contains the logic for executing jobs and managing schedules.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu7i2qy5f7lhxq6xrujc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhu7i2qy5f7lhxq6xrujc.png" alt="Background Job"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Methods:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Execute(IJobExecutionContext context)&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Executes a job when triggered by Quartz.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Retrieves job data from the context and logs the execution.&lt;/li&gt;
&lt;li&gt;  Calls the REST API to run jobs for the schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;code&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await _restAPI.RunJobs(int.Parse(context.JobDetail.Key.Name));
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;AddOrUpdateSchedule(JobScheduleDTO schedule)&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Adds or updates a schedule in the Quartz scheduler.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CRON Expression Generation: Constructs a CRON expression based on the schedule’s recurrence type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Daily: &lt;code&gt;0 [Minutes] [Hours] * * ?&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Weekly: &lt;code&gt;0 [Minutes] [Hours] ? * [Days] * (e.g., MON,TUE).&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Monthly: &lt;code&gt;0 [Minutes] [Hours] [DayOfMonth] * ? *.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;code&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (schedule.RecurrenceType == 1) // Daily
{
    cronSchedule += " " + schedule.StartTime.GetValueOrDefault().Minutes.ToString();
    cronSchedule += " " + schedule.StartTime.GetValueOrDefault().Hours.ToString();
    cronSchedule += " * * ?";
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Job and Trigger Creation: Creates a Quartz job and trigger with the CRON expression.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var job = JobBuilder.Create&amp;lt;BackgroundJob&amp;gt;()
    .WithIdentity(name: schedule.Id.ToString(), group: "JOB_" + schedule.ClientId.ToString())
    .Build();

var trigger = TriggerBuilder.Create()
    .WithIdentity(name: "TRIGGER_" + schedule.Id.ToString(), group: "TRIGGER_" + schedule.ClientId.ToString())
    .WithCronSchedule(cronSchedule, x =&amp;gt; x.InTimeZone(TimeZoneInfo.Utc))
    .StartNow()
    .Build();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scheduling Logic: Reschedules, schedules, or deletes a job based on its definition in the database.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  5. Queue Trigger (Functions.cs)
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Functions&lt;/code&gt; class contains a static method to handle dynamic schedule updates via Azure Storage Queues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hu7hy900vdosgi6zgqp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hu7hy900vdosgi6zgqp.png" alt="Queue Trigger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ProcessQueueMessage&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Triggered by a queue message, deserializes it into a &lt;code&gt;JobScheduleDTO&lt;/code&gt;, and calls &lt;code&gt;AddOrUpdateSchedule&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JobScheduleDTO jobScheduleDTO = JsonConvert.DeserializeObject&amp;lt;JobScheduleDTO&amp;gt;(myQueueItem);
await myBackgroundJob.AddOrUpdateSchedule(jobScheduleDTO);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  How It Works
&lt;/h1&gt;
&lt;h3&gt;
  
  
  1. Startup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The application starts in &lt;code&gt;Main()&lt;/code&gt;, configures the host, and initializes the scheduler.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Services like &lt;code&gt;IRestAPIClient&lt;/code&gt; and &lt;code&gt;IBackgroundJob&lt;/code&gt; are registered via dependency injection.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Schedule Loading:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;RestAPIClient&lt;/code&gt; fetches job schedules from the REST API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each schedule is passed to &lt;code&gt;AddOrUpdateSchedule&lt;/code&gt;, which generates a CRON expression and creates a Quartz job and trigger.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Execution:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Quartz triggers jobs based on their CRON schedules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;BackgroundJob.Execute&lt;/code&gt; method runs, invoking the REST API to execute the jobs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. Dynamic Updates:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Queue messages trigger the &lt;code&gt;ProcessQueueMessage&lt;/code&gt; function, allowing real-time schedule updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  5. Concurrency:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Each schedule runs in its own thread, managed by Quartz’s thread pool (max 10 concurrent threads).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcbdooxtfz4ivfd0r4vt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcbdooxtfz4ivfd0r4vt.png" alt="Job Scheduler System"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Key Features
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Azure WebJobs: Ensures the scheduler runs continuously in Azure, providing reliability and scalability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quartz.NET: Offers robust scheduling capabilities with CRON-based triggers for daily, weekly, or monthly jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;REST API Integration: Dynamically fetches schedules from a database, decoupling configuration from code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CRON Expressions: Flexibly defines recurrence patterns based on schedule properties.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Threading: Independent execution of schedules in separate threads, controlled by Quartz.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;This implementation demonstrates a scalable and maintainable scheduler built with Quartz.NET and hosted as an Azure WebJob. By integrating with a REST API for dynamic schedule management and supporting various recurrence patterns via CRON expressions, it provides a flexible solution for automated job scheduling in the cloud. The use of Azure Storage Queues for real-time updates and comprehensive logging further enhances its adaptability and observability, making it a solid choice for managing recurring tasks in a distributed environment.&lt;/p&gt;



&lt;p&gt;Curious to see it in action? Explore the complete implementation and code on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/luigbi" rel="noopener noreferrer"&gt;
        luigbi
      &lt;/a&gt; / &lt;a href="https://github.com/luigbi/AzureJobScheduler" rel="noopener noreferrer"&gt;
        AzureJobScheduler
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Job Scheduler with Quartz.NET and Azure WebJobs
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quartz.NET Scheduler for Azure WebJobs&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A .NET-based scheduler using Quartz.NET, designed to run as a continuous Azure WebJob. It fetches job definitions from a REST API and supports daily, weekly, or monthly triggers via CRON expressions.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Dynamic schedule loading from a REST API&lt;/li&gt;
&lt;li&gt;CRON-based triggers (daily, weekly, monthly)&lt;/li&gt;
&lt;li&gt;Independent thread execution&lt;/li&gt;
&lt;li&gt;Azure Storage Queue integration&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Clone the repo: &lt;code&gt;git clone https://github.com/luigbi/AzureJobScheduler.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;appsettings.json&lt;/code&gt; with your REST API URL and credentials.&lt;/li&gt;
&lt;li&gt;Build and deploy as an Azure WebJob.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Learn More&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Check out the full article on dev.to: &lt;a href="https://dev.to/luigbi/job-scheduler-with-quartznet-and-azure-webjobs-2dne" rel="nofollow"&gt;https://dev.to/luigbi/job-scheduler-with-quartznet-and-azure-webjobs-2dne&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;MIT License&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/luigbi/AzureJobScheduler" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
    </item>
  </channel>
</rss>
