NETGeniumConnection.dll library for .NET Framework 4.7.2 | .NET 9 | .NET 10

1. General information

  • Console applications are used to call custom C# program code, either on the server side or on the client side.
  • Console applications are used in cases where
    • one-time service activities need to be run over the database or
    • these activities need to be performed at regular intervals.
  • Console applications can be set to run regularly in the Windows Task Scheduler, or they can be customized to run as a Windows service.
  • Console applications are also used to prepare and debug external functions. A detailed description of external functions is provided in the separate guide External functions.
  • Console applications connect to the NET Genium database
    • directly from an application server that is located on the same network as the database server (the application server and the database server are often the same computer), or
    • using the web services that are part of every NET Genium.
  • Direct connection to the NET Genium database is the recommended method because it is an order of magnitude faster than connection via web services, and it supports database transactions that cannot be used at all in the case of web services.
  • Debugging or editing source codes that use a direct connection to the NET Genium database requires a database ideally located on the local computer or on the same network. This is often not possible either for security reasons or because the database is too large, which is not realistic to back up on the database server and transfer to the local computer. In this case, it is recommended to debug the source code using APIs in conjunction with web services.
  • Console applications use objects and methods from the “NETGeniumConnection.dll” library, which is located in the “N:\NetGenium\Projekty\NetGenium\References” directory. The “NETGeniumConnection.dll” library provides core functions for working with the database and file attachments.
  • Console applications are developed through a separate project in the application “Visual Studio 2015” and higher, respectively. programming the source code of this project, and then compiling the project into an “exe” file.

2. Creating a console application

  • In the first step, it is necessary to create a new console application project in Visual Studio using the following steps, and add a reference to the “NETGeniumConnection.dll” library:
    • Start Visual Studio
    • From the menu on the main bar, select “File / New / Project...” (Ctrl+Shift+N)
      • Project type: Console App (.NET Framework)
      • Project name: ConsoleApp1
      • Location: optional project location
      • Solution: Create new solution
      • Place solution and project in the same directory: Yes
      • Create directory for solution: No (Visual Studio 2015)
      • Framework: .NET Framework 4.7.2
    • Right-click on “References”, select “Add Reference...”, and select the path to the file “References\NETGeniumConnection.dll” on the computer disk
    • Choose “Debug” compilation mode
      • “Debug” mode generates “exe” and “pdb” files by default
      • Thanks to the “pdb” file, errors and interrupts in console applications are easily detected, because the “Stack Trace” of each error also includes the file name and the line number on which the interrupt occurred.
      • The “Release” mode is only recommended for the final version of the tuned source code in the console application. By default, “Release” mode only generates an “exe” file.
    • Start project – select “Debug” / “Start Debugging” (F5) from the menu on the main bar

2.1. Direct connection to the NET Genium database

  • The direct connection to the database uses the “DbConnection” class located in the “NETGenium” namespace. This class behaves analogously to the usual class “DbConnection” from the namespace “System.Data” or “SqlConnection” from the namespace “System.Data.SqlClient”.
  • The “DbConnection” class is universal, and is used for both Firebird and MSSQL database servers.
using NETGenium;
using System;

namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
NETGeniumConsole console = new NETGeniumConsole();

using (DbConnection conn = new DbConnection(@"driver=firebird;datasource=localhost;user=SYSDBA;password=masterkey;database=C:\Firebird\netgenium.fdb;charset=WIN1250;collation=WIN_CZ"))
// using (DbConnection conn = new DbConnection("server=(local);Trusted_Connection=true;database=netgenium"))
using (DbCommand cmd = new DbCommand(conn))
{
conn.RootPath = "C:\\inetpub\\wwwroot\\netgenium";
conn.Open();
console.WriteLine("Connected to database");
}

console.Exit();
}
}
}

2.2. Connect to the NET Genium database using web services

  • An alternative to connecting directly to the database using the “DbConnection” object is to use the “NETGeniumConnection” object. It is a class located in the “System” namespace of the “NETGeniumConnection.dll” library, which makes it possible to connect to the NET Genium database using web services.
  • Web services as well as the “NETGeniumConnection.dll” library form a single API, and are part of every NET Genium.
  • Most of the methods used to work with data in the “NETGeniumConnection.dll” library have implemented both the method variant specified for the “DbConnection” object and for “NETGeniumConnection”.
  • After creating an instance of the “NETGeniumConnection” object, it is necessary to log in with the name and password of a user with administrator rights. This login is subject to the IP address restrictions defined in the NET Genium settings, and if a password strength is set, the “Administrator” account with an empty password logs in only from the IP address 127.0.0.1. An account for which the “NETGenium.LoginCode” external function starts two-factor authentication cannot log in this way; a detailed description of the function is provided in the separate guide External functions. Each such login draws licenses to log in to NET Genium, so it is important to log out at the end of the work. A login of a user without administrator rights succeeds and draws a license, but the user has no access to data or attachments: apart from logging out, only calling an external function with the “ngef” method succeeds, and only for a function allowed by the “NETGenium.RegisterExternalFunction” callback or by the “Public external functions” list for the user's user groups.
  • A connection through web services that has not sent any request for more than an hour ends as soon as the same user logs in again through web services, with a token through the integrated MCP server (only by a request that starts a new session for the token – the first one or one after the previous session of the token has ended; requests within an existing session of the token do not end the connection), or through Windows from an address with the “autologin” parameter; further method calls then fail and it is necessary to log in again. A long-running service should therefore expect a new login after a longer period of inactivity, or use its own account.
  • Regardless of inactivity, a connection through web services also ends when NET Genium restarts no sooner than a minute after the previous restart (including an update; with multiple instances over one database when any other instance restarts no sooner than 6 hours after the previous restart), when the session is logged out in the list of online users, when the user is deleted and – if the “SingleLogin.txt” configuration file exists – with every further login of the same user through web services, with a token through the integrated MCP server (only by a request that starts a new session for the token) or through Windows from an address with the “autologin” parameter; further method calls then fail and it is necessary to log in again, the library does not log in again by itself. A long-running service should therefore expect a new login at any time.
  • The “NETGeniumConnection” class is universal, and is used for both Firebird and MSSQL database servers.
  • Communication with the remote NET Genium should always be secured with an SSL certificate. The only exception when we can use insecure communication is the connection to the local NET Genium via the address “http://localhost/...”.
  • Connecting through web services is not supported by the NET Genium Online cloud edition (instances at “identifier.netgenium.online”). Remote database access through the “NETGeniumConnection” library is available only with a full installation of NET Genium; data of a NET Genium Online instance is accessed from custom code through the MCP server (see NET Genium Online).
