Creating custom messaging channel

In this article, we describe two ways for creating a custom messaging channel:

  1. Creating separate text files on the server;
  2. Writing messages in a database.

In this example, all system messages (messages, notifications on overdue tasks and new assignments, etc.) will be sent through your own message channel. Using the extension point you can send messages to Twitter, ICQ, Jabber, if necessary.

Example of the Data Display

Fig.1 Creating separate text files on the server

 

Fig. 2 Writing messages to a database

Extension methods (interface)

An extension point (interface) IMessageChannel has the following methods: 

/// <summary>
/// Unique Channel Identifier
/// </summary>
Guid Uid { get; } 
 
/// <summary>
/// Channel Name
/// </summary>
string Name { get; }
 
/// <summary>
/// Display Name
/// </summary>
string DisplayName { get; }
 
/// <summary>
/// Use a Default Name
/// </summary>
bool Default { get; }
 
/// <summary>
/// Send a Message
/// </summary>
/// <param name="message">Message</param>
void Send(IMessage message);

Example of the extension point class

Creating separate text files on the server. 

[Component]
public class MessageChannel : IMessageChannel
{
    private readonly Guid _uid = new Guid("{B2D745F9-9624-40c4-9C07-ABF44281F066}");
    public Guid Uid 
    {
        get { return _uid; }
    }
 
    public string Name
    {
        get { return "TextFileChannel"; }
    }
 
    public string DisplayName
    {
        get { return "The Channel writes messages into text files"; }
    }
 
    public bool Default
    {
        get { return true; }
    }
 
    private static readonly string Filepath = Locator.GetServiceNotNull<IRuntimeApplication>().Configuration.Config.FilePath;
    private static readonly string Fullpath = Path.GetDirectoryName(Filepath);
    private static readonly string HeadDir = Path.Combine(Fullpath, "Messages");
 
    public void Send(IMessage message)
    {
        //Check the message
        if (message == null) throw new ArgumentNullException("message");
 
        //Check the recipient
        var recipient = message.Recipient as IUser;
        if (recipient == null)
        {
            return;
        }
        string recipientDir = Path.Combine(headDir, recipient.ToString());
        if (!Directory.Exists(headDir))
            Directory.CreateDirectory(headDir);
        if (!Directory.Exists(recipientDir))
            Directory.CreateDirectory(recipientDir);
        string path = Path.Combine(recipientDir, string.Format("{0}_{1}-{2}-{3}.{4}.txt",
                                DateTime.Now.ToShortDateString(),
                                DateTime.Now.Hour,
                                DateTime.Now.Minute,
                                DateTime.Now.Second,
                                DateTime.Now.Millisecond));
        if (!File.Exists(path))
        {
            using (StreamWriter file1 =
                new StreamWriter(path, true))
            {
                file1.WriteLine("Subject: {0}\r\n Message text: {1}", message.Subject, message.FullMessageText);
            }
        }
    }
}
Note
The implementation of the extension point implies the following: the Messages folder is created in the configuration.config folder. Inside The Messages folder, the system creates other folders with the names of users who receive messages. The folder with the recipient name contains a text file with the message subject and text.

 

Writing messages to a database.

[Component]
public class MessageChannelDB : IMessageChannel
{
  private readonly Guid _uid = new Guid("{FA1B0A61-B3F6-4f16-A57F-9D6253710D50}");
  public Guid Uid
  {
    get { return _uid; }
  }
 
  public string Name
  {
    get { return "DBChannelMessage"; }
  }
 
  public string DisplayName
  {
    get { return "Database Message Channel"; }
  }
 
  public bool Default
  {
    get { return true; }
  }
     
  public void Send(IMessage message)
  {
    // Check the message
    if (message == null) throw new ArgumentNullException("message");
 
    // Check the recipient
    var recipient = message.Recipient as IUser;
    if (recipient == null)
    {
      return;
    }
    var author = message.Author as IUser;
    if (author == null)
    {
      return;
    }
 
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("SUBJECT", message.Subject);
    parameters.Add("RECIPIENT", recipient.Id);
    parameters.Add("TEXT", message.FullMessageText);
    parameters.Add("AUTHOR", author.Id);
 
 
    FireBirdConnection.SqlQuery("insert into MESSAGES (ID, SUBJECT, RECIPIENT, TEXT, \"DATE\", AUTHOR) values (gen_id(GEN_MESSAGES_ID, 1), @SUBJECT, @RECIPIENT, @TEXT, current_timestamp, @AUTHOR)", 
                  parameters);
  }
}

