When I first started working with Opalis (now Orchestrator) and the SDK, I was taught that you could create activities (and Integration Packs) using the declarative approach or the imperative approach. The way it was relayed, these were two mutually-exclusive paths toward the same end goal. As I work more and more with Orchestrator and now as I am in charge of the SDK as a feature of the product, I’ve learned a lot more about it and also learned that everything is not always as advertised.
As “the OIT Guy”, I get frequent questions both internally and externally about what the best practices are for creating new activities. Should I use the declarative style, should I use the imperative style, or should I use a mixture of both? Well, the answer to that is “it depends” (I hope you saw that coming). To some people, using the declarative style with attributes and statically-defined inputs and outputs is enough. For others, they want more dynamic activities and prefer the imperative style. The imperative style also gives you additional capabilities you can’t access using the declarative style. Likewise, there are some things you can’t access using just the imperative style, so you end up with a hybrid if you want to be able to utilize everything in the SDK.
Note: to understand the basics of creating activities using the two styles, see these pages on MSDN:
Also, you can find the complete description of the SDK classes on MSDN here: Microsoft.SystemCenter.Orchestrator.Integration
In order to understand where you should use which type of model in creating custom activities, it helps to clearly delineate the capabilities of each. That kind of comparison isn’t really available anywhere, so I thought I would create one for everybody. We’ll start with the activities themselves.
Each activity encapsulates all of the functionality required to show the properties at design time as well as to execute the actions of the activity at runtime.
I actually threw you a curve ball with this first section. The [Activity] attribute and IActivity interface are not actually similar entities. You can’t choose whether or not to use [Activity]. You have to use it. It’s the only way to define that the class you’re creating is actually an activity. The question is whether or not you also inherit your class from IActivity. If you do, you’ll now have to implement the Design() and Execute() methods, but you don’t have to change anything. You could still have statically-defined inputs, filters and outputs if you want, but you are now able to define your properties in a different way and display different things to the user that you weren’t able to by just using the [Activity] attribute alone.
So, to sum it up for the Activity as a whole:
Input properties are the items displayed to the user on the Properties tab or, in the case of optional properties, displayed when the user clicks the Optional Properties button. Properties can be displayed in only one way (in a test box), but getting the value to be displayed can be via simple typed-in value, published data subscription, or via a browser of some kind. The types of browsers and functionality available depends on how you’re defining and adding the property to the activity, whether it’s using the [ActivityInput] attribute or via the AddInput() method of the IActivityDesigner interface.
Looking at the above, the only thing you give up by using the imperative approach is the ability to specify a description for the input property. And given that I’ve yet to find out where you can actually see the description displayed, I would say not having that functionality is perfectly fine. What you gain by using the imperative approach is much, much greater. By not using the attribute, you now have the ability to dynamically populate lists with values from enums, variables or even directly from methods. You also have several new types of browsers that you don’t have with the attribute, including Boolean, File, Folder, Computer and Date/Time.
The only time you cannot use the imperative approach to define your properties is when you’re defining a structure / group of properties in a class with the ActivityData attribute. Of course, if I can figure out a way to use dynamic inputs in an ActivityData class, I’ll be sure to let you know
My recommendations for Input Properties:
Filter properties are shown to the user on the Filters tab and represent a way to limit the input that is returned from the activity. They’re vastly similar in usage to Input Properties, with the exception of having comparators (relations) that need to be defined so the user can compare the output in some way to a value they define.
Like for Input Properties, the AddFilter() method doesn’t allow you to define a description for a filter, but guess what… neither does the attribute! So in the case of filters, you lose absolutely nothing by using the imperative approach and like inputs, you gain flexibility in the list browser as well as gaining the other types of browsers.
My recommendations for Filters:
Output properties are how published data gets sent to the data bus. Output properties must be defined at design time, otherwise you won’t have access to published data from the activity in subsequent activities in the runbook.
Unlike the other two methods, the AddOutput() method does allow you to define a description for the output property.So in the case of output properties, you lose absolutely nothing by using the imperative approach. There are no browsers associated with output properties, so you don’t get anything there, but you do get the ability to specifically define the output type, which you can’t using the attribute. For example, is “1” supposed to be a string or a number? Well you can define that using the imperative approach. So once again the imperative approach wins out in terms of flexibility and features.
My recommendations for Output properties:
The following represent attributes where there is no interface equivalent available, so the attribute must be used if you want that functionality.
This attribute is used to define a monitor instead of a normal activity. Like the [Activity] attribute, you have to use this attribute to define the actual activity class. You can still inherit the class from IActivity to get the benefits of dynamic inputs and outputs.
This attribute is used to define the configuration settings for activities. These are the items that show up in the Options menu and at the top of activities that specify a configuration value.
This attribute is used to define a special class of input, output, and/or filter types that are grouped together. For use within an activity. These are frequently used within configurations to define a set of properties associated with the configuration. To use this group of properties as output data, you must use the IActvity / IActivityDesigner interface and the AddCorellatedData() method. Note that there is no way to define dynamic properties within this attributed class, so if you want grouped properties and correlated data, you will need to define the outputs statically.
This attribute is used to define one or more methods that will be invoked during runtime of the activity. The order in which they are invoked is indeterminate. The closest equivalent would be the Execute() method within the IActivity interface, which is always invoked at runtime. Note: you can define activities that do not have any ActivityMethod methods, but they either must have the execution logic in the getters of the output properties or they need to inherit from IActivity and have the Execute() method
As I mentioned above, you use the ActivityData attribute to specify a class containing multiple properties such as outputs, but in order to use that data as published data, it needs to be output in a format similar to a table, and that required the AddCorellatedData() method, which is only available if your activity inherits from IActivity and you’re within the Design() method, which takes a parameter of the type IActivityDesigner.
This interface is really just something defining an input parameter to the Execute() method in activities that inherit from IActivity. You will never use this interface directly to create another extended class. However, I mention it because you can use it within the Execute method to step through values of the Inputs and Filters associated with the activity at runtime.
Like IActivityRequest, you won’t be inheriting from this interface, but you will use it if your activity inherits from IActivity, because the Execute() method has a parameter of this type which allows you to publish data out from the activity as part of its runtime execution. There are a couple of methods that you’ll use all the time to publish data, and a bunch of methods that are really useful for handling errors and reporting those to the user. Of particular importance:
If you didn’t catch the distinction above (it took me a second to realize it the first time I saw these), Publish handles single objects, and PublishRange handles a collection of objects. This interface also gives you the following methods (which I’ll have to detail in an upcoming article):
If you take anything away from this rather long article it should be that you shouldn’t choose the simple declarative approach using attributes alone, and you can’t use just the imperative approach and use nothing but interfaces. You should be using a hybrid approach that utilizes the attributes when required, but uses the interfaces in all other cases. This approach gives you the most flexibility and functionality when designing custom activities.
Be sure to keep watching the Orchestrator team blog for lots of useful tips and examples on using all of the cool stuff in the SDK to its fullest!
We are trying to implement some items for the Opalis CA service desk IP, but for Orchestrator and CA SD v12.
Can you tell me how are multiple output items handled? I see that we can gather output using AddOutput(“HWOutput”).AsNumber(), which I have working ok for a single object, but what if i have multiple objects? For example, I have a reference number added as Output, which works if I return one Incident in my query, but how do I handle 2 incident? Thanks.