using NETGenium;
using System;

namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
NETGeniumConsole console = new NETGeniumConsole();

if (true) // Option 1
{
using (DbConnection conn = new DbConnection())
{
conn.Open("http://localhost/netgenium", "NETGeniumConnection", "password");
}
}

if (true) // Option 2
{
NETGeniumConnection conn = new NETGeniumConnection("http://localhost/netgenium");
conn.Login("NETGeniumConnection", "password");
conn.Logout();
}

console.Exit();
}
}
}

3. Reading data from the database

3.1. Retrieve records from an SQL query into a DataTable object

// using NETGenium;
// using System;
// using System.Data;

DataTable data = Data.Get("SELECT * FROM sholiday WHERE date_ > " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);
Console.WriteLine(conn.User.FormatDataTableText(data));

foreach (DataRow row in data.Rows)
{
Console.WriteLine(row["id"]);
}

3.2. Saves the DataTable object to disk and reloads it from disk

// using NETGenium;
// using System;
// using System.Data;

DataTable data = Data.Get("SELECT * FROM sholiday WHERE date_ > " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);

// HTML
Files.Write(Config.RootPath + "sholiday.html", conn.User.FormatDataTable(data));

// TXT
Files.Write(Config.RootPath + "sholiday.txt", conn.User.FormatDataTableText(data));

// XML
DataSet ds = new DataSet();
ds.Tables.Add(data);
ds.WriteXml(Config.RootPath + "sholiday.xml", XmlWriteMode.WriteSchema);

ds = new DataSet();
ds.ReadXml(Config.RootPath + "sholiday.xml", XmlReadMode.ReadSchema);
data = ds.Tables[0];

// JSON
Files.Write(Config.RootPath + "sholiday.json", JSon.ToString(data));
data = JSon.ToDataTable(File.ReadAllText(Config.RootPath + "sholiday.json"));

3.3. Retrieve records from an SQL query into a DbRow object

// using NETGenium;
// using System;

DbRow row = new DbRow("SELECT * FROM sholiday WHERE date_ > " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);
if (row.Read())
{
Console.WriteLine(row["id"]);
Console.WriteLine(row.Report());
}

3.4. Value parsing

// using NETGenium;
// using System;

DbRow row = new DbRow("SELECT * FROM sholiday WHERE date_ > " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);
if (row.Read())
{
int id = (int)row["id"];
Console.WriteLine("id: " + id);

int pid = Parser.ToInt32(row["pid"]);
Console.WriteLine("pid: " + pid);

double _pid = Parser.ToDouble(row["pid"]);
Console.WriteLine("pid: " + _pid);

string name = row["name"].ToString();
Console.WriteLine("name: " + name);

DateTime date = Parser.ToDateTime(row["date_"]);
Console.WriteLine("date: " + conn.User.FormatDateTime(date));
}

3.5. Indexing of retrieved records by primary key

// using NETGenium;
// using System;
// using System.Collections.Generic;
// using System.Data;

DataTable data = Data.Get("SELECT * FROM sholiday", conn);
Dictionary<int, DataRow> dictionary = Data.DictionaryInt32(data);

int id = 1;
if (dictionary.ContainsKey(id))
{
DataRow row = dictionary[id];
Console.WriteLine(row["id"]);
}

3.6. Indexing of retrieved nested records according to the foreign key “pid”

// using NETGenium;
// using System;
// using System.Collections.Generic;
// using System.Data;

DataTable data = Data.Get("SELECT * FROM sholiday", conn);
Dictionary<int, List<DataRow>> dictionary = Data.DictionaryInt32(data, "pid", true);

int pid = 0;
if (dictionary.ContainsKey(pid))
{
List<DataRow> rows = dictionary[pid];
Console.WriteLine(rows.Count + " rows");
}

3.7. Sequential reading of records

// using NETGenium;
// using System;

using (DbCommand cmd = new DbCommand("SELECT id, date_ FROM sholiday WHERE date_ > " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn))
using (DbDataReader dr = cmd.ExecuteReader())
while (dr.Read())
{
Console.WriteLine(dr["id"] + ": " + dr["date_"]);
}

3.8. Sequential reading of records into the dictionary

// using NETGenium;
// using System;
// using System.Collections.Generic;

