app-stitch extensibility: creating a custom app-stitch action

In case this is the first time you hear about app-stitch, you owe it to yourself (or to me 😉) to have a look at our newest project. App-stitch is a visual rules engine, currently for VS LightSwitch projects only, that provides a visual interface to your server project, where  you and/or your end users can create custom business rules at run-time.

We’re currently still developing app-stitch horizontally: adding more capabilities to it, so that we can reach a stable API. Once our API is stable, we and hopefully other people too can build out additional channels. Each channel gives your app-stitch instance additional connections to custom or 3rd party services: twilio, paypal, …

Some people, myself included, simply cannot wait for a public, documented API, and want to start building out custom channels right now. We can do nothing but applaud this enthusiasm, so I decided to write this first introduction into our extensibility model.  This post assumes you have a LightSwitch application with a pretty up-to-date version of app-stitch installed (you can install app-stitch on any LightSwitch server project, you don’t need a license until 30 days after you’ve published an app). This LightSwitch application has a concepts of ‘Tasks’, and the goal of this blog post is to allow the end users to schedule a task as part of an app-stitch rule.

Creating the channel

In LightSwitch, there’s two different worlds: your entities and the definition of your entities (the lsml metadata files). App-stitch is very similar, to get things up and running you’ll need to provide app-stitch with two types of data: the actual triggers&actions, and classes that describe your channel and it’s triggers and actions to app-stitch (the metadata).

Let’s start with the metadata: describing your ‘task’ channel and ‘create a task’ action to app-stitch.  Right-click on your server project and add a new class called “TaskChannel.cs”. I’m going to do this post in C# but the code should be very easy to port to VB.

Describing your channel to app-stitch means you have to create a class which inherits AppStitch.Channel. The constructor of AppStitch.Channel takes 5 arguments:

– a name: this is the unique identifier of your channel. It should be a single word with no special characters (“TaskChannel”)

– a display name: this is the actual name that is shown to end users when your channel is in the UI (“Tasks”)

– a description: this is shown next to the channel so that users that are not familiar with your channel have access to a quick.. .well… description 😉

– a list of Trigger definitions: empty at the moment (triggers are a bit trickier than actions so I’ll dedicate a separate post to it)

– a list of Action definitions: the available actions on this channel, currently containing one new ‘CreateATaskAction’

    public class TaskChannel : AppStitch.Channel
    {
        public const string TaskChannelName = "TaskChannel";
        public const string TaskChannelDisplayName = "Tasks";
        public const string TaskChannelDescription = "This channel allows you to schedule tasks";
        public TaskChannel()
            : base(
                TaskChannel.TaskChannelName,
                TaskChannel.TaskChannelDisplayName,
                TaskChannel.TaskChannelDescription,
                new AppStitch.TriggerDefinition[] { },
                new AppStitch.ActionDefinition[] { new CreateATaskAction() }
                )
        {

        }
    }

That won’t compile though, because there is no ‘CreateATaskAction’ class yet. Below your channel, add the ‘CreateATaskAction’ class. This class should inherit the AppStitch.ActionDefinition class and pass 6 constructor arguments:

– a name: this is the unique id of your action

– display name: this is the actual name that is used when your task is shown in the UI

– a description: this is shown next to the action so that users that are not familiar with your action have access to a quick.. .well… description 😉

– one list of PropertyDefinitions that define the properties of your action. Properties are ‘values’ that the user has to fill in when he creates a rule for that uses this action. Each PropertyDefinition needs the name of the action it belongs to, the holy grail of ‘Name, DisplayName and Description’, and a list of possible string values (which will be displayed in a combobox), OR an ‘AppStitch.PropertyType’ which will determine how to display this property in the UI (password box, integer, string, HTML, …)  In addition, properties can be mandatory or optional too. I’ve added two properties in this code sample: ‘TaskPriority’ (a mandatory property which can be ‘Low’, ‘Normal’ or ‘High”) and ‘CCAddress’ (optional property which can be any string).

