Plugin Architecture and Customization

The StoreBuilder plugin architecture is uniquely powerful.

Basic Plugin Definition

All StoreBuilder plugins should contain at least one class which inherits from StoreBuilder.Extensibility.Plugin and implements inherited abstract members.

Additionally the plugin assembly should be decorated with a StoreBuilder.Extensibility.PluginAttribute which is used to discover StoreBuilder plugins at runtime efficiently. The attribute reduces the cost of scanning all types in all assemblies at app pool startup, as we only need to scan assemblies decorated with this attribute. This results in faster website startup performance.

public class SamplePlugin : Plugin
{
    internal const string PluginAlias = "sampleplugin";
    internal const string PluginName = "Sample Plugin Friendly Name";

    public override void Initialize() { }

    public override void Migrate(string connectionStringName) { }

    public override void Seed(StorefrontContext context) { }

    /// <summary>
    /// Gets the plugin alias.
    /// </summary>
    public override string Alias
    {
        get { return PluginAlias; }
    }

    /// <summary>
    /// Gets the plugin name.
    /// </summary>
    public override string Name
    {
        get { return PluginName; }
    }
}

Registering Provider Implementations

Any plugin can optionally contain implementations for one or more core functions of StoreBuilder by implementing "Providers". Common examples include payment gateways implementing StoreBuilder.Providers.PaymentProvider and Shipping Carrier plugins implementing StoreBuilder.Providers.ShippingProvider.

You can register any provider implementation in your plugin's Initialize method. For example:

StorefrontApplication.RegisterPaymentProvider<SamplePaymentProvider>();

Non-code Plugin Resources

Any plugin can contain embedded resources to be used with StoreBuilder. This could be images, javascript, stylesheets, handlebars templates or anything else.

All embedded resources should be contained within a folder named EmbeddedResources within your plugin assembly project.

There is an implicit standard is that if the plugin contains an embedded resource called icon.png it will be used in the administration portal on the plugin admin view. A plugin icon can be helpful for end users to quickly locate the plugin visually within the admin portal.

Working with ThemeEngine

Any plugin can provide some static resources to StoreBuilder ThemeEngine by calling ThemeEngine.ImportResource(). It is expected that StoreBuilder plugins should be fully functional "out of the box", but that presentation should be customizable by the store owner or implementer of a StoreBuilder based website.

Example of importing static files from a plugin's Embedded Resources:

ThemeEngine.ImportResource(this, "Templates.sb-websitefeedback.hbs");
ThemeEngine.ImportResource(this, "Templates.storebuilder.websitefeedback.js");

This will copy the specified embedded resources into the active StoreBuilder theme, unless the exported files already exist.

When consuming the ThemeEngine resources later, plugins should use the built in ThemeEngine methods for retrieving resources or rendering templates.

Because theme resources are first provided to ThemeEngine and then later requested back from ThemeEngine, it provides the opportunity to customize the presentation of even closed source plugins by editing the handlebars templates or other files the plugin provides/uses.

Fulfilling Requests with Content Finder

Any StoreBuilder plugin can register a ContentFinder implementation with StoreBuilder to handle requests made to the website. This routing abstraction allows plugins to drop into multiple different types of web projects such as a regular ASP.net MVC project or a modified environment such as the Umbraco CMS which has its own routing mechanisms for StoreBuilder to tie into. It also allows plugins to be free of any unnecessary dependencies or web framework references.

A content finder is registered with StoreBuilder just like Providers. For example:

StorefrontApplication.RegisterContentProvider<HelloWorldContentProvider>();

The ContentProvider implementation could look something like this:

public class HelloWorldContentProvider : ContentProvider
{
    public override bool TryFindContent(ContentContext contentContext)
    {
        contentContext.Result = 
            contentContext.Match("GET", "helloworld", GetHelloWorld);

        return contentContext.Result != null;
    }

    private static StringContentResult GetHelloWorld(ContentContext context)
    {
        return new StringContentResult("Hello World!", "text/html");
    }
}

The ContentContext parameter supplied to TryFindContent contains a reference to the current HttpContext plus some helper methods for matching request URLs and Http Method.

If your TryFindContent method implementation returns true, the request is considered handled, otherwise StoreBuilder will continue attempt to find a ContentFinder to handle the current request.

The above example will handle a request to your website for /helloworld and respond with a string "Hello World!". For the purpose of this example it is obviously simplistic, normally you'd be either rendering a handlebars template and returning html markup or serializing a view model and returning some JSON or XML.

There is no built-in mechanism for preventing URL collisions between plugins, it is first come first serve in the ContendFinder pipeline. If your plugin is using a request for some kind of "hidden" function such as an ajax request the user doesn't see in their browser bar, then it makes sense to use a suitably unique url slug for your plugin. If the URL your plugin wishes to handle will be public facing and potentially indexable by search engines or users (bookmarks) it should also be suitably unique, but you might consider offering the ability to customize the url slug within your plugin via some configuration option.

Using the Database

Plugins are free to use EntityFramework in parallel with StoreBuilder with their own DataContext.