Dictionary<int, DateTime> dictionary = new Dictionary<int, DateTime>();

using (DbCommand cmd = new DbCommand("SELECT id, date_ FROM sholiday WHERE date_ > " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn))
using (DbDataReader dr = cmd.ExecuteReader())
while (dr.Read())
{
dictionary.Add((int)dr["id"], (DateTime)dr["date_"]);
}

3.9. Sequential reading of records into the sheet

// using NETGenium;
// using System;
// using System.Collections.Generic;

private class Item
{
public int ID;
public DateTime Date;

public Item(DbDataReader dr)
{
ID = (int)dr["id"];
Date = (DateTime)dr["date_"];
}
}

List<Item> items = new List<Item>();

using (DbCommand cmd = new DbCommand("SELECT id, date_ FROM sholiday WHERE date_ > " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn))
using (DbDataReader dr = cmd.ExecuteReader())
while (dr.Read())
{
items.Add(new Item(dr));
}

4. Writing data to the database

4.1. INSERT INTO – create a new record in the database

4.1.1. DataSaver – create one record

// using NETGenium;
// using System;

DataSaver ds = new DataSaver("sholiday", 0, cmd);
ds.Add("name", "New year");
ds.Add("date_", new DateTime(DateTime.Today.Year, 1, 1));
ds.Execute();

Console.WriteLine(ds.Report());
 

4.1.2. DataSaver – creation of two records in a separate transaction

A transaction opened by the “BeginTransaction” method on a connection applies to all commands and reads on that connection, so reads inside the transaction see writes that have not been committed yet. If the “using” block ends without calling “Commit”, typically by an exception, the transaction is rolled back. On a connection opened via web services (the “Open” method with a URL, login name and password), the “BeginTransaction” method reports no error but creates no transaction – each write is committed immediately and nothing is rolled back.

// using NETGenium;
// using System;

using (DbTransaction trans = conn.BeginTransaction())
{
DataSaver ds = new DataSaver("sholiday", 0, cmd);
ds.Add("name", "New year");
ds.Add("date_", new DateTime(DateTime.Today.Year, 1, 1));
ds.Execute();

Console.WriteLine(ds.Report());

ds = new DataSaver("sholiday", 0, cmd);
ds.Add("name", "New year");
ds.Add("date_", new DateTime(DateTime.Today.Year + 1, 1, 1));
ds.Execute();

Console.WriteLine(ds.Report());

trans.Commit();
}
 

4.1.3. DataSaver – create one record by copying another record

// using NETGenium;
// using System;

int id = 1;

DbRow row = new DbRow("SELECT * FROM sholiday WHERE id = " + id, conn);
if (row.Read())
{
// row["ng_enteredby"] = conn.User.LoginName;
// row["ng_enteredwhen"] = DateTime.Now;
// row["ng_changedby"] = DBNull.Value;
// row["ng_changedwhen"] = DBNull.Value;

DataSaver ds = new DataSaver("sholiday", 0, cmd);
ds.Add(row);
ds.Execute();

Console.WriteLine(ds.Report());
}
 

4.1.4. DataSaverSynchro – creation of one record, including creation of history record and ensuring record synchronization

// using NETGenium;
// using System;

DataSaverSynchro ds = new DataSaverSynchro("sholiday", 0, conn);
ds.Add("name", "New year");
ds.Add("date_", new DateTime(DateTime.Today.Year, 1, 1));
ds.Save(cmd);

Console.WriteLine(ds.Report());
 

4.1.5. DataSaverSynchro – creation of two records in a separate transaction

// using NETGenium;
// using System;

using (DbTransaction trans = conn.BeginTransaction())
{
DataSaverSynchro ds = new DataSaverSynchro("sholiday", 0, conn);
ds.Add("name", "New year");
ds.Add("date_", new DateTime(DateTime.Today.Year, 1, 1));
ds.Save(cmd);

Console.WriteLine(ds.Report());

ds = new DataSaverSynchro("sholiday", 0, conn);
ds.Add("name", "New year");
ds.Add("date_", new DateTime(DateTime.Today.Year + 1, 1, 1));
ds.Save(cmd);

Console.WriteLine(ds.Report());

trans.Commit();
}

Inside a transaction, a record is always saved by the “Save(cmd)” method – the overload without a command, “Save(user, ng_rootPath)”, would write in its own transaction on a separate connection.

4.2. UPDATE – editing an existing record in the database

4.2.1. DataSaver – editing a record whose ID is retrieved from the database

// using NETGenium;
// using System;

int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("New year") + " AND date_ = " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);
if (id != 0)
{
DataSaver ds = new DataSaver("sholiday", id, cmd);
ds.Add("name", "New year – TEST");
ds.Execute();

Console.WriteLine(ds.Report());
}
 

4.2.2. DataSaver – editing a record whose ID is stored in a variable; if the record does not exist, it will be created

// using NETGenium;
// using System;

int id = 1;

DataSaver ds = new DataSaver("sholiday", id, true, cmd);
ds.Add("name", "New year – TEST");
ds.Execute();

Console.WriteLine(ds.Report());
 

4.2.3. DataSaverSynchro – editing a record whose ID is retrieved from the database, including creating a history record and ensuring record synchronization

// using NETGenium;
// using System;

int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("New year") + " AND date_ = " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);
if (id != 0)
{
DataSaverSynchro ds = new DataSaverSynchro("sholiday", id, conn);
ds.Add("name", "New year – TEST");
ds.Save(cmd);

Console.WriteLine(ds.Report());
}
 

