Custom template registries

You can easily provide your own template registry implementation by implementing ITemplateRegistry and setting an instance of your registry on the compiler options. Note, that a template registry does not have to take care of caching, since the compiled template will be cached after the template was read.

public interface ITemplateRegistry
{
    /// <summary>
    /// Retrieve the source of a template.
    /// </summary>
    /// <param name="templateName">The name of the template. Paths are delimited with the &quot;/&quot; character.</param>
    /// <returns>The template source.</returns>
    string this[string templateName]
    {
        get;
    }
}

As you can see, the only thing you need to do is, to implement an indexer that accepts the template name as a parameter. Make sure to deal with the forward slash as a path delimiter in a way that makes sense to your registry.

Custom pipes

To provide a custom pipe, you first need to inherit from the base class Pipe and provide at least a constructor and an override for the Evaluate method.

Parameterless pipes

Here is the CountPipe as an example:

internal class CountPipe : Pipe
{
    public CountPipe(IUtilities utilities)
        : base(utilities, null)
    {
    }

    public override object Evaluate(object input, PipeContext context)
    {
        IEnumerable<object> inputAsList = input as IEnumerable<object>;
        return (inputAsList ?? Enumerable.Empty<object>()).Count();
    }
}

You need to have at least the IUtilities parameter on the constructor to be able to pass it up to the base constructor. For parameterless pipes (like this one), you pass null as the second parameter. The Evaluate method gets the input value for the pipe and an additional context that provides access to the data context. The result of the pipe is just returned from this method.

Parameterized pipes

Let's look at a pipe, that has additional parameters. The BooleanConverterPipe is not just a pipe, but also a converter, but we'll come back to this later. Let's just assume it's a regular pipe.

internal class BooleanConverterPipe : ConverterPipe
{
    public BooleanConverterPipe(IUtilities utilities, CompilerOptions compilerOptions, string expression)
        : base(utilities, compilerOptions, expression)
    {
    }

    protected override PipeParameter[] Parameters
    {
        get
        {
            return new PipeParameter[]
            {
                PipeParameter.Optional("TrueValue","X",ParameterType.Literal,ParameterType.DataContext),
                PipeParameter.Optional("FalseValue"," ",ParameterType.Literal,ParameterType.DataContext)
            };
        }
    }

    public override object Evaluate(object input, PipeContext context)
    {
        if (input == null)
        {
            return m_CompilerOptions.ConverterSettings.BooleanFalseValue;
        }

        bool booleanValue = (bool)input;
        return booleanValue ?
            (string)GetParameterValue(context, "TrueValue") ?? m_CompilerOptions.ConverterSettings.BooleanTrueValue :
            (string)GetParameterValue(context, "FalseValue") ?? m_CompilerOptions.ConverterSettings.BooleanFalseValue;
    }
}

As explained in the advanced/pipes section, a pipe defines the type of parameters it accepts.

Overriding the Parameters collection allows you to do just that. Here we define two optional paramerters. A parameter has a name (the name is used in the Evaluate method by the GetParameterValue function to reference it. For optional parameters you define the default value if it was not specified (in this case for the TrueValue parameter that is "X". Then you specify all allowed types (literal/data context) in preference order. In this case this means, that the TrueValue parameter allows literals and data context values and assumes literals (because this types appears first in the list) if a parameter is not explicitly typed.

In this example, the pipe needs access to the CompilerOptions used. This is done by enlisting the object in the constructor and assigning it to a backing store.

Registering pipes

After implementing the pipe, you need to register it with PBars. This is done by adding it to the CustomPipes dictionary on the CompilerOptions.

compilerOptions.CustomPipes.Add("MyPipe", typeof(MyPipe));

Custom converters

Converters themselves are annotated pipes, everything you learned about pipes before applies to them too. Each converter you want to use implicitly must only have optional parameters. To register a converter for a type, you need to annotate it with an attribute and add it to the CustomConverters list on the CompilerOptions.ConverterSettings object.

[ConverterPipe("BooleanConverter", typeof(bool))]
internal class BooleanConverterPipe : ConverterPipe

The ConverterPipe attribute marks the pipe as the default converter type for the type bool.

compilerOptions.ConverterSettings.CustomConverters.Add(typeof(MyConverter));

Registering a custom converter will also register it as a pipe (the converter above will be available like this).

{{MyValue | BooleanConverter}}

If you define a custom converter for a type, PBars defines it's own converter for, your converter will replace the existing one. In addition you can add converters for any type of object.

Custom auto encoders

Creating a custom auto encoder happens by implementing a regular pipe and registering it on the CompilerOptions.

public class MyHtmlEncodePipe : Pipe
{
    public MyHtmlEncodePipe(IUtilities utilities)
        : base(utilities, null)
    {
    }

    public override object Evaluate(object input, PipeContext context)
    {
        return WebUtility.HtmlEncode(m_Utilities.StringHelper.ToSafeString(input));
    }
}
compilerOptions.AutoEncoder = typeof(MyHtmlEncodePipe);