NETGeniumConnection.dll library for .NET Framework 4.7.2 | .NET 9 | .NET 10
1. General information
- External functions are used to call custom C# program code on the server side. The result of calling an external function is always a value expressed as a text string.
- External functions are used in cases where
- it is not possible or appropriate to provide the required functionality via a server-side script,
- it is necessary to call the server code from JavaScript, typically in the form of an AJAX request.
- The server function “ngef(string id, string arg0, string arg1, string arg2, ...)” is used to call external functions
- The mandatory parameter "id" specifies the external function identifier.
- Other parameters are optional and are passed to the external function as the "string[] args" array in the same order in which they are listed when calling.
- External functions can only be used by administrators when designing applications. A regular user does not have direct access to their calls from the application user interface, and external functions are always launched only indirectly through applications designed by administrators.
- An error may occur during the execution of an external function or its execution may be interrupted. The system's behavior in such a case depends on the context from which the external function was called:
- External functions run from a script terminate the execution of the script in the event of an error or interruption. The system returns to the edit form or the view page from which the script was called and displays an error message to the user. For the "OnBeforeSave" and "OnBeforeDelete" scripts, such an interruption means preventing the planned saving or deletion of the database record.
- External functions run from "HTML" or "JavaScript" controls in the event of an error or interruption terminate the loading of the edit form or view page and display an error message to the user.
- External functions run from other controls in the event of an error or interruption terminate only the loading of data into the given control. The error message is displayed directly in this control and the loading of other controls is not affected.
- External functions are not available in NET Genium Online – a custom “ngef.dll” library cannot be uploaded to the cloud. The database query designer therefore does not offer the “ngef(NETGenium.DataTable)” field and NET Genium settings do not display the list of public external functions.
2. Library “ngef.dll”
- External functions are part of the “ngef.dll” library located in the “NETGenium\bin” directory. A clean installation of NET Genium has a default library “ngef.dll” in the “bin” directory, which is the result of compiling the default source code located in the “NETGenium\bin\ngef.cs” file.
using System;
namespace NETGenium
{
public class ExternalFunctions
{
public static string ngef(string id, string[] args, bool test, DbCommand cmd, DbConnection conn)
{
if (test) return "";
switch (id)
{
// case "MyFirstFunction": return MyFirstFunction();
default: return conn == null ? "" : conn.ExternalFunctionNotFound(id, cmd);
}
}
}
}
- The source code for external functions uses objects and methods from the “NETGeniumConnection.dll” library, which can be downloaded from the links at the beginning of this guide. “NETGeniumConnection.dll” is a library with basic functions for working with databases and file attachments.
- Modifications of the “ngef.dll” library can only be performed through a separate project in the “Visual Studio 2015” application and higher, resp. by programming the source code of this project, and then compiling the project into the library “ngef.dll”.
- A clean installation of NET Genium does not contain a project with source codes for external functions. Before programming external functions, it is necessary to create a new library 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: Class Library (.NET Framework)
- Project name: ngef
- 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 the file “Class1.cs”, and select “Delete”
- Right-click on the project name “ngef”, select “Add” / “Existing Item...” (Shift+Alt+A), and select the path to the file “NETGenium\bin\ngef.cs” on the computer disk
- Right-click on “References”, select “Add Reference...”, and select the path to the file “References\NETGeniumConnection.dll” on the computer disk
- Right-click on “References” / “NETGeniumConnection”, select “Properties” (Alt+Enter), and set the “Copy Local” attribute to “False”
- Choose “Debug” compilation mode
- “Debug” mode generates “ngef.dll” and “ngef.pdb” files by default
- Thanks to the “ngef.pdb” file, errors and interrupts in external functions 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 “ngef.dll” library. By default, the “Release” mode only generates the “ngef.dll” file, which is sufficient for NET Genium, but when interrupted, finding the cause of the error is very complicated.
- Compile the project – from the menu on the main bar select “Build” / “Build Solution” (Ctrl+Shift+B)
- Copy the files “ngef.dll” and “ngef.pdb” from the directory “bin\Debug” to the directory “NETGenium\bin” if the library is compiled in “Debug” mode, or the file “ngef.dll” from the directory “bin\Release” to the “NETGenium\bin” directory when compiling the “ngef.dll” library in “Release” mode. In the case of “Release” mode, it is important to delete the “ngef.pdb” file from the “NETGenium\bin” directory.
- Uploading a new version of the “ngef.dll” library will always restart the web application, as will any change in the “NETGenium\bin” directory.
- The source code of external functions can be freely modified, but the following conventions in the “ngef.cs” file must not be changed:
- namespace NETGenium
- public class ExternalFunctions
- public static string ngef(string id, string[] args, bool test, DbCommand cmd, DbConnection conn)
- if (test) return "";
- default: return conn == null ? "" : conn.ExternalFunctionNotFound(id, cmd);
- The “default” line determines what happens when an identifier is called for which the dispatcher has no “case”: in a script (the “cmd” object is not “null”) it throws the exception “The external function ... does not exist!”, outside a script it returns this text as the result. Internal external functions called by NET Genium itself (chapter 5) that the library does not implement therefore have no effect – the returned text does not start with the “OK\r\n” prefix.
3. Parameters for the “public static string ngef” function
- string id
- An external function identifier that uniquely identifies a function or program code executed by calling the server function “ngef(id)”.
- It is necessary to create a separate “case” for each identifier inside the “switch” command in the “public static string ngef” dispatcher.
- string[] args
- External function parameters that are part of the server function call “ngef(id, arg0, arg1, arg2,...)” in the second and next position in the parameter list.
- The “args” parameter list can have 0 elements if the call to the “ngef(id)” server function contains only the identifier of the external function without additional parameters.
- The “id” identifier is not part of the “args” array. The “args” array therefore always has one element fewer than the number of parameters of the “ngef” server function call itself. The “arg0” parameter corresponds to the “args[0]” element, the “arg1” parameter to the “args[1]” element and so on.
- All parameters are passed as text, even if they have the form of a number or a date. An omitted parameter is passed as an empty string and the number of elements of the “args” array is not affected — the call “ngef(id, A, , C)” passes an array of three elements whose second element is an empty string.
- Spaces at the beginning and end of each parameter are removed before it is passed. A parameter that is to contain a comma or begin or end with a space must be enclosed in double quotation marks – a detailed description is provided in the separate guide Server functions.
- The “ngef2” server function cannot be called generally from a script or from an HTML control. It is written as the default value of a database control with the “Read only” and “Hidden field” check boxes selected; when a record is displayed in a view table, tree, calendar, picker or export, it is rewritten to an “ngef” call whose last element of the “args” array is the ID of the displayed record (see chapter 10.1).
- bool test
- The logical value “test” determines whether the external function is called from the script designer using the “Run script” button, or when a script query of an edit form is displayed or saved in the database query designer. The designer evaluates a script query of a button on a view page with the value “false”.
- The default source code for external functions includes an “if(test) return "" ;” statement on the first line of the “public static string ngef” function, which ensures that the external function from the script designer does not run unintentionally when debugging the script.
- DbCommand cmd
- The “cmd” object specifies a database object of the “DbCommand” type, which is used to write data to the database.
- External functions executed from a script using the “ngef” server function, together with other script commands, use a single database object “cmd”, which is used to write or delete data from the database in a single transaction of type “IsolationLevel.ReadCommitted”.
- The “cmd” object writes through the database connection “cmd.Connection”, which is the primary connection of the script with an open transaction. It is therefore not the same connection as the one passed to the external function in the “conn” parameter – that one is a separate connection without a transaction.
- Database operations performed via the “cmd” object do not take effect in the database until the transaction is committed. This occurs automatically at the end of each successful script, or by calling the server function “COMMIT()”.
- Any error or interruption during the execution of the external function or the script itself will ensure a “rollback” of all database operations performed via the “cmd” object that have not yet been committed by the “COMMIT()” server function.
- The use of the “cmd” object is not appropriate in cases of bulk data imports, or in general in cases where data entry is not required within the transaction. Typical data changes through the “cmd” object contain at most units of write or delete commands.
- Using the “cmd” object can cause a “deadlock” of the database. When working with an object, it is always important to load everything needed from the database first, and only then write it to the database. Deadlock arises in situations where the programmer first writes data to a database table and then tries to read from the same database table.
- Until the transaction is committed, the written records stay locked and other database connections run into that lock. How this manifests depends on the database server, its version and settings, and on the version of the client library – the read or write may wait until the transaction ends, may fail with a conflict error, or may succeed and return the last committed data, i.e. the state before the write. The scope of the lock varies as well, from a single record up to the entire database table.
- None of these variants can be relied upon, nor can they be derived from behaviour verified on one particular installation – a different version of the database server behaves differently. The only reliable defence is the order “load everything first, write afterwards”.
- Whenever it is not necessary to write to the database within a single transaction together with a script, it is recommended to use a custom object “cmd” – for example using the command “using (DbCommand cmd = new DbCommand(conn)) {}”.
- An external function executed from a location other than the script has the “cmd” object set to “null”.
- DbConnection conn
- The “conn” object specifies a database object of the “DbConnection” type, which represents a connection to the NET Genium database, and is used to read or write data to the database.
- External functions executed from a script have in the “conn” object a separate database connection without a transaction, which NET Genium uses for reading data throughout the whole run of the script. Writes performed within the script transaction, on the other hand, go through the “cmd.Connection” connection – see the description of the “cmd” parameter.
- The roles of both objects are therefore unambiguous – the “conn” object is used for reading, and the “cmd” object is used for writing within the script transaction. From the database point of view, the two connections are two different clients, so locks held by an uncommitted write through the “cmd” object block reading through the “conn” object. It is therefore always necessary to load all the required data first, and only then write.
- An external function executed from a location other than the script has in the “conn” object the connection of the calling context, and the “cmd” object set to “null” – reading and any writing through its own “cmd” object therefore go through the same “conn” connection.
- The internal external functions “NETGenium.CSS” and “NETGenium.Log” receive the “conn” object with the value “null”.
4. Debugging external functions in the console application
- The most convenient way to write external functions is to design a prototype of an external function in a console application, and then copy the tuned source code to the “ngef.dll” library project.
- Compiling and running a console application is very fast, and allows easy debugging and tracing either using “breakpoints” or by listing information to the console using the “Console.WriteLine()” command.
- When designing an external function, it is important to choose a new external function declaration so that the resulting source code can be easily transferred to the “ngef.dll” library project using “Ctrl+C” and “Ctrl+V”.
4.1. 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)
- Framework: .NET Framework 4.7.2
- Project Type: Console Application
- Name: ConsoleApp1
- Location: optional project location
- Solution: Create new solution
- Create directory for solution: No
- Add to Source Control: No
- 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
- Start project – select “Debug” / “Start Debugging” (F5) from the menu on the main bar
4.2. NET Genium environment simulation
- In the second step, it is necessary to modify the default source code in the “Program.cs” file so that the console application simulates the NET Genium environment, which runs external functions by calling “public static string ngef” with the parameters “args”, “cmd” and “conn”.
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.Open();
conn.RootPath = "C:\\inetpub\\wwwroot\\netgenium";
console.WriteLine(MyFirstFunction(new string[] { "a", "b", "c" }, cmd, conn));
}
console.Exit();
}
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
return "Hello World! args: " + string.Join(";", args);
}
}
}
4.3. Writing the source code of an external function
- In the third step, it is then possible to write the source code of the external function “MyFirstFunction”.
5. Internal external functions called by NET Genium itself
- The following external functions are not called by a user script, but directly by the NET Genium framework.
- They are called automatically in precisely defined situations.
- External functions can return a string. If the return value is to be processed, it must start with the prefix: OK\r\n
- This prefix is followed by the actual content (HTML, JavaScript, CSS, color, value, file path, etc.). Exceptions to this rule are stated for the function concerned.
- Internal external functions are always called with the “cmd” object set to “null”, i.e. outside a transaction.
5.1. NETGenium.Campaigns.Image
- Triggered when the Campaigns/Image.aspx?guid page is displayed, which occurs when trying to read an email due to an image that refers to Image.aspx
- html = html.Replace("[UNSUBSCRIBE]", "<a href=\"" + url + "/Unsubscribe.aspx?" + guid + "\">Unsubscribe</a> from these emails...") + "<img src=\"" + url + "/Image.aspx?" + guid + "\">";
- args = new string[] { queryString }
5.2. NETGenium.Campaigns.Unsubscribe
- Triggered when the Campaigns/Unsubscribe.aspx?guid page is displayed, which occurs when the user clicks on the link in the email
- html = html.Replace("[UNSUBSCRIBE]", "<a href=\"" + url + "/Unsubscribe.aspx?" + guid + "\">Unsubscribe</a> from these emails...") + "<img src=\"" + url + "/Image.aspx?" + guid + "\">";
- args = new string[] { queryString }
- The function can return html that is displayed over the whole page, the string must start with OK\r\n
5.3. NETGenium.Campaigns.View
- Triggered when the Campaigns/View.aspx?guid page is displayed, which occurs when the user clicks on the link in the email
- html = html.Replace("[BROWSER]", "<a href=\"" + url + "/View.aspx?" + guid + "\">View in web browser...</a>");
- args = new string[] { queryString }
- The function can return html that forms the whole response of the page, the string must start with OK\r\n
5.4. NETGenium.Copyright
- Runs in the navigator (on the Apps.aspx or App.aspx page) every time the navigator is displayed, and makes it possible to replace the copyright with any html code
- The function must return html, e.g. OK\r\nhtml
5.5. NETGenium.CSS
- Runs before the CSS file is saved to disk and when the layout is previewed, separately for each browser and text direction
- args = new string[] { value, browser.ToString() }
- The “conn” object is “null”; an exception is ignored
- The function must return the changed CSS, the string must start with OK\r\n
5.6. NETGenium.DataGrid2Excel
- Runs after exporting data from a datagrid so that the exported file can be opened and modified
- Regular datagrid – only for the "xlsx" format
- Statistical datagrid – allows only the "xlsx" format without a choice
- args = new string[] { excel.path, "Q" + qb.ID, qb.Form.ID.ToString(), qb.date1, qb.date2 }
- The dates can be empty
- The return value of the function is not used
5.7. NETGenium.DataTable
- Runs when data is loaded from the database, and allows the loaded data to be changed just before it is rendered
- Runs only for a query with the “ngef(NETGenium.DataTable)” check box selected, for all controls, prints and exports that use the query
- args = new string[] { "Q" + qb.ID, form.ToString(), date1, date2 } – the Planner control adds the view type
- The loaded data is in the conn.Container2[args[0]] object of the “DataTable” type; the columns of a regular view table are named “c” + control ID, the columns of a statistical view table “c” + column position
- The return value of the function is not used
- More in the guide Database query designer
5.8. NETGenium.Download
- Runs every time the path of a file attachment on disk is resolved – when an attachment is downloaded (Download.aspx), attached to an e-mail message, read through the MCP server, when an image is rendered and in server functions that work with attachments
- args = new string[] { id.ToString(), filename, path }
- The function can return the resulting path of the file on disk that is used instead of the original file, the string must start with OK\r\n
- The function has no information about where it was called from – the path replacement therefore applies in all the situations listed
5.9. NETGenium.Elearning.Test.ERROR
- Triggered after failing a test
- args = new string[] { test["id"].ToString(), sb_ok.ToString(), sb_ko.ToString(), sb_db.ToString() }
- test["id"].ToString() – Test ID
- sb_ok.ToString() - a semicolon-separated list of IDs of correctly answered questions
- sb_ko.ToString() - semicolon-separated list of IDs of incorrectly answered questions
- sb_db.ToString() - HTML test including questions and answers, which is stored in the database table ng_eltestvysl in the column ng_test0
- The function can return html, the string must start with OK\r\n
5.10. NETGenium.Elearning.Test.OK
- It will start after successfully passing the test
- args = new string[] { test["id"].ToString(), sb_ok.ToString(), sb_ko.ToString(), sb_db.ToString() }
- test["id"].ToString() – Test ID
- sb_ok.ToString() - a semicolon-separated list of IDs of correctly answered questions
- sb_ko.ToString() - semicolon-separated list of IDs of incorrectly answered questions
- sb_db.ToString() - HTML test including questions and answers, which is stored in the database table ng_eltestvysl in the column ng_test0
- The function can return html, the string must start with OK\r\n
5.11. NETGenium.ExcelPassword
- Runs before the sheets of an Excel file printed from a print template with locking enabled are locked
- args = new string[] { }
- The function must return the password for locking the sheets of the Excel file, the string must start with OK\r\n
- This is protection of the sheets against editing, not encryption of the file – the file can be opened without a password. If the function does not return a password, the default NET Genium password is used, which is the same in all installations – the locking then prevents only accidental editing, not deliberate editing
- Word templates are locked without a password and do not call the function
5.12. NETGenium.Import
- Runs when the Import.aspx page is displayed, also for a user who is not logged in
- args = new string[] { }
- The function can return html that is inserted into the response; the OK\r\n prefix is not needed
- If the function returns nothing, an empty string is saved in the response
5.13. NETGenium.ImportXml
- Runs when a ZIP file is uploaded to the ImportXml.aspx page, separately for each file of the archive that contains an XML dataset named by the encrypted session ID of a logged-in user
- The loaded dataset is stored in the conn.Container object
- args = new string[] { }
- The function can return html that is inserted into the response; the OK\r\n prefix is not needed
- If the function returns nothing, an empty string is saved in the response
- If the processing ends with an exception (including an exception thrown by the function), a general error message is saved in the response and the exception is written to the error log; the text of the exception is not returned to the caller
5.14. NETGenium.LockRecord
- Runs after a database record has been successfully locked, and again every time an open edit form renews the lock
- Does not run if the lock is held by another user
- args = new string[] { form.ToString(), id.ToString() }
- The return value of the function is not used
5.15. NETGenium.Log
- Runs after a NET Genium system log entry has been written to disk; writes made by the L.E, L.W and L.N methods do not run the function
- args = new string[] { filenameWithoutExtension, message, logLevel.ToString(), loginname }
- The “conn” object is “null”; an exception is ignored
5.16. NETGenium.LoginCode
- Runs after the user has been found at every login with a user name and password
- args = new string[] { userid.ToString(), loginname }
- The function can start two-factor authentication – if it returns a string that does not start with the OK\r\n prefix, two-factor authentication is skipped
- The returned string must start with OK\r\n, followed by the authentication code that the framework automatically stores in the session
- Optionally, an HTML message displayed to the user (e.g. instructions or a QR code) can follow after another \r\n
- If the HTML message is missing, the default system message is displayed: "The authentication code will be sent to your mobile phone."
5.17. NETGenium.ValidateLoginCode
- Runs to verify the authentication code entered by the user during two-factor login
- args = new string[] { secret, code, loginname }
- secret – the code returned by the NETGenium.LoginCode function (without the OK\r\n prefix and without the HTML message)
- code – the code entered by the user
- loginname – the user's login name
- OK\r\n – the code is valid, the login continues
- OK\r\n<error message> – the code is invalid, the message is displayed to the user
- any string without the OK\r\n prefix (including the result of a function that is not implemented) – the default comparison is used (secret == code)
5.18. NETGenium.OnAfterLogout
- Runs after the user logs out with the link in the header or in the “Change password” dialog
- args = new string[] { }
- A function can return javascript, e.g. OK\r\nalert('The user just logged out...'); - so the string must start OK\r\n
5.19. NETGenium.Menu
- Launches in the navigator (on the page Apps.aspx or App.aspx) every time the navigator is displayed, and makes it possible to insert any html code at the beginning of the page
- The function must return html, e.g. OK\r\nhtml
5.20. NETGenium.Menu1
- Runs in the edit form, on the view page, in portlets and on other pages of the user mode, only if the portal uses the layout without frames (NoFrames) and the page is not opened in a picker
- args = new string[] { }
- The function must return html that replaces the default menu at the beginning of the page, e.g. OK\r\nhtml
5.21. NETGenium.Menu2
- Runs under the same conditions as NETGenium.Menu1, and only if NETGenium.Menu1 returned a string with the OK\r\n prefix
- Also runs in the navigator, where the result is inserted before the copyright
- args = new string[] { }
- The function must return html that is inserted at the end of the page, e.g. OK\r\nhtml
5.22. NETGenium.OnAfterLogin
- Runs after a successful login of the user in the browser – with a name and password, through Active Directory and through Microsoft, Google, Facebook or Apereo; does not run for the anonymous user
- Does not run when logging in with an MCP token, through the web services of the NETGeniumConnection library or through a custom API – preventing login with this function therefore does not apply to these accesses; access of the account is restricted there by its user groups or by blocking the account
- Makes it possible to run javascript in the client browser using return "OK\r\n", followed by the javascript code itself, e.g. return "OK\r\nalert('Welcome...');"
5.23. NETGenium.OnAfterPrint
- Runs immediately after printing to a print template, separately for each printed document – when printing from a view page or an edit form and when printing to an attachment from a script – so the file can still be modified by the external function
- The printed record is registered in the “conn” object, so conn["ng_..."] returns its values
- conn.PrintingProcess.FilePath - path of the file being printed to; for the “txt”, “csv”, “htm” and “html” formats the file is overwritten after the function returns with the content of conn.PrintingProcess.Content
- conn.PrintingProcess.FileName - name of the file that will be offered to the user for download - can be changed
- conn.PrintingProcess.Template - name of the print template
- conn.PrintingProcess.Content - content of the printed file for templates in the “txt”, “csv”, “htm” and “html” formats - can be changed; null for other formats
- conn.PrintingProcess.Form - ID of the form from which printing was started
- conn.PrintingProcess.ViewPage - ID of the view page from which printing was started
- conn.PrintingProcess.SaveAs - the way the document is finished; assigning a “SaveAsPdf” object finishes the document as PDF including conversion from HTML, letterhead underlay, page numbering and inserting attachments
- The return value of the function is not used; an exception ends printing with an error message
- The function may end the response itself (e.g. by sending its own content to the client and throwing EndResponseException); printing then does not continue and the framework does not treat it as an error
- A detailed description of programming print templates in the “pdf” format is provided in the separate guide Programming of print templates in PDF format.
5.24. NETGenium.OnBeforeLogin
- Triggered in the following situations:
- Login with username and password: args = new string[] { "", loginname, password }
- The external function is run just before attempting to authenticate the user (by finding their username in the "susers" table in the "loginname" column), and
- makes it possible to prevent user login and possibly run javascript in the client browser using return "OK\r\n", which can be followed by the javascript code itself, e.g. return "OK\r\nalert('Sorry...');", or
- makes it possible to influence user authorization – the function can ensure external user authorization in LDAP, Azure Active Directory, etc., and reset the list of their user groups based on the result.
- Automatic login thanks to Active Directory (WindowsAuthentication) and LoginByIdentity.txt: args = new string[] { account }
- The external function is run just before the user logs in after successful authentication, and
- makes it possible to influence user authorization – the function can ensure external user authorization in LDAP, etc., and reset the list of their user groups based on the result.
- Login through Microsoft (LoginByMicrosoft.aspx), Google, Facebook or Apereo: args = new string[] { email }
- For Apereo without an e-mail address, args[0] contains the “sub” user identifier
- The external function is run after the user has successfully authenticated with the identity provider, and just before attempting to find their email address in the "susers" table in the "email" column, and
- makes it possible to prevent a user from logging in by deleting their user account in the "susers" table, or
- makes it possible to influence user authorization – the function can ensure external authorization of a user in Azure Active Directory, etc., and reset the list of his user groups based on the result.
- Login with username and password: args = new string[] { "", loginname, password }
- A return value with the OK\r\n prefix is evaluated only for a login with a name and password; for an automatic login through Active Directory and through an identity provider the return value is not used.
- The function is not run when logging in with an MCP token, through the web services of the NETGeniumConnection library or through a custom API.
- For a login with a name and password, the limit of failed login attempts is checked before the function is run. If the limit is exceeded for the given user name, the login is rejected and the function is not run.
- If the function itself rejects the login (with a return value with the OK\r\n prefix), NET Genium does not count the attempt among failed login attempts. If the function verifies the password in an external system and rejects an invalid password itself, protection against password guessing must be provided by that external system.
- A typical reason for modifying the external function NETGenium.OnBeforeLogin is to authorize a user in an external application that uses a user role system (permitted user activities in the information system):
- LDAP – the list of user roles is determined by calling LDAP web services
- Azure Active Directory – the list of user roles is determined by calling the Microsoft Graph API web services, or the list of user roles is part of the login token, which NET Genium stores in the session object "microsoft_access_token" when authenticating the user
- The equivalent of user roles in an external application are our user groups
- A user can be a member of none, one, or more user groups at the same time
- A user role in NET Genium is defined as a user group with the "Role" box checked - the string "(R)" is automatically placed at the beginning of the user group name, followed by the user group name.
- A user role in an external application determines the scope of permitted user activities
- A user role in an external application cannot be linked 1:1 to our user group because
- the user role system in the external application does not use a rights system for controls, applications, etc. (the external application probably does not even use a hierarchy such as controls, forms, applications, etc.),
- the list of user roles in the external application is not sufficient for us to correctly determine the list of all other user groups that must be set up for the user along with the "Role" user groups,
- we use standardized applications such as ERP, etc., or we create tailor-made applications for the customer at a stage when we do not have a final list of user roles from them.
- To correctly set up user groups in NET Genium based on user roles in an external application, we use rights groups
- Rights group is not equivalent to a user role in an external application
- A rights group defines a set of user groups in which a user is a member
- Rights group is an attribute of a user, a user can only be assigned one rights group (this attribute is a foreign key and can be filtered by it)
- The rights group is evaluated automatically when a user is saved by comparing the list of user groups of the given user with the list of rights groups
- The rights group contains the "Member of" field - this field is the equivalent of the users role in an external application
- By filling in "Member of" for at least one rights group, we say that each time a user logs in, the list of their user groups according to user roles in the external application should be replaced. From the list of user roles, it is determined which rights groups correspond to these roles, and from these rights groups, a list of all user groups is compiled, which are then set for the given user. If the resulting list of user groups has at least one element, the user has permissions; if the resulting list is empty, the user does not have permissions.
- If the user has permissions, the list of user groups defined in NET Genium will be replaced by the equivalent of user roles
- If the user does not have permission, the list of user groups in NET Genium will be deleted
- If "Member of" is not filled in for any rights group, authorization in the external application is only used to determine whether the user has permission to log in or not.
- If the user has permissions, he will be left with a list of user groups defined in NET Genium
- If the user does not have permission, the list of user groups in NET Genium will be deleted
- The user role does not correspond meaningfully to either the user group (in ERP we use the "Role" checkbox, which distinguishes the type of user group), or the rights group, which defines the list of user groups, and appears in the user form as a foreign key. From our point of view, this is a user role in an external application, the list of which is limited for us, and insufficient for us to be able to correctly edit our applications according to it. The user role is a semantically separate entity for which a new table could exist, however, it is unnecessary at the moment - at the moment we are only interested in the string ID of the user role, which we fill in the rights group form, and due to the 1:1 binding and uniqueness of the user role ID, this is not filled anywhere other than in one rights group.
5.25. NETGenium.OpenForm
- Runs in the edit form immediately after opening a database connection
- args = new string[] { ds.Form.ID.ToString() }
- The function is the only place where it is possible to set the value conn.Page.UserGroupIds - a list of user group IDs that the user has temporarily assigned to work in the edit form
5.26. NETGenium.OpenPortlets
- Runs on the main page of the first application group after the login is verified and after the redirection to the first application group, before the portlets are loaded – also when the user has no portlet and in admin mode, except when a selected application is displayed in admin mode
- args = new string[] { appgroup.ToString() } – ID of the first application group the user is authorized to access
- Runs on every request to the main page of the first application group, including the refresh of a single portlet; it does not run on other application groups
- The return value of the function is not used
5.27. NETGenium.OpenViewPage
- Runs on the view page immediately after opening a database connection
- args = new string[] { viewpage.ID.ToString() } – “0” for a non-existing view page
- Runs on every request to the view page, including the refresh of a single control, even before the redirect according to the page type and before the permission check
- The return value of the function is not used; an exception ends the loading of the whole page
5.28. NETGenium.PageProcessingTime
- Runs in the edit form and on the view page just before sending the HTML to the user, only when query logging is enabled and not for a silent user
- args = new string[] { (int)MathExt.Round(time.TotalMilliseconds).ToString() }
- An exception is ignored
5.29. NETGenium.Payment
- Runs when the Payment.aspx page is displayed with parameters in the URL; does not run in the NET Genium Online cloud edition
- args = new string[] { }
- The function can return html, the string must start with OK\r\n
5.30. NETGenium.PaymentNotification
- Runs when the PaymentNotification.aspx page is displayed; does not run in the NET Genium Online cloud edition
- args = new string[] { }
- The function can return the response of the page, the string must start with OK\r\n
5.31. NETGenium.PlannerCellColor
- Triggered when a cell is displayed in the planner
- args = new string[] { viewfield.ToString(), "0", date1.ToString(), date2.ToString(), resource, StartDate.ToString(), EndDate.ToString() };
- args = new string[] { viewfield.ToString(), "1", date1.ToString(), date2.ToString(), resource, date1.Date.ToString(), date1.Date.ToString() };
- The function must return the cell color in R,G,B format, the string must start with OK\r\n
5.32. NETGenium.PlannerCellColorWeekend
- Triggered when a cell is displayed in the planner
- args = new string[] { }
- The function must return the cell color on the weekend in R,G,B format, the string must start with OK\r\n
5.33. NETGenium.PlannerCellHtml
- Runs when a cell is displayed in the planner, only if the planner allows overlapping events
- args = new string[] { viewfield.ToString(), "0", date1.ToString(), date2.ToString(), resource, StartDate.ToString(), EndDate.ToString() };
- args = new string[] { viewfield.ToString(), "1", date1.ToString(), date2.ToString(), resource, date1.Date.ToString(), date1.Date.ToString() };
- The function must return html, the string must start with OK\r\n
5.34. NETGenium.RegisterExternalFunction
- Runs when the ngef.aspx page is displayed in a situation where the external function is not authorized for the currently logged-in user (the ngef.aspx call was not placed, for example, in an HTML or JavaScript control), and also when an external function is called by the “ngef” method of the NETGeniumConnection library through web services
- Such a situation is, for example, calling the ngef.aspx page directly in a web browser
- args = new string[] { id, arg0, arg1, ... } – unlike other functions, args[0] contains the identifier of the called external function, followed by its parameters as sent by the client
- Runs also for a user who is not logged in; the function must therefore validate the parameters itself
- The function can return OK\r\n, which allows the given external function to run
- Calling this function is an alternative to the list of public external functions defined in the NET Genium settings
5.35. NETGenium.SendMessage
- Runs after an e-mail message has been sent from the "New e-mail message" form
- The paths to the file attachments on the server disk are passed in args, so these files can, for example, be packed into a ZIP and attached as an attachment in NG, etc.
- The conn.Container2 object contains the sent message under the “message” key (MailMessage), the flag of saving to sent items under the “save” key and the state under the “state” key; the values of the message (to, subject, body...) are returned by conn["..."]
- If the function returns the string “skipsave” (without the OK\r\n prefix), the message is not saved to sent items
- At the time of the call the message has already been sent; an exception displays an error page but does not undo sending
5.36. NETGenium.SortOrder
- Runs after the order of records has been set either in a datagrid or a tree
- args = new string[] { "Q" + qb.ID, qb.Form.ID.ToString() };
5.37. NETGenium.TimeTableCellColor
- Runs when a cell is displayed in the timetable in a Gantt chart
- args = new string[] { viewfield.ToString(), viewtype.ToString(), date1.ToString(), date2.ToString(), resource, mindate.ToString(), maxdate.ToString() };
- date2 – the end of the cell reduced by one day
- resource – the title of the resource
- The function must return the cell colour in the R,G,B format, the string must start with OK\r\n
5.38. NETGenium.Translate
- Runs when the Translate.aspx page is displayed, only for a logged-in user, with the Google translator key set and the source language filled in
- args = new string[] { sourcelanguage, targetlanguage, sourcevalue, format }
- The function can return the translated text, the string must start with OK\r\n; otherwise the Google translator is used
5.39. NETGenium.UnlockRecord
- Triggered when a database record is unlocked
- args = new string[] { form.ToString(), id.ToString() }
5.40. NETGenium.Url
- Runs before an e-mail message is sent by the “Send e-mail” script step with the link to the record enabled
- args = new string[] { user.DatabaseSettings.UrlEmails.Length != 0 ? user.DatabaseSettings.UrlEmails : user.DatabaseSettings.Url }
- The function can return a changed address, e.g. OK\r\nhttp://localhost/netgenium; several addresses are separated by a semicolon
5.41. NETGenium.LoginHtml
- Runs when the login window is rendered
- args = new string[] { }
- The function can return html that is inserted below the login window, the string must start with OK\r\n
5.42. NETGenium.OnAfterOpen
- Runs on the first load of an edit form, before the “OnAfterOpen” script runs; does not run if running the “OnAfterOpen” script is disabled on the form
- args = new string[] { form.ToString() }
- The “conn” object has no record of the form registered, so conn["..."] returns null
- The return value of the function is not used
5.43. NETGenium.OnAfterSave
- Runs after a record has been saved in an edit form, before the “OnAfterSave” script runs; does not run if running the “OnAfterSave” script is disabled on the form
- args = new string[] { form.ToString() }
- The values of the saved record are returned by conn["..."] only if the “OnBeforeSave” script ran before; otherwise conn["..."] returns null
- At the time of the call the record has already been saved – an exception displays an error page but does not undo saving
- The return value of the function is not used
5.44. NETGenium.OnAfterDelete
- Runs after a record has been deleted in an edit form, before the “OnAfterDelete” script runs, even if the deletion was skipped by the SKIPDELETE() server function; does not run if running the “OnAfterDelete” script is disabled on the form
- args = new string[] { form.ToString() }
- The values of the deleted record are returned by conn["..."] only if the “OnBeforeDelete” script ran before; otherwise conn["..."] returns null
- At the time of the call the record has already been deleted – an exception displays an error page but does not undo deletion
- The return value of the function is not used
6. Reading data from the database
6.1. Retrieve records from an SQL query into a DataTable object
using NETGenium;
using System;
using System.Data;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
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"]);
}
return "";
}
6.2. Retrieve records from an SQL query into a DbRow object
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
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());
}
return "";
}
6.3. Value parsing
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
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));
}
return "";
}
6.4. Indexing of retrieved records by primary key
using NETGenium;
using System;
using System.Collections.Generic;
using System.Data;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
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"]);
}
return "";
}
6.5. Indexing of retrieved nested records according to the foreign key “pid”
using NETGenium;
using System;
using System.Collections.Generic;
using System.Data;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
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");
}
return "";
}
7. Writing data to the database
7.1. INSERT INTO – create a new record in the database
7.1.1. DataSaver – create one record
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
DataSaver ds = new DataSaver("sholiday", 0, cmd);
ds.Add("name", "Nový rok");
ds.Add("date_", new DateTime(DateTime.Today.Year, 1, 1));
ds.Execute();
Console.WriteLine(ds.Report());
return "";
}
7.1.2. DataSaver – create one record by copying another record
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = 1;
DbRow row = new DbRow("SELECT * FROM sholiday WHERE id = " + id, conn);
if (row.Read())
{
// row["ng_zadanokym"] = conn.User.LoginName;
// row["ng_zadanokdy"] = DateTime.Now;
// row["ng_zmenenokym"] = DBNull.Value;
// row["ng_zmenenokdy"] = DBNull.Value;
DataSaver ds = new DataSaver("sholiday", 0, cmd);
ds.Add(row);
ds.Execute();
Console.WriteLine(ds.Report());
}
return "";
}
7.1.3. DataSaverSynchro – creation of one record, including creation of history record and ensuring record synchronization
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
DataSaverSynchro ds = new DataSaverSynchro("sholiday", 0, conn);
ds.Add("name", "Nový rok");
ds.Add("date_", new DateTime(DateTime.Today.Year, 1, 1));
ds.Save(cmd);
Console.WriteLine(ds.Report());
return "";
}
7.2. UPDATE – editing an existing record in the database
7.2.1. DataSaver – editing a record whose ID is retrieved from the database
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("Nový rok") + " 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", "Nový rok – TEST");
ds.Execute();
Console.WriteLine(ds.Report());
}
return "";
}
7.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;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = 1;
DataSaver ds = new DataSaver("sholiday", id, true, cmd);
ds.Add("name", "Nový rok – TEST");
ds.Execute();
Console.WriteLine(ds.Report());
return "";
}
7.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;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("Nový rok") + " 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", "Nový rok – TEST");
ds.Save(cmd);
Console.WriteLine(ds.Report());
}
return "";
}
7.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;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = 1;
DataSaverSynchro ds = new DataSaverSynchro("sholiday", id, true, conn);
ds.Add("name", "Nový rok – TEST");
ds.Save(cmd);
Console.WriteLine(ds.Report());
return "";
}
7.3. Data synchronization of two database tables according to a single key
using NETGenium;
using System;
using System.Collections.Generic;
using System.Data;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
string key = "ng_osobnicislo";
DataTable data1 = Data.Get("SELECT * FROM ng_data1", conn), data2 = Data.Get("SELECT * FROM ng_data2", conn);
Dictionary<int, DataRow> dictionary = Data.DictionaryInt32(data1, key);
foreach (DataRow row2 in data2.Rows)
{
DataRow row1 = Data.DataRow(dictionary, Parser.ToInt32(row2[key]));
if (row1 == null)
{
DataSaver ds = new DataSaver("ng_data1", 0, cmd);
for (int i = 1; i < data2.Columns.Count; i++)
{
ds.Add(data2.Columns[i].ColumnName, row2[data2.Columns[i].ColumnName]);
}
ds.Execute();
}
else
{
DataSaver ds = new DataSaver("ng_data1", (int)row1["id"], cmd);
for (int i = 1; i < data2.Columns.Count; i++)
if (row1[data2.Columns[i].ColumnName].ToString() != row2[data2.Columns[i].ColumnName].ToString())
{
ds.Add(data2.Columns[i].ColumnName, row2[data2.Columns[i].ColumnName]);
}
if (!ds.Empty)
{
ds.Execute();
}
}
}
return "";
}
8. Deleting data from the database
8.1. DELETE FROM – delete a record from the database
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("Nový rok – 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();
}
return "";
}
8.2. DataSaverSynchro – deleting a record from the database, including creating a history record and ensuring record synchronization
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = Data.ExecuteScalarInt32("SELECT id FROM sholiday WHERE name = " + conn.Format("Nový rok – 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);
}
return "";
}
9. Edit forms
9.1. Find out the ID of the currently open edit form
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
string dbname = conn.FormData.Table.TableName;
int form = conn.Page.QS_ToInt32("form");
return "";
}
9.2. Retrieve the value of a control on an edit form
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
string name = conn["name"].ToString();
return "";
}
9.3. Save the value to a control in an edit form
using NETGenium;
using System;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
conn["name"] = "Nový rok – TEST";
return "";
}
9.4. Ajax call from javascript
9.4.1. JavaScript
var p0 = 'ěščřžýáíé', p1 = 'ĚŠČŘŽÝÁÍÉ';
loadUrl('ngef.aspx?MyFirstFunction,' + urlEncode(p0) + ',' + urlEncode(p1), 'ajaxResponse', 'test', 'p0=' + urlEncode(p0) + '&p1=' + urlEncode(p1));
function ajaxResponse(html)
{
alert(html);
}
9.4.2. External functions
case "MyFirstFunction": MyFirstFunction(args, conn); return "";
using NETGenium;
using System;
using System.Text;
private static void MyFirstFunction(string[] args, DbConnection conn)
{
StringBuilder sb = new StringBuilder();
sb.Append("MyFirstFunction REPORT");
sb.Append(" | GET args: ");
sb.Append(string.Join(", ", args));
sb.Append(" | POST args: ");
sb.Append(conn.Page.Form("p0"));
sb.Append(", ");
sb.Append(conn.Page.Form("p1"));
Html.FlushAjaxContent(sb.ToString(), conn);
}
- The ngef.aspx page calls the external function only if it is authorized for the current session. Authorization is provided by:
- rendering the text “ngef.aspx?Identifier” enclosed in quotation marks in JavaScript, in a button, in the AutoSum control or in the copyright; in the HTML control only the notation in double quotation marks is recognized,
- a return value with the OK\r\n prefix from the NETGenium.RegisterExternalFunction external function,
- including the function in the list of public external functions in the NET Genium settings.
- Authorization applies for the whole session and for any parameters – the called function can therefore be run with any parameter values and must validate them itself. An unauthorized call returns the text “UNAUTHORIZED”.
- The string after the question mark is first decoded as a whole and only then split on commas, so a comma cannot be passed in a parameter, not even encoded by the urlEncode function – a value containing a comma must be sent in the request body (the “params” parameter of the loadUrl function). Parameters are trimmed of spaces.
- The external function is called with the “test” parameter set to “false” and the “cmd” object set to “null”. The return value is written into an HTML page; a plain text response for JavaScript is provided by the “Html.FlushAjaxContent” method.
- The loadUrl function always processes only one request – a call made while a request is in progress is discarded.
9.5. Simulation of opening a specific record in an edit form from a console application and reading the value of a control
using NETGenium;
using System;
using System.Data;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
int id = 1;
DataTable data = Data.Get("SELECT * FROM sholiday WHERE id = " + id, conn);
if (data.Rows.Count != 0)
{
conn.Register(data.Rows[0]);
}
string name = Parser.ToString(conn["name"]);
Console.WriteLine(name);
return "";
}
9.6. Capture events in the edit form
9.6.1. Open the edit form
// case "NETGenium.OnAfterOpen": OnAfterOpen(args, conn); return "";
using NETGenium;
using System;
private static void OnAfterOpen(string[] args, DbConnection conn)
{
int form = Parser.ToInt32(args[0]);
}
- The values of the record are available in the “conn” object only in the callback after saving and after deleting, and only if the “OnBeforeSave” or “OnBeforeDelete” script ran before; otherwise conn["id"] returns null and Parser.ToInt64 returns 0. Details are provided in chapters 5.42 to 5.44.
9.6.2. Save the record
// case "NETGenium.OnAfterSave": OnAfterSave(args, conn); return "";
using NETGenium;
using System;
private static void OnAfterSave(string[] args, DbConnection conn)
{
int form = Parser.ToInt32(args[0]);
long id = Parser.ToInt64(conn["id"]);
}
9.6.3. Deleting a record
// case "NETGenium.OnAfterDelete": OnAfterDelete(args, conn); return "";
using NETGenium;
using System;
private static void OnAfterDelete(string[] args, DbConnection conn)
{
int form = Parser.ToInt32(args[0]);
long id = Parser.ToInt64(conn["id"]);
}
10. View tables
10.1. Execute an external function with “ngef2” from a view table
10.1.1. Edits in NET Genium
- Create a new textbox in the “User” form
- Name: “Test”
- Check “Read-only”
- Check “Hidden field”
- Default value: ngef2(ngef2test)
- Check “Fill in default value every time the edit form is opened”
- Create a new view page with a datagrid that will only display the “Test” column
10.1.2. Edits in external function
// case "ngef2test": return ngef2test(args, conn);
using NETGenium;
using System;
using System.Collections.Generic;
using System.Data;
private static string ngef2test(string[] args, DbConnection conn)
{
int id = Parser.ToInt32(args[args.Length - 1]);
string key = "ngef2test";
Dictionary<int, DataRow> dictionary;
if (!conn.Container2.ContainsKey(key))
{
DataTable data = Data.Get("SELECT id, loginname FROM susers", conn);
dictionary = Data.DictionaryInt32(data);
conn.Container2.Add(key, dictionary);
}
else
{
dictionary = (Dictionary<int, DataRow>)conn.Container2[key];
}
if (dictionary.ContainsKey(id))
{
return dictionary[id]["id"] + ": " + dictionary[id]["loginname"].ToString();
}
return id.ToString();
}
10.2. Filling in the values in the view table
10.2.1. Edits in NET Genium
- Create a new textbox in the “User” form
- Name: “Test”
- Find out the textbox ID: for example “7125”
- Create a new view page with a datagrid that will only display the “Test” column
- Check “ngef(NETGenium.DataTable)” in the data source on the “Other” tab
- Copy the source code sample under the check box to the clipboard:
- if (args[0] == "Q987" && args[1] == "1")
- {
- DataTable data = (DataTable)conn.Container2[args [0]];
- int form = Parser.ToInt32(args [1]);
- }
10.2.2. Edits in external function – version 1 for a small number of records
// case "NETGenium.DataTable": QueryBuilder(args, conn); return "";
using NETGenium;
using System;
using System.Collections.Generic;
using System.Data;
private static void QueryBuilder(string[] args, DbConnection conn)
{
if (args[0] == "Q987" && args[1] == "1")
{
DataTable data = (DataTable)conn.Container2[args[0]];
int form = Parser.ToInt32(args[1]);
string key = "c7125";
if (data.Columns.Contains(key) && data.Rows.Count != 0)
{
List<int> ids = new List<int>();
foreach (DataRow row in data.Rows)
{
int id = (int)row["id"];
if (id != 0)
{
ids.Add(id);
}
}
if (ids.Count == 0)
{
return;
}
DataTable data2 = Data.Get("SELECT id, loginname FROM susers WHERE id IN (" + Sql.Ids(ids.ToArray()) + ")", conn);
Dictionary<int, DataRow> dictionary = Data.DictionaryInt32(data2);
foreach (DataRow row in data.Rows)
{
int id = (int)row["id"];
if (dictionary.ContainsKey(id))
{
row[key] = dictionary[id]["id"] + ": " +
dictionary[id]["loginname"].ToString();
}
else
{
row[key] = id.ToString();
}
}
}
}
}
10.2.3. Edits in external function – version 2 for a large number of records
// case "NETGenium.DataTable": QueryBuilder(args, conn); return "";
using NETGenium;
using System;
using System.Collections.Generic;
using System.Data;
private static void QueryBuilder(string[] args, DbConnection conn)
{
if (args[0] == "Q987" && args[1] == "1")
{
DataTable data = (DataTable)conn.Container2[args[0]];
int form = Parser.ToInt32(args[1]);
string key = "c7125";
if (data.Columns.Contains(key) && data.Rows.Count != 0)
{
Dictionary<int, DataRow> dictionary = new Dictionary<int, DataRow>();
List<int> ids = new List<int>();
foreach (DataRow row in data.Rows)
{
int id = (int)row["id"];
if (id != 0 && !dictionary.ContainsKey(id))
{
dictionary.Add(id, row);
ids.Add(id);
}
}
if (ids.Count != 0)
foreach (string group in Sql.IdsInGroupsOf100(ids.ToArray()))
using (DbCommand cmd = new DbCommand("SELECT id, loginname FROM susers WHERE id IN (" + group + ")", conn))
using (DbDataReader dr = cmd.ExecuteReader())
while (dr.Read())
{
int id = (int)dr["id"];
if (dictionary.ContainsKey(id))
{
dictionary[id][key] = id + ": " + dr["loginname"].ToString();
}
}
}
}
}
10.3. Export data from a statistical view table
// case "NETGenium.DataGrid2Excel": DataGrid2Excel(args, conn); return "";
using NETGenium;
using System;
private static void DataGrid2Excel(string[] args, DbConnection conn)
{
string path = args[0], query = args[1];
int form = Parser.ToInt32(args[2]);
DateTime date1 = Parser.ToDateTime(args[3]);
DateTime date2 = Parser.ToDateTime(args[4]);
}
11. File attachments
11.1. Creating a file attachment
using NETGenium;
using System;
using System.Text;
private static string MyFirstFunction(string[] args, DbConnection conn)
{
int file = Attachment.Add("test.txt", Encoding.UTF8.GetBytes("abc"), conn);
Console.WriteLine(file);
return "";
}
11.2. Load the contents of a file attachment
using NETGenium;
using System;
using System.Text;
private static string MyFirstFunction(string[] args, DbConnection conn)
{
int file = Attachment.Add("test.txt", Encoding.UTF8.GetBytes("abc"), conn);
Console.WriteLine(file);
string s = conn.GetAttachmentAsString(file, Encoding.UTF8);
if (s != null)
{
Console.WriteLine(s);
}
return "";
}
11.3. Prevent downloading of a file attachment – exchange of content for an empty file
// case "NETGenium.Download": return Download(args, conn);
using NETGenium;
using System;
private static string Download(string[] args, DbConnection conn)
{
int id = Parser.ToInt32(args[0]);
string filename = args[1], path = args[2];
L.N("Download: " + conn.User.LoginName + "; " + id + "; " + filename + "; " + path);
path = conn.RootPath + "Images\\1x1.gif";
return "OK\r\n" + path;
}
- The path replacement applies wherever the path of an attachment is resolved – also when an attachment is attached to an e-mail message, read through the MCP server and in server functions. The file is offered under its original name and extension, so the substituted image does not open for other formats. In the NET Genium Online cloud edition there is no “Images” directory in the root of the instance.
12. E-mails
12.1. Sending an e-mail message
using NETGenium;
using System;
using System.Net.Mail;
private static string SendMessage(string[] args, DbConnection conn)
{
string html = NETGenium.Email.Message.Container("<b>Hello</b>");
using (MailMessage message = new MailMessage())
{
message.From = new MailAddress("info@mycompany.com");
message.To.Add(new MailAddress("info@netgenium.com"));
message.Subject = "Test";
message.AlternateViews.Add(Config.CreateTextAlternateView(Html.ToText(html)));
message.AlternateViews.Add(Config.CreateHtmlAlternateView(html, conn));
string error = Config.SendMessage(message, conn);
return error != null ? error : "";
}
}
- The Config.SendMessage method does not throw an exception – on failure it returns the error text and saves the message into the “SMTP” directory, otherwise it returns null. The Config.SendMessage(message, false, conn) variant sends the message through SMTP server 2.
12.2. Capture the event of sending an e-mail message via the “New e-mail” form, and skip saving the message to the sent ones
// case "NETGenium.SendMessage": return SendMessage(args, conn);
using NETGenium;
using System;
using System.Net.Mail;
private static string SendMessage(string[] args, DbConnection conn)
{
MailMessage message = (MailMessage)conn.Container2["message"];
bool save = (bool)conn.Container2["save"];
return "skipsave";
}
- The returned string “skipsave” is compared without the OK\r\n prefix and case-sensitively; it applies only if saving the message is selected. The “message” object is disposed after the function returns, so it cannot be kept.
13. Printing to printing templates
13.1. Capture a print event in a print template, and change the contents or name of the printed file
// case "NETGenium.OnAfterPrint": OnAfterPrint(args, conn); return "";
using NETGenium;
using System;
private static void OnAfterPrint(string[] args, DbConnection conn)
{
if (conn.PrintingProcess.Template == "Test.pdf")
{
string path = conn.PrintingProcess.FilePath;
Files.Write(path, "abc");
conn.PrintingProcess.FileName = "abc.txt";
}
}
13.2. Change the password for locking Excel print templates
// case "NETGenium.ExcelPassword": return ExcelPassword();
using NETGenium;
using System;
private static string ExcelPassword()
{
return "OK\r\n" + Guid.NewGuid().ToString();
}
- The password locks the sheets against editing; the file itself is not encrypted and can be opened without a password.
14. User login
14.1. Capture the “OnBeforeLogin” event immediately before the user automatically logs on through Active Directory
// case "NETGenium.OnBeforeLogin": return OnBeforeLogin(args, conn);
using NETGenium;
using System;
private static string OnBeforeLogin(string[] args, DbConnection conn)
{
string loginby = args[0];
DbRow row;
if (loginby.Length != 0)
{
row = new DbRow("SELECT id, loginname FROM susers WHERE account = " + conn.Format(loginby) + " OR email = " + conn.Format(loginby), conn);
}
else
{
string loginname = args[1];
row = new DbRow("SELECT id, loginname FROM susers WHERE loginname = " + conn.Format(loginname), conn);
}
if (row.Read())
{
// external authorization of the user, e.g. changing their user groups
}
return "";
}
- For a login through Active Directory args[0] contains the user's account, for a login through an identity provider their e-mail address; for a login with a name and password args[0] is empty and args[1] contains the login name.
- The OK\r\n return value can prevent login only for a login with a name and password; for an automatic login the return value is not used.
14.2. Prevent users from logging on
// case "NETGenium.OnAfterLogin": return OnAfterLogin(args, conn);
using NETGenium;
using System;
public static string OnAfterLogin(string[] args, DbConnection conn)
{
string error = Error(conn);
if (error != null)
{
conn.HttpContext.Session.Abandon();
return "OK\r\nalert(" + JS.ToString(error) + "); if (opener != null) window.close(); else top.location = 'Logout.aspx';";
}
return "";
}
private static string Error(DbConnection conn)
{
if (conn.User.ID != 1 && conn.User.Attributes.Contains("ng_active") && conn.User["ng_active"].ToString().Length == 0)
{
return "Invalid login.";
}
return null;
}
- JavaScript returned with the OK\r\n prefix runs only in the portal login window and in the “Change password” dialog.
- The function is not run when logging in with an MCP token, through the web services of the NETGeniumConnection library or through a custom API, so the restriction made by this function does not apply to these accesses; access of the account is restricted there by its user groups or by blocking the account.
15. Other
15.1. Logging to disk in the “Logs” directory
using NETGenium;
using System;
using System.IO;
private static string MyFirstFunction(string[] args, DbCommand cmd, DbConnection conn)
{
try
{
throw new NullReferenceException("args");
}
catch (Exception ex)
{
L.E("MyFirstFunction", ex);
// L.LogError("MyFirstFunction", ex);
L.W("MyFirstFunction", ex);
// L.LogWarning("MyFirstFunction", ex);
L.N("MyFirstFunction", ex);
// L.LogNotice("MyFirstFunction", ex);
}
return "";
}
- 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'”.
15.2. Capture a database record lock event
// case "NETGenium.LockRecord": LockRecord(args, conn); return "";
// case "NETGenium.UnlockRecord": UnlockRecord(args, conn); return "";
using NETGenium;
using System;
private static void LockRecord(string[] args, DbConnection conn)
{
int form = Parser.ToInt32(args[0]);
long id = Parser.ToInt64(args[1]);
}
private static void UnlockRecord(string[] args, DbConnection conn)
{
int form = Parser.ToInt32(args[0]);
long id = Parser.ToInt64(args[1]);
}
15.3. Change the content of the text “Copyright”
// case "NETGenium.Copyright": return Copyright(args, conn);
using NETGenium;
using System;
private static string Copyright(string[] args, DbConnection conn)
{
return "OK\r\n © NetGenium " + conn.Now.Year;
}
15.4. Additional CSS style adjustments when saving the layout
// case "NETGenium.CSS": return CSS(args);
using NETGenium;
using System;
private static string CSS(string[] args)
{
string value = args[0], browser = args[1];
return "OK\r\n" + value + ".teststyle { color: red; }";
}