[ELMA3] Создание собственного канала отправки сообщений
В статье приведено два примера создания собственного канала отправки сообщений:
- Создание сообщений отдельными текстовыми файлами на сервере.
- Запись сообщений в базу данных.
В данном примере все сообщения системы (отправка сообщений, оповещения о просрочке задач, активация задач и так далее) будут отправляться через Ваш собственный канал сообщений. С помощью данной точки расширения можно реализовать канал отправки сообщений в 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);
}
}
}
}
Запись сообщений в базу данных.
[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 с полями: ID, SUBJECT, RECIPIENT, TEXT, DATE, 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);
}
}
Запись сообщений в базу данных
В данном примере формируется запрос в базу данных 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]