-
-
Notifications
You must be signed in to change notification settings - Fork 104
Data Source Extensions
Source extensions translate complex types into objects, which can be represented as a string
. Each source extensions handles one object or interface type (e.g. an IList
).
Required ISource
extensions must be registered with the SmartFormatter
.
Data sources are added by calling
SmartFormatter.AddExtensions(...)
SmartFormatter.InsertExtension(...)
With AddExtensions(...)
all WellKnownExtensionTypes.Sources
and WellKnownExtensionTypes.Formatters
are automatically inserted to the extension list at the place where they usually should be.
InsertExtension(...)
lets you insert an extension to the desired position in the list.
From a performance perspective, only register source extensions that are actually needed:
// Add needed source extensions
var smart = new SmartFormatter()
.AddExtensions(new ReflectionSource(), new DefaultSource());
// Add all default source extensions (and formatters)
smart = Smart.CreateDefaultFormatter();
The SmartFormatter
evaluates the selectors in a Placeholder
one by one. Fore each selector, the registered ISource
s are invoked. As soon as an ISource
can detect a matching variable from the data arguments, it returns the value.
That's why the order in the list of registered data sources is important.
If no matching data can be found for a Placeholder
across all ISource
s, a FormattingException
will throw.
After the selector was successfully evaluated, IFormatter
(s) will be invoked.
For SmartSettings.CaseSensitivity == CaseSensitivityType.CaseInsensitive
, and multiple members share the same name but differ in case, the first encountered member will be selected.
DefaultSource
-
ListFormatter
(implementingISource
) ReflectionSource
DictionarySource
KeyValuePairSource
StringSource
NewtonsoftJsonSource
SystemTextJsonSource
XmlSource
ValueTupleSource
GlobalVariablesSource
PersistentVariablesSource
All data source extensions are included in the core NuGet package SmartFormat unless otherwise noted.
Criteria | Details |
---|---|
Data type | Any object, including anonymous types |
Looks for | arguments by index |
Example | Smart.Format("{0} {1} {2}", 123, "456", default(object)) |
Like with string.Format
several parameters to the formatter are allowed. Indexed and named selectors can be combined like this:
var data1 = new KeyValuePair<string, object>("Name", "John");
var data2 = new KeyValuePair<string, object>("City", "Zurich");
// 1st notation:
Smart.Format("1st notation: {0:{Name}} from {1:{City}}", data1, data2);
// Outputs: "1st notation: John from Zurich"
// alternative notation:
Smart.Format("2nd notation: {0.Name} from {1.City}", data1, data2);
// Outputs: "2nd notation: John from Zurich"
Criteria | Details |
---|---|
Data type | IList |
Looks for | elements in the IList
|
Remarks | See more under Formatter Extensions |
Example:
Smart.Format("{0:list:{}|, |, and }", new List<string> { "one", "two", "three" });
// Outputs: "one, two, and three"
Criteria | Details |
---|---|
Data type | Any object, including anonymous types |
Looks for | Property names, field names, and names of paramterless methods |
Example | Smart.Format("{Item}", new { Item = 999 }) |
Remarks | Uses caching for accessing object members for best performance. |
The cache is static
and shared among all instances of ReflectionSource
.
By default, the cache is enabled. To disable the cache, set ReflectionSource.IsTypeCacheEnabled
to false
. The size of the cache can be set with ReflectionSource.MaxCacheSize
; defaults to ReflectionSource.DefaultCacheSize
. When the cache exceeds the maximum size limit, it removes the oldest entry first, adhering to a First-In-First-Out (FIFO) strategy.
Criteria | Details |
---|---|
Data types |
IDictionary , IDictionary<string, object> , IReadOnlyDictionary , dynamic
|
Looks for | Dictionary key names, dynamic property names |
Example |
Smart.Format("{Key}", new Dictionary<string, object>(){ { "Key", 999 } } ) Smart.Format("{Key}", (dynamic)(new { Key = 999 }))
|
Dictionaries may be nested.
For classes that only implement the generic IReadOnlyDictionary
interface, property DictionarySource.IsIReadOnlyDictionarySupported
must be set to true
. Although caching (for the current instance) is used, this is still slower than the other types.
The KeyValuePairSource
as a simple, cheap and performant way to create named placeholders.
Criteria | Details |
---|---|
Data type | KeyValuePair<string, object?> |
Looks for | Dictionary key names, dynamic property names |
Example | Smart.Format("{placeholder}", new KeyValuePair<string, object?>("placeholder", "some value") |
Important: The type arguments for
KeyValuePair
must be<string, object?>
.KeyValuePair
s may be nested.
Criteria | Details |
---|---|
Data type | string |
Looks for |
string s, built-in parameterless methods |
Example | Smart.Format("{0.ToUpper}", "lower") |
Built-in methods:
Method | Input | Output |
---|---|---|
Length | "A name" | 6 |
ToUpper | "dış" | "DIŞ" Turkish DIŞ or dış (outside) |
ToUpperInvariant | "dış" | "DıŞ" Turkish dış (outside) becomes DıŞ (tooth) |
ToLower | "DIŞ" | "dış" Turkish DIŞ or dış (outside) |
ToLowerInvariant | "DIŞ" | "diş" Turkish DIŞ (outside) becomes diş (tooth) |
TrimStart | " abc" | "abc" |
TrimEnd | "abc " | "abc" |
Trim | " abc " | "abc" |
Capitalize | "word" | "Word" |
CapitalizeWords | "john DOE" | "John Doe" |
ToBase64 | "xyz" | "eHl6" |
FromBase64 | "eHl6" | "xyz" |
ToCharArray | "abc" | "abc".ToCharArray() |
Criteria | Details |
---|---|
Data type |
JObject , JValue
|
Looks for | child elements by their name |
Example | Smart.Format("{Name}", JObject.Parse("{ 'Name':'John'}")) |
Included in NuGet package SmartFormat.Extensions.Newtonsoft.Json
Criteria | Details |
---|---|
Data type | JElement |
Looks for | child elements by their name |
Example | Smart.Format("{Name}", JsonDocument.Parse("{ \"Name\":\"John\"}").RootElement) |
Included in NuGet package SmartFormat.Extensions.System.Text.Json
JSON also comes in handy when processing data in a web API application where the argument submitted from the browser to the controller is JSON.
Another scenario is working with queries from SQL Server:
SELECT 'John' AS [FirstName], 'Doe' AS [LastName], 32 AS [Age]
FOR JSON PATH, ROOT('Father')
You can parse the query result into a JObject
(Newtonsoft.Json
) or JsonElement
(System.Text.Json
) and give it to SmartFormat as an argument. JObject
or JElement
may contain arrays.
Criteria | Details |
---|---|
Data type | XElement |
Looks for | child elements by their name |
Example | Smart.Format("{Name}", XElement.Parse("<root><Name>Joe</Name></root>");) |
Included in NuGet package SmartFormat.Extensions.Xml
The ValueTupleSource
is a special source, because it acts merely as a container for data sources.
Assume, we have 3 objects we need for formatting a string:
var data1 = new { ItemInt = 123 };
var data2 = new { ItemBool = true };
var data3 = new { ItemString = "an item" };
An obvious option to format is:
Smart.Format("{0.ItemInt} * {1.ItemBool} * {2.ItemString}", data1, data2, data3 );
// Outputs: "123 * True * an item"
If all data objects are stored in a ValueTuple
argument to the formatter, you can omit the indexed arguments:
Smart.Format("{ItemInt} * {ItemBool} * {ItemString}", (data1, data2, data3));
Smart.Format("{ItemInt} * {ItemBool} * {ItemString}", (data3, data1, data2));
// Both output: "123 * True * an item"
There is no need to care about the index and the sequence of arguments to the formatter.
This is very handy if you need the output of a variable depending on values of other variables. Let's output ItemString
only if ItemInt == 123
and ItemBool == true
. This keeps the business logic out of the format:
Smart.Format("{Show:cond:{ItemString}|Don't show}",
(data3, new { Show = data1.ItemInt == 123 && data2.ItemBool }));
// Output: "an item"
A
ValueTuple
containing otherValueTuple
s will be flattened.
Both provide global variables that are stored in VariablesGroup
containers to the SmartFormatter
. These variables are not passed in as arguments when formatting a string. Instead, they are taken from these registered ISource
s.
VariablesGroup
s may contain Variable<T>
s or other VariablesGroup
s. The depth of such a tree is unlimited.
a) GlobalVariableSource
variables are static and are shared with all SmartFormatter
instances.
b) PersistentVariableSource
variables are stored per instance.
PersistentVariableSource
and GlobalVariableSource
must be configured and registered as ISource
extensions as shown in the example below.
PersistentVariablesSource
or GlobalVariablesSource (Containers for Variable / VariablesGroup children)
|
+---- VariablesGroup "group"
| |
| +---- StringVariable "groupString", Value: "groupStringValue"
| |
| +---- Variable<DateTime> "groupDateTime", Value: 2024-12-31
|
+---- StringVariable "topInteger", Value: 12345
|
+---- StringVariable "topString", Value: "topStringValue"
Here, we use the PersistentVariablesSource
:
// The top container
// It gets its name later, when being added to the PersistentVariablesSource
var varGroup = new VariablesGroup();
// Add a (nested) VariablesGroup named 'group' to the top container
varGroup.Add("group", new VariablesGroup
{
// Add variables to the group
{ "groupString", new StringVariable("groupStringValue") },
{ "groupDateTime", new Variable<DateTime>(new DateTime(2024, 12, 31)) }
});
// Add more variables to the container
varGroup.Add("topInteger", new IntVariable(12345));
varGroup.Add("topString", new StringVariable("topStringValue"));
// The formatter for persistent variables requires only 2 extensions
var smart = new SmartFormatter();
smart.FormatterExtensions.Add(new DefaultFormatter());
var pvs = new PersistentVariablesSource
{
// Here, the top container gets its name
{ "global", varGroup }
};
// Best to put it to the top of source extensions
smart.AddExtensions(0, pvs);
// Note: We don't need args to the formatter for PersistentVariablesSource variables
_ = smart.Format(CultureInfo.InvariantCulture,
"{global.group.groupString} {global.group.groupDateTime:'groupDateTime='yyyy-MM-dd}");
// result: "groupStringValue groupDateTime=2024-12-31"
_ = smart.Format("{global.topInteger}");
// result: "12345"
_ = smart.Format("{global.topString}");
// result: "topStringValue"
- Syntax, Terminology
- Placeholders and Nesting
- string.Format Compatibility
- Character Literals in Format Strings
- HTML With CSS or JavaScript
- Data Source Extensions
- Default _ DefaultFormatter
- Lists _ ListFormatter
- Choose _ ChooseFormatter
- Condition _ ConditionalFormatter
- Null _ NullFormatter
- SubString _ SubStringFormatter
- RegEx _ IsMatchFormatter
- Pluralization _ PluralLocalizationFormatter
- Localization _ LocalizationFormatter
- Templates _ TemplateFormatter
- TimeSpan _ TimeFormatter
- XML _ XElementFormatter
- Extension Methods
- Home
- Common Pitfalls
- HTML with CSS or JavaScript
- Overview
- Main Features
- Formatters
- Extra Features
- Console and StringBuilder
- TemplateFormatter
- SmartSettings to control Smart.Format behavior
- Additional Info
- License