– one list of PropertyDefinitions that define the variables of your action. In app-stitch lingo, ‘properties’ need to be set when using the app-stitch UI to build a rule that contains this action, and ‘variables’ are simply values that exist on your action without needing to be configured. We’ll handle these in a next blog post.

– a priority. There’s three types of priorities: Validation, Instant and Low. You’ll likely only ever need ‘Instant’ or ‘Low’, and only need to remember that ‘Instant’ actions are executed whenever the rule fires, ‘Low’ actions will be scheduled to execute on a background worker thread, whenever your rule fires. The latter is useful if your action takes a while to complete (print a report, send an email, …)

    public class CreateATaskAction : AppStitch.ActionDefinition
    {
        public const string CreateATaskActionName = "CreateATaskAction";
        public const string CreateATaskActionDisplayName = "Create a task";
        public const string CreateATaskActionDescription = "This allows you to create a task";

        public const string PriorityPropertyName = "TaskPriority";
        public const string PriorityPropertyDisplayName = "The priority";
        public const string PriorityPropertyDescription = "The priority of this task: low, normal or high";

        public const string CCPropertyName = "CC";
        public const string CCPropertyDisplayName = "A CC email address";
        public const string CCPropertyDescription = "An email address that will receive a copy of this task";
        public CreateATaskAction()
            : base(
                TaskChannel.TaskChannelName,
                CreateATaskAction.CreateATaskActionName,
                CreateATaskAction.CreateATaskActionDisplayName,
                CreateATaskAction.CreateATaskActionDescription,
                new AppStitch.PropertyDefinition[] {
                new AppStitch.PropertyDefinition(
                    CreateATaskAction.CreateATaskActionName, 
                    CreateATaskAction.PriorityPropertyName, 
                    CreateATaskAction.PriorityPropertyDisplayName,
                    CreateATaskAction.PriorityPropertyDescription, 
                    new[]{"Low", "Normal", "High"}, 
                    isMandatory: true),
                    new AppStitch.PropertyDefinition(
                    CreateATaskAction.CreateATaskActionName, 
                    CreateATaskAction.CCPropertyName, 
                    CreateATaskAction.CCPropertyDisplayName,
                    CreateATaskAction.CCPropertyDescription, 
                    AppStitch.PropertyType.String,
                    isMandatory: false),
            },
                new AppStitch.PropertyDefinition[] { },
                AppStitch.ElementPriority.Instant
                )
        {

        }

        public override void ProcessEvent(AppStitch.IPropertyResolver propertyResolver)
        {
            //Create the task...             
        }
    }

Before we go into the ‘ProcessEvent’ method, we need one more step to finish the channel creation: the IChannelProvider.  This is a very simple class that takes your channel definition (and the action linked to it), and provides it to app-stitch on demand.

    public class TaskChannelProvider : AppStitch.IChannelProvider
    {
        private TaskChannel actualChannel = new TaskChannel();
        public IEnumerable GetChannels()
        {
            return new[] { actualChannel };
        }
    }

Your channel and it’s action are now fully exposed to app-stitch, and you and or your end users can now build rules against them. Create a new rule that fires on any trigger (your channel won’t be listed yet because it has no triggers), and once you get to add an action in your rule, you’ll see your channel appear:Image 233 Besides the channel’s display name and description, there’ll be a logo associated with your channel. Because you haven’t added a logo yet, it’ll just show the default app-stitch @-logo. If you drop a png named “TaskChannel.png” in your server project > appstitch > content > images, it’ll use that instead.

Clicking on the channel will reveal only one action.

Image 234

When you select your action, both your mandatory (priority) and your optional (CC address) property will be presented so that the user can configure your ‘Create a task’ action.

Image 235

Executing the action

Your channel is ready and can be used in a rule, but when that rule fires, nothing will happen yet. This is because your action has a method ‘ProcessEvent’, which is the method that will be called when your rule fires on a particular event, but it has no implementation yet.

        public override void ProcessEvent(AppStitch.IPropertyResolver propertyResolver)
        {
            //Create the task... 
        }

