How can we set second picklist value dynamically with string value is know in CRM Dynamics 4.0 - workflow

I have two pick-list in Car details entity. I'm setting the Model (cir_model) Picklist value with from the input parameter (that is CrmNumber) of Custom Workflow activity and it's working as expected, and the second pick-list Marque (cir_marque) will be set logically using the Model pick-list.
Logic should be if Model is set to 'Ac Ace' then Marque should be set to 'Ac'. Take value 'Ac' using Split() from the string 'Ac Ace'.
Normally in C# this can be done easily but in CRM 4.0 how this can be achieve (How I'll set 'Ac' to Marque)
public static DependencyProperty modelProperty = DependencyProperty.Register("model",
typeof(int), typeof(CreateCardetails));
[CrmInput("Model")]
public int model
{
get
{
return (int)base.GetValue(modelProperty);
}
set
{
base.SetValue(modelProperty, value);
}
}
public static DependencyProperty ContactProperty =
DependencyProperty.Register("Contact", typeof(Lookup), typeof(CreateCardetails));
[CrmInput("Contact ID")]
[CrmReferenceTarget("contact")]
public Lookup Contact
{
get
{
return (Lookup)base.GetValue(ContactProperty);
}
set
{
base.SetValue(ContactProperty, value);
}
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext
executionContext)
{
//Create an car details record which will be linked to the contact record
DynamicEntity cardetails = new DynamicEntity("cir_cardetails");
cardetails["cir_carsdetailsid"] = Contact;
//Setting the picklist value of Model
Picklist modelPickList = new Picklist();
modelPickList.Value = model.Value;
cardetails.Properties.Add(new PicklistProperty("cir_model",modelPickList));
/*
Here the logic should be done for setting Marque (cir_model) value
Picklist marquePickList = new Picklist();
marquePickList.Value = ???
cardetails.Properties.Add(new PicklistProperty("cir_marque",marquePickList));
*/
//Creating the car details record
Guid carkey = crmService.Create(cardetails);
}
How we can set the Marque value logically, I have left the code blank for this like below
/*
Here the logic should be done for setting Marque (cir_marque) value
Picklist marquePickList = new Picklist();
marquePickList.Value = ???
cardetails.Properties.Add(new PicklistProperty("cir_marque",marquePickList));
*/
Please arrange to help me out on this, all suggestions are welcome.

There is no language CRM 4.0, in CRM 4.0 you code in c#. The only thing that change is the way you work with new types.
In Workflow you don't work with controls, you work with entities and the related attributes. So you "just" need to get the attribute cir_model, do a subtring and find the available options in Marque and set the corrected value. Check this sample from SDK.

You can use JavaScript or C# (Plug-In, Workflow) to accomplish this. There are some considerations to think of when choosing which approach to use.
If you want the user to be able to see the result in real time (when they select) then you can use JavaScript.
If you don't care for the user to see the result, or there is data coming in from an outside source (not the user form), then think about using a plugin.
I don't think you should have to use a WF to do this, plugins are just as easy to write and will happen instantaneously instead of waiting for the async process to complete.

Related

How to validate inputs and prevent save actions using databinding in eclipse?

I want to create input forms which validate user input and prevent the model from being saved with invalid data. I have been using databinding which works up to a point but my implementation is not as intuitive as I would like.
Imagine an input which contains '123' and the value must not be empty. The user deletes the characters one by one until empty. The databinding validator shows an error decoration.
However, if the user saves the form and reloads it, then a '1' is displayed in the field - i.e. the last valid input. The databinding does not transmit the invalid value into the model.
I have a ChangeListener but this is called before the databinding so at that point the invalid state has not been detected.
I would like the error to be displayed in the UI but the model remains valid (this is already so). Also, for as long as the UI contains errors, it should not be possible to save the model.
/**
* Bind a text control to a property in the view model
**/
protected Binding bindText(DataBindingContext ctx, Control control,
Object viewModel, String property, IValidator validator)
{
IObservableValue value = WidgetProperties.text(SWT.Modify).observe(
control);
IObservableValue modelValue = BeanProperties.value(
viewModel.getClass(), property).observe(viewModel);
Binding binding = ctx.bindValue(value, modelValue, getStrategy(validator), null);
binding.getTarget().addChangeListener(listener);
ControlDecorationSupport.create(binding, SWT.TOP | SWT.LEFT);
return binding;
}
private UpdateValueStrategy getStrategy(IValidator validator)
{
if (validator == null)
return null;
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setBeforeSetValidator(validator);
return strategy;
}
private IChangeListener listener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
// notify all form listeners that something has changed
}
};
/**
* Called by form owner to check if the form contains valid data e.g. before saving
**/
public boolean isValid()
{
System.out.println("isValid");
for (Object o : getDataContext().getValidationStatusProviders())
{
ValidationStatusProvider vsp = (ValidationStatusProvider) o;
IStatus status = (IStatus)vsp.getValidationStatus()
.getValue();
if (status.matches(IStatus.ERROR))
return false;
}
return true;
}
Your best bet is to steer clear of ChangeListeners - as you've discovered, their order of execution is either undefined or just not helpful in this case.
Instead, you want to stick with the 'observable' as opposed to 'listener' model for as long as possible. As already mentioned, create an AggregateValidationStatus to listen to the overall state of the DataBindingContext, which has a similar effect to your existing code.
Then you can either listen directly to that (as below) to affect the save ability, or you could even bind it to another bean.
IObservableValue statusValue = new AggregateValidationStatus(dbc, AggregateValidationStatus. MAX_SEVERITY);
statusValue.addListener(new IValueChangeListener() {
handleValueChange(ValueChangeEvent event) {
// change ability to save here...
}
});
You can use AggregateValidationStatus to observe the aggregate validation status:
IObservableValue value = new AggregateValidationStatus(bindContext.getBindings(),
AggregateValidationStatus.MAX_SEVERITY);
You can bind this to something which accepts an IStatus parameter and it will be called each time the validation status changes.

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.

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

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.

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);

Saving a Hashtable using Mongo & the Play framework?

I've got a model defined like the following...
#MongoEntity
public class Ent extends MongoModel{
public Hashtable<Integer, CustomType> fil;
public int ID;
public Ent(){
fil = new Hashtable<Integer, CustomType>();
}
}
CustomType is a datatype I've created which basically holds a list of items (among other things). At some point in my web application I update the hashtable from a controller and then read back the size of the item I just updated. Like the following...
public static void addToHash(CustomType type, int ID, int key){
//First I add an element to the list I'm storing in custom type.
Ent ent = Ent.find("byID",ID).first();
CustomType element = user.fil.get(key);
if(element == null) element = new CustomType();
element.add(type);
ent.save();
//Next I reset the variables and read back the value I just stored..
ent = null;
ent = User.find("byID",ID).first();
element = ent.fil.get(ID);
System.out.println("SIZE = " + element.size()); //null pointer here
}
As you can see by my above example I add the element, save the model and then attempt to read back what I have just added and it has not been saved. The above model Ent is a minimal version of the entire Model I'm actually using. All other values in the model including List's, String's, Integer's etc. update correctly when they're updated but this Hashtable I'm storing isn't. Why would this be happening and how could I correct it?
You should probably post on the play framework forum for better help..
Alternatives for a mongodb framework are morphia and springdata which have good documentation.
Not sure how Play maps a hash table to a document value, but it seems it cannot update just the hash table using a mongo operator.
You should be able to mark the whole document for update which would work but slower.