Work Management service in SharePoint 2013: A Short Overview for Developers

  Editor’s note: The following post was written by SharePoint MVP Adis Jugo

Work Management service in SharePoint 2013: a short overview for developers

We all know the dilemma of having our tasks scattered all over the place: some are in Exchange (Outlook), some are in SharePoint tasks lists, and some are in Project Server. Taking care of all those tasks can be a challenge. That’s why Microsoft has introduced in SharePoint Server 2013 and SharePoint Online a new service application, called “Work Management Service Application” (WMA).

This service is colloquially well-known as “Task aggregation service”, and, for the end users, it is manifested through the Tasks menu in their personal sites. Users can find all their SharePoint, Project and Exchange tasks (if opted in), aggregated and sorted, under unified “My Tasks” experience. They can sort their tasks, “pin” them (mark them as important), and mark tasks as completed - directly in the aggregated task view. Special views of “Active tasks”, “Important and upcoming tasks” and “Completed tasks” are accessible within a click. All tasks updates are synced back to their originating systems – the task providers.

 

 

Providers and refreshes

Under the hood, the whole system is based on the model of “Task providers”. Task providers are systems from which the WMA can aggregate tasks:

 

At the moment, three tasks providers are supported: Microsoft SharePoint, Microsoft Exchange and Microsoft Project Server. This means, that the users will see the tasks from those three sources, in their “My Tasks” area. Adding own task providers in not supported at the moment. Different providers are actually very different underlying systems, and there is no easy, “unique” way to do the aggregation and synchronization. Microsoft was planning to include an official API and methodology for adding own providers, but it never made its way in production bits, and it seems that it got a bit off the agenda. Pity, since there is great demand for it – that is actually always the first question being asked, after users learn of existence of this service application.

 

WMA initiates the task sync with providers through so called “provider refreshes”. In the WMA lingo, a refresh is an initiation of the task aggregation (sync) process. For SharePoint and Microsoft Project, provider refreshes are triggered on demand. That means, there is no background process refreshing the tasks for the users in some regular time intervals. Refresh is triggered at that very moment when a user accesses her MyTasks section. A small spinning circle indicates that a refresh is in progress. After refresh is completed, tasks are “reloaded”, and user gets to see her tasks. There is an artificial limit of one refresh every five minutes, not to jeopardize the overall SharePoint server performace. This limit can be changed with PowerShell commands for SharePoint 2013 (there is no GUI for Work Management Application management). For SharePoint Online in Office 365, this limit of one refresh per five minuts cannot be changed.

Provider refresh for Exchange tasks is somewhat different, and it is based on a background timer job (“Work Management Synchronize with Exchange”) which actually does the sync. The job is initially set to run every minute.

Tasks are internally cached in a hidden list in user’s My Site. That means that all the tasks which provider refresh discovers, and eventually updates, are locally stored in the WmaAggregatorList_User in the MySite.

Locations

Providers look in so called “Locations” to get the updates for existing tasks, and to discover new tasks. Locations are different and specific for each provider. For SharePoint 2013 as task provider, for example, locations are task lists in different SharePoint sites.

How does the provider know where to look, where to find the task updates for the current user? There is a collection of so called “hints”, which tells to the provider – please go and look there. In the case of SharePoint, there is an event receiver on each tasks list (new “Tasks with timeline”, content type 171), which, whenever a task for a user is added, stores that list in the hints collection. During the provider refresh, SharePoint provider looks for the tasks in that list, and, if found caches the tasks, and the list. In that process, that task list becomes a known “location”, and the tasks for the user that belong to that list are displayed under that “location” in the MyTasks.

There are also tasks without a location, so called “personal tasks”. The “My Tasks” page inside user’s My Site, is meant to be a place where users can organize their personal tasks, which do not belong to any of the tasks lists in the SharePoint Sites, or Projects. Those tasks are stored only in the “WmaAggregatorList_User” list, and, if Exchange Sync is configured, they are pushed to the Exchange, and therefore accessible in Outlook, mobile devices, etc.

All tasks without location can be “promoted” to the location tasks, what means, they can be “pushed” to a SharePoint list or to Project Server. Once when such a task gets a location, and after it is promoted, its location cannot be changed anymore (tasks cannot be moved across the locations).

Now, this was just an introduction to the WMA concepts and lingo. For anyone who wants to know more how WMA works under the hood, there is a great white paper written by Marc Boyer from Microsoft, which covers all installation and configuration aspects of Work Management Service (bit.ly/1ioO2Zz).

Let’s see now, what can be done with WMA from a developers’ perspective.

What is in it for developers?

