Sharpy was designed with extensibility in mind. This means that developers can write their own functions and modifiers and use them in views. To allow extensibility Sharpy uses MEF.

Sharpy exposes 4 different contracts for developers to implement. Keep in mind that the built-in functions in Sharpy have been built within the exactly the same framework - the same functionality is available to internal and external functions. All exported functions and modifiers must be decorated with the MEF Export attribute contained in the System.ComponentModel.Composition namespace.

Once you have implemented a contract you will need to copy the assembly to a folder your website has access to and configure the view engine to look at this folder. You can also look at this example.

Contracts


The following code contracts (interfaces) reside in the Sharpy.ViewEngine namespace.

public interface IBlockFunction
{
    string Name { get; }
    IList<string> TagsToIgnore();
    string Evaluate(IDictionary<string, object> attributes, IFunctionEvaluator evaluator, string content);
}

public interface IExpressionFunction
{
    string Name { get; }
    IList<string> TagsToIgnore();
    string Evaluate(string functionDetails, IFunctionEvaluator evaluator, string content);
}

public interface IInlineFunction
{
    string Name { get; }
    string Evaluate(IDictionary<string, object> attributes, IFunctionEvaluator evaluator);
}

public interface IVariableModifier
{
    string Name { get; }
    object Evaluate(object input, IEvaluator evaluator, params object[] parameters);
}

The same concept applies for implementing any of the contracts - any input values are evaluated before they are passed to the function/modifier. Every function/modifier also has access to an Evaluator which exposes the functionality such as accessing the ViewData or evaluating expressions.

public interface IEvaluator
{
    IDictionary<string, object> ViewData { get; }
    IDictionary<string, object> LocalData { get; }
    object Model { get; }

    string Evaluate(string content);
    string EvaluateUrl(string contentPath);
    string EvaluateTemplate(string templatePath);
    string EvaluateTemplate(string templatePath, object model);
    string EvaluateTemplate(string templatePath, object model, IDictionary<string, object> viewData);
    string EvaluateTemplate(string templatePath, IDictionary<string, object> viewData);
    object EvaluateExpression(string expression);

    string Encode(string content);
}

Most of these members are reasonably straightforward - for example, ViewData and Model are available in regular ASP.NET views. LocalData is similar to ViewData, but contains variables declared in the views themselves. This is similar to declaring variables within a regular ASP.NET view - the scope of these variables are confined to the view itself. For example, the assign function stores variables in LocalData.

The FunctionEvaluator available to functions is very similar.

public interface IFunctionEvaluator : IEvaluator
{
    IDictionary<string, object> FunctionData { get; }
}

FunctionData is another dictionary, similar to LocalData and ViewData, but is only available to same instance of a function. For example, the cycle function stores variables in FunctionData. Since FunctionData is specific to a certain instance we can have multiple instances of the cycle function within a loop and each will access their own dictionary. This is to deal with the following special case.

<ul>
{foreach from=$list item='int'}
    <li bgcolor='{cycle values='#eeeeee,#d0d0d0'}'>{$int}</li>
{/foreach}
</ul>
<ul>
{foreach from=$list item='int'}
    <li bgcolor='{cycle values='#eeeeee,#d0d0d0'}'>{$int}</li>
{/foreach}
</ul>