WF 4.5 : Creating Custom composite activity - workflow-foundation-4

I am looking to create a custom composite activity in WF 4.5
The problem statement :
Create a custom activity with some pre defined properties and variables.
The developer should be able to use this activity as a base and drag and drop other out of the box/ custom activities in it.
I have looked at various samples on the net and I have found the following sample which does exactly what i need.
http://msdn.microsoft.com/en-us/library/Aa480200
However it seems that with WF 4.5 the classes mentioned in the sample above have been deprecated.
Is there a way to achieve what is done the sample above in WF 4.5 ?
Any links articles or samples which show how this can be done in WF 4.5 would help.

In WF4 composite activities can be achieved through IActivityTemplateFactory:
public sealed class CompositeActivity : IActivityTemplateFactory
{
public Activity Create(DependencyObject target)
{
return new Sequence
{
Variables = {
new Variable<string>("MyStringVar"),
new Variable<int>("MyIntegerVar")
},
Activities = {
new WriteLine { Text = "My first activity within the composite" },
new Delay { Duration = new InArgument<TimeSpan>(a => TimeSpan.FromSeconds(5)) },
new WriteLine { Text = "My third activity within the composite" }
}
};
}
}
The designer knows IActivityTemplateFactory so it will show up on toolbox as any other activity.

Related

How to show icons on Tree Control in D365FO

