Monday, March 26, 2012

No-Code (XAML) Custom Workflow Activities in CRM 2011

With the integration of WF4 in CRM 2011, custom workflow activities for CRM can be created by writing code (CodeActivity) or alternatively they can be created in the WF4 designer using XAML. This post provides a sample/tutorial on how to author a XAML custom workflow activity for CRM.

One thing to note is that a XAML workflow is just a special case in which the XAML custom activity also happens to be the root activity. There is a sample in the SDK on how to do this, but the disadvantage of a XAML workflow is that it cannot be updated/created from the CRM workflow designer. However, in this post I am exploring how to register a XAML custom activity and then use it from the CRM workflow designer like any other custom activity (CodeActivity).

Scenario:

When an account is deactivated, all the child contacts must also be deactivated and then an email must be sent to the account owner.



Solution using XAML Custom Workflow Activity


1. Create a generic “RetrieveMultiple” CodeActivity

This is a custom activity that is simply an implementation of RetrieveMultiple in workflow. It takes as input parameter a string (the fetchXml) and it returns an EntityCollection (the entities retrieved matching the fetchXml):

public sealed class RetrieveMultiple : CodeActivity
{
    [RequiredArgument]
    public InArgument<string> FetchXml { get; set; }

    public OutArgument<EntityCollection> EntityCollection { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        IOrganizationServiceFactory osf = context.GetExtension<IOrganizationServiceFactory>();
        IOrganizationService service = osf.CreateOrganizationService(Guid.Empty);

        FetchExpression fe = new FetchExpression(FetchXml.Get(context));
        EntityCollection.Set(context, service.RetrieveMultiple(fe));
    }
}

Note that this activity has an OutArgument type which is not supported in the CRM workflow designer (EntityCollection), but that is OK because we will not use this activity from the CRM workflow designer.


2. Create the XAML Custom activity

On your VS project (it can be the same as the project for the CodeActivity above) you need to add a new item: Workflow –> Activity. This will add a XAML activity to your project. Opening this activity will open the WF4 designer.

1. Add a reference to Microsoft.Xrm.Sdk.Workflow.dll and Microsoft.Xrm.Sdk.dll to your project.

2. Add the out-of-the-box CRM activities to your toolbox: click Toolbox, under General, right-click and select Choose Items. Click the System.Activities Components tab. Click Browse, and then locate and select the Microsoft.Xrm.Sdk.Workflow.dll file, and then click Open. All Microsoft Dynamics CRM workflow activities will appear in the Choose Toolbox Items dialog box with a check mark next to their name. Click OK. The Microsoft Dynamics CRM workflow activities are now visible in the General tab of the toolbox, and can be used with the designer.

3. Drop a “Sequence” activity in the canvas. The configure some variables as follows:
image


4. Drop a “GerPrimaryEntity” activity inside the sequence. This activity is one of the CRM SDK activities. Then configure the properties as follows:
image
image


5. Drop the “RetrieveMultiple” activity after the GetPrimaryEntity. This corresponds to the code activity we built earlier. If the activity does not appear in the toolbox that’s because you need to build first. Then configure the activity as follows:
image
You need to pass the FetchXml to the activity. In this case, we pass a fetchXML that corresponds to all the child contacts of the parent account:
"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>" &
"    <entity name='contact'>" &
"        <attribute name='fullname'/> " &
"        <attribute name='telephone1'/> " &
"       <attribute name='contactid'/>" &
"       <order attribute='fullname' descending='false'/>" &
"       <filter type='and'>" &
"           <condition attribute='parentcustomerid' operator='eq' value='{" + primaryEntity.Id.ToString() + "}'/>" &
"       </filter>" &
"   </entity>" &
"</fetch>"
Note that the value of the parent account is dynamic and it corresponds to the Id of the primary record on which the workflow was triggered.


6. Insert a “ForEach” activity after the previous one and configure it as follows:
image
This activity is a WF4 activity that will loop through the EntityCollection of contacts we have retrieved.


7. Inside the body of the ForEach activity, drop a SetState activity (this activity comes with CRM SDK). This activity will deactivate all the contacts. Configure it as follows:
image


8. Now you are done building your XAML activity. It should look like this:
image


9. Compile your project and register the plugin assembly just like any other plugin assembly. The custom activities will appear on the CRM workflow designer. Note that the RetrieveMultiple CodeActivity should not be valid for the designer because you can only populate the arguments from a XAML activity. Therefore, you can hide this activity from the workflow designer by setting the Name and WorkflowActivityGroupName to <blank> in the Plugin Registration Tool:
image


10. Now you can author your workflow in CRM designer as per the requirements
image



Conclusion

