How to get outArgument WorkflowApplication when wf wait for response(bookmark OR idle) and not complete - workflow

Accessing Out Arguments with WorkflowApplication when wf wait for response(bookmark OR idle) and not complete

I also used Tracking to retrieve the values, but instead of saving it to a database I come up with the following solution.
Make a Trackingparticipant and collect the data from an activity.
You can fine tune the tracking participant profile with a spefic tracking query.
I have added a public property Output to set the value of the data from the record.
public class CustomTrackingParticipant : TrackingParticipant
{
//TODO: Fine tune the profile with the correct query.
public IDictionary<String, object> Outputs { get; set; }
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
if (record != null)
{
if (record is CustomTrackingRecord)
{
var customTrackingRecord = record as CustomTrackingRecord;
Outputs = customTrackingRecord.Data;
}
}
}
}
In your custom activity you can set the values you want to expose for tracking with a CustomTrackingRecord.
Here is a sample to give you an idea.
protected override void Execute(NativeActivityContext context)
{
var customRecord = new CustomTrackingRecord("QuestionActivityRecord");
customRecord.Data.Add("Question", Question.Get(context));
customRecord.Data.Add("Answers", Answers.Get(context).ToList());
context.Track(customRecord);
//This will create a bookmark with the display name and the workflow will go idle.
context.CreateBookmark(DisplayName, Callback, BookmarkOptions.None);
}
On the WorklfowApplication instance you can add the Tracking participant to the extensions.
workflowApplication.Extensions.Add(new CustomTrackingParticipant());
On the persistable idle event from the workflowApplication instance I subscribed with the following method.
In the method I get the tracking participant from the extensions.
Because we have set the outputs in the public property we can access them and set them in a member outside the workflow. See the following example.
private PersistableIdleAction PersistableIdle(WorkflowApplicationIdleEventArgs
workflowApplicationIdleEventArgs)
{
var ex = workflowApplicationIdleEventArgs.GetInstanceExtensions<CustomTrackingParticipant>();
Outputs = ex.First().Outputs;
return PersistableIdleAction.Unload;
}
I hope this example helped.

Even simpler: Use another workflow activity to store the value you are looking for somewhere (database, file, ...) before starting to wait for a response!

You could use Tracking.
required steps would be:
define a tracking profile which queries ActivityStates with the state closed
Implement an TrackingParticipant to save the OutArgument in process memory, a database or a file on disk
hook everything together
The link cotains all the information you will need to do this.

Related

Accessing Pipeline within DoFn

I'm writing a pipeline to replicate data from one source to another. Info about data sources is stored in db (BQ). How I can use this data it to build read/write endpoints dynamically?
I tried to pass Pipeline object to my custom DoFn but it can't be serialized. Later I tried to call method getPipeline() on a passed view but it doesn't work as well. -- which is actually expected
I can't know all tables I need to serialize in advance so I have to read all data from db (or any other source).
// builds some random view
PCollectionView<IdWrapper> idView = ...;
// reads tables meta and replicates data per each table
pipeline.apply(getTableMetaEndpont().read())
.apply(ParDo.of(new MyCustomReplicator(idView)).withSideInputs(idView))
private static class MyCustomReplicator extends DoFn<TableMeta, TableMeta> {
private final PCollectionView<IdWrapper> idView;
private DataReplicator(PCollectionView<IdWrapper> idView) {
this.idView = idView;
}
// TableMeta {string: sourceTable, string: destTable}
#ProcessElement
public void processElement(#Element TableMeta tableMeta, ProcessContext ctx) {
long id = ctx.sideInput(idView).getValue();
// builds read endpoint which depends on table meta
// updates entities
// stores entities using another endpoint
idView
.getPipeline()
.apply(createReadEndpoint(tableMeta).read())
.apply(ParDo.of(new SomeFunction(tableMeta, id)))
.apply(createWriteEndpoint(tableMeta).insert());
ctx.output(tableMetadata);
}
}
I expect it to replicate data specified by TableMeta but I can't use pipeline within DoFn object because it can't be serialized/deserialized.
Is there any way to implement the intended behavior?

How do I warm up an actor's state from database when starting up?