4.2.4. DataSaverSynchro – editing a record whose ID is stored in a variable, including creating a history record and ensuring record synchronization; if the record does not exist, it will be created

// using NETGenium;
// using System;

int id = 1;

DataSaverSynchro ds = new DataSaverSynchro("sholiday", id, true, conn);
ds.Add("name", "New year – TEST");
ds.Save(cmd);

Console.WriteLine(ds.Report());

4.3. Data synchronization of two database tables according to a single key

// using NETGenium;
// using System;
// using System.Collections.Generic;
// using System.Data;

string table1 = "ng_book1", table2 = "ng_book2", key = "ng_isbn";

DataTable data1 = Data.Get("SELECT * FROM " + table1, conn), data2 = Data.Get("SELECT * FROM " + table2, conn);
Dictionary<object, DataRow> dictionary = Data.Dictionary(data2, key);

foreach (DataRow row1 in data1.Rows)
{
DataRow row2 = Data.DataRow(dictionary, row1[key]);
if (row2 == null)
{
DataSaver ds = new DataSaver(table2, 0, cmd);

foreach (DataColumn c in data2.Columns)
if (c.ColumnName != "id" && c.ColumnName != "fulltexttimestamp" && data1.Columns.Contains(c.ColumnName))
{
ds.Add(c.ColumnName, row1[c.ColumnName]);
}

ds.Execute();
}
else
{
DataSaver ds = new DataSaver(table2, (int)row2["id"], cmd);

foreach (DataColumn c in data2.Columns)
if (c.ColumnName != "id" && c.ColumnName != "fulltexttimestamp" && data1.Columns.Contains(c.ColumnName) && row2[c].ToString() != row1[c.ColumnName].ToString())
{
ds.Add(c.ColumnName, row1[c.ColumnName]);
}

if (!ds.Empty)
{
ds.Execute();
}
}
}

5. Deleting data from the database

5.1. DELETE FROM – delete a record from the database

// using NETGenium;
// using System;

int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("New year – TEST") + " AND date_ = " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);
if (id != 0)
{
cmd.CommandText = "DELETE FROM sholiday WHERE id = " + id;
cmd.ExecuteNonQuery();
}

5.2. DataSaverSynchro – deleting a record from the database, including creating a history record and ensuring record synchronization

// using NETGenium;
// using System;

int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("New year – TEST") + " AND date_ = " + conn.Format(new DateTime(DateTime.Today.Year, 1, 1)), conn);
if (id != 0)
{
DataSaverSynchro ds = new DataSaverSynchro("sholiday", id, conn);
ds.Delete(cmd);
}

6. File attachments

6.1. Creating a file attachment

// using NETGenium;
// using System;
// using System.IO;

string temp = Path.GetTempFileName();
Files.Write(temp, "abc");

int file = Attachment.Add("test.txt", temp, conn);
File.Delete(temp);
Console.WriteLine(file);

6.2. Retrieve the contents of a file attachment using a DbConnection object

// using NETGenium;
// using System;
// using System.IO;

string temp = Path.GetTempFileName();
Files.Write(temp, "abc");

int file = Attachment.Add("test.txt", temp, conn);
File.Delete(temp);
Console.WriteLine(file);

string path = Attachment.FilePath(file, conn);
if (File.Exists(path))
{
string s = File.ReadAllText(path);
Console.WriteLine(s);
}

6.3. Retrieve the contents of a file attachment using the NETGeniumConnection object

// using NETGenium;
// using System;
// using System.IO;

string temp = Path.GetTempFileName();
Files.Write(temp, "abc");

int file = Attachment.Add("test.txt", temp, conn);
File.Delete(temp);
Console.WriteLine(file);

// Attachment
Attachment = conn.GetAttachment(id);
if (attachment != null)
{
Console.WriteLine(attachment.Name + ": " + attachment.ContentBytes.Length + " bytes");
}

// byte[]
byte[] buffer = conn.GetAttachmentAsBytes(id);
if (buffer != null)
{
Console.WriteLine(buffer.Length + " bytes");
}

// string
string s = conn.GetAttachmentAsString(id, Encoding.UTF8);
if (s != null)
{
Console.WriteLine(s);
}

7. E-mails

7.1. Sending an e-mail message

// using NETGenium;
// using System;
// using System.Net.Mail;

string html = NETGenium.Email.Message.Container("<b>Hello</b>");

MailMessage message = new MailMessage();
message.From = new MailAddress("@");
message.To.Add(new MailAddress("@"));
message.Subject = "";
message.AlternateViews.Add(Config.CreateTextAlternateView(Html.ToText(html)));
message.AlternateViews.Add(Config.CreateHtmlAlternateView(html, conn));

Config.SendMessage(message, conn);

8. Logging

8.1. Logging to disk in the “Logs” directory

// using NETGenium;
// using System;
// using System.IO;

