Programmatically Process all Sitecore items with Workflow - c#

I have situation where I have a lot items in the Workbox that I need to clear out.
I need to process these items so that they can be assigned a workflow state (without triggering workflow actions) according to this rule:
If the item has a state of draft or waiting for approval and there is a published version higher than the current version then set the workflow state to be x worflowstate.
I haven't done much experimenting with workflow, so does anybody have any code samples or thoughts of how I could achieve this?

Here is a blog post about Changing workflow state of Sitecore items programmatically .
First you need to find all items in chosen workflow states:
IWorkflow[] workflows = Sitecore.Context.Database.WorkflowProvider.GetWorkflows();
IWorkflow chosenWorkflow = workflows[..]; // choose your worfklow
WorkflowState[] workflowStates = chosenWorkflow.GetStates();
foreach (WorkflowState state in workflowStates)
{
if (!state.FinalState)
{
DataUri[] itemDataUris = chosenWorkflow.GetItems(state.StateID);
foreach (DataUri uri in itemDataUris)
{
Item item = Sitecore.Context.Database.GetItem(uri);
/* check other conditions - newer version exists etc */
ChangeWorkflowState(item, newWorkflowStateId);
}
}
}
The simplest code for changing workflow state of Sitecore item without executing any actions related to the new workflow state is:
public static WorkflowResult ChangeWorkflowState(Item item, ID workflowStateId)
{
using (new EditContext(item))
{
item[FieldIDs.WorkflowState] = workflowStateId.ToString();
}
return new WorkflowResult(true, "OK", workflowStateId);
}
public static WorkflowResult ChangeWorkflowState(Item item, string workflowStateName)
{
IWorkflow workflow = item.Database.WorkflowProvider.GetWorkflow(item);
if (workflow == null)
{
return new WorkflowResult(false, "No workflow assigned to item");
}
WorkflowState newState = workflow.GetStates()
.FirstOrDefault(state => state.DisplayName == workflowStateName);
if (newState == null)
{
return new WorkflowResult(false, "Cannot find workflow state " + workflowStateName);
}
return ChangeWorkflowState(item, ID.Parse(newState.StateID));
}

Related

How to load messages faster? EWS C#

I have a question about loading methods for EWS. I have a lot of messages in my outlook and I'm also using EWS and WinForms.
Right know my code is this:
while (more)
{
FindItemsResults<Item> findResults1 = allItemsFolder.FindItems("System.Message.DateReceived:01/01/2011..12/31/2022", iv);
foreach (var item in findResults1)
{
if (item.GetType() == typeof(Microsoft.Exchange.WebServices.Data.EmailMessage))
{
listik.Add(item);
}
}
more = findResults1.MoreAvailable;
if (more)
{
iv.Offset += 1000;
}
}
It took 12 minutes already for me for running my application and I decided to stop it, because something is wrong. I think I have more than 61k messages.
The idea is to show only those messages, which have my criteria, like email address. So, I should go through all the messages and folders and put suitable in one list to show them. I'm not sure how to do this better.
Anyone can help me with that?
Edit//Added more code
PropertySet psPropSet = new PropertySet(BasePropertySet.FirstClassProperties)
{
RequestedBodyType = BodyType.Text
};
Added more code:
foreach (var item in listik)
{
if (item != null)
{
if (((EmailMessage)(item))?.From?.Address != null)
{
var d = ((EmailMessage)(item)).From.Address;
if (d.Contains(comboBox2.SelectedItem.ToString()))
{
item.Load(psPropSet);
emails.Add((EmailMessage)item);
if (item.Subject != null)
{
listBox1.Items.Add(item.Subject);
}
}
}
}
}
I found a link from Microsoft:
https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/email-properties-and-elements-in-ews-in-exchange
It's about first-class properties and I can see here EWS element - FROM, but I can't figure out how to set it in iv.PropertySet.
I would try to remove the search and try again its pretty pointless anyway with such a broad date range, the all items folder is a search folder so your applying one search on top of the other which generally doesn't work well. Also use a property set to limit the result that is returned to just the properties you want which will also make the query run faster eg
iv.PropertySet =
new PropertySet(
BasePropertySet.IdOnly,
ItemSchema.Subject);
No email app, including Outlook, ever loads all messages in a folder. Use a virtual listview/grid and retrieve the messages (using the appropriate offset) only when displaying them to an end user.
I found the way how it's working. Thanks, everyone for your help.
iv.PropertySet = new PropertySet(EmailMessageSchema.From, ItemSchema.Subject);
while (more)
{
FindItemsResults<Item> findResults1 = allItemsFolder.FindItems(iv);
foreach (var item in findResults1)
{
if (item.GetType() == typeof(Microsoft.Exchange.WebServices.Data.EmailMessage))
{
listik.Add(item);
}
}
more = findResults1.MoreAvailable;
if (more)
{
iv.Offset += 1000;
}
}