First thing to note is that the method is passed an AppStitch.IPropertyResolver.  You can ask this propertyResolver to resolve the values of the ‘Priority’ and ‘CCAddress’ property.  Because app-stitch allows you to use variables when designing your rules, this propertyResolver makes sure when the CCAddress property is set to ‘{{Customer:Email}}’, your action gets access to the actual meaningful, resolved value (‘bob@marley.com’, for example).

        public override void ProcessEvent(AppStitch.IPropertyResolver propertyResolver)
        { 
                var taskPriorityValue = propertyResolver.ParseStringProperty(CreateATaskAction.CreateATaskActionName, CreateATaskAction.PriorityPropertyName); 

            //Optional: send a CC
            if (propertyResolver.HasProperty(CreateATaskAction.CreateATaskActionName, CreateATaskAction.CCPropertyName))
            {
                var ccEmailAddress= propertyResolver.ParseStringProperty(CreateATaskAction.CreateATaskActionName, CreateATaskAction.CCPropertyName);
            } 
        }

Second thing of interest when implementing your action code, is how you can work with the ApplicationDataContext.  Your action could be firing as part of a rule that works within LightSwitch, in which case there’ll be an active ApplicationDataContext, but it could also fire as part of a rule that triggers outside of your LightSwitch application (‘new Twilio message’, ‘Timed event’, …).

This code sample shows you how to nail both cases in one:

            //Create the task
            ServerApplicationContext ctx;
            using (ItemUserAndContextProvider.GetCurrentOrNewApplicationContext(out ctx)) {
                
                var hasChanges = ctx.DataWorkspace.ApplicationData.Details.HasChanges; 
                //Create a task here:
                //var task = ctx.DataWorkSpace.ApplicationData.Tasks.AddNew();
                //task.Priority = taskPriorityValue;


                if (!hasChanges)
                    ctx.DataWorkspace.ApplicationData.SaveChanges();

            }  

Small recap: here’s how you could implement an action:

        public override void ProcessEvent(AppStitch.IPropertyResolver propertyResolver)
        {
            //Create the task
            ServerApplicationContext ctx;
            using (ItemUserAndContextProvider.GetCurrentOrNewApplicationContext(out ctx)) {
                var taskPriorityValue = propertyResolver.ParseStringProperty(CreateATaskAction.CreateATaskActionName, CreateATaskAction.PriorityPropertyName);
             
                var hasChanges = ctx.DataWorkspace.ApplicationData.Details.HasChanges; 
                //Create a task here:
                var task = ctx.DataWorkSpace.ApplicationData.Tasks.AddNew();
                task.Priority = taskPriorityValue;


                if (!hasChanges)
                    ctx.DataWorkspace.ApplicationData.SaveChanges();

            }  

            //Optional: send a CC
            if (propertyResolver.HasProperty(CreateATaskAction.CreateATaskActionName, CreateATaskAction.CCPropertyName))
            {
                var ccEmailAddress= propertyResolver.ParseStringProperty(CreateATaskAction.CreateATaskActionName, CreateATaskAction.CCPropertyName);
            //Send the CC
            } 
        }

Conclusion

That was crazy.

No seriously, extending app-stitch will not be an easy task, but if you follow this guide and copy-paste along, I’m sure you’ll reach that ‘aha’-moment and will be creating custom channels in no time.

If you do happen to create a custom channel that isn’t specific to a single application, please do drop us a message. We’re some iterations away from starting on our channels-store, at which point you could gift or sell your channel to the app-stitch end users!

Keep rocking LS!

Jan

PS: if you run into problems when creating your custom channels/triggers/actions, mail us your issue and we’ll do whatever needs to be done 😉

PPS: Yes, this API is bound to change at some point as we add new features. We’ll provide you with a smooth upgrade path.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s