In this example, a FireBird database query is generated. The query adds a new entry to the MESSAGES table. To implement database connection with the FireBird database and create a database query, the FireBirdConnection.cs class was created.

Code of the FireBirdConnection.cs Class:

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using EleWise.ELMA.Logging;
using EleWise.ELMA.Runtime;
using EleWise.ELMA.Services;
using FirebirdSql.Data.FirebirdClient;
 
namespace MessageChannel.Connection
{
  public static class FireBirdConnection
  {
    private static readonly string Filepath = Locator.GetServiceNotNull<IRuntimeApplication>().Configuration.Config.FilePath; // The path to the configuration file
    private static readonly string HeadDir = Path.GetDirectoryName(Filepath); // Configuration File Directory
    private const string DbName = "BASEMESSAGES.FDB"; //Database name
 
    //Generate a connection string
    private static readonly string Fbconnection = new FbConnectionStringBuilder
        {
          DataSource = "127.0.0.1",
          UserID = "sysdba",
          Password = "masterkey",
          Port = 3056,
          Dialect = 3,
          ServerType = 0,
          Database = Path.Combine(HeadDir, DbName),
          Charset = "UNICODE_FSS"
        }.ToString();
 
    private readonly static FbConnection Fb = new FbConnection(Fbconnection);
 
    public static void SqlQuery(string query, Dictionary<string, object> parameters = null)
    {
      if (Fb.State == ConnectionState.Closed) // if the connection is closed - open it
        Fb.Open();
 
      //Generate a query
      using (var fbCommand = new FbCommand(query, Fb))
      {
        var fbt = Fb.BeginTransaction();
 
        if (parameters != null)
        {
          foreach (var items in parameters)
          {
            fbCommand.Parameters.AddWithValue(items.Key, items.Value);
          }
        }
        fbCommand.Transaction = fbt;
 
        try
        {
          fbCommand.ExecuteNonQuery(); // it is necessary to call this method for queries that do not return a set of data (insert, update, delete)
          fbt.Commit(); // if an entry added successfully - commit the transaction
        }
        catch (Exception exception)
        {
          Logger.Log.Error(exception.Message);
          fbt.Rollback();
        }
      }
    }
  }
}

In the example, we used a FireBird database where we added the new MESSAGES table with the following fields: ID, SUBJECT, RECIPIENT, TEXT, DATE, and AUTHOR. The ID field must have Primary Key and NotNull attributes. You also need to create a generator (in this example the generator has GEN_MESSAGES_ID name).

Script for creating tables in the FireBird database:

CREATE TABLE MESSAGES (
    ID         BIGINT NOT NULL,
    SUBJECT    VARCHAR(255),
    RECIPIENT  INTEGER,
    TEXT       VARCHAR(255),
    "DATE"     TIMESTAMP,
    AUTHOR     INTEGER
);
ALTER TABLE MESSAGES ADD PRIMARY KEY (ID);

The following example shows how to create a message channel for multiple recipients. The messages will be sent to a database in a single transaction.

In this example, all system messages (messages, notifications on overdue tasks and new assignments, etc.) will be sent through your own message channel. If necessary, using the extension point you can send messages to Twitter, ICQ, Jabber.

In this example, we implemented the extension point IGroupingMessageChannel, which has the public void method Send(IMessage message, IEnumerable <EleWise.ELMA.Security.IUser> recipients). This allows you to send messages to all recipients at once and saves your time.

Example of Data Display

Fig.1 Writing messages in a database

Extension Method (interface)

An extension point (interface) IGrouping MessageChannel has the following methods:

/// <summary>
/// The unique channel identifier
/// </summary>
Guid Uid { get; } – You need to define the Uid for your messaging channel 
 
/// <summary>
/// Chanel name
/// </summary>
string Name { get; }
 
/// <summary>
/// Display name
/// </summary>
string DisplayName { get; }
 
/// <summary>
/// Use the default name
/// </summary>
bool Default { get; }
 
/// <summary>
/// Send a message
/// </summary>
/// <param name="message">Message</param>
void Send(IMessage message);
/// <summary>
/// Send a message to multiple recipients
/// </summary>
/// <param name="message"> Message</param>
/// <param name="recipients">List of recipients </param>
void Send(IMessage message, IEnumerable<IUser> recipients);