try
{
throw new NullReferenceException("args");
}
catch (Exception ex)
{
L.E("MyFirstFunction", ex);
// L.LogError("MyFirstFunction", ex);
// L.Error("MyFirstFunction", ex);

L.W("MyFirstFunction", ex);
// L.LogWarning("MyFirstFunction", ex);
// L.Warning("MyFirstFunction", ex);

L.N("MyFirstFunction", ex);
// L.LogNotice("MyFirstFunction", ex);
// L.Notice("MyFirstFunction", ex);
}
  • A call with a single text parameter (the message, optionally with an exception) writes the entry into the daily log file “Logs\yyyy-MM-dd.log” – in the example above, “MyFirstFunction” is therefore the message text, not the file name.
  • An entry is written into a separate log file by a call whose first parameter is the file name prefix, for example “L.E("MyFirstFunction", "Message", ex)”. The file name is built from the prefix as follows:
    • the prefix does not contain “yyyy” – the file is monthly, for example “Logs\MyFirstFunction-2026-09.log”,
    • the prefix contains “yyyy” – the text before “yyyy” stays unchanged and the rest of the prefix from “yyyy” on is used as a .NET date format, so the prefix “PDDP-yyyy-MM-dd” creates the daily file “Logs\PDDP-2026-09-29.log” and the prefix “PDDP-yyyy” the yearly file “Logs\PDDP-2026.log”,
    • the prefixes “Errors”, “System”, “Version” and “Warnings” – a file without a date, for example “Logs\Errors.log”.
  • All text after “yyyy” is interpreted as a date format, so letters such as “d”, “M”, “H”, “m”, “s” or “t” are replaced with date values. Text that is to remain unchanged after the date is enclosed in apostrophes, for example “PDDP-yyyy-MM-dd'-import'”.

8.2. General disk logging

// using NETGenium;
// using System;

Files.Write(Config.RootPath + "test.log", "TEST");

try
{
Files.WriteLine(Config.RootPath + "test.log", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + Convert.ToChar(160) + "TEST");
}
catch { }

Files.WriteLineTryCatch(Config.RootPath + "test.log", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + Convert.ToChar(160) + "TEST");

9. Services

9.1. General information

  • Console applications can be installed as a Windows service, so they then run in the background of the operating system and run at regular intervals.
  • Console applications in service mode (“Services”) must have a defined service name and a unique GUID of the console application project.
    • The service name is used when installing the service, and under this name the service can be found (stopped, started, restarted) in the list of Windows operating system services or in the Task Manager.
    • The project GUID is used to safely start only one instance of the service at a time so that multiple instances of the service do not start in parallel (when copying an existing console application project, it is necessary to set a new project GUID so that the two services do not conflict with each other). The protection applies to each iteration separately and the service name is decisive together with the GUID: a second instance of the same service waits 3 seconds, writes a warning to the log and skips its iteration.
  • The services use configuration files from which they read the connection settings to the database, the address of the outgoing mail server, the start and end time of the technological pause during which the service is not working, etc.
  • The services do not connect to the database themselves, and use a unified system to call the main source code that occurs in a common console application within the “static void Main (string[] args)” method.
  • Services do not have access to attached disk drives, only hard or network drives. Disks mounted using the “mount” command must be accessed using a network path, such as “\\192.168.0.1\D\NETGenium”.
  • The console application of a service requires administrator privileges to be assigned using the manifest (see the Manifest chapter), otherwise basic operations such as backup, read or write to disk, etc. fail when it is run in the console and in the service settings dialog (including the installation of the service). The installed service runs under the account selected during installation (the default is “LocalSystem”), and the manifest does not affect it.

9.2. Create a service envelope and edit the “Program.cs” file

  • In the first step, it is necessary to move the console application logic located in the “static void Main (string[] args)” method to the new “Service.cs” file.
  • The instance of the “DbConnection”, “DbCommand”, and NET Genium directory settings must be removed from this source code.
  • The minimum source code of the service shows the following example, the namespace and the name of the service are set to “CRMService” for clarity:
using NETGenium;
using System;

namespace CRMService
{
public class Service : ServiceTemplate, IServiceTemplate
{
public override string ServiceName { get { return "CRMService"; } }

public Service()
{
}

protected override void HandleProcess(string[] args, Config config, DbCommand cmd, DbConnection conn)
{
DateTime now = DateTime.Now;
}
}
}
  • Besides the “HandleProcess” method, the “ServiceTemplate” class offers other methods and properties that can be overridden or used in the derived class:
Method or property Description
HandleInitialize(string[] args) called once at the beginning of every iteration, before the configuration files are processed
HandleProcess(string[] args, Config config, DbCommand cmd, DbConnection conn) called in every iteration for each configuration file separately, with an open connection to its database
HandleFinish() called once at the end of every iteration, after all configuration files have been processed
OnStart(), OnStop(), OnCustomCommand(int command) called only for an installed service, when it starts, when it stops and when it receives a custom service command
NextRunTime the next run time written to the “ng_sluzbawindows” table (see the Service run records chapter); it can be overwritten in the service code
SingleInstance, SingleInstanceTimeOut protection against two iterations of the same service running at the same time; the default values are “true” and 3000 ms
ConfigDirectory the directory in which the XML configuration files are searched for
TempDirectory the “Temp” directory for temporary files; if it does not exist, it is created when used
RunScript(DbConnection conn, string buttonId, string userId, int timeout) runs the script of a button with the “RunScript.exe” program from the “bin” directory of the NET Genium installation specified by the “rootPath” key and returns its standard output
LogServiceTask(string taskName, DateTime? nextRunTime, DbCommand cmd) writes the run of the service to the “ng_sluzbawindows” table; the framework calls it on its own after each successful processing of a configuration file, except for a service with a negative interval
  • In the second step, it is necessary to change the content of the “Program.cs” file and set the service startup interval.
  • From now on, the console application will not be able to start normally using the “Debug” / “Start Debugging” command (F5), so it is important to schedule this step until the console application is tuned and stable.