You might argue that it would just be simpler to register one CodeActivity that does everything for you (retrieve the related contacts and deactivate them) or to use cascading, which is probably true. However, this was just a sample to illustrate the power of XAML custom workflow activities. The reason why this is useful is that now that you have your RetrieveMultiple generic activity, then it will be very easy to implement any kind of iteration in a workflow. Additionally, you no longer have the limitation on what datatypes are acceptable as In/OutArguments in custom workflow activities. With XAML custom activities you can have arguments of any type and global variables of any type. You might also be able to create custom activities without writing virtually any code.

On the other hand, the fact that you cannot populate the input/output of XAML workflow activities from the CRM workflow designer is a huge drawback, it would be a really cool future improvement.

16 comments:

  1. I am unable to set the Variable type to either EntityReference or EntityCollection. I clicked on the dropdown list, clicked on "Browse or Types" and typed "EntityReference" into the "Type Name" textbox. Nothing came up.

    What am I doing wrong?

    ReplyDelete
  2. When you select "Browse for Types" then you need to select the Referenced Assemblies --> Microsoft.Xrm.Sdk and you will find the types under the Microsoft.Xrm.Sdk namespace

    ReplyDelete
  3. I had to add a reference to Microsoft.Xrm.Sdk.dll, in addition to the Microsoft.Xrm.Sdk.Workflow.dll reference you gave in the instructions.

    Thank you.

    ReplyDelete
  4. Continuing on from my comment above, RetrieveMultiple does not appear in the ToolBox. I did note you said it might not, and the solution would have to be Built first. My Build failed with "The type or namespace name 'EntityCollection' could not be found (are you missing a using directive or as assembly reference?)"

    EntityCollection shows up under Microsoft.Xrm.Sdk after I added that reference. Why isn't it being found?

    ReplyDelete
  5. In your RetrieveMultiple class make sure that you add "using Microsoft.Xrm.Sdk" so EntityCollection type can resolve during compilation.

    ReplyDelete
  6. Great Post!

    From within the foreach, instead of setting the state of the child, I want to update the value of a field on the child record. I am trying to figure out which of the workflow activity(ies) controls I need to drop inside the for loop. Is the the SetEntityProperty? What about the UpdateEntity activity? Would I also need to incorporate an UpdateEntity for each child inside my loop? I tried dragging both controls. However, it appears the visual designer only allows a single control.

    I can't find any documentation that explains how to use the setentityproperty or updateentity.
    Any guidance would be appreciated.

    Cheers!

    ReplyDelete
  7. hi Markpittsnh. For each field you want to update you would need to drop one "SetEntityProperty", and then finally to save the changes, you would need an UpdateEntity activity. Because you need multiple activities in the foreach body, you would first need to drop a "Sequence" activity and the drop the SetEntityProperties and UpdateEntity inside the sequence.

    ReplyDelete
  8. Hi,
    i am facing the following error when i try to invoke xaml workflow from CRM 2011 UI.
    Can anyone have any idea how to resolve this?

    Workflow paused due to error: Unhandled Exception: System.Xaml.XamlObjectWriterException: Cannot create unknown type '{clr-namespace:CRMTestLibrary.XAMLWorkflowLibrary;assembly=CRMTestLibrary.XAMLWorkflowLibrary}RetrieveMultiple'.

    ReplyDelete
  9. I am getting the same error as Ahmet.. Any ideas?

    ReplyDelete
  10. Hi, I have managed to get your example to work, and have also had success with adding an updateEntity sequence. However, I would like to add a createEntity to the foreach body, to add a new Case record for each contact. This fails with error message: "Unhandled Exception: System.NullReferenceException: Cannot create an L-value from the given expression with property 'set_Item' because the target object is null". Can you shed any light on this?

    ReplyDelete
  11. Gonzales, your article would be my solution but the RetrieveMultiple activity is no longer part of the Microsoft.Xrm.Sdk.Workflow.Activities namespace of the current 5.0.12 SDK release.

    ReplyDelete
  12. I am sorry I did not read your post carefully enough.

    ReplyDelete
  13. This comment has been removed by a blog administrator.

    ReplyDelete
  14. anybody .. same as Ahmet

    ReplyDelete
  15. Cannot create unknown type '{clr-namespace:CRMTestLibrary.XAMLWorkflowLibrary;assembly=CRMTestLibrary.XAMLWorkflowLibrary}RetrieveMultiple'.

    you need to put dll in GAC in order to solve this issue

    ReplyDelete
  16. Hello Gonz, and thanks for the article. I am working on a project that has similar aspects to what you described here, and since I couldn't find help any other place I want to ask you :)
    I have a custom WF4 designer GUI, that is basically a wrapper on WF that loads some custom built activities from a DLL. whenever these activities contain types that are not referenced in the GUI solution I am getting a blank activity. Is it possible to load the required DLLs for these types at runtime? I tried Assembly.Load but it's not working.

    Thanks a lot!

    ReplyDelete