4.6.0: Token Replace
DotNetNuke 4.6.0 is going to include a new Token Replace engine. It searches text for tokens, analyses them and replaces the tokens with their related framework values. The syntax is quite simple: [Object:Property]. For example the token [User:Lastname] will be replaced with the last name of the current user. You might already know this kind of notation; it is the same that has been already used for system messages inside the language pack.
History
At the end of last year Sebastian and I were working on a request for User Defined Table 3.4.0. We were asked to replace the caption of a link with context information. Sebastian as editor of the German Language pack was familiar with the token syntax, so we took the code out of localization and used it in our module. Quickly we realized that this code was too slow to be called for each row of a User Defined Table. We rewrote that code from scratch using Regular Expressions, and it was impressive enough to get part of DotNetNuke 4.4.1. inside the Localization namespace.
We now had got a fast token replacement service, and some developers already started on reusing the code inside their own projects. Reusing meant that they copied the code and wrote their own, specialized token engines. As copying code is not really an Object Orientated technique, I started again a major refactoring which resulted in a new set of classes located in the new DotNetNuke.Services.Tokens namespace. These new services provide new enhancements and are open for own extensions. The previous TokenReplace class inside the Localization namespace still exists, but it is now deprecated and is actually just an adapter to the new classes.
Usage
TokenReplace allows the following notations:
[Object :Property]
[Object :Property|Format]
[Object :Property|Format|IfEmpyReplacement]
TokenReplace recognizes the following Object names as valid source for values:
Object |
Class / Data source |
Default Content** |
“Host” |
System.Collection.Hashtable |
Secure Hostsettings |
“Portal” |
DotNetNuke.Entities.Portals.PortalSettings |
current PortalSettings |
“Tab” |
DotNetNuke.Entities.Tabs.TabInfo |
current TabInfo |
“Module” |
DotNetNuke.Entities.Modules.ModuleInfo |
Nothing |
“Culture” |
System.Globalization.CultureInfo |
currrent Culture |
|
|
|
“User” |
DotNetNuke.Entities.Users.UserInfo |
current User |
“Profile” |
DotNetNuke.Entities.Profile |
current User.Profile |
“Membership” |
DotNetNuke.Entities.Users.Membership |
current User.Membership |
|
|
|
“Date”, “DateTime”, “Time” |
System.DateTime |
current DateTime |
“Ticks” |
System.Int64 (Long) |
current DateTime in ticks |
|
|
|
“Row”, “Field” |
System.Data.DataRow |
Nothing |
“Custom”* |
System.Collections.ArrayList |
Nothing |
userdefined Token |
Any Object by Reflection |
Nothing |
userdefined Token |
Any System.Collection.IDictionary |
Nothing |
* “custom” can be replaced with custom text, passed as separate parameter
** The content depends also on the scope and profile visibility setup.
Scope |
Description |
NoSettings |
Only access to Date and Time |
Configuration |
Tokens for Host, Portal, Tab (, Module) |
DefaultSettings |
Configuration and Current User |
SystemMessages |
System notifications to users and adminstrators |
Debug |
internal debugging, error messages, logs |
The first four scopes will only return public relevant information. For example the only public avaiable property is HostName. All others are only accessible to the "Debug" scope. Critical information (host password...) is not available through TokenReplace.
The output can be formatted using a format string. You can use the usual Date and Time Format Strings for date values or Numeric Format Strings for any numeric value. String values are handled using String.Format.
Example:
[User:Lastname|Dear Mr./Mrs . {0}] Dear Mr./Mrs. Walker
[Date:Now|dddd] Monday
The “Format” string can be followed by an additional “IfEmptyReplacement”, which will be returned when is requested property is not found or not set.
Example:
[User:Firstname|Hi {0}|Hello Guest]
Reusing
TokenReplace was built with extensible in mind and was therefore created using inheritance. The top level class is BaseTokenReplace. This class handles the tokenization using Regular Expressions. If you only want to handle one single or only few custom token, this class is your friend:
Public Class DateTimeTokenReplace
Inherits Services.Tokens.BaseTokenReplace
Public Function ReplaceDateTimeTokens(ByVal strSource As String) As String
Return MyBase.ReplaceTokens(strSource)
End Function
Protected Overrides Function replacedTokenValue(ByVal strObjectName As String, _
ByVal strPropertyName As String, ByVal strFormat As String) As String
Dim result As String = String.Empty
If strFormat = String.Empty Then strFormat = "g"
If strObjectName.ToLower = "datetime" Then
If strPropertyName.ToLower = "today" Then
result = System.DateTime.Today.ToString(strFormat)
Else
result = System.DateTime.Now.ToString(strFormat)
End If
End If
Return result
End Function
End Class
'sets "Today is Monday" on Mondays
Dim r as String = new DateTimeTokenReplace().ReplaceDateTimeTokens("Today is [Datetime:today|dddd]")
If you need to handle multiple kinds of Object tokens, you might want to handle every data source in its own custom class. This class needs to implement IPropertyAccess which consists out of the function GetProperty and the property Cacheability.
Public Interface IPropertyAccess
Function GetProperty(ByVal strPropertyName As String, ByVal strFormat As String, _
ByVal formatProvider As Globalization.CultureInfo, _
ByVal AccessingUser As UserInfo, ByVal AccessLevel As Scope, _
ByRef PropertyNotFound As Boolean) As String
ReadOnly Property Cacheability() As CacheLevel
End Interface
BaseCustomTokenReplace is able to handle multiple data sources. The previous example can now be rewritten. First we need the property access class:
Class DateTimeAccess
Implements Services.Tokens.IPropertyAccess
Public Function GetProperty(ByVal strPropertyName As String, ByVal strFormat As String, _
ByVal formatProvider As System.Globalization.CultureInfo, ByVal AccessingUser As Entities.Users.UserInfo, _
ByVal AccessLevel As Scope, ByRef PropertyNotFound As Boolean) As String Implements IPropertyAccess.GetProperty
Dim result As String = String.Empty
If strFormat = String.Empty Then strFormat = "g"
If strPropertyName.ToLower = "today" Then
result = System.DateTime.Today.ToString(strFormat, formatprovider)
Else
result = System.DateTime.Now.ToString(strFormat. formatprovider)
End If
Return result
End Function
Public ReadOnly Property Cacheability() As CacheLevel Implements Services.Tokens.IPropertyAccess.Cacheability
Get
Return CacheLevel.secureforCaching
End Get
End Property
End Class
The main token replace is now more compact:
Public Class DateTimeTokenReplace2
Inherits Services.Tokens.BaseCustomTokenReplace
Sub New()
PropertySource("datetime") = New DateTimeAccess
End Sub
Public Function ReplaceDateTimeTokens(ByVal strSource As String) As String
Return MyBase.ReplaceTokens(strSource)
End Function
End Class
…
Dim dttr2 As New DateTimeTokenReplace2()
dttr2.DebugMessages = True
dttr2.Language = "de-de"
'sets "Montag" on Mondays
Dim s As String = dttr2.ReplaceDateTimeTokens("[datetime:today|dddd]")
There are three different CacheLevel:
CacheLevel | Meaning |
notCacheable | Caching of the text is not suitable and might expose security risks |
secureforCaching | Caching of the text might result in inaccurate display (e.g. time), but does not expose a security risk |
fullyCacheable | Caching of the text can be done without limitations or any risk |
You can ask TokenReplace whether your current [Token] string whether is cachable or not.
Dim cl as CacheLevel = dttr2.Cacheability("Is [Date:Now] cacheable")
'cl = CacheLevel.SecureForCaching
Class TokenReplace is the main class inside the tokens namespace. It uses to handle 13 different data sources. A new class does not need to handle each data source. If there is already an info class, the interface can also be put inside. Examples for these are UserInfo or ModuleInfo.
The method ReplaceEnviromentTokens has a number of overloads, which allow also to use DataRows, Dictionaries (HashTables) or ArrayLists as source for the tokenization.
Public Function ReplaceEnvironmentTokens(ByVal strSourceText As String) As String
Public Function ReplaceEnvironmentTokens(ByVal strSourceText As String, _
ByVal row As DataRow) As String
Public Function ReplaceEnvironmentTokens(ByVal strSourceText As String, _
ByVal Custom As ArrayList, ByVal CustomCaption As String) As String
Public Function ReplaceEnvironmentTokens(ByVal strSourceText As String, _
ByVal Custom As IDictionary, ByVal CustomCaption As String) As String
Public Function ReplaceEnvironmentTokens(ByVal strSourceText As String, _
ByVal Custom As ArrayList, ByVal CustomCaption As String, _
ByVal Row As System.Data.DataRow) As String
TokenReplace is not sealed, so you can extend it with your own data sources.
TokenReplace can also handle object less [PROPERTY] tokens, which might help to renew some already existing Token Engines inside DotNetNuke.
Example:
Public Class DateTimeTokenReplace3
Inherits DotNetNuke.Services.Tokens.TokenReplace
Public Sub New()
MyBase.new(Services.Tokens.Scope.Configuration)
UseObjectLessExpression = True
PropertySource(ObjectLessToken) = New DateTimeAccess
End Sub
End Class
…
dim s as String = new DateTimeTokenReplace3().ReplaceEnvironmentTokens( _
"hello [User:Displayname|{0}|Guest], today is [TODAY]")