using NETGenium;

namespace CRMService
{
class Program
{
static void Main(string[] args)
{
if (false)
{
new Service().Run(args);
return;
}

new Service().Process(args,
new ServiceTemplateInstaller.InstallOptions()
{
Interval = 5
});
}
}
}
  • The startup interval of the installed service is specified in minutes by the “Interval” property of the “InstallOptions” object and may also be a decimal number:
    • a positive value – iterations run regularly after the specified number of minutes; the countdown runs from the beginning of the first iteration, regardless of how long the individual iterations take,
    • 0 (the default value) – the service runs once a day at 08:00; the first regular iteration takes place at the nearest 08:00 and the next ones always after 24 hours,
    • a negative value – only a single iteration takes place after the service starts and no further iterations are run.
  • The first iteration always takes place 15 seconds after the service starts, regardless of the interval set.
  • Iterations do not overlap: if the time of the next iteration falls while the previous iteration is still running, that next iteration is skipped and the warning “Previous OnTimer() is still running” is written to the log.
  • At any time in the future, the console application can be retroactively modified so that it can be started in the usual way using the “Debug” / “Start Debugging” command (F5), and further debugging or testing of the console application is possible.
using NETGenium;

namespace CRMService
{
class Program
{
static void Main(string[] args)
{
if (true)
{
new Service().Run(args);
return;
}

new Service().Process(args,
new ServiceTemplateInstaller.InstallOptions()
{
Interval = 5
});
}
}
}

9.3. Manifest

  • In the third step, it is necessary to grant the console application administrator privileges using a manifest file:
    • Start Visual Studio and open the console application project
    • Right-click on the project name in the Solution Explorer, select “Add / New Item...”, type “manifest” in the search field, find the item “Application Manifest File”, and select “Add”
    • In the contents of the manifest file, find “<requestedExecutionLevel level="asInvoker" uiAccess="false" />” and change to “<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />”

9.4. CRMService example

  • The following example demonstrates a simple “CRMService” service that downloads technology page content, backs up incoming e-mails to the “POP3Downloads” directory, and prints e-mail details to the console application window and the “Logs” directory to disk.
  • Contents of the “Service.cs” file:
using NETGenium;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;

namespace CRMService
{
public class Service : ServiceTemplate, IServiceTemplate
{
public override string ServiceName { get { return "CRMService"; } }

private string dir, backupdir;

public Service()
{
AddRequiredConfigKey("pop3Email");
AddRequiredConfigKey("pop3Url");
AddRequiredConfigKey("pop3Name");
AddRequiredConfigKey("pop3Password");

Directory.CreateDirectory(dir = Config.RootPath + "Temp\\");
Directory.CreateDirectory(backupdir = Config.RootPath + "POP3Downloads\\");
}

protected override void HandleProcess(string[] args, Config config, DbCommand cmd, DbConnection conn)
{
DateTime now = DateTime.Now;
Files.DeleteDirectoriesYYYYMMDD(backupdir, now.AddMonths(-6));
Files.DeleteLogs(Config.RootPath, now.AddYears(-1));

try
{
using (var pop3 = new NETGenium.Email.Pop3(0, config["pop3Url"], config["pop3Name"], config["pop3Password"]))
{
pop3.Open();

for (int index = 0; index < pop3.Count; index++)
{
foreach (string file in Directory.GetFiles(dir))
{
File.Delete(file);
}

string backupdir2 = backupdir + now.ToString("yyyy-MM-dd") + "\\";
Directory.CreateDirectory(backupdir2);

string eml = backupdir2 + Guid.NewGuid().ToString().ToUpper() + ".eml";

var message = pop3.ReadMessage_SaveEmlAndAttachments(index, eml, dir);
Email.Eval(config, message, eml, true, "ID " + index, dir, ServiceName, now, cmd, conn);

// pop3.DeleteMessage(index); // Delete messages from the POP3 server only after debugging the service
if (index == 499)
{
break;
}
}
}
}
catch (Exception ex)
{
L.EW("Download e-mails from POP3", ex);
}
}
}
}
  • The logic of processing the incoming e-mail message is solved in a separate file “Email.cs”.
  • Contents of the “Email.cs” file:
using NETGenium;
using NETGenium.Email;
using System;
using System.IO;

