-
-
Notifications
You must be signed in to change notification settings - Fork 106
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 ISources 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 ISources, 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) ReflectionSourceDictionarySourceKeyValuePairSourceStringSourceNewtonsoftJsonSourceSystemTextJsonSourceXmlSourceValueTupleSourceGlobalVariablesSourcePersistentVariablesSource
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
KeyValuePairmust be<string, object?>.KeyValuePairs may be nested.
| Criteria | Details |
|---|---|
| Data type | string |
| Looks for |
strings, 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
ValueTuplecontaining otherValueTuples 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 ISources.
VariablesGroups may contain Variable<T>s or other VariablesGroups. 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
3.6