My requirement is to start a long running process to tag all the products that are expired. This is run every night at 1:00 AM. The customers may be accessing some of the products on the website, so they have instances around the time when the job is run. The others are in the persistent media, not yet having instances because the customers are not accessing them.
Where should I hook up the logic to read the latest state of an actor from a persistent media and create a brand new actor? Should I have that call in the Prestart override method? If so, how can I tell the ProductActor that a new actor being created.
Or should I send a message to the ProductActor like LoadMeFromAzureTable which will load the state from the persistent media after an actor being created?
There are different ways to do it depending on what you need, as opposed to there being precisely one "right" answer.
You could use a Persistent Actor to recover state from a durable store automatically on startup (or in case of crash, to recover). Or, if you don't want to use that module (still in beta as of July 2015), you could do it yourself one of two ways:
1) You could load your state in PreStart, but I'd only go with this if you can make the operation async via your database client and use the PipeTo pattern to send the results back to yourself incrementally. But if you need to have ALL the state resident in memory before you start doing work, then you need to...
2) Make a finite state machine using behavior switching. Start in a gated state, send yourself a message to load your data, and stash everything that comes in. Then switch to a receiving state and unstash all messages when your state is done loading. This is the approach I prefer.
Example (just mocking the DB load with a Task):
public class ProductActor : ReceiveActor, IWithUnboundedStash
{
public IStash Stash { get; set; }
public ProductActor()
{
// begin in gated state
BecomeLoading();
}
private void BecomeLoading()
{
Become(Loading);
LoadInitialState();
}
private void Loading()
{
Receive<DoneLoading>(done =>
{
BecomeReady();
});
// stash any messages that come in until we're done loading
ReceiveAny(o =>
{
Stash.Stash();
});
}
private void LoadInitialState()
{
// load your state here async & send back to self via PipeTo
Task.Run(() =>
{
// database loading task here
return new Object();
}).ContinueWith(tr =>
{
// do whatever (e.g. error handling)
return new DoneLoading();
}).PipeTo(Self);
}
private void BecomeReady()
{
Become(Ready);
// our state is ready! put all those stashed messages back in the mailbox
Stash.UnstashAll();
}
private void Ready()
{
// handle those unstashed + new messages...
ReceiveAny(o =>
{
// do whatever you need to do...
});
}
}
/// <summary>
/// Marker interface.
/// </summary>
public class DoneLoading {}

WF 4 different IDs on the same activities

Due to a strange behavior in my application, i am forced to reload the designer before calling WorkflowInvoker.Invoke on it.
wd.Flush();
SaveXamlFile(currentXamlPath, wd.Text);
I just flush the content, and write the wd.Text to a file.
//cleanup the previous designer
if (wd != null)
{
wd.ModelChanged -= new EventHandler(Designer_ModelChanged);
}
//designer
wd = new WorkflowDesigner();
designerArea.Child = wd.View;
this.DebuggerService = this.wd.DebugManagerView;
//property grid
propertiesArea.Child = wd.PropertyInspectorView;
//event handler
wd.ModelChanged += new EventHandler(Designer_ModelChanged);
//error service
wd.Context.Services.Publish<IValidationErrorService>(errorService);
wd.Context.Items.Subscribe<Selection>(OnItemSelected);
I then recreate a new instance of the WorkflowDesigner and load the previously saved file.
wd.Load(currentXamlPath);
I call WorkflowInvoker.Invoke and inside my custom activity which derives from CodeActivity i am taking it's name:
OK, fine until now, i have a 1.2 Id there.
I want to update some of the fields of this Activity via its ModelItem in order to display them in the GUI right away.
IEnumerable<ModelItem> activityCollection = currentWorkflow.Find(currentWorkflow.Root, typeof(Activity));
But here comes the issue:
I can't find that my Activity id there. Is now transformed from 1.2 to 2. Why is this happening?
I've tried to send a this reference from my Activity Execute method and searched it by ref but all i get is nulls.
ModelItem temp = activityCollection.FirstOrDefault((m) => (m.GetCurrentValue() == a));
I am sure i am missing something here, but i can't figure out what is it.
I found a workaround on this :
On my custom activities i am adding a Guid property and I override CacheMetadata:
public Guid unique { get; set; }
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
if (unique.ToString() == "00000000-0000-0000-0000-000000000000")
unique = Guid.NewGuid();
}
When i drag the activity on the designer, the unique id is generated. I make sure that this portion of code is called only once.
Why is that?
Because after a call like this,
IEnumerable<ModelItem> activityCollection = currentWorkflow.Find(currentWorkflow.Root, typeof(Activity));
each model in the activity collection contains that property ( unique of type Guid ) with the value of the first assignment made in CacheMetadata. I can't explain this behavior, i've just taken it into consideration.
Who calls again that CacheMetadata ? something like this :
Activity root = ActivityXamlServices.Load(currentXamlPath);
WorkflowInspectionServices.CacheMetadata(root);
And so, the Guid is changed and its utility is gone.
This way, i am able to get the ModelItem for my custom activity and update some of its properties which are immediately displayed in the GUI.

Tridion Workflows - How to get the Component at the Activity in Event Handler