Please how to show icons on FormTreeControl in Dynamics 365 For Finance and Operations
Here is my actual code, using to show icons in D365FO, #ResAppl Macro
public class IconsTree extends FormRun
{
public void init()
{
SalesTable _Table;
TreeItemIdx _TreeItemid;
super();
MyTreeView.deleteAll(); //This is my Tree Control on Form
MyTreeView.lock();
ImageListAppl dd = new ImageListAppl();
ImageRes imageRes = dd.image(#ImageFormButtonGroup);
MyTreeView.setImagelist(dd.imageList());
while select * from _Table
{
_TreeItemid = SysFormTreeControl::addTreeItem(MyTreeView, "Sales Order :" + _Table.SalesId ,FormTreeAdd::Root, _Table.RecId, #ImageOverlayYellowLock);
SysFormTreeControl::addTreeItem(MyTreeView, " Customer Account : " + _Table.CustAccount ,_TreeItemid , _Table.RecId);
SysFormTreeControl::expandTree(MyTreeView,_TreeItemid);
SysFormTreeControl::setOverlayImage(MyTreeView, _TreeItemid, imageRes);
}
}
}
and the result , no icons :
From what I have understood and what Microsoft indicates it's not allowed use image or icons on tree control in D365FO.
What is allowed build extensible controls, check this link Check box support in tree controls

Changelog method based on project tracker template

Based on the project tracker I have integrated a changelog into my app that relates my UserSettings model to a UserHistory model. The latter contains the fields FieldName, CreatedBy, CreatedDate, OldValue, NewValue.
The relation between both models works fine. Whenever a record is modified, I can see the changes in a changelog table. I now want add an "undo"-button to the table that allows the admin to undo a change he clicks on. I have therefore created a method that is handled by the widget that holds the changelog record:
function undoChangesToUserRecord(changelog) {
if (!isAdmin()) {
return;
}
var fieldName = changelog.datasource.item.FieldName;
var record = changelog.datasource.item.UserSettings;
record[fieldName] = changelog.datasource.item.OldValue;
}
In theory method goes the connection between UserHistory and UserSettings up to the field and rewrites its value. But when I click on the button, I get a "Failed due to circular reference" error. What am I doing wrong?
I was able to repro the issue with this bit of code:
google.script.run.ServerFunction(app.currentPage.descendants.SomeWidget);
It is kinda expected behavior, because all App Maker objects are pretty much complex and Apps Script RPC has some limitations.
App Maker way to implement it would look like this:
// Server side script
function undoChangesToUserRecord(key) {
if (!isAdmin()) {
return;
}
var history = app.models.UserHistory.getRecord(key);
if (history !== null) {
var fieldName = history.FieldName;
var settings = history.UserSettings;
settings[fieldName] = history.OldValue;
}
}
// Client side script
function onUndoClick(button) {
var history = widget.datasource.item;
google.script.run
.undoChangesToUserRecord(history._key);
}

Relational Query - 2 degrees away

I have three models:
Timesheets
Employee
Manager
I am looking for all timesheets that need to be approved by a manager (many timesheets per employee, one manager per employee).
I have tried creating datasources and prefetching both Employee and Employee.Manager, but I so far no success as of yet.
Is there a trick to this? Do I need to load the query and then do another load? Or create an intermediary datasource that holds both the Timesheet and Employee data or something else?
You can do it by applying a query filter to the datasource onDataLoad event or another event. For example, you could bind the value of a dropdown with Managers to:
#datasource.query.filters.Employee.Manager._equals
- assuming that the datasource of the widget is set to Timesheets.
If you are linking to the page from another page, you could also call a script instead of using a preset action. On the link click, invoke the script below, passing it the desired manager object from the linking page.
function loadPageTimesheets(manager){
app.showPage(app.pages.Timesheets);
app.pages.Timesheets.datasource.query.filters.Employee.Manager._equals = manager;
app.pages.Timesheets.datasource.load();
}
I would recommend to redesign your app a little bit to use full power of App Maker. You can go with Directory Model (Manager -> Employees) plus one table with data (Timesheets). In this case your timesheets query can look similar to this:
// Server side script
function getTimesheets(query) {
var managerEmail = query.parameters.ManagerEmail;
var dirQuery = app.models.Directory.newQuery();
dirQuery.filters.PrimaryEmail._equals = managerEmail;
dirQuery.prefetch.DirectReports._add();
var people = dirQuery.run();
if (people.length === 0) {
return [];
}
var manager = people[0];
// Subordinates lookup can look fancier if you need recursively
// include everybody down the hierarchy chart. In this case
// it also will make sense to update prefetch above to include
// reports of reports of reports...
var subortinatesEmails = manager.DirectReports.map(function(employee) {
return employee.PrimaryEmail;
});
var tsQuery = app.models.Timesheet.newQuery();
tsQuery.filters.EmployeeEmail._in = subortinatesEmails;
return tsQuery.run();
}

WCF Transaction with multiple inserts

When creating a user, entries are required in multiple tables. I am trying to create a transaction that creates a new entry into one table and then pass the new entityid into the parent table and so on. The error I am getting is
The transaction manager has disabled its support for remote/network
transactions. (Exception from HRESULT: 0x8004D024)
I believe this is caused by creating multiple connections within a single TransactionScope, but I am unsure on what the best/most efficient way of doing this is.
[OperationBehavior(TransactionScopeRequired = true)]
public int CreateUser(CreateUserData createData)
{
// Create a new family group and get the ID
var familyGroupId = createData.FamilyGroupId ?? CreateFamilyGroup();
// Create the APUser and get the Id
var apUserId = CreateAPUser(createData.UserId, familyGroupId);
// Create the institution user and get the Id
var institutionUserId = CreateInsUser(apUserId, createData.AlternateId, createData.InstitutionId);
// Create the investigator group user and return the Id
return AddUserToGroup(createData.InvestigatorGroupId, institutionUserId);
}
This is an example of one of the function calls, all the other ones follow the same format
public int CreateFamilyGroup(string familyGroupName)
{
var familyRepo = _FamilyRepo ?? new FamilyGroupRepository();
var familyGroup = new FamilyGroup() {CreationDate = DateTime.Now};
return familyRepo.AddFamilyGroup(familyGroup);
}
And the repository call for this is as follows
public int AddFamilyGroup(FamilyGroup familyGroup)
{
using (var context = new GameDbContext())
{
var newGroup = context.FamilyGroups.Add(familyGroup);
context.SaveChanges();
return newGroup.FamilyGroupId;
}
}
I believe this is caused by creating multiple connections within a single TransactionScope
Yes, that is the problem. It does not really matter how you avoid that as long you avoid it. A common thing to do is to have one connection and one EF context per WCF request. You need to find a way to pass that EF context along.
The method AddFamilyGroup illustrates a common anti-pattern with EF: You are using EF as a CRUD facility. It's supposed to me more like a live object graph connected to the database. The entire WCF request should share the same EF context. If you move in that direction the problem goes away.

Multiple TrackingParticipants not working, have funny side effects?

We are rying to use WF with multiple tracking participants which essentially listen to different queries - one for activity states, one for custom tracknig records which are a subclass of CustomTrackingRecord.
The problem is that we can use both TrackingParticipants indivisually, but not together - we never get our subclass from CustomTrackingRecord but A CustomTrackingRecord.
If I put bopth queries into one TrackingParticipant and then handle everythign in one, both work perfectly (which indicates teh error is not where we throw them).
The code in question for the combined one is:
public WorkflowServiceTrackingParticipant ()
{
this.TrackingProfile = new TrackingProfile()
{
ActivityDefinitionId = "*",
ImplementationVisibility = ImplementationVisibility.All,
Name = "WorkflowServiceTrackingProfile",
Queries = {
new CustomTrackingQuery() { Name = "*", ActivityName = "*" },
new ActivityStateQuery() {
States = {
ActivityStates.Canceled,
ActivityStates.Closed,
ActivityStates.Executing,
ActivityStates.Faulted
}
},
}
};
}
When using two TrackingParticipants we have two TrackingProfile (with different names) that each have one of the queries.
in the track method, when using both separate, the lines:
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
Console.WriteLine("*** ActivityTracking: " + record.GetType());
if (record is ActivityBasedTrackingRecord)
{
System.Diagnostics.Debugger.Break();
}
never result in the debugger hitting, when using only the one to track our CustomTrackingRecord subclass (ActivityBasedTrackingRecord) then it works.
Anyone else knows about this? For now we have combined both TrackingParticipants into one, but this has the bad side effect that we can not dynamically expand the logging possibilities, which we would love to. Is this a known issue with WWF somewhere?
Version used: 4.0 Sp1 Feature Update 1.
I guess I encounterad the exact same problem.
This problem occurs due to the restrictions of the extension mechanism. There can be only one instance per extension type per workflow instance (according to Microsoft's documentation). Interesting enough though, one can add multiple instances of the same type to one workflow's extensions which - in case of TrackingParticipant derivates - causes weird behavior, because only one of their tracking profiles is used for all participants of the respective type, but all their overrides of the Track method are getting invoked.
There is a (imho) ugly workaround to this: derive a new participant class from TrackingParticipant for each task (task1, task2, logging ...)
Regards,
Jacob
I think that this problem isn't caused by extension mechanism, since DerivedParticipant 1 and DerivedParticipant 2 are not the same type(WF internals just use polymorphism on the base class).
I was running on the same issue, my Derived1 was tracking records that weren't described in its profile.
Derived1.TrackingProfile.Name was "Foo" and Derived2.TrackingProfile.Name was null
I changed the name from null to "Bar" and it worked as expected.
Here is a WF internal reference code, describing how is the Profile selected
// System.Activities.Tracking.RuntimeTrackingProfile.RuntimeTrackingProfileCache
public RuntimeTrackingProfile GetRuntimeTrackingProfile(TrackingProfile profile, Activity rootElement)
{
RuntimeTrackingProfile runtimeTrackingProfile = null;
HybridCollection<RuntimeTrackingProfile> hybridCollection = null;
lock (this.cache)
{
if (!this.cache.TryGetValue(rootElement, out hybridCollection))
{
runtimeTrackingProfile = new RuntimeTrackingProfile(profile, rootElement);
hybridCollection = new HybridCollection<RuntimeTrackingProfile>();
hybridCollection.Add(runtimeTrackingProfile);
this.cache.Add(rootElement, hybridCollection);
}
else
{
ReadOnlyCollection<RuntimeTrackingProfile> readOnlyCollection = hybridCollection.AsReadOnly();
foreach (RuntimeTrackingProfile current in readOnlyCollection)
{
if (string.CompareOrdinal(profile.Name, current.associatedProfile.Name) == 0 && string.CompareOrdinal(profile.ActivityDefinitionId, current.associatedProfile.ActivityDefinitionId) == 0)
{
runtimeTrackingProfile = current;
break;
}
}
if (runtimeTrackingProfile == null)
{
runtimeTrackingProfile = new RuntimeTrackingProfile(profile, rootElement);
hybridCollection.Add(runtimeTrackingProfile);
}
}
}
return runtimeTrackingProfile;
}

Resources