namespace CRMService
{
internal class Email
{
public static bool Eval(Config config, Message message, string eml, bool pop3message, string name, string dir, string serviceName, DateTime now, DbCommand cmd, DbConnection conn)
{
L.N(name);

foreach (string file in Directory.GetFiles(dir))
{
L.N(Path.GetFileName(file));
}

L.N(message.DateOriginal.ToString());
L.N(message.From.EmailAddress);
L.N(message.To);
L.N(message.Subject);
L.N(message.Text);

return true;
}
}
}
  • Instead of the “Pop3” class, mail can also be downloaded over IMAP with the “NETGenium.Email.Imap” class, which has the same constructor and the same methods for reading and deleting messages. The server address is specified in the same way, the default port is 143 and a secure SSL connection is used only on port 993 (for example “imap.company.com:993”). Messages are read only from the “INBOX” folder. A message deleted with the “DeleteMessage” method is removed from the mailbox only when the connection is closed with the “Close” method, so the order of the other messages does not change until then. In addition, the “Open” method permanently removes all messages marked as deleted right after opening the mailbox – including those marked so by another mail client – so a message whose connection did not end with a call of “Close” is removed at the next opening at the latest. If the server does not allow removing messages from the mailbox, they remain there and the “Close” method writes a warning to the log.
  • Both classes support signing in over OAuth 2.0: an access token is set in the “OAuth_access_token” property instead of the password before the “Open” method is called.
  • Attachments packed in a “winmail.dat” attachment (TNEF format) are extracted to the given directory by the “Parse” method of the “NETGenium.Email.TnefMessage” class. Attachments of a signed message are extracted from a “smime.p7m” attachment in the same way by the “NETGenium.Email.SmimeMessage” class, whose “Message” property also carries the signed message including its text; the class cannot read an encrypted message.
  • A message whose processing fails repeatedly can be discarded with the help of the “NETGenium.Email.FailedMessages” class; its instance must survive between service iterations. The “Discard” method counts failed attempts to process a message: until their number reaches the “MaxAttempts” property (default value 3), it writes a warning to the log and returns “false”; on the last attempt it writes an error to the log, saves a copy of the “.eml” file to the directory specified in the constructor and returns “true” – the message must then be deleted from the mailbox. A message whose “.eml” file does not exist or cannot be copied is never discarded.
  • A calendar invitation in the iCalendar format is accessible through the properties of the “Message” class starting with the word “Calendar”, for example “CalendarSummary”, “CalendarStart”, “CalendarEnd” or “CalendarAppointments” with the individual occurrences of a recurring event (at most 1000).

9.5. Service configuration files

  • Services can contain two types of configuration files:
    • The main configuration file (for example, “CRMService.exe.config”), the name of which must be based on the application name (in this case “CRMService.exe”). It is processed only if it contains the “connectionString” or “rootPath” key.
    • XML configuration files that are used when the service serves multiple databases at once (for example, “netgenium1.xml”, “netgenium2.xml”, etc.). Every file with the “xml” extension is processed. The “shared.xml” or “globalconfiguration.xml” file is not processed on its own – its keys are added to all the other configuration files, including the main configuration file, if they do not have them. There may be only one shared file: if both are in the directory, only one of them is used as shared and the other is processed as an ordinary configuration file.
  • The service source code runs for each configuration file separately. Acceptable combinations include:
    • CRMService.exe.config
    • netgenium1.xml, netgenium2.xml, ...
    • CRMService.exe.config, netgenium1.xml, netgenium2.xml, ...
  • The configuration files must be located in the same directory as “CRMService.exe”. The exception are the XML configuration files of a service started from anywhere under the “bin” subdirectory (typically “bin\Debug” when debugging in Visual Studio) – they are searched for in the directory that contains “bin”, i.e. in the project directory. If we are in the debugging phase of the service in Visual Studio, it is not necessary to create the configuration file manually, because each console application project automatically contains the default configuration file “App.config”. This file is copied to the “Debug” or “Release” directory (depending on the selected compilation mode) to a file named “abc.exe.config” each time the project is compiled. In our case, the contents of the configuration file “App.config” are copied to the file “CRMService.exe.config” when the project is compiled.
  • Example configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>

<add key="rootPath" value="C:\inetpub\wwwroot\netgenium" />

<add key="stopFrom" value="00:00" />
<add key="stopTo" value="06:00" />

<add key="smtpServer" value="localhost" />

<add key="errorFrom" value="crmservice@firma.cz" />
<add key="errorTo" value="support@firma.cz" />

<add key="pop3Url" value="pop3.firma.cz" />
<add key="pop3Email" value="crm@firma.cz" />
<add key="pop3Name" value="crm@firma.cz" />
<add key="pop3Password" value="pop3heslo" />