Filter Change Notifications in Active Directory: Create, Delete, Undelete

I am currently using the Change Notifications in Active Directory Domain Services in .NET as described in this blog. This will return all events that happen on an selected object (or in the subtree of that object). I now want to filter the list of events for creation and deletion (and maybe undeletion) events.
I would like to tell the ChangeNotifier class to only observe create-/delete-/undelete-events. The other solution is to receive all events and filter them on my side. I know that in case of the deletion of an object, the atribute list that is returned will contain the attribute isDeleted with the value True. But is there a way to see if the event represents the creation of an object? In my tests the value for usnchanged is always usncreated+1 in case of userobjects and both are equal for OUs, but can this be assured in high-frequency ADs? It is also possible to compare the changed and modified timestamp. And how can I tell if an object has been undeleted?
Just for the record, here is the main part of the code from the blog:
public class ChangeNotifier : IDisposable
{
static void Main(string[] args)
{
using (LdapConnection connect = CreateConnection("localhost"))
{
using (ChangeNotifier notifier = new ChangeNotifier(connect))
{
//register some objects for notifications (limit 5)
notifier.Register("dc=dunnry,dc=net", SearchScope.OneLevel);
notifier.Register("cn=testuser1,ou=users,dc=dunnry,dc=net", SearchScope.Base);
notifier.ObjectChanged += new EventHandler<ObjectChangedEventArgs>(notifier_ObjectChanged);
Console.WriteLine("Waiting for changes...");
Console.WriteLine();
Console.ReadLine();
}
}
}
static void notifier_ObjectChanged(object sender, ObjectChangedEventArgs e)
{
Console.WriteLine(e.Result.DistinguishedName);
foreach (string attrib in e.Result.Attributes.AttributeNames)
{
foreach (var item in e.Result.Attributes[attrib].GetValues(typeof(string)))
{
Console.WriteLine("\t{0}: {1}", attrib, item);
}
}
Console.WriteLine();
Console.WriteLine("====================");
Console.WriteLine();
}
LdapConnection _connection;
HashSet<IAsyncResult> _results = new HashSet<IAsyncResult>();
public ChangeNotifier(LdapConnection connection)
{
_connection = connection;
_connection.AutoBind = true;
}
public void Register(string dn, SearchScope scope)
{
SearchRequest request = new SearchRequest(
dn, //root the search here
"(objectClass=*)", //very inclusive
scope, //any scope works
null //we are interested in all attributes
);
//register our search
request.Controls.Add(new DirectoryNotificationControl());
//we will send this async and register our callback
//note how we would like to have partial results
IAsyncResult result = _connection.BeginSendRequest(
request,
TimeSpan.FromDays(1), //set timeout to a day...
PartialResultProcessing.ReturnPartialResultsAndNotifyCallback,
Notify,
request
);
//store the hash for disposal later
_results.Add(result);
}
private void Notify(IAsyncResult result)
{
//since our search is long running, we don't want to use EndSendRequest
PartialResultsCollection prc = _connection.GetPartialResults(result);
foreach (SearchResultEntry entry in prc)
{
OnObjectChanged(new ObjectChangedEventArgs(entry));
}
}
private void OnObjectChanged(ObjectChangedEventArgs args)
{
if (ObjectChanged != null)
{
ObjectChanged(this, args);
}
}
public event EventHandler<ObjectChangedEventArgs> ObjectChanged;
#region IDisposable Members
public void Dispose()
{
foreach (var result in _results)
{
//end each async search
_connection.Abort(result);
}
}
#endregion
}
public class ObjectChangedEventArgs : EventArgs
{
public ObjectChangedEventArgs(SearchResultEntry entry)
{
Result = entry;
}
public SearchResultEntry Result { get; set; }
}
I participated in a design review about five years back on a project that started out using AD change notification. Very similar questions to yours were asked. I can share what I remember, and don't think things have change much since then. We ended up switching to DirSync.
It didn't seem possible to get just creates & deletes from AD change notifications. We found change notification resulted enough events monitoring a large directory that notification processing could bottleneck and fall behind. This API is not designed for scale, but as I recall the performance/latency were not the primary reason we switched.
Yes, the usn relationship for new objects generally holds, although I think there are multi-dc scenarios where you can get usncreated == usnchanged for a new user, but we didn't test that extensively, because...
The important thing for us was that change notification only gives you reliable object creation detection under the unrealistic assumption that your machine is up 100% of the time! In production systems there are always some case where you need to reboot and catch up or re-synchronize, and we switched to DirSync because it has a robust way to handle those scenarios.
In our case it could block email to a new user for an indeterminate time if an object create were missed. That obviously wouldn't be good, we needed to be sure. For AD change notifications, getting that resync right that would have some more work and hard to test. But for DirSync, its more natural, and there's a fast-path resume mechanism that usually avoids resync. For safety I think we triggered a full re-synchronize every day.
DirSync is not as real-time as change notification, but its possible to get ~30-second average latency by issuing the DirSync query once a minute.