Example of the extension point class

Messages are written in the database.

[Component]
public class GroupingMessageChannel : IGroupingMessageChannel
{
  private readonly Guid _uid = new Guid("{F2E1B073-DADA-4b13-805C-FE2CE3FA9375}");
 
  public Guid Uid
  {
    get { return _uid; }
  }
 
  public string Name
  {
    get { return "DBGropingMessageChannel"; }
  }
 
  public string DisplayName
  {
    get { return "A channel to send group messages to a DB"; }
  }
 
  public bool Default
  {
    get { return true; }
  }
 
  public void Send(IMessage message)
  {
    var recipient = message.Recipient as IUser;
    if (recipient == null)
    {
      return;
    }
    Send(message, new[] {recipient});
  }
 
  public void Send(IMessage message, IEnumerable<EleWise.ELMA.Security.IUser> recipients)
  {
    //Check message
    if (message == null) throw new ArgumentNullException("message");
 
    var recUsers = recipients as IUser[] ?? recipients.ToArray();
    if (recUsers.Length == 0) return;
 
    var author = message.Author as IUser;
    if (author == null)
    {
      return;
    }
 
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("SUBJECT", message.Subject);
    parameters.Add("TEXT", message.FullMessageText);
    parameters.Add("AUTHOR", author.Id);
 
    string query = string.Empty;
 
    foreach (IUser recipient in recUsers)
    {
      query +=
        string.Format(
          "insert into MESSAGES (SUBJECT, RECIPIENT, TEXT, DATE, AUTHOR) values (@SUBJECT, {0}, @TEXT, current_timestamp, @AUTHOR) ",
          recipient.Id);
    }
    MSSQLConnection.SqlQuery(query, parameters);
  }
}
Note
In this example, messages are sent to all recipients at once in a single transaction. In order to avoid duplication of the code in the public void Send (IMessage message) method, the public void Send (IMessage message, IEnumerable <EleWise.ELMA.Security.IUser> recipients) method is called.

Writing messages in the database

In this example, the MSSQL database query is generated. The query adds multiple entries in the MESSAGES table in a single transaction. To implement MSSQL database connection and create a database query the MSSQLConnection.cs class was created.

Code of the MSSQLConnection.cs Class:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using EleWise.ELMA.Logging;
 
namespace MessageChannel.Connection
{
  public static class MssqlConnection
  {
    //Generate a connection string 
    private static readonly string MssqlconnectionString = new SqlConnectionStringBuilder
    {
      DataSource = "(local)",
      UserID = "sa",
      Password = "p@ssworD",
      InitialCatalog = "BASEMESSAGES"
    }.ToString();
 
    private static readonly SqlConnection SqlConnection = new SqlConnection(MssqlconnectionString);
 
    public static void SqlQuery(string query, Dictionary<string, object> parameters = null)
    {
      if (SqlConnection.State == ConnectionState.Closed) // if the connection is closed - open it
        SqlConnection.Open();
 
      //Generate a query
      using (var sqlCommand = new SqlCommand(query, SqlConnection))
      {
        var transaction = SqlConnection.BeginTransaction();
 
        if (parameters != null)
        { 
          foreach (var items in parameters)
          {
            sqlCommand.Parameters.AddWithValue(items.Key, items.Value);
          }
        }
        sqlCommand.Transaction = transaction;
 
        try
        {
          sqlCommand.ExecuteNonQuery(); // it is necessary to call this method for the queries that do not return a set of data (insert, update, delete)
          transaction.Commit(); // if an entry added successfully - commit the transaction

        }
        catch (Exception exception)
        {
          Logger.Log.Error(exception.Message);
          transaction.Rollback();
        }
      }
    }
 
  }
}

In the example, we used a MSSQL database, which needs to be created. The following is the script to create a table in the database BASEMESSAGES:

 CREATE TABLE [BASEMESSAGES].[dbo].[MESSAGES](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [SUBJECT] [nvarchar](255) NULL,
    [RECIPIENT] [bigint] NULL,
    [TEXT] [nvarchar](255) NULL,
    [AUTHOR] [bigint] NULL,
    [DATE] [datetime] NULL,
 CONSTRAINT [PK_MESSAGES] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

 Links to API elements 

IMessageChannel
IGroupingMessageChannel

Attachments