Hello World. Date is: {{ DateTime.Now.ToString() }}
{{ await Script.RenderPartialAsync("./Templates/Time_Partial.csscript") }}
Done.
""";
Console.WriteLine(script + "\n---" );
var scriptParser = new ScriptParser();
scriptParser.AddAssembly(typeof(ScriptParserTests));
string result = await scriptParser.ExecuteScriptAsync(script, model);
Console.WriteLine(result);
Console.WriteLine(scriptParser.GeneratedClassCodeWithLineNumbers);
Assert.IsNotNull(result, scriptParser.ErrorMessage);
```
The template is just a file with text and script expressions embedded:
```csharp
Current Time:
Hello World. Date is: {{ DateTime.Now.ToString() }}
{{ Model.Name }}
{{ await Script.RenderScriptAsync(Model.Expression,null) }}
Done.
""";
Console.WriteLine(script + "\n---");
var scriptParser = new ScriptParser();
scriptParser.AddAssembly(typeof(ScriptParserTests));
string result = await scriptParser.ExecuteScriptAsync(script, model);
Console.WriteLine(result);
Console.WriteLine(scriptParser.Error + " " + scriptParser.ErrorType + " " + scriptParser.ErrorMessage + " " );
Console.WriteLine(scriptParser.GeneratedClassCodeWithLineNumbers);
Assert.IsNotNull(result, scriptParser.ErrorMessage);
```
Partials - both file and string - are treated as separate scripts so they are compiled and cached in the same ways as top level scripts are. Even a one liner string template is turned into a separate script assembly.
### ScriptParser Methods and Properties
Here's a run down of the key members of the Script Parser:
**Main Execution**
* `ExecuteScript()`
* `ExecuteScriptAsync()`
**Script Parsing**
* `ParseScriptToCode()`
**C# Script Engine Configuration and Access**
* `ScriptEngine`
* `AddAssembly()`
* `AddAssemblies()`
* `AddNamespace()`
* `AddNamespaces()`
The `ScriptEngine` property is initialized using default settings which use:
* `AddDefaultReferencesAndNamespaces()`
* `SaveGeneratedCode = true`
You can optionally replace `ScriptParser` instance with a custom instance that is configured exactly as you like:
```cs
var scriptParser = new ScriptParser();
var exec = new CSharpScriptExection();
exec.AddLoadedReferences();
scriptParser.ScriptEngine = exec;
string result = scriptParser.ExecuteScript(template, model);
```
**Error and Debug Properties**
* `ErrorMessage`
* `ErrorType`
* `GeneratedClassCode`
* `GeneratedClassCodeWithLineNumbers`
> The various `Addxxxx()` methods and error properties are directly forwarded from the `ScriptEngine` instance as readonly properties.
### Some Template Usage Examples
An example usage is for the [Markdown Monster Editor](https://markdownmonster.west-wind.com/) which uses this library to provide text snippet expansion into Markdown (or other) documents.
A simple usage scenario might be to expand a DateTime stamp into a document as a snippet via a hotkey or explicitly
```markdown
---
- created on {{ DateTime.Now.ToString("MMM dd, yyyy") }}
```
You can also use this to expand logic. For example, this is for a custom HTML expansion in a Markdown document by wrapping an existing selection into the template markup:
```markdown
### Breaking Changes
Welcome back, {{ Model.Name }}
{{ Script.RenderPartial("Partial.html", Model) }}
This is the detail page content {{ DateTime.Now.ToString("t") }}
{{%
for(var i = 1; i <= 5; i++) {
// any C# code
Model.Name = "Name " + i;
}}
{{ Script.RenderPartial("Partial.html", Model) }}
{{% } }}
{{ Add(8,10)}}
{{%
// Example of an inline function
int Add(int a, int b)
{
return a + b;
}
writer.WriteLine(Add(5, 10));
}}
{{%
var text = "This is & text requires \"escaping\".";
}}
Force Encoded:
{{: text }}
Force Unencoded:
{{! text }}
default (depends on ScriptDelimiters.HtmlEncodeExpressionsByDefault):
{{ text }}
{{%
// write from within code blocks
writer.WriteLine("Hello world, " + Model.Name); // unencoded
// write with HtmlEncoding
writer.WriteHtmlEncoded( "this text is basic, but \"encoded\".\n" );
%}}
{{ Script.Section("Headers") }}
{{ Script.EndSection("Headers") }}
{{ Script.Section("Scripts") }}
{{ Script.EndSection("Scripts") }}
```
The code starts with a Section at the very top which can be useful if you need to pass or create values that need to be used at the top of the layout page. Keep in mind that code is written from top to bottom with the Layout Page at the top, with the content rendered into it. By the time the code in content renders the top of the Layout Page has already rendered, so if you need code that dynamically sets values to embed into the Layout page like the Script.Title for example, you'll want to use a Section at the top of the document like the example.
Next some basic script expansion. Note that the Model passed to the Detail page is automatically available in the Layout page so `{{ Model.Name }}` works to expand.
Next there's a partial page that is rendered with `{{ Script.RenderPartial("Partial.html") }}`. Partial Pages are separately loaded and compiled at runtime, unlike the Layout/Content pages which are merged and executed as a single page.
The next block shows how to create and call an inline method with a code block and call it with an expression or by explicitly writing out via `{{ writer.WriteLine() }}`. Note that methods are pulled forward into code so you can call methods **before they have been defined**:
```html
{{ Add(8,10)}}
{{%
// Example of an inline function
int Add(int a, int b)
{
return a + b;
}
// You can write output from within a code block
writer.WriteLine(Add(5, 10));
}}
{{ Add(8, 10) }}
```
### Html Encoding
If you're generating Web output for display in a browser you likely want to encode most text written out from expressions as Html Encoded strings to minimize cross-script attacks from script code in potentially user facing text and just for proper display in Html.
**By default expressions are not Html Encoded**. You can explicitly force individual expressions to be Html encoded with `{{: expr }}` syntax:
```csharp
string script = @"Hello World, {{: Model.Name}}";
// Executed script produces
// Hello World, Rick & Dale
```
Alternately you can automatically encode all `{{ expr }}` using `HtmlEncodeExpressionsByDefault=true` and then alternately explicitly force raw text with `{{! expr }}`
```csharp
var scriptParser = new ScriptParser();
// Automatically encode any {{ expr }}
scriptParser.ScriptingDelimiters.HtmlEncodeExpressionsByDefault = true;
var model = new TestModel { Name = "Rick & Dale"};
string script = @"
Hello World, {{ Model.Name}}
Hello unencoded World {{! Model.Name }}
";
var scriptParser = new ScriptParser();
scriptParser.ScriptingDelimiters.HtmlEncodeExpressionsByDefault = true;
scriptParser.AddAssembly(typeof(ScriptParserTests));
scriptParser.AddNamespace("Westwind.Scripting.Test");
string result = scriptParser.ExecuteScript(script, model);
```
If you execute this you should get the following text with the first value encode by default expression, and the second unencoded by way of the raw override.
```text
Hello World, Rick & Dale
Hello unencoded World, Rick & Dale
```
Note that functions that you call that want to embed raw html when auto encoding is on by using the `IRawString` interface.
You can use `{{! expr }}` or `{{ Script.Raw(value) }}` to return a raw string. `Script.Raw()` (and also `new RawString(value)` and `RawString.Raw(value)`) return `IRawString` and if you call methods that want to explicitly return raw HTML they can return an `IRrawString` instance:
```cs
public IRawString CallMe()
{
return new RawString("Call me");
}
```
Here's another example that demonstrates HTML encoding:
```html
{{%
var text = "This is & text requires \"escaping\".";
}}
Encoded:
{{: text }}
Unencoded:
{{! text }}
{{ Script.Raw(text) }}
{{ RawString.Raw(text) }}
{{ new RawString(text) }}
default (depends on ScriptDelimiters.HtmlEncodeExpressionsByDefault):
{{ text }}
{{%
// write from within code blocks
writer.WriteLine("Hello world, " + Model.Name); // unencoded
// write with HtmlEncoding
writer.WriteHtmlEncoded( $"this text is basic {Model.Name}, but \"encoded\".\n" );
}}
```
The raw HTML output generated is:
```txt
Encoded:
This is & text requires "escaping".
Unencoded:
This is & text requires "escaping".
This is & text requires "escaping".
This is & text requires "escaping".
This is & text requires "escaping".
default (depends on ScriptDelimiters.HtmlEncodeExpressionsByDefault):
This is & text requires "escaping".
Hello world, Rick
this text is basic Rick, but "encoded".
```
### Layout Page
So far we've only looked at the content page. The content page requests a Layout page via:
```html
{{%
Script.Layout = "Layout.html";
Script.Title = "My Great Page Caper";
}}
```
This instructs the parser to load `Layout.html` and then embed the content of this content page into it, where the Layout page specifies:
```html
{{ Script.RenderContent() }}
```
Here's what the full layout page looks like:
```html
{{ Script.RenderSection("StartDocument") }}