SPListItem.Tasks always empty

I have a custom sharepoint (2007) list (named testlist) on which I attached a test workflow (built with sharepoint designer 2007 and named testwf), which only task defined in the 'Actions' section at 'Step 1' is to wait until april 2014.
When I add a new item to the testlist the testwf will start and, when I switch to the grid view, the item has the field "testwf" as running.
Now I need to access the workflow associated with the item and then "complete" this task via code by changing its status but, using the following code, I always get the item.Tasks list empty (but I can see that the internal variable m_allTaskListTasks has 1 element).
using (SPSite site = new SPSite("http://mysp"))
{
site.AllowUnsafeUpdates = true;
SPWeb web = site.OpenWeb();
web.AllowUnsafeUpdates = true;
foreach (SPList list in web.Lists)
{
if (list.Title != "testlist") continue;
foreach (SPListItem item in list.Items)
{
item.Web.AllowUnsafeUpdates = true;
if(item.Tasks.Count > 0)
//do work
}
}
}
Maybe I'm missing something...
I use this code to access my workflowtasks:
Guid taskWorkflowInstanceID = new Guid(item["WorkflowInstanceID"].ToString());
SPWorkflow workflow = item.Workflows[taskWorkflowInstanceID];
// now you can access the workflows tasks
SPTask task = workflow.Tasks[item.UniqueId];
Cross-posted question.
#petauro, have you made any headway on this? I can corroborate #moontear's answer based on the following code that I have used with success in the past:
...
// get workflow tasks for SPListItem object item
if (item != null && item.Workflows != null && item.Workflows.Count > 0)
{
try
{
var workflows = site.WorkflowManager.GetItemActiveWorkflows(item);
foreach (SPWorkflow workflow in workflows)
{
// match on some indentifiable attribute of your custom workflow
// the history list title is used below as an example
if (workflow.ParentAssociation.HistoryListTitle.Equals(Constants.WORKFLOW_HISTORY_LIST_TITLE))
{
var workflowTasks = workflow.Tasks;
if (workflowTasks != null && workflowTasks.Count > 0)
{
// do work on the tasks
}
}
}
}
catch
{
// handle error
}
}
...
While only slightly different from the code you posted in your latest comment, see if it helps.
Another minor point: are there multiple instances of lists titled "testlist" within your SPWeb? If not, why iterate over web.Lists? Just get the one list directly and avoid some superfluous CPU cycles: SPWeb.GetList()
You have to go differently about this. You need to get the workflow task list and retrieve your task from there and finish it.
First you would need to check whether a workflow is running on your item: if (item.Workflows > 0) from there you could iterate through all the workflow instances on the list item, get the SPWorkflowAssociation and the associated task and history list. From there you would only need to find the task you are looking for in the associated task list.

Update Child Collection with EF 4.3 - Losing association in DB