Even if there isn’t much developer documentation out there, WMA has one of the best and cleanest APIs in the SharePoint world, with equally good SSOM and CSOM implementations. Now, this is something that SharePoint developers have been asking all along – that CSOM APIs get more power – and Microsoft is delivering on its promise. A drawback here is that there are no REST endpoints for WMA API, so you are stuck with CSOM on the client side, for better or worse.

SSOM side API can be found in

Microsoft.Office.Server.WorkManagement .dll (in GAC)

CSOM side API can be found in

Microsoft.SharePoint.WorkManagement.Client .dll (in Hive15/ISAPI)

Since the both development models are actually parallel, the examples below will be in SSOM, but they can be 1:1 translated to CSOM side (the last example in this post will be CSOM).

Namespaces and classes

UserSettingsManager is the first thing you will want to initialize when working with task aggregation. It contains all of the user-related WMA information, like Locations, important Locations, timeline settings (how does the timeline looks like for a user), etc.

Microsoft.Office.Server.WorkManagement.UserSettingsManager usm = new UserSettingsManager(context);

//Get all locations
LocationClientCollection allLocations = usm.GetAllLocations();

UserOrderedSessionManager is used to manage WMA sessions for a user. You will need to pass a SPServiceContext (for SSOM), or ClientContext (for CSOM) to the UserOrderedSessionManager constructor, to initialize a session manager.

//Gets the session manager and create session
Microsoft.Office.Server.WorkManagement.UserOrderedSessionManager osm = new UserOrderedSessionManager(context);

Once you have your session manager, you can create a new WMA session. There are two kinds of sessions you can create: UserOrderedSession and LocationOrientedUserOrderedSession. They are for the most part the same, just that the LocationOrientedUserOrderedSession contains location info within the tasks, and this is the type of session you will usually want to use in most of the cases.

 

LocationOrientedUserOrderedSession uos = osm.CreateLocationOrientedSession();

This session object will be your main tool for executing task and provider refresh related actions. You will be able to create, edit, and delete tasks, to start a provider refresh, or Exchange sync, and a whole lot of the other cool things that can be done with tasks. Let’s take a look at some of them.

Querying the tasks

When you have a session manager object, you can retrieve all the tasks for a user, or filter the tasks to get only those that you need. There are seven possible filters that you can apply when querying the tasks:

  • CustomAttributeFilter
  • FieldFilter
  • KeywordFilter
  • LastModifiedDateRangeFilter
  • LocationFilter
  • PinnedFilter
  • PrivacyFilter

Most of them are self-explanatory. You can filter the tasks by custom attributes (which you can freely assign to each task, although only programmatically), by standard fields, by keywords, by last modified info, location, is task pinned (important) and task privacy. Of course, the filters can be freely combined.

It is often a requirement to get only those tasks for a user, which are stored in one SharePoint site and its subsites. Using the locations collection, and location filter, this becomes very easy to implement:

// Create a new task query

Microsoft.Office.Server.WorkManagement.TaskQuery tq = new TaskQuery();

//Get all locations which are under a specified URL
IEnumerable<Location> myLocations = allLocations.Where(a => a.Url.Contains("https://demo/sites/subsite"));
Location[] locationKeys = myLocations.ToArray();

// Create location filter
LocationFilter locationFilter = new LocationFilter() { IncludeUncategorizedTasks = false, LocationKeys = locationKeys };

tq.LocationFilter = locationFilter;

// Read filtered tasks from the task session
LocationGroupClientCollection tcc = uos.ReadTasks(tq);

 

This way, we got all the SharePoint tasks for a user, which are stored in all task lists in the https://demo/sites/subsite site, or in one of its subsites.

Creating a task

To create a task, we will use the session object created above. When we create a task through Work Management Application, it is initially always a personal task, stored only in the task cache list in user’s My Site. Even if we are able to set a task location when creating a task, that task is still only a personal task.

There are two overloads of the CreateTask() method, one with, and one without location. Setting the location to null, we can create the task without location even with the location-aware overload:

TaskWriteResult taskWriteResult = uos.CreateTask("My task name", "My task description", "03/24/2014", "03/27/2014", isCompleted, isPinned, "", locationId);

Task myNewTask = taskWriteResult.Result;

With those two lines, we create a task with a name, description, start date (localized string), due date (localized string), booleans that define if the task is completed and pinned, task edit link (this would be interesting if we would have our own task providers), and the location id (nullable integer).

As mentioned above, this task is still a personal task, even if there is a location info associated with it.

To “promote” this task to its location (basically, to push it to the associated SharePoint list, Project etc), we will use one of the two overloads of the Promote method. If location information has already been associated with the task, this will suffice:

taskWriteResult = uos.PromotePersonalTaskToProviderTask(myNewTask.Id);

