How to extend Sharpy

I recently introduced Sharpy – a view engine designed to allow developers and designers to work together.  Today I’m going to show how you can extend Sharpy with your own functions and variable modifiers.

As I’ve mentioned before – I designed Sharpy with extensibility in mind.  I therefore decided to use MEF to allow easy extensibility.  Sharpy exposes four different contracts (interfaces) – three for the three types of functions and one for variable modifiers.

Writing your own function

In this example I’m going to implement a for function.  Sharpy already has a foreach function but there is no for function.  I therefore need to implement the IBlockFunction interface.  (I specifically chose this type of function because I think it will be the most difficult type of function to implement)

[Export(typeof(IBlockFunction))]
public class For : IBlockFunction
{
    public string Name
    {
        get { throw new NotImplementedException(); }
    }

    public IList<string> TagsToIgnore()
    {
        throw new NotImplementedException();
    }

    public string Evaluate(IDictionary<string, object> attributes, IFunctionEvaluator evaluator, string content)
    {
        throw new NotImplementedException();
    }
}

Notice that I have already added the MEF export attribute to my class.

Let’s run through the different members specified by the contract.  The Name property is simply the name we will use to reference the function.  The TagsToIgnore property specifies any tags which may be contained within the content for the function which must be ignored.  For example, the foreach function specifies that the tag foreachelse should be ignored – otherwise the view engine will throw an exception saying that the function foreachelse doesn’t exist.  The Evaluate method is where the real magic happens – we are given the content of the function, a dictionary of evaluated attributes and a reference to an evaluator.  This evaluator basically exposes things like the view data and model as well as allowing us to evaluate expressions and write to the output stream.

I’m going to require 4 attributes in our implementation – item, initial, condition and increment.  Item is the name of the variable to declare, initial is the initial value for the declared variable, condition is the boolean expression to evaluate and increment is the expression to assign to the declared variable after each iteration.  If this sounds a little confusing take a look at the regular C# for loop.

for (var = 0; i < 10; i++)

In this example i would be the item, initial would be 0, condition is i < 10 and increment is i+1.  (Unfortunately increment can’t be i++ because Sharpy will try to evaluate the expression which is of type void.) Let’s take a look at the implementation.

Implementing the function

public class For : IBlockFunction
{
    public string Name
    {
        get { return "for"; }
    }

    public IList<string> TagsToIgnore()
    {
        return null;
    }

    public string Evaluate(IDictionary<string, object> attributes, IFunctionEvaluator evaluator, string content)
    {
        var item = attributes.GetRequiredAttribute<string>("item");
        var initial = attributes.GetRequiredAttribute<int>("initial");
        var condition = attributes.GetRequiredAttribute<string>("condition");
        var increment = attributes.GetRequiredAttribute<string>("increment");

        var output = new StringBuilder();
        evaluator.LocalData[item] = initial;
        while ((bool)evaluator.EvaluateExpression(condition))
        {
            output.Append(evaluator.Evaluate(content));
            evaluator.LocalData[item] = (int)evaluator.EvaluateExpression(increment);
        }
        evaluator.LocalData.Remove(item);

        return output.ToString();
    }
}

Here I am making use of an extension method exposed by Sharpy.  There is also a GetOptionalAttribute method you can use.  The implementation is reasonably simple – we simply keep evaluating the function’s content until the specified condition is valid.  At the end of each iteration we simply need to evaluate the increment expression and assign the value to the looping variable.

Using the function

You will need to compile the new function and copy the dll into a folder your website can access.  We then need to tell Sharpy to look at this folder and use any exported functions.  To do this we first need to add a configuration section in our web.config – *just before the closing *configSections tag.

<section name="sharpy" type="Sharpy.Configuration.SharpySectionHandler, Sharpy"/>

Now we need to create this section and specify the plugin folder (just before the closing configuration tag).

<sharpy>
    <plugins folder="SharpyPlugins"/>
</sharpy>

This will tell Sharpy to look for plugins in a folder called SharpyPlugins within our web application.  (The folder name is relative to the root of the site)  And that’s all we need!  I wrote a simple example to use our new for function.

<body>
    <ul>
    {for item='i' initial=0 condition='$i<5' increment='$i+1'}
        <li>{$i}</li>
    {/for}
    </ul>
</body>

And here you can see the output.

Result

Which is pretty cool!

Conclusion

Sharpy uses MEF to allow developers to implement their own functions and modifiers.  All the built-in functions are also built using this exact same framework – the same functionality is available to both internal and external functions.

Extensibility is one of the strongest features in Sharpy.  This should allow us to leverage any functionality available in a normal ASP.NET view while maintaining simple views and straightforward markup.

Happy coding.