I need to get the component associated to a Activity at the event system.
I try to get the component ID using:
public void OnActivityInstanceFinishPost(ActivityInstance activityInstance, string finishMessage, string nextActivity, string dynamicAssignee)
{
if (activityInstance.ProcessInstance.ProcessDefinition.Title.Equals("Component Process IESE"))
{
if (activityInstance.ActivityDefinition.Title.Equals("Create or Edit Component"))
{
WFE workflow = tdse.GetWFE();
try
{
Component comp = (Component)activityInstance.ProcessInstance.Item;
XMLReadFilter filter = new XMLReadFilter();
String processHistoryId = activityInstance.ProcessInstance.ID.Replace("131076", "131080");
ProcessHistory hist = (ProcessHistory)tdse.GetObject(activityInstance.ProcessInstance.ID, EnumOpenMode.OpenModeView, Constants.URINULL, filter);
}
catch (Exception e)
{ }
}
}
}
we try different options:
Component comp = (Component)activityInstance.ProcessInstance.Item;
But this solution returns a null.
Then I found in internet the next solution:
XMLReadFilter filter = new XMLReadFilter();
String processHistoryId = activityInstance.ProcessInstance.ID.Replace("131076", "131080");
ProcessHistory hist = (ProcessHistory)tdse.GetObject(activityInstance.ProcessInstance.ID, EnumOpenMode.OpenModeView, Constants.URINULL, filter);
Component comp = hist.Item as Component;
But the ProcessHistory object is null.
How can I determine the component associated to the activityInstance?
Thank you.
After reviewing the functionality needed by Guskermitt, I've shown him a neater way to do what he needs to do. In short, EventSystem is not needed in this case.
His goal is to send an email after a component has been approved, the approach will be the following:
Add to workflow a new automatic activity.
Create a new .NET assembly, in this case a C# class to do what he needs to do.
Register the assembly in the GAC.
Add logic in the new automatic activity in workflow to use the .NET assembly.
2#
[ProgId("WfHelper")]
[ComVisible(true)]
public class Helper
{
public void SendMail(string workItemId)
{
var session = new Session();
.
.
.
4#
dim helper
set helper = CreateObject("WfHelper")
call helper.SendMail(CurrentWorkItem.ID)
set helper = nothing
FinishActivity “Email has been sent"
ActivityInstance has a WorkItems property (inherited from Activity) that contains a reference to your Component.
OnActivityInstanceFinishPost means that your activity is finished. Therefore there is no more work item associated with it. However, you are getting the process instance and the work item associated with that. If you get null there, then it suggests your workflow process is done and the component has moved out of workflow. From looking at your code, it is quite likely that your ProcessInstance is completed (it won't be null, but it won't have any item associated with it).
I suspect that you've read this post http://www.tridiondeveloper.com/autopublishing-on-workflow-finish suggesting to look in the history. Have you looked into the history via the CM GUI, is the history item there? If it isn't, that's why you get null. A workflow process gets moved to history when it is completed. So double check that you are indeed on the last workflow activity before looking at the history.
By looking at your code, the error seems to be that you are trying to get a history object using activityInstance.ProcessInstance.ID. GetObject() should return an item, but your cast to a ProcessHistory should break and then you quietly eat the exception. You need to pass in the History ID, not the ProcessInstance ID as follows:
ProcessHistory hist = (ProcessHistory)tdse.GetObject(processHistoryId, EnumOpenMode.OpenModeView, Constants.URINULL, filter);

How can I write a custom WorkFlow 4 Code Activity that includes a "Body Block"?

Is this possible? I know it is for MS since they have WF activity packs but I'm not sure how it's done. It would be nice to be able to have Activities with Body blocks to insert other activities, buttons, etc. If not too much trouble and/or time consuming that is.
Its easy enough if you follow a few rules. Here's an example of a NativeActivity that has a child:
[Designer(typeof(MyActivityDesigner)), ContentProperty("Child")]
public sealed class MyActivity :
NativeActivity, IActivityTemplateFactory
{
// this "activity delegate" holds our child activity
public ActivityAction Child { get; set; }
// may be necessary to do this
protected override void
CacheMetadata(NativeActivityMetadata metadata)
{
metadata.AddDelegate(Child);
}
protected override void
Execute(NativeActivityContext context)
{
// do some work here, then
context.ScheduleAction(Child);
}
// better to use a template factory than a constructor to do this!
Activity IActivityTemplateFactory
.Create(System.Windows.DependencyObject target)
{
return new MyActivity
{
// HAVE to have this set, or it fails in the designer!
Child = new ActivityAction()
};
}
}
Note a few things: We use an Activity Delegate type to hold our child. Second, we implement IActivityTemplateFactory to configure our activity for the designer. Its always better/more stable to do this than set stuff up in the constructor. We will be binding to a property of the delegate, so we have to set an instance; otherwise the binding will fail.
When we execute, all you have to do is schedule your child when appropriate and return. You shouldn't block, of course.
Then, in the designer, you'd bind to Child like this:
<sap:WorkflowItemPresenter
HintText="Add children here!"
Item="{Binding Path=ModelItem.Child.Handler}" />
The Pro WF : Windows Workflow in .Net 4 book by Bruce Bukovics also has lots of examples. You might want to check that out.
You need to start with a NativeActivity instead of a CodeActivity. The NativeActivity lets you schedule child activities through its execution context. There is no template for the NativeActivity, instead you just create a class and derive from NativeActivity.