Azure Whatsapp Plugin
A plugin for Notification Service utilize Azure Communication Service's Advance Messaging with Whatsapp.
Prerequisite
- Know how to set up and maintain Advance Messaging for WhatsApp In Azure Communication Service
- Understand WhatsApp for Business and how to establish conversations
Getting start
- Setup Advance Messaging for WhatsApp in Azure Communication
- Establish conversations with end devices. One possible way to do this is using free-entry point conversation, which is initiating the conversation from end devices by sending a WhatsApp message to the WhatsApp channel on Azure. Each conversation will be active for 24-hours from the time the message is sent, and if there is any notification message to the end device, that conversation will be active for 72-hours. After that, the conversation needs to be refresh by sending another message from end device. For more options in establishing conversations, please refer to WhatsApp documentation.
- Obtain WhatsApp Channel ID and Connection String. Create a Generic Credential in Windows Credentials with Channel ID as Username and Connection String as Password. Give it a desirable name in "Internet or network address" field
Configuration
Configuring the Notification Plugin
For the configuration, there will be seven available parameters which will be configured in the Notification Plugin table in the ConfigurationString section. Each parameter will be separated by a semi-colon ( ; ). Keywords are not case sensitive, values are.
| Keyword | Short | |
|---|---|---|
| Credential | cred | Windows Credential name containing the username/password for the SMTP server. The credential's type must be generic and not windows. Notes: Make sure the credential is on the same account that the service is running. |
| TraceLevel (Optional) | tl | Define trace level for logging. Default value is 3. (Off = 0, Error = 1, Warning = 2, Info =3, Verbose =4, Very Verbose = 5) |
Configuring the Notification Subscription
In the Notification Subscription section, the ConfigurationString will contain a list of phone numbers that will receive notifications. Phone number must be in international format with country code and separated by a semi-colons.
e.g.: +358 #########; +1 #########;
Source Codes
namespace ABB.Vtrin.NotificationService
{
public class NotificationServicePlugin_Whatsapp : INotificationServicePlugin
{
private static readonly int mDefaultTraceLevel =(int)System.Diagnostics.TraceLevel.Info;
private static readonly Diagnostics.cTraceSwitch mLog =new("WhatsappPlugin", string.Empty, "Info");
private readonly cConfiguration mConfiguration;
private class cAccount
{
public readonly string? Username;
public readonly System.Security.SecureString? Password;
public cAccount(string? username, System.Security.SecureString? password)
{
Username=username;
Password=password;
}
}
private class cConfiguration
{
public readonly string Credential;
public readonly int TraceLevel;
public cConfiguration(string credential, int tracelevel)
{
Credential=credential;
TraceLevel=tracelevel;
}
public static cConfiguration GetConfiguration(string configurationstring)
{
string credential =string.Empty;
int tracelevel =NotificationServicePlugin_Whatsapp.mDefaultTraceLevel;
var parts=configurationstring.Split(';', System.StringSplitOptions.RemoveEmptyEntries);
foreach(string part in parts)
{
var keyvalue=part.Split('=');
if(keyvalue.Length==2)
{
var key=keyvalue[0].Trim().ToLower();
var value=keyvalue[1].Trim();
if(string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
{
mLog.LogError($"Cannot read configuration string '{part}'. Key or Value is missing.");
continue;
}
switch (key)
{
case "credential":
case "cred":
credential=value;
break;
case "tracelevel":
case "tl":
tracelevel=Utils.ParseInt(value, tracelevel);
break;
default:
mLog.LogError($"Unrecognized keyword '{key}'");
break;
}
} else
{
mLog.LogError($"Check configuration string: '{part}'");
}
}
mLog.LogInfo(
$"""
Whatsapp Plugin Configuration:
- Credential : {credential}
- Trace Level : {tracelevel}
""");
return new cConfiguration( credential, tracelevel );
}
}
public NotificationServicePlugin_Whatsapp(string configuration)
{
mConfiguration=cConfiguration.GetConfiguration(configuration);
mLog.IntLevel=mConfiguration.TraceLevel;
}
public void HandleMessage(string title, string message, string configurationstring)
{
var credential=mFetchCredential(mConfiguration.Credential);
if(credential is null)
{
mLog.LogError($"Failed to fetch credential: '{mConfiguration.Credential}'");
return;
}
if(credential.Username is null)
{
mLog.LogError($"UserName in credential '{mConfiguration.Credential}' is null");
return;
}
var client=new Azure.Communication.Messages.NotificationMessagesClient(mConvertToUnsecureString(credential.Password));
var channelregistrationid=new System.Guid(credential.Username);
string[] recipients=mGetRecipients(configurationstring);
var messagewithtitle=$"{title}\n {message}";
mLog.LogVerbose($"Sending notification:\n{messagewithtitle}");
//Only one phone number is currently supported in the recipient list.
//https://learn.microsoft.com/en-us/azure/communication-services/quickstarts/advanced-messaging/whatsapp/get-started?tabs=visual-studio%2Cconnection-string&pivots=programming-language-csharp
foreach(var recipient in recipients)
{
mLog.LogVerbose($"Sending notification to recipient: '{recipient}'");
var textnotificationcontent=new Azure.Communication.Messages.TextNotificationContent(channelregistrationid, new System.Collections.Generic.List<string> {recipient}, messagewithtitle);
var sendtextmessageresult=client.Send(textnotificationcontent);
mLog.LogVerbose(sendtextmessageresult);
}
}
private static cAccount? mFetchCredential(string credential)
{
mLog.LogVerbose($"Fetching credential: {credential}");
try
{
var cred=ABB.Vtrin.cDataLoader.cCredential.Read(credential);
if(cred is not null)
{
mLog.LogVerbose($"Succesfully fetched credential: '{credential}'.");
return new cAccount(cred.UserName, cred.Password);
} else
{
mLog.LogVerbose($"Fetching credential '{credential}' returned NULL.");
}
} catch(System.Exception ex)
{
mLog.LogError($"Exception raised when fetching credential '{credential}'. Message: {ex.Message}." );
}
return null;
}
private static string? mConvertToUnsecureString(System.Security.SecureString? securestring)
{
if (securestring==null)
{
return null;
}
var unmanagedstring=nint.Zero;
try
{
unmanagedstring = System.Runtime.InteropServices.Marshal.SecureStringToGlobalAllocUnicode(securestring);
return System.Runtime.InteropServices.Marshal.PtrToStringUni(unmanagedstring);
}
finally
{
System.Runtime.InteropServices.Marshal.ZeroFreeGlobalAllocUnicode(unmanagedstring);
}
}
private static string[] mGetRecipients(string recipients)
{
string[] recipientarray = recipients.Split(new char[] { ';' }, System.StringSplitOptions.RemoveEmptyEntries);
return recipientarray;
}
}
}
Updated 5 months ago
