Создание собственного канала отправки сообщений

 В статье приведено два примера создания собственного канала отправки сообщений:

  1. Создание сообщений отдельными текстовыми файлами на сервере.
  2. Запись сообщений в базу данных.

В данном примере все сообщения системы (отправка сообщений, оповещения о просрочке задач, активация задач и так далее) будут отправляться через Ваш собственный канал сообщений. С помощью данной точки расширения можно реализовать канал отправки сообщений в Twitter, ICQ, Jabber при необходимости.

Пример отображения данных

Рис. 1. Создание сообщений отдельными текстовыми файлами на сервере

Рис. 2. Запись сообщений в БД

Методы расширения (интерфейса)

Точка расширения (интерфейс) IMessageChannel имеет следующие методы:

/// <summary>
/// Уникальный идентификатор канала
/// </summary>
Guid Uid { get; } 

/// <summary>
/// Имя канала
/// </summary>
string Name { get; }

/// <summary>
/// Имя для отображения
/// </summary>
string DisplayName { get; }

/// <summary>
/// Использовать по умолчанию
/// </summary>
bool Default { get; }

/// <summary>
/// Отправить сообщение
/// </summary>
/// <param name="message">Сообщение</param>
void Send(IMessage message);

Пример класса точки расширения

Создание сообщений отдельными текстовыми файлами на сервере.

[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 "Канал отправки сообщений в текстовые файлы"; }
    }

    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)
    {
        //Проверка на наличие сообщения
        if (message == null) throw new ArgumentNullException("message");

        //Проверка получателя
        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("Тема: {0}\r\n Текст сообщения: {1}", message.Subject, message.FullMessageText);
            }
        }
    }
}
Примечание
Данная реализация точки расширения выполняет следующее: создает папку Messages в папке с конфигурацией configuration.config. Внутри этой папки будут созданы папки с именами пользователей в зависимости от получателей сообщения. В папке с получателем сообщения создается новый текстовый файл, внутри которого находится тема сообщения и текст сообщения.

Запись сообщений в базу данных.

[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 "Канал отправки сообщений в БД"; }
  }

  public bool Default
  {
    get { return true; }
  }
    
  public void Send(IMessage message)
  {
    //Проверка на наличие сообщения
    if (message == null) throw new ArgumentNullException("message");

    //Проверка получателя
    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);
  }
}

В данном примере формируется запрос в базу данных FireBird, который добавляет новую запись в таблицу MESSAGES. Для реализации подключения к базе данных FireBird и создания запроса в базу данных был создан класс FireBirdConnection.cs.

Код класса FireBirdConnection.cs:

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; //Путь до файла конфигурации
    private static readonly string HeadDir = Path.GetDirectoryName(Filepath); //Директория файла конфигурации
    private const string DbName = "BASEMESSAGES.FDB"; //Имя базы данных

    //Формируем строку подключения
    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) //если соединение закрыто - откроем его
        Fb.Open();

      //Создаем запрос
      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(); //для запросов, не возвращающих набор данных (insert, update, delete) надо вызывать этот метод
          fbt.Commit(); //если вставка прошла успешно - коммитим транзакцию
        }
        catch (Exception exception)
        {
          Logger.Log.Error(exception.Message);
          fbt.Rollback();
        }
      }
    }
  }
}

В данном примере использовалась база данных FireBird, которую необходимо создать, добавить в неё новую таблицу MESSAGES с полями: IDSUBJECTRECIPIENTTEXTDATE, AUTHOR. Поле ID необходимо сделать Primary Key и NotNull, а также создать генератор (в примере генератор имеет имя GEN_MESSAGES_ID).

Скрипт создания таблицы в БД FireBird:

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);

В следующем примере показано как создать свой канал сообщений с несколькими получателями. Сообщения будут отправляться в базу данных одной транзакцией.

В данном примере все сообщения системы (отправка сообщений, оповещения о просрочке задач, активация задач и так далее) будут отправляться через Ваш собственный канал сообщений. При необходимости с помощью данной точки расширения можно реализовать канал отправки сообщений в Twitter, ICQ, Jabber.

В данном примере реализована точка расширения IGroupingMessageChannel, в которой есть метод public void Send(IMessage message, IEnumerable<EleWise.ELMA.Security.IUser> recipients), позволяющий отправлять сообщения всем получателям сразу. Это позволяет сократить время на отправку сообщений.

Пример отображения данных

Рис. 1. Запись сообщений в БД

Методы расширения (интерфейса)

Точка расширения (интерфейс) IGroupingMessageChannel имеет следующие методы:

/// <summary>
/// Уникальный идентификатор канала
/// </summary>
Guid Uid { get; } – генерировать Uid для своего канала отправки сообщений нужно самостоятельно.

/// <summary>
/// Имя канала
/// </summary>
string Name { get; }

/// <summary>
/// Имя для отображения
/// </summary>
string DisplayName { get; }

/// <summary>
/// Использовать по умолчанию
/// </summary>
bool Default { get; }

/// <summary>
/// Отправить сообщение
/// </summary>
/// <param name="message">Сообщение</param>
void Send(IMessage message);
/// <summary>
/// Отправить сообщение сразу нескольким получателям
/// </summary>
/// <param name="message">Сообщение</param>
/// <param name="recipients">Список пользователей - получателей сообщения</param>
void Send(IMessage message, IEnumerable<IUser> recipients);

Пример класса точки расширения

Запись сообщений в базу данных.

[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 "Канал отправки сообщений в БД одной транзакцией"; }
  }

  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)
  {
    //Проверка на наличие сообщения
    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);
  }
}
Примечание
в данном примере сообщения отправляются сразу всем получателям в одной транзакции. Во избежание дублирования кода, в методе public void Send(IMessage message) вызывается метод public void Send(IMessage message, IEnumerable<EleWise.ELMA.Security.IUser> recipients)

Запись сообщений в базу данных

В данном примере формируется запрос в базу данных MSSQL, который добавляет несколько записей в одной транзакции в таблицу MESSAGES. Для реализации подключения к базе данных MSSQL и создания запроса в базу данных был создан класс MSSQLConnection.cs.

Код класса MSSQLConnection.cs:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using EleWise.ELMA.Logging;

namespace MessageChannel.Connection
{
  public static class MssqlConnection
  {
    //Формируем строку подключения
    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) //если соединение закрыто - откроем его
        SqlConnection.Open();

      //Создаем запрос
      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(); //для запросов, не возвращающих набор данных (insert, update, delete) надо вызывать этот метод
          transaction.Commit(); //если вставка прошла успешно - коммитим транзакцию
        }
        catch (Exception exception)
        {
          Logger.Log.Error(exception.Message);
          transaction.Rollback();
        }
      }
    }

  }
}

В данном примере использовалась база данных MSSQL, которую необходимо создать, скрипт создания таблицы в базе данных 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]

Ссылки на элементы API

Прикрепленные файлы