If there was no location information, we can set it during the task promotion:

taskWriteResult = uos.PromotePersonalTaskToProviderTaskInLocation(myNewTask.Id, locationId);

Now, this task can be found in its new location as well, and the location information cannot be changed anymore.

More task operations

Once we have a task, there are numerous task related methods within the Session object. We can, for example, pin and unpin tasks to the timeline (equals to the “important” tasks in Outlook, or high priority tasks in other systems):

taskWriteResult = uos.PinTask(myTask.Id);
taskWriteResult = uos.RemovePinOnTask(myTask.Id);

Editing tasks can be done via UpdateTaskWithLocalizedValue method. There is an enumeration WritableTaskField, that defines which task fields can be updated. This is important, since tasks are coming from different providers, which might have different task properties (SharePoint tasks are not the same as Exchange tasks), and those properties are basically the least common denominator, which can be found in each task provider. Those are:

  • Title
  • Description
  • StartDate
  • DueDate
  • IsCompleted
  • EditUrl

Please note IsCompleted property here: by updating it, you can set the task status to “completed” (100% task progress in some providers), or not completed (0% progress).

This line of code would update the task description:

uos.UpdateTaskWithLocalizedValue(myNewTask.Id, WritableTaskField.Description, “New Description”);

This line of code sets the task to the “completed” status:

uos.UpdateTaskWithLocalizedValue(myNewTask.Id, WritableTaskField.IsCompleted, “true”);

Deleting task is also quite simple, even if the word “deleting” might not be a right expression here. The method DeleteTask actually removes the task from your MySite timeline:

TaskWriteResult twr = uos.DeleteTask(myNewTask.Id);

For personal tasks, which live only inside the timeline, this is ok – they are really deleted. But the provider tasks are not – only their cached copy is removed from the timeline, and the tasks can still be found in the respective SharePoint lists and Project tasks. If you need to delete them from there, you will need to call respective provider specific APIs.

Working with provider refreshes

As mentioned above, a provider refresh is a process of retrieving and caching tasks for a user. It is done on demand, with no background job processing the tasks. Refresh will be started at that moment, when a user access its “My Tasks” area. There is an artificial limit of 5 minutes between provider refreshes, which can be changed through PowerShell for SharePoint on premise.

Provider refresh can also be initialized programmatically, from the task session:

CreateRefreshJobResult refreshResult = uos.BeginCacheRefresh();

The resulting CreateRefreshJobResult will tell us if the provider refresh process has started successfully (it will be probably not yet completed). If the provider refresh process could not be started from some reason, CreateRefreshJobResult will hold the error information and it’s Correlation Id (mostly caused by the imposed 5 minutes limit).

If CreateRefreshJobResult was executed successfully, it will contain an Id of the newly created provider refresh process, which can be used to check the provider refresh job status. You can check the general status, the status for each task provider separately, and update status for each task location separately.

This short code snippet explains it all:

RefreshResult rr = uos.GetRefreshStatus(jobId);

Console.WriteLine("Refresh state: " + rr.AggregatorRefreshState.ToString());

Console.WriteLine("Corellation id: " + rr.CorrelationId);

Console.WriteLine("Refresh finished: " + rr.RefreshFinished.ToShortDateString() + " " + rr.RefreshFinished.ToShortTimeString());

 

Console.WriteLine("");

Console.WriteLine("Provider statuses:");

Console.WriteLine("------------------");

foreach (ProviderRefreshStatus prs in rr.ProviderStatuses)

{

Console.WriteLine("Provider key: " + prs.ProviderKey);

Console.WriteLine("Provider name: " + prs.ProviderLocalizedName);

Console.WriteLine("Root location id: " + prs.RootLocationId.ToString());

Console.WriteLine("Provider Refresh started: " + prs.RefreshStarted.ToShortDateString() + " " + prs.RefreshStarted.ToShortTimeString());

Console.WriteLine("Provider finished: " + prs.RefreshFinished.ToShortDateString() + " " + prs.RefreshFinished.ToShortTimeString());

Console.WriteLine("");

}

 

Console.WriteLine("");

Console.WriteLine("Location update results:");

Console.WriteLine("------------------------");

foreach (LocationUpdateResult lur in rr.TaskChangesByLocation)

{

Location loc = allLocations.Where(a => a.Id == lur.RootLocationId).FirstOrDefault();

 

Console.WriteLine("Location: " + lur.RootLocationId);

Console.WriteLine("Added: " + lur.AddedCount.ToString());

Console.WriteLine("Active added: " + lur.ActiveAddedCount.ToString());

Console.WriteLine("Removed: " + lur.RemovedCount.ToString());

Console.WriteLine("Updated: " + lur.UpdatedCount.ToString());

}

