Examples
{#
string output = string.Empty;
/* Any number of local variables can be declared */
foreach(var ticket in Ticket.SuccessorTickets)
/* The property "SuccessorTickets" returns a list of tickets
and the variable "ticket" (small letters) is also a ticket and supports
all properties like the actual "ticket". */
{
output += ticket.FullId + ": " + ticket.Title + Environment.NewLine;
}
output
/* This is the actual output expression, therefore without semicolon. */
#}
Output:
CE_00026: Create WebAPI for B2BWebServices functionality
CR_15911: WebAPI for the CMDB to replace the current B2B-WebServices
CR_15912: WebApi for Expense Management to replace the current B2B WebServices
Tip:
The string variable output is not absolutely necessary. It is used for a simple output of the results of expressions to better illustrate them.
{#
string output = string.Empty;
output = "Absence from: " + DateTime.Parse(Ticket.Fields["Absence_from"].Text) +
"/Absence to: " + DateTime.Parse(Ticket.Fields["Absence_to"].Text);
output
#}
Output:
Absence from: 15.02.2018 06:00:00/Absence to: 15.02.2018 15:00:00
When accessing ticket fields with Sharp-Expressions, the case that the field does not exist must always be considered.
For an expression (such as {#String.IsNullOrEmpty(Ticket.Fields["MyField"].Text)#}), even the first (bold) part can be without result if the ticket does not (or no longer) have the field. If this is the case, accessing .Text internally throws a NullReferenceException. The execution of the expression aborts or runs into the next catch() (if available).
So before the statement can be used without any problems, it must always be checked if this part is not equal to null (!= null). However, this method is complex and difficult to read.
Furthermore, single-line expressions without semicolons are executed faster than longer ones.
A better variant is the expression GetFieldText(). It returns the field content or NULL, but no NullReferenceException and saves the otherwise necessary test for not equal to null.
{#String.IsNullOrEmpty(Ticket.GetFieldText("MyField"))#}
For the following objects the return values of the Sharp-Expressions are always the field text/field value or, if the field does not exist, NULL:
| Object | Sharp-Expression |
|---|---|
| Ticket |
CxTicket.GetFieldText("MyField") CxTicket.GetFieldValue("MyField") |
| CI |
CxCi.GetFieldText("MyField") CxCi.GetFieldValue("MyField") |
| Zone |
CxCiZone.GetFieldText("MyField") CxCiZone.GetFieldValue("MyField") |
| Form |
CxForm.GetFieldText("MyField") CxForm.GetFieldValue("MyField") |
| Service Portfolio |
CxService.GetFieldText("MyField") CxService.GetFieldValue("MyField") |
| Workflow Item |
CxWorkflowItem.GetFieldText("MyField") CxWorkflowItem.GetFieldValue("MyField") |
In addition, field access to elements via their names, e.g. Ticket.Fields["MyField"] is case-insensitive. Example: Ticket.Fields["MyField"] selects the same field as Ticket.Fields["myfield"].
This is possible with the following expressions:
| CxCi.Fields[...] | CxService.Fields[...] |
| CxCiZone.Fields[...] | CxServiceTransaction.ProcessRoles[...] |
| CxForm.Fields[...] | CxContractPriority.EscalationTimes[...] |
| CxKbArticle.TitleAllLanguages[...] | CxTicket.AttachmentsByCategory[...] |
| CxKbArticle.KeywordsAllLanguages[...] | CxTicket.Fields[...] |
| CxKbArticle.ContentAllLanguages[...] | CxTicket.CustomAttributes[...] |
| CxKbArticle.DescriptionAllLanguages[...] | CxTicket.Escalations[...] |
| CxUser.CustomAttributes[...] | CxTicket.PendingEscalations[...] |
| CxGroup.CustomAttributes[...] | CxTicketSchema.ProcessRoles[...] |
| CxUser.TextBuildingBlocks[...] | CxWorkflowItem.Fields[...] |
| CxGroup.TextBuildingBlocks[...] |
{#
string output = string.Empty;
int i = 0;
while(i < 10)
{
if(i%2 == 0)
{
output += i.ToString() + " is even" + Environment.NewLine;
}
else
{
output += i.ToString() + " is odd" + Environment.NewLine;
}
i++;
}
output
#}
Output:
0 is even
1 is odd
2 is even
3 is odd
4 is even
5 is odd
6 is even
7 is odd
8 is even
9 is odd
{#
string output = string.Empty;
foreach(var ticket in Ticket.SuccessorTickets)
{
output += ticket.FullId + ": " + ticket.Title + Environment.NewLine;
if(ticket.Title.IndexOf("language", StringComparison.OrdinalIgnoreCase) >= 0)
{
output += "... has something to do with languages ..." + Environment.NewLine;
}
}
output
#}
Output:
CE_00026: Create WebAPI for B2BWebServices functionality
CR_15911: WebAPI for the CMDB to replace the current B2B-WebServices
CR_15912: WebApi for Expense Management to replace the current B2B WebServices
{#
string output = "Successor tickets of " + Ticket.FullId +
" with WebAPI context:" + Environment.NewLine;
foreach(var ticket in Ticket.SuccessorTickets.Where(st => st.Title.Contains("WebAPI")))
{
output += ticket.FullId + ": " + ticket.Title + Environment.NewLine;
}
output
#}
Output:
Successor tickets of SR_00089 with WebAPI context:
CE_00026: Create WebAPI for B2BWebServices functionality
CR_15911: WebAPI for the CMDB to replace the current B2B-WebServices
CR_15913: WebAPI for the User Management to replace the current B2B-WebServices
{#
string output = "Successor tickets of " + Ticket.FullId +
" with {#"WebAPI"#} context:" + Environment.NewLine;
foreach(var ticket in Ticket.SuccessorTickets.Where(st => st.Title.Contains("{#"WebAPI"#}")))
{
output += ticket.FullId + ": " + ticket.Title + Environment.NewLine;
}
output
#}
Output:
Successor tickets of SR_00089 with WebAPI context:
CE_00026: Create WebAPI for B2BWebServices functionality
CR_15911: WebAPI for the CMDB to replace the current B2B-WebServices
CR_15913: WebAPI for the User Management to replace the current B2B-WebServices
{#
string output = string.Empty;
output = Ticket.Comments.OrderByDescending(c => c.CreateDateTime).First().Text;
output
#}
/* "OrderByDescending(c => c.CreateDateTime)" can also be omitted,
as the comments are delivered in descending order. "First()" is semantically
misleading without the explicit sorting, since it is not the first comment,
but the last one. */
Output:
Ticket successfully converted into a KB article.
Tip:
The LINQ methods .First()/.Last()/.Single() generate an exception for an empty list. It is better to use the variants .FirstOrDefault()/.LastOrDefault()/
-
These return a default(Object), which is NULL in the case of references and takes the default value (e.g. 0 for int) for value types.
-
The return value can be better checked within the expression, especially if the result has to be processed further in the expression script.
An easy way to provide generic links to specific objects on the 446 Plattform® is to use TinyURLs.
General form:
{protocol}://{host}(:{port})/{entitytype}?{id}
TinyURL handlers are available for the following object types:
Tickets, CIs, Tasks, Expenses and Knowledge Base articles
{protocol}://{host}(:{port})/ticket?{ticketfullid}
{protocol}://{host}(:{port})/ci?{visibleid}
{protocol}://{host}(:{port})/task?{taskid}
{protocol}://{host}(:{port})/expense?{expenseid}
{protocol}://{host}(:{port})/knowledgebase?{articleid}
Sharp-Expressions:
{#General.HelpDeskLink + "ticket?" + Ticket.FullId#}
{#General.HelpDeskLink + "ci?" + Ci.VisibleId#}
{#General.HelpDeskLink + "task?" + Task.FullId#}
{#General.HelpDeskLink + "expense?" + Expense.DataId#}
{#General.HelpDeskLink + "knowledgebase?" + KbArticle.FullId#}
For the following types the tab to be opened can be specified optionally:
Tickets, CIs, Tasks and Knowledge Base articles
{protocol}://{host}(:{port})/{entitytype}?{id}&tab={tabname}
| Ticket | CI | Task | KB | ||||
|---|---|---|---|---|---|---|---|
| UI name | tabname | UI name | tabname | UI name | tabname | UI name | tabname |
| Overview | Overview | Details | Details | Details | Details | Overview | Overview |
| Details | Details | Affected users | AffectedPrincipals | Fields | Fields | Content | Content |
| Fields | Fields | Files | UploadFiles | Actions | Actions | Files | Attachment |
| File attachments | UploadFiles | Fields | Fields | Comments | Comments | Rating | Comments |
| Actions | Actions | Relations | Relations | Files | Files | History | History |
| Tasks | Tasks | Tickets | Tickets | Relations | Relations | Actions | Actions |
| Messages | Mails | Services | Services | Expenses | Expenses | Versions | Revisions |
| History | History | History | History | History | History | Tickets | Tickets |
| Comments | Comments | Comments | Comments | ||||
| Solution | Solution | SLA | SLA | ||||
| CMDB | Cm | ||||||
| Scheduler | Todos | ||||||
| Expenses | Expenses | ||||||
| Knowledge Base | Knowledgebase | ||||||
Examples:
http://servicedesk.isonet.ch/ci?Ci_1234&tab=fields
http://servicedesk.isonet.ch/task?tsk_1234
http://servicedesk.isonet.ch/expense?1234
http://servicedesk.isonet.ch/knowledgebase?KB-1234&tab=history
Similarly, for the respective object type there is a direct expression for a link to the respective object (e.g. {#Expense.Url#}; see respective expression reference).
{#
using Isonet.TxpEnterprise.BusinessManagers.SharpExpressions.ContextObjects.Cmdb;
string scheme_hw = "Explizite Hardware (HW)";
string scheme_hwt = "Generische Hardware (HWT)";
string scheme_sw = "Explizite Software (SW)";
string scheme_sys = "Explizite Anlage (SYS)";
string scheme_syst = "Generische Anlage (SYST)";
string field_sw_version = "SW version";
string field_hw_devnr = "HW device nr";
string field_hw_port = "HW port";
string field_hwt_devtype = "HWT device";
string field_sys_world = "SYS world";
string field_sys_functionnr = "SYS function nr";
string field_syst_function = "SYST function";
string default_empty_field = "";
string sep_other = "#";
string sep_sw = "$";
private CxCi mySourceCi = Ci;
/* Set separator */
string sep;
if(mySourceCi.Schema.FriendlyName == scheme_sw)
{
sep = sep_sw;
}
else
{
sep = sep_other;
}
/*===========================================*/
/* Get field contents, if set */
private string AddSep(string input)
{
if(input != "" && input.Substring(0,1) == sep)
{
return input;
}
else
{
if(input == "" )
{
return sep;
}
else
{
return sep + input;
}
}
}
/*===========================================*/
/* Get field contents, if set */
private string GetFieldText(CxCi ci, string field)
{
try
{
return ci.Fields[field].Text != "" ? ci.Fields[field].Text : default_empty_field;
}
catch
{
return default_empty_field;
}
}
/*===========================================*/
/* Get location path, if set */
private string GetLocation(CxCi ci)
{
string zone = "";
try
{
zone += ci.Zone.Fields["ENV site"].Text ;
}
catch{}
try
{
zone += ci.Zone.Fields["ENV object"].Text;
}
catch{}
try
{
zone += ci.Zone.Fields["ENV floor"].Text;
}
catch{}
try
{
zone += ci.Zone.Fields["ENV room"].Text;
}
catch{}
try
{
zone += ci.Zone.Fields["ENV cabinet zone"].Text;
}
catch{}
return zone;
}
/*===========================================*/
/* Get initial field contents, if set */
private string GetInitialFieldText(CxCi ci)
{
string result = default_empty_field;
if(ci.Schema.FriendlyName == scheme_hw)
{
result = GetFieldText(ci, field_hw_port);
}
if(ci.Schema.FriendlyName == scheme_sw)
{
result = GetFieldText(ci, field_sw_version);
}
return (result != default_empty_field && result != "" && result.Substring(0, 1) != "/") ? "/" +
result : result;
}
/*===========================================*/
/* Get (first) parent-CI from the specified schema, if available */
private int SearchForSchemeInParents(CxCi ci, string scheme)
{
int anz = ci.ParentCis.Count;
if(anz > 0)
{
for(int i = 0; i < anz; i++)
{
if(ci.ParentCis[i].Schema.FriendlyName == scheme)
{
return i;
}
}
}
return -1;
}
/*===========================================*/
/* Get Recursive Marking Guide */
private string GetNextSubstring(CxCi ci, string StringSoFar, bool OwnLoc)
{
string result = "";
/* transferred CI is HW-CI */
if(ci.Schema.FriendlyName == scheme_hw)
{
int hw = SearchForSchemeInParents(ci, scheme_hw);
int hwt = SearchForSchemeInParents(ci, scheme_hwt);
int sys = SearchForSchemeInParents(ci, scheme_sys);
string hwt_part = "_undefined HWT_";
if(hwt > -1)
{
hwt_part = GetNextSubstring(ci.ParentCis[hwt], StringSoFar, true);
}
if(hw == -1 && sys == -1)
{
/* if neither linked to SYS nor HW, then done, because spare */
result = hwt_part + GetFieldText(ci, field_hw_devnr) + StringSoFar;
return (OwnLoc ? GetLocation(ci) : "") + AddSep(result);
}
if((hw == -1 ? 0 : 1) + (sys == -1 ? 0 : 1) != 1)
{
/* if (both SYS and HW) or (more than 1 SYS) or
(more than 1 HW) linked, it is not sure where */
result = "_undefined ParentCI_" + hwt_part +
GetFieldText(ci, field_hw_devnr) + StringSoFar;
return (OwnLoc ? GetLocation(ci) : "") + AddSep(result);
}
int next = hw > sys ? hw : sys;
/* if HW found, then HW, otherwise SYS */
string loc = "";
bool loc_req = true;
if(OwnLoc)
{
loc = GetLocation(ci);
if(loc != "")
{
loc_req = false;
}
}
return loc + GetNextSubstring(ci.ParentCis[next], StringSoFar, loc_req) +
hwt_part + GetFieldText(ci, field_hw_devnr) + StringSoFar;
}
/* transferred CI is HWT-CI */
if(ci.Schema.FriendlyName == scheme_hwt)
{
return GetFieldText(ci, field_hwt_devtype);
}
/* transferred CI is SYS-CI */
if(ci.Schema.FriendlyName == scheme_sys)
{
int i = SearchForSchemeInParents(ci, scheme_syst);
if(i != -1){
CxCi NextCi = ci.ParentCis[i];
result = GetFieldText(ci, field_sys_world) + GetNextSubstring(NextCi, StringSoFar,true) +
GetFieldText(ci, field_sys_functionnr) + StringSoFar;
}
else
{
result = GetFieldText(ci, field_sys_world) + "_undefined SYST_" +
GetFieldText(ci, field_sys_functionnr) + StringSoFar;
}
return (OwnLoc ? GetLocation(ci) : "") + AddSep(result);
}
/* transferred CI is SYST-CI */
if(ci.Schema.FriendlyName == scheme_syst)
{
return GetFieldText(ci, field_syst_function);
}
/* transferred CI is SW-CI */
if(ci.Schema.FriendlyName == scheme_sw)
{
int hw = SearchForSchemeInParents(ci, scheme_hw);
if(hw == -1)
{
/* if not linked to HW, it is not sure which way */
result = "_undefined ParentCI_" + GetFieldText(ci, field_sw_version) + StringSoFar;
}
else
{
result = GetNextSubstring(ci.ParentCis[hw], StringSoFar, true) + StringSoFar;
}
return GetLocation(ci) + AddSep(result);
}
return "_undefined PartOfMarkingGuide_";
}
/*===========================================*/
string final = GetNextSubstring(mySourceCi, GetInitialFieldText(mySourceCi), true);
if(final.Contains("_undefined"))
{
final = "XX:" + final;
}
else if(mySourceCi.Schema.FriendlyName == scheme_hw)
{
final = "HW:" + final;
}
else if(mySourceCi.Schema.FriendlyName == scheme_sys)
{
final = "SYS:" + final;
}
else if(mySourceCi.Schema.FriendlyName == scheme_sw)
{
final = "SW:" + final;
}
else
{
final = "XX:" + final;
}
final
#}
{#
var ticket = Ticket;
while(ticket.HasParent)
{ticket = ticket.ParentTicket;}
ticket.FullId
#}
Option 1:
{# Ticket.ParentTicket != null && Ticket.ParentTicket.ClonedTickets.Contains(Ticket) #}
Option 2:
{# Ticket.HasParent && Ticket.ParentTicket.ClonedTickets.Contains(Ticket) #}
- Save REST JSON
{#
WorkflowItem.Fields["REST_returnData"].Text
#}
- Extract first element from JSON
{#
var c = WorkflowItem.Fields["L4Milestones"].Text;
c.Split(new string[] { "\"visibleId\" : \"" }, StringSplitOptions.None)[1].Split('\"')[0]
#}
- Remove already processed JSON part
{#
var c = WorkflowItem.Fields["L4Milestones"].Text;
c.Split(new string[] { "\"visibleId\" : \"" + WorkflowItem.Fields["MILESTONE_Name"].Text }, StringSplitOptions.None)[1]
#}
- Again to 2nd and then to 3rd
Repeat the instructions until all results are processed in a loop.
{#
Ticket.Fields["Title"].Text.Equals("hello world", StringComparison.OrdinalIgnoreCase)
#}
/*Count lines*/
{#
Ticket.Fields["table1"].Value.ConvertFromJson().Count
#}
/*Value of line 1 column 'ID'*/
{#
Ticket.Fields["table1"].Value.ConvertFromJson()[0].ID
#}
/*Value of line 2 column 'displayname'*/
{#
Ticket.Fields["table1"].Value.ConvertFromJson()[1].displayName
#}
/*JSON string*/
{#
Ticket.Fields["table1"].Value
#}
/*JSON object*/
{#
Ticket.Fields["table1"].Value.ConvertFromJson()
#}
(dynamic -> typeof List<Dictionary<string,string>> list of dictionary with column values --> columns header text as key of dictionary)
{#
string input, result;
try
{
input = Ticket.Fields["_next_date_start"].Text;
}
catch
{
input = "";
}
if(!String.IsNullOrEmpty(input))
{
DateTime dt = DateTime.Parse(input);
string timezone = "no_tz";
try
{
timezone = General.CurrentUser.TimeZone.Id;
}
catch{}
if(timezone != "no_tz")
{
try
{
result = TimeZoneInfo.ConvertTimeFromUtc(dt, TimeZoneInfo.FindSystemTimeZoneById(timezone)).ToString();
}
catch
{
result = dt.ToString() + " (UTC)";
}
}
else
{
result = dt.ToString() + " (UTC)";
}
}
else
{
result = "?";
}
result
#}
{#
string sep = "#X#"; CxCi ci;
try
{
ci = Ticket.Fields["in01_category_system"].As<CxCi>();
}
catch
{
return null;
}
string search = ci.Title;
search = search.Substring(search.LastIndexOf("(")).Trim(new char[] { '(', ')'});
foreach(var x in ci.RelatedCis)
{
if(x.Schema.DataId == 24/*Release*/ && x.VisibleId == search)
{
return x.VisibleId + sep + x.ObjectGuid.ToString().ToLower().Replace("-","");
}
}
#}
Compile a table with all ticket fields as a string assembly and return the result of the expression.
{#
string result = "";
foreach(var fieldName in Ticket.Fields.Keys)
{
result += fieldName + ": " + Ticket.Fields[fieldName].Text + Environment.NewLine;
}
/* This is the actual return */
result
#}
To be noted:
- The return variable result must be declared and initialized at the beginning.
- The end of the expression does not require a semicolon.
- Environment.NewLine is used to insert a line break.
Warning:
If the expression output is used within HTML, e.g. in a UI view, the line break must be inserted as HTML tag (... + "<br>")
- The table is not sorted. You can sort the table with this function:
{# foreach(var fieldName in Ticket.Fields.Keys.OrderBy(f => f) #}
Certain return types of expressions are not strings.
For example:
- Ticket.LastModification.ZonedDateTime (DateTime)
- Ticket.ObjectGuid (Guid)
- Ticket.DataId (Int32)
- Ticket.HasParent (Boolean)
- Ticket.EstimatedTimeExpense (TimeSpan)
To be able to compare data types with each other, both variables must be of the same type. A simple method is to convert the type into a string. This can be done with a .ToString() at the end:
#}
string identifier = Ticket.ObjectGuid.ToString();
ZonedDateTime.ToString("s");
#}
"s" stands for sortable format identifier
Output: 2020-05-05T13:45:30
Most simple data types can be converted into a character string. However, for complex data types, such as Lists, Dictionaries, or all Expression context objects, the result is only the type name of the object.
Converting a ticket field from a string to a user expression provides access to the properties of the expression:
{# Ticket.Fields["Standard_ProjectManager"].As<CxPrincipal>() #}
With the methods of Ticket.LinkedTickets. the linked tickets can be listed.
First linked ticket:
{#Ticket.LinkedTickets.First().Ticket.FullId#}
{#Ticket.LinkedTickets.First().Created#}
Last linked ticket:
{#Ticket.LinkedTickets.Last().Ticket.FullId#}
{#Ticket.LinkedTickets.Last().Created#}
Count of linked tickets:
{#Ticket.LinkedTickets.Count()#}
Index based access of first 3 linked tickets (0 based index):
{#Ticket.LinkedTickets[0].Ticket.FullId#}
{#Ticket.LinkedTickets[0].Created#}
{#Ticket.LinkedTickets[1].Ticket.FullId#}
{#Ticket.LinkedTickets[1].Created#}
{#Ticket.LinkedTickets[2].Ticket.FullId#}
{#Ticket.LinkedTickets[2].Created#}
With these functions, the text of a ticket can be converted for different uses:
HtmlDecode()
Example:
string decodedHtml = "<span>Example</span>".HtmlDecode()
Result: "<span>Example</span>"
JavascriptEncode()
Examples:
string encodedJS = "alert('test')".JavascriptEncode()
Result: "alert(\u0027test\u0027)"
ConvertToJson()
Examples:
string jsonTicket = ticket.ConvertToJson()
{# Ticket.Fields["WorknoteToSBB"].Text.ConvertToJson() #}
ConvertFromJson()
Examples:
Ticket ticket = "{...}".ConvertFromJson()
UrlEncode()
Examples:
string encodedUrl = "http://isonet.ch".UrlEncode()
Result: "http%3A%2F%2Fisonet.ch"
UrlDecode()
Examples:
string decodedUrl = "http%3A%2F%2Fisonet.ch".UrlDecode()
Result: "http://isonet.ch"
XmlEncode()
Examples:
string encodedXml = "Ticket title".XmlEncode()
Result: "Ticket_x0020_title"
XmlDecode()
Examples:
string decodedXml = "Ticket_x0020_title".XmlDecode()
Result: "Ticket title"