This is driving me nuts. I'm not sure what else to try. This is my latest attempt to update a list of objects within another object using EF 4.3.
The scenario is that a user has added a new Task to an Application that already has one task in its Tasks property. The Application is not attached to the DB context because it was retrieved in a prior logic/DB call. This is the class and property:
public class Application : EntityBase
{
public ObservableCollection<TaskBase> Tasks { // typical get/set code here }
}
This is my attempt to update the list. What happens is that the new Task gets added and the association correctly exists in the DB. However, the first task, that wasn't altered, has its association removed in the DB (its reference to the Application).
This is the Save() method that takes the Application that the user modified:
public void Save(Application newApp)
{
Application appFromContext;
appFromContext = this.Database.Applications
.Include(x => x.Tasks)
.Single(x => x.IdForEf == newApp.IdForEf);
AddTasksToApp(newApp, appFromContext);
this.Database.SaveChanges();
}
And this is the hooey that's apparently necessary to save using EF:
private void AddTasksToApp(Application appNotAssociatedWithContext, Application appFromContext)
{
List<TaskBase> originalTasks = appFromContext.Tasks.ToList();
appFromContext.Tasks.Clear();
foreach (TaskBase taskModified in appNotAssociatedWithContext.Tasks)
{
if (taskModified.IdForEf == 0)
{
appFromContext.Tasks.Add(taskModified);
}
else
{
TaskBase taskBase = originalTasks.Single(x => x.IdForEf == taskModified.IdForEf); // Get original task
this.Database.Entry(taskBase).CurrentValues.SetValues(taskModified); // Update with new
}
}
}
Can anyone see why the first task would be losing its association to the Application in the DB? That first task goes through the else block in the above code.
Next, I'll need to figure out how to delete one or more items, but first things first...
After continual trial and error, this appears to be working, including deleting Tasks. I thought I'd post this in case it helps someone else. I'm also hoping that someone tells me that I'm making this more complicated than it should be. This is tedious and error-prone code to write when saving every object that has a list property.
private void AddTasksToApp(Application appNotAssociatedWithContext, Application appFromContext)
{
foreach (TaskBase taskModified in appNotAssociatedWithContext.Tasks)
{
if (taskModified.IdForEf == 0)
{
appFromContext.Tasks.Add(taskModified);
}
else
{
TaskBase taskBase = appFromContext.Tasks.Single(x => x.IdForEf == taskModified.IdForEf); // Get original task
this.Database.Entry(taskBase).CurrentValues.SetValues(taskModified); // Update with new
}
}
// Delete tasks that no longer exist within the app.
List<TaskBase> tasksToDelete = new List<TaskBase>();
foreach (TaskBase originalTask in appFromContext.Tasks)
{
TaskBase task = appNotAssociatedWithContext.Tasks.Where(x => x.IdForEf == originalTask.IdForEf).FirstOrDefault();
if (task == null)
{
tasksToDelete.Add(originalTask);
}
}
foreach (TaskBase taskToDelete in tasksToDelete)
{
appFromContext.Tasks.Remove(taskToDelete);
this.Database.TaskBases.Remove(taskToDelete);
}
}

How can I get a list of possible transitions for a given role in a state machine workflow?

On a given state machine workflow, how do we find out the possible transitions for a given role. In my scenario, only certain roles have the permission to perform certain activities. I have to get that list. The helper class StateMachineWorkflowInstance isn't helpful here as it just returns all the possible transitions, ignoring the role of the actor.
Any help here would be appreciated.
Thanks,
Socratees.
Looks like there is no straight forward way to do this. I wrote this method based roughly on the solution at Ruurd Boeke's blog . I'm getting the list of possible events, and then looking if they can be executed by the user role. It's a work around, but still works fine.
public string[] GetTransistions(string strUser)
{
string[] strRoles = System.Web.Security.Roles.GetRolesForUser(strUser);
List<string> strActivity = new List<string>();
ReadOnlyCollection<WorkflowQueueInfo> queues = workflowInstance.GetWorkflowQueueData();
foreach (WorkflowQueueInfo info in queues)
{
if (!info.QueueName.Equals("SetStateQueue"))
{
foreach (string subscribedActivity in info.SubscribedActivityNames)
{
HandleExternalEventActivity heea = workflowInstance.GetWorkflowDefinition().GetActivityByName(subscribedActivity) as HandleExternalEventActivity;
#region check roles
if (heea.Roles != null)
{
foreach (WorkflowRole workflowRole in heea.Roles)
{
foreach (string strRole in strRoles)
{
if (workflowRole.Name.Equals(strRole))
{
strActivity.Add(heea.EventName);
//permissionLog += workflowRole.Name + " can perform " + heea.EventName + " Activity. ";
}
}
}
}
#endregion
}
}
}
return strActivity.ToArray();
}

Categories