The result from this code snippet will look like the screenshot below. We can see, that there is only one provider active (SharePoint, in this case), and that 3 tasks have been updated in the location with id 3:

 

You can retrieve a provider refresh history for a user, with all provider refreshes from some time interval until now.

Those few lines of code will enable you to check refresh status for the past 7 days, and to analyze potential problems – for each unsuccessful provider refresh, Correlation Id associated with the refresh status, for the further analysis.

RefreshHistory rh = uos.GetRefreshHistory(DateTime.Now.AddDays(-7));

 

foreach (RefreshResult rr in rh.Refreshes)

{

        // Check refresh status

}

And on the client side?

I have mentioned already that the CSOM side have been implemented with almost the same methods as the server side. To finish this blog post with a goodie, I will create a Windows 8 store app, with a Windows Runtime Component which will reference the Work Management APIs (“Microsoft.SharePoint.WorkManagement.Client.dll” from Hive15/ISAPI), and fetch the task data.

You will, of course, need a ClientContext to create UserSettingsManager (instead of SPServiceContext which was used within the server side model).

ClientContext context = new ClientContext("https://server");

 

//user settings manager

Microsoft.SharePoint.WorkManagement.Client.UserSettingsManager usm = new Microsoft.SharePoint.WorkManagement.Client.UserSettingsManager(context);

 

//get all locations from usm

Microsoft.SharePoint.WorkManagement.Client.LocationClientCollection locations = usm.GetAllLocations();

context.Load(locations);

 

//user ordered session manager

Microsoft.SharePoint.WorkManagement.Client.UserOrderedSessionManager osm = new Microsoft.SharePoint.WorkManagement.Client.UserOrderedSessionManager(context);

 

//location oriented session

Microsoft.SharePoint.WorkManagement.Client.LocationOrientedUserOrderedSession uos = osm.CreateLocationOrientedSession();

 

//task query

Microsoft.SharePoint.WorkManagement.Client.TaskQuery tq = new Microsoft.SharePoint.WorkManagement.Client.TaskQuery(context);

 

//read tasks

Microsoft.SharePoint.WorkManagement.Client.LocationGroupClientCollection tcc = uos.ReadTasks(tq);

 

//batching done, client execute

context.Load(tcc);

context.ExecuteQuery();

 

//iterate through results

List<Microsoft.SharePoint.WorkManagement.Client.Task> allTasks = tcc.ToList<Microsoft.SharePoint.WorkManagement.Client.Task>();

 

List<SpTask> tasks = new List<SpTask>();

foreach (Microsoft.SharePoint.WorkManagement.Client.Task task in allTasks)

{

//iterate through results, create an IEnumerable that can be bound to the View

}

 

The task data will be bound to an items page in the C# Windows Store app. This will work with both SharePoint on premise, and with Office 365. The result could look like:

 

I hope that this short walkthrough the Work Management Service Application development model, could give you a brief overview of the things that can be done. I deeply feel that WMA is somehow neglected in the SharePoint 2013 and Office 365, although it offers some great user scenarios.

You can find out more about Work Management Service application development model at my blog https://adisjugo.com, or, if you have specific questions, feel free drop me a direct message at twitter handle @adisjugo. 

 

About the author

Adis Jugo is a software architect with over 20 years of experience, and a Microsoft MVP for SharePoint server. He first met SharePoint (and Microsoft CMS server) back in 2002, and since 2006 his focus was completely shifted towards architecture and development of solutions based on the SharePoint technology. Adis is working as a Head of Development for deroso Solutions, a global consulting company with headquarters in Germany. He is an internationally recognized speaker with over 10 years of speaker experience, speaking at various Microsoft, Community and SharePoint conferences worldwide, where he is often awarded for being one of the top speakers. Adis is also one of the initiators and organizers of the annual SharePoint and Project Conference Adriatics.

You contact Adis on Twitter at @adisjugo, and visit his SharePoint architecture, development and governance blog at https://www.adisjugo.com.

The MVP Monday Series is created by Melissa Travers. In this series we work to provide readers with a guest post from an MVP every Monday. Melissa is a Community Program Manager, formerly known as MVP Lead, for Messaging and Collaboration (Exchange, Lync, Office 365 and SharePoint) and Microsoft Dynamics in the US. She began her career at Microsoft as an Exchange Support Engineer and has been working with the technical community in some capacity for almost a decade. In her spare time she enjoys going to the gym, shopping for handbags, watching period and fantasy dramas, and spending time with her children and miniature Dachshund. Melissa lives in North Carolina and works out of the Microsoft Charlotte office.