Creating an executor
When you have created an API describing the different messages and their content, the next thing to do is to create an executor that will define how an incoming message should be handled.
The executor is never shared between connectors or Automation scripts. You create it at the destination of a message and it is unique to that connector. It will hold all the methods that can access the content of the message and do something with it.
A few common examples:
- The methods in the executor could be used to translate the content of your InterApp message into a serial, SNMP, HTTP, etc. command to be sent to the device.
- The methods could be used to perform parameter gets and sets based on the message content.
- An executor could be created in an Automation script to create an information message to indicate that a message was received.
- etc.
You create an executor by making a new class that inherits from MessageExecutor<T>
, where T is a class from your messages defined in the API.
public class MyMessageExecutor : MessageExecutor<MyMessage>
The Visual Studio IDE will then assist you in correctly implementing your executor.
The executor has several methods that will by default be called when a message is executed (in the specified order):
- DataGets (always)
- Parse (always)
- Validate (always)
- Modify (if validate was true)
- DataSets (if validate was true)
- CreateReturnMessage (always)
Default execute code (happens in background):
executor.DataGets(dataSource);
executor.Parse();
bool result = executor.Validate();
if (result)
{
executor.Modify();
executor.DataSets(dataDestination);
}
optionalReturnMessage = executor.CreateReturnMessage();
return result;
You now have an executor that can do what you want. It can access your received message by calling the Message
property.
public override bool Validate ()
{
if (String.IsNullOrWhiteSpace(Message.MyProperty))
{
return false;
}
return true;
}
Some methods, like DataSets and DataGets, have an object argument. This will mostly be SLProtocol
or IEngine
. These can also be used for a custom class with data, a database object, etc so you as a developer have more flexibility.
public override void DataSets(object dataDestination)
{
SLProtocol protocol = (SLProtocol)dataDestination;
protocol.DeleteRow(Parameter.Mytable.tablePid, Message.MyProperty);
}
Lastly, you will also find one method that has a return type, i.e. CreateReturnMessage. This is optional. It can be used to create a message to return and have it bubble up to the calling methods. If you do not need this, you can just return null. You could consider it the "return" for the entire executor.
public override Message CreateReturnMessage()
{
return null;
}
In other cases, you want to immediately create a message that will eventually need to be returned to the sender.
public override Message CreateReturnMessage()
{
return new MyResponse { Guid = Message.Guid, Success = true };
}
The executor follows a design pattern called the Template Method pattern. This has the following benefits:
- Consistency: The overall process is consistent and follows the defined sequence.
- Flexibility: Specific steps can be overridden in subclasses if different behavior is needed for certain executors.
- Clarity: The sequence of steps is clearly defined in one place (the execute method of Executor).
This design pattern ensures that the core structure of the execution process remains intact while allowing for customization and extension where necessary.
Note
A return message does not necessarily need to be something to send to an external destination. A message could also be part of an internal API used to move data between classes, methods or QActions within your own connector. This can also be returned.
Tip
If your logic does not require the standard Method Template, consider using the simple executor. This approach is ideal for simpler situations and helps avoid code bloat.