</appSettings>
</configuration>
  • rootPath – absolute disk path to the NET Genium directory; the key is mandatory in all modes unless the configuration file contains the “connectionString” key
  • connectionString – optional database connection string; if it is not specified, it is taken from the “Config\ConnectionString.txt” file in the NET Genium directory specified by the “rootPath” key. A configuration file without both the “rootPath” and “connectionString” keys is skipped and a warning is written to the log.
  • stopFrom – the start time of the technological pause in the format “HH:mm”, during which the service is not working; the pause may span midnight (for example 22:00–06:00)
  • stopTo – end time of the technological pause in the format “HH:mm”, during which the service is not working
  • The “stopFrom” and “stopTo” keys are mandatory in service mode, even when the service is started by the “Run” method; a configuration file without them is skipped and a warning is written to the log. In console mode (started with the “console” argument, see the Service installation chapter) they are not mandatory; however, if both are specified in the configuration file, the technological pause applies there as well.
  • smtpServer – the address of the outgoing mail server, optionally with a port number in the form “server name:port number”
  • errorFrom – e-mail address from which notifications of possible errors in the service are sent
  • errorTo – e-mail address to which notifications of possible errors in the service are sent; if it is not filled in, the notifications go to NetGenium support. Notifications are sent only when the “smtpServer” and “errorFrom” keys are filled in, and only for errors, never for warnings. The framework writes an exception from the “HandleProcess” method to the log as an error only if it is not a transient infrastructure error (network, database, disk), a rejected sign-in to a POP3 or IMAP mail server or some errors when reading a message from it – such an error is written as a warning, so it does not trigger an e-mail. For other errors, the e-mail is sent immediately on the first occurrence and the same error is then sent at most once an hour. A transient error that the service code writes to the log directly as an error does not trigger an e-mail on its first occurrence; the e-mail is sent only if the same error recurs between 1 and 24 hours after its first occurrence (or after the last e-mail sent).
  • pop3Url – incoming mail server address, optionally with a port number in the form “server name:port number” (the default port is 110); a secure SSL connection is used only on port 995 (for example “pop3.firma.cz:995”), on any other port the service connects without SSL
  • pop3Email – e-mail address of the technology box
  • pop3Name – login name to the technology box
  • pop3Password – password for the technology box
  • Values of keys whose name contains the word “password” are decrypted automatically when the configuration file is loaded, so the service code reads them already in plain text. A value written in plain text (such as “pop3heslo” in the example above) is replaced in the file with an encrypted value when the service loads the configuration file; the configuration file must therefore be writable for the account under which the service runs, otherwise the password remains in plain text in the file. The same applies to a key whose name is given as the value of an “encrypt” key written in the file before it; each “encrypt” key specifies a single key.
  • In addition to the keys above, the framework reads these optional keys from the configuration file:
    • logLevel – levels of entries written to the log, separated by a semicolon or a comma (VERBOSE, NOTICE, WARNING, ERROR, FATAL, or ALL for all of them); without the key, all levels are written
    • logDir – the log directory of the service; without the key, the log is written to the “Logs” subdirectory of the directory in which the service program is located, and if the program is located anywhere under the “bin” directory, to the “Logs” subdirectory of the directory that contains “bin”
    • proxy, proxyUser, proxyPassword – a proxy server for outgoing HTTP requests of the service and the credentials for it; an empty value of the “proxy” key turns the proxy server off
    • logServiceConnectionString – connection string of the database whose “ng_sluzbawindows” table receives the run records of the service (see the Service run records chapter)
    • errorCc – e-mail address of the recipient of a copy of notifications of errors in the service
    • smtpUserName, smtpPassword – credentials for the outgoing mail server
    • smtpSsl – secure connection to the outgoing mail server; it applies only when the “smtpUserName” and “smtpPassword” keys are filled in
    • headerLabel – text inserted at the beginning of the subject of e-mails sent according to the service configuration
    • defaultSenderEmail – e-mail address whose domain is used to build the “Message-Id” header of e-mails sent according to the service configuration
  • The “logLevel”, “logDir” and “proxy” keys apply only to the configuration file in which they are specified; before each next configuration file is processed and after the last one, the settings return to the values that applied after the “HandleInitialize” method finished.

9.6. Service installation

  • The service is installed using the “service-name - user.lnk” shortcut, which is created automatically in the program directory when the console application “service-name.exe” is started by the “Process” method. At the same time, the “service-name - console.lnk” shortcut is created, which runs one iteration of the service in console mode.
  • After running the “service-name - user.lnk” shortcut, it is important to check the settings of the account under which the service is to run. The default setting is “LocalSystem”, which ensures safe service startup without possible future complications with service authorization.
  • The service is installed by clicking on the “Install” button.

Obrázek.png

  • The service is uninstalled by clicking on the “Uninstall” button.
  • The startup mode of the console application of the service (the “Process” method) is determined by the first command line argument; letter case does not matter and any other argument ends with an error:
Argument Mode
no argument run of the installed service – this is how the Windows service manager starts it
user the service settings dialog (the “service-name - user.lnk” shortcut)
console one iteration in console mode (the “service-name - console.lnk” shortcut)
install, register installation of the service without the dialog and its start; the account and the start mode are taken from the “Account”, “Username”, “Password” and “ServiceMode” properties of the “InstallOptions” object in the “Program.cs” file; if the service is already installed, nothing happens
uninstall, unregister uninstallation of the service without the dialog

9.7. Exchange of service program files

  • Once the service is installed, it is not possible to change the service's program files because they are constantly protected by the operating system.
  • Before exchanging program files, it is necessary to stop the service using the “Stop” button, and close the service settings dialog using the cross icon.
  • Subsequently, the service program files can be overwritten and the service restarted using the “Start” button.

9.8. Service run records

  • If the database contains a form with the “ng_sluzbawindows” table, the service writes the time of the last run and the time of the next run to it after each successful processing of a configuration file. The overview of services out of operation and the notifications about them are provided by the application built on this table.
  • The record is written only if the “HandleProcess” method finishes without an exception. It is not written during the technological pause (“stopFrom” to “stopTo”), with an invalid or incomplete configuration, with an unavailable database, or if the “Config\UnderConstruction.txt” file exists in the NET Genium directory specified by the “rootPath” key.
  • The table row is named after the service name (“ServiceName”). If there is a shared “shared.xml” or “globalconfiguration.xml” file among the configuration files, each XML configuration file gets its own row in the form “service name (file name without extension)”; otherwise all configuration files whose records go to the same database write to a single row.
  • For an installed service with a positive interval, the next run time is the start time of the iteration increased by the interval. A service with a negative interval writes neither the last nor the next run time. In all other cases – an interval of 0, a direct call of the “Run” method (for example from a scheduled Windows task) and a start outside service mode – it is the start time of the iteration increased by 1 day. If the calculated time falls within the technological pause, it is moved to its end. The time is written in the time zone of the instance.
  • The “logServiceConnectionString” key redirects the record to a database other than the one the service works with; an error when writing to this database is not logged anywhere.
  • Programs that are not built on the “ServiceTemplate” class (for example batch files run by a scheduled Windows task) write to the same table with the “LogService.exe” program – see the Utilities guide.