Introduction
One of the most exciting features included in SharePoint 2007 is workflow support and the possibility of developing our own custom workflows (for more information about workflow development in the SharePoint 2007 platform please click here).
While it is possible to develop workflows to automate a series of activities without any human intervention, the full power of workflows in the SharePoint platform can only be achieved with human-oriented workflows. These workflows are characterized by creating and assigning tasks to users that must complete them in order to fulfill the purpose of the workflow. Some examples of out of the box human-oriented workflows that are included with MOSS 2007 are Approval, Collect Feedback and Issue Tracking.
In the SharePoint world, the most common usage of human-oriented workflows is in the context of document libraries, to perform some work when a new document is uploaded into a document library. Typically, in such scenario, tasks are created and assigned to users throughout the workflow lifetime that will complete them until the workflow purpose has been fulfilled. Having the workflow goal to be achieved is the desired and most common scenario, but unexpected actions may occur before the end of the workflow such as a user to delete the document. The following example will show you how to handle this situation in a custom workflow by using the OnWorkflowItemDeleted activity.
Example
The OnWorkflowItemDeleted activity can be used within a custom workflow to handle an item deletion in any SharePoint list. In this example, this activity will be used to delete all uncompleted tasks associated with the deleted item. After dragging and dropping the OnWorkflowItemDeleted activity into the workflow designer, the Invoke property must be set to the event handler that will perform the work.
For the purpose of this example, please consider the simple approval workflow in the following image:
In this workflow, a parallel activity with two branches is used. One contains the main workflow logic while the other contains a OnWorkflowItemDeleted activity that will handle the item deletion. The Invoke event property was set to be handled by an event handler method called OnWorkflowItemDeleted_Invoked. Let's take a look at the code:
public sealed partial class ApprovalWorkflow : SequentialWorkflowActivity
{
/// <summary>
/// Handles the workflow item deleted event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnWorkflowItemDeleted_Invoked(object sender, ExternalDataEventArgs e)
{
//delete uncompleted tasks when
//an item is deleted
SPWorkflow workflowInstance =
workflowProperties.Workflow;
SPWorkflowTaskCollection taskCollection =
GetWorkflowTasks(workflowInstance);
for (int i = taskCollection.Count; i > 0; i--)
{
SPWorkflowTask task =
taskCollection[i - 1];
using (SPWeb web =
workflowProperties.Web)
{
if (task[SPBuiltInFieldId.TaskStatus]
.ToString() != SPResource.GetString
(new CultureInfo((int)web.Language, false),
"WorkflowTaskStatusComplete", new object[0]))
{
task.Delete();
}
}
}
}
/// <summary>
/// Reads the workflow tasks. This method
/// is implemented because the Tasks property
/// of the SPWorkflow instance takes a
/// while to be populated.
/// </summary>
public static SPWorkflowTaskCollection
GetWorkflowTasks(SPWorkflow workflowInstance)
{
SPWorkflowTaskCollection taskCollection = null;
bool tasksPopulated = false;
while (!tasksPopulated)
{
try
{
taskCollection = workflowInstance.Tasks;
tasksPopulated = true;
}
catch { }
}
return taskCollection;
}
}
In the previous example, the following actions are being performed:
- In the OnWorkflowItemDeleted_Invoked event handler, the workflow instance task collection is obtained by calling the GetWorkflowTasks method;
- The task collection is iterated and the each task status is checked to verify if it is still uncompleted;
- If so, the task is deleted.
Introduction
One of the areas where the SharePoint 2007 platform (both WSS 3.0 and MOSS 2007) still needs to improve is deployment despite all the major improvements made when compared to its antecessor, the 2003 version. One of the faults I ran recently into is related with updating list content types event handlers when site content types event handlers are updated.
For those who don't know, let's start by a brief introduction about content types and explain the difference between a site content type and a list content type. Content types are a new concept introduced in SharePoint 2007 and is basically a reusable collection of settings that can be applied to a certain category of content. Content types basically define the metadata (they are composed by a reusable set of columns called site columns) and behaviors (event handlers, workflows, etc) of a certain category of content. They are stored centrally and are reusable throughout a site or site collection depending on its scope. For more information about content types please click here.
So what is the difference between a site content type and a list content type? Probably, the easiest way to explain this is to think of site content types as templates (in the sense that they are reusable) and list content types as instances of those templates. Site content types are defined at the site level and are visible to the site where they are created and all its subsites (this concept is called content type scope) and they are reusable and not bound to any specific list, meaning that they can be associated with any list within the sites under its scope. When a site content type is associated with a list, a copy of the site content type is copied into the list, creating a new instance. This instance is called a list content type.
Example
The best approach to deploy content types is to use features (for more information about deploying site content types as a feature please click here). Registering content type event handlers can also be achieved using a feature (check this article for more information) or by using Patrick Tisseghem's Event Handler Explorer. The problem is when there are already list content types based on a site content type and we need to add a new event handler to it and have all list content types also updated. In my experience, both using the feature model approach or using Patrick's application didn't update the list content types. The remainder of this post will show you how to do this programmatically.
The following example basically uses a console application to register a event handler and associate it to a content type.
class Program
{
private const string URL = "http://myIntranet";
private const string CONTENT_TYPE = "MyContentType";
private const string EVENT_RECEIVER_NAME = "MyEventReceiver";
private const string EVENT_RECEIVER_ASSEMBLY =
"MyEventReceivers.MyEventReceiver, MyEventReceiver, " +
"Version=1.0.0.0, Culture=neutral, PublicKeyToken=5989549632a535cf";
private const string EVENT_RECEIVER_CLASS_NAME =
"MyEventReceivers.MyEventReceiver";
static void Main(string[] args)
{
//open site collection
using (SPSite siteCollection = new SPSite(URL))
{
using (SPWeb site = siteCollection.OpenWeb())
{
//get content type
SPContentType contentType =
site.ContentTypes[CONTENT_TYPE];
// add ItemUpdating event handler
ContentTypeHelper.AddEventHandler(contentType,
EVENT_RECEIVER_NAME, EVENT_RECEIVER_ASSEMBLY,
EVENT_RECEIVER_CLASS_NAME, 10000,
SPEventReceiverInstallerType.ItemUpdating);
site.Update();
}
}
}
}
internal class ContentTypeHelper
{
/// <summary>
/// Register an event handler in a list
/// </summary>
/// <param name="contentType">SharePoint Content Type</param>
/// <param name="receiverName">Event receiver name</param>
/// <param name="className">Event handler class name</param>
/// <param name="sequenceNumber">Sequence number</param>
public static void AddEventHandler(SPContentType contentType,
string receiverName, string assemblyName,
string className, Int32 sequenceNumber,
SPEventReceiverInstallerType eventReceiverInstallerType)
{
//get assembly full name
string assemblyFullName = System.Reflection.Assembly
.GetAssembly(Type.GetType(assemblyName)).FullName;
// remove existing event handlers
RemoveEventHandlers(contentType, assemblyFullName);
SPEventReceiverDefinition receiverDefinition = null;
//ItemAdding event handler
if (SPEventReceiverInstallerTypeHelper.CheckOption
(eventReceiverInstallerType,
SPEventReceiverInstallerType.ItemAdding))
{
// add ItemAdding event handler
receiverDefinition = contentType.EventReceivers.Add();
receiverDefinition.Name = receiverName;
receiverDefinition.Type =
SPEventReceiverType.ItemAdding;
receiverDefinition.Assembly = assemblyFullName;
receiverDefinition.Class = className;
receiverDefinition.SequenceNumber = sequenceNumber;
receiverDefinition.Update();
}
//ItemAdded event handler
if (SPEventReceiverInstallerTypeHelper.CheckOption
(eventReceiverInstallerType,
SPEventReceiverInstallerType.ItemAdded))
{
// add ItemAdded event handler
receiverDefinition = contentType.EventReceivers.Add();
receiverDefinition.Name = receiverName;
receiverDefinition.Type =
SPEventReceiverType.ItemAdded;
receiverDefinition.Assembly = assemblyFullName;
receiverDefinition.Class = className;
receiverDefinition.SequenceNumber = sequenceNumber;
receiverDefinition.Update();
}
//ItemUpdating event handler
if (SPEventReceiverInstallerTypeHelper.CheckOption
(eventReceiverInstallerType,
SPEventReceiverInstallerType.ItemUpdating))
{
// add ItemUpdating event handler
receiverDefinition = contentType.EventReceivers.Add();
receiverDefinition.Name = receiverName;
receiverDefinition.Type =
SPEventReceiverType.ItemUpdating;
receiverDefinition.Assembly = assemblyFullName;
receiverDefinition.Class = className;
receiverDefinition.SequenceNumber = sequenceNumber;
receiverDefinition.Update();
}
//ItemUpdated event handler
if (SPEventReceiverInstallerTypeHelper.CheckOption
(eventReceiverInstallerType,
SPEventReceiverInstallerType.ItemUpdated))
{
// add ItemUpdating event handler
receiverDefinition =contentType.EventReceivers.Add();
receiverDefinition.Name = receiverName;
receiverDefinition.Type =
SPEventReceiverType.ItemUpdated;
receiverDefinition.Assembly = assemblyFullName;
receiverDefinition.Class = className;
receiverDefinition.SequenceNumber = sequenceNumber;
receiverDefinition.Update();
}
//ItemCheckingIn event handler
if (SPEventReceiverInstallerTypeHelper.
CheckOption(eventReceiverInstallerType,
SPEventReceiverInstallerType.ItemCheckingIn))
{
// add ItemCheckingIn event handler
receiverDefinition = contentType.EventReceivers.Add();
receiverDefinition.Name = receiverName;
receiverDefinition.Type =
SPEventReceiverType.ItemCheckingIn;
receiverDefinition.Assembly = assemblyFullName;
receiverDefinition.Class = className;
receiverDefinition.SequenceNumber = sequenceNumber;
receiverDefinition.Update();
}
//ItemCheckedIn event handler
if (SPEventReceiverInstallerTypeHelper.CheckOption
(eventReceiverInstallerType,
SPEventReceiverInstallerType.ItemCheckedIn))
{
// add ItemCheckedIn event handler
receiverDefinition = contentType.EventReceivers.Add();
receiverDefinition.Name = receiverName;
receiverDefinition.Type =
SPEventReceiverType.ItemCheckedIn;
receiverDefinition.Assembly = assemblyFullName;
receiverDefinition.Class = className;
receiverDefinition.SequenceNumber = sequenceNumber;
receiverDefinition.Update();
}
//update content type - setting the updateChildren
//parameter to true will update list content types
contentType.Update(true);
}
/// <summary>
/// Remove an event handler registration from a list.
/// </summary>
/// <param name="list">SharePoint list</param>
/// <param name="receiverClass">Event receiver class name</param>
internal static void RemoveEventHandlers(
SPContentType contentType,
string receiverClass)
{
SPEventReceiverDefinition receiverDefinition = null;
// cycle through all the receiver definitions and
//delete the receiver with the supplied class name
for (int i = contentType.EventReceivers.Count - 1;
i >= 0; i--)
{
receiverDefinition = contentType.EventReceivers[i];
if (receiverDefinition.Class == receiverClass)
{
receiverDefinition.Delete();
}
}
}
}
The statement that does the trick is "contentType.Update(true);" in the end of the AddEventHandler method. Setting the updateChildren parameter of this method to true will update list content types.
Introduction
This blog post will show you how to check if a certain workflow instance is completed in a SharePoint list. In this example, let's assume that the default "Documents" document library in a team site is configured with a Collect Feedback workflow and that a new instance is started every time a new document is added to the library. The workflow is configured with 2 reviewers. My purpose is to start a custom workflow (associated with a custom workflow) on each document every time all reviewers have reviewed them, i.e., every time a Collect Feedback workflow instance is completed.
Example
The following example shows how the previously described scenario can be implemented. One first note is that when a workflow instance gets completed on a list item (thus changing the workflow status of the item), the item is not updated. This means that the obvious solution that would be to have an event handler running on the document library to detect the completion of the workflow for each document can't be implemented. The following example uses an alternative approach that makes use of an event handler associated with the "Task" content type, that is used on workflow tasks lists. The event handler will run every time a task is updated and will get the task associated list item (the document) and start a custom workflow on the document when its Collect Feedback workflow instance gets completed. Let's take a look at the code:
/// <summary>
/// Event handler associated with the "Task" content types.
/// </summary>
public class WorkflowCompletionCheckerEventReceiver : SPItemEventReceiver
{
//custom content type name
private const string CUSTOM_CONTENT_TYPE_NAME =
"My Custom Content Type";
//task list workflow list id field
public const string TASK_LIST_WORKFLOW_LIST_ID_FIELD_NAME =
"ows_WorkflowListId";
//task list workflow item id field
public const string TASK_LIST_WORKFLOW_ITEM_ID_FIELD_NAME =
"ows_WorkflowItemId";
//collect feedback workflow id
public const string COLLECT_FEEDBACK_WORKFLOW_ID =
"46c389a4-6e18-476c-aa17-289b0c79fb8f";
//custom workflow id
public const string CUSTOM_WORKFLOW_ID =
"96be29b0-1be4-4b99-a703-656e5470962b";
/// <summary>
/// Event handler for item updated event.
/// </summary>
/// <param name="properties"></param>
public override void ItemUpdated(SPItemEventProperties properties)
{
//start a custom workflow on a list item when a
//collect feedback workflow gets completed
StartCustomWorkflowOnTaskItemCompletion(properties);
}
/// <summary>
/// Checks if the "Collect Feedback" workflow
/// instance on an item is completed. This method checkes a
/// workflow task and verifies if they have been created by a
/// "Collect Feedback" workflow and if the associated item is of
/// my custom content type. If yes, it verifies if the workflow has
/// been completed and if complete, starts a custom workflow
/// on the associated item.
/// </summary>
/// <param name="properties"></param>
private void StartCustomWorkflowOnTaskItemCompletion
(SPItemEventProperties properties)
{
//get the task item
SPListItem listItem = properties.ListItem;
if (listItem != null)
{
//get associated item id and associated list id
//list id - ows_WorkflowListId field
object associatedWorkflowListId =
listItem[TASK_LIST_WORKFLOW_LIST_ID_FIELD_NAME];
//item id - ows_WorkflowItemId field
object associatedWorkflowItemId =
listItem[TASK_LIST_WORKFLOW_ITEM_ID_FIELD_NAME];
//get associated item
if (associatedWorkflowItemId != null
&& associatedWorkflowListId != null)
{
//get task associated list item using
//its list id and item id
SPListItem associatedListItem = listItem.Web.Lists
.GetList(new Guid(associatedWorkflowListId.ToString()), false)
.GetItemById(int.Parse(associatedWorkflowItemId.ToString()));
//check if item is of my custom content type
if (associatedListItem.ContentType.Name
.Equals(CUSTOM_CONTENT_TYPE_NAME))
{
//check if item has any workflow association
if (associatedListItem.Workflows.Count > 0)
{
//iterate workflow associations and lookup
//the collect feedback association
foreach (SPWorkflow workflow in
associatedListItem.Workflows)
{
Guid collectFeedbackWorkflowId =
new Guid(COLLECT_FEEDBACK_WORKFLOW_ID);
//check if collect feedback workflow
//instance is completed
if (workflow.ParentAssociation.BaseId
== collectFeedbackWorkflowId
&& workflow.InternalState
== SPWorkflowState.Completed)
{
//if collect feedback workflow instance is
//completed, start custom workflow that is
//associated with the item
StartContentTypeWorkflow(associatedListItem,
CUSTOM_WORKFLOW_ID);
}
}
}
}
}
}
}
/// <summary>
/// Starts a workflow associated to a content type programmatically.
/// </summary>
/// <param name="properties"></param>
public static void StartContentTypeWorkflow(SPListItem listItem,
string workflowId)
{
//start specified workflow that is associated with the item
SPWorkflowManager manager = listItem.Web.Site.WorkflowManager;
//get list item content type
SPContentType contentType = listItem.ContentType;
//get list item workflow associations
SPWorkflowAssociationCollection associationCollection =
contentType.WorkflowAssociations;
//iterate all item workflow associations
foreach (SPWorkflowAssociation association in associationCollection)
{
Guid workflowGuid = new Guid(workflowId);
//check if current workflow association matches
//the specified workflow
if (association.BaseId == workflowGuid)
{
string data = association.AssociationData;
//start workflow
SPWorkflow wf = manager.StartWorkflow(listItem, association,
data, false);
}
}
}
}
In the previous example, the following actions are being performed:
- The ItemUpdated event in the event handler is being handled;
- In the event handler, a reference to the task is obtained;
- The associated list item is obtained using two columns of the task list item:
- "ows_WorkflowListId" - holds the id of the list where the associated list item is created;
- "ows_WorkflowItemId" - holds the id of the associated list item;
- The content type of the associated list item is checked for a match to my custom content type;
- If there is a match, the list item workflow associations are checked for a match with the Collect Feedback workflow;
- If there is a match and the workflow instance is completed, the custom workflow is started.
This blog post will show you how to create an expression box in an InfoPath 2007 form whose value is based on the result of an conditional sum. Consider the following InfoPath form template:
The previous image shows a simple expense report. To support the introduction of the expense information, a Repeating Table control is used with three columns:
- Expense - Expense Description. A simple Text Box control;
- Value - Expense value. A simple Text Box control;
- Expense Type - Drop-down List Box control that allows 4 expense types: Food, Land Travel, Air Travel and Parking.
Below the repeating table there are 5 expression boxes that show the total amount of the expense report and the total amount for each expense type. The total amount expression box is based on a simple sum expression and each of the expense type expression boxes are based on conditional sums filtered by the value of the expense type Drop-down List Box control. The expressions used for each expense type are the following:
| Expression Box | Expression | XPath Expression |
| Expense Total | sum(expensevalue) | sum(my:accounting/my:expensevalue) |
| Food Expense Total | sum(accounting[expensetype = "Food"]/expensevalue) | sum(my:accounting[my:expensetype = "Food"]/my:expensevalue) |
| Land Travel Expense Total | sum(accounting[expensetype = "Land Travel"]/expensevalue) | sum(my:accounting[my:expensetype = "Land Travel"]/my:expensevalue) |
| Air Travel Expense Total | sum(accounting[expensetype = "Air Travel"]/expensevalue) | sum(my:accounting[my:expensetype = "Air Travel"]/my:expensevalue) |
| Parking Expense Total | sum(accounting[expensetype = "Parking"]/expensevalue) | sum(my:accounting[my:expensetype = "Parking"]/my:expensevalue) |
As the previous table shows, the conditional sum expressions for each expense type are relatively simple and pretty straightforward for those who are familiarized with XPath, since XPath syntax is used for each expression. This comes as no surprise since the underlying data of the InfoPath form is stored in XML. The following image shows an expense report example filled with some sample values:

When creating a new custom workflow project using Visual Studio 2008, a strong key file (.snk) file is automatically included in the project to sign the workflow assembly. A few days ago, I started developing a SharePoint 2007 Sequential Workflow project. After finishing developing the workflow, I deployed it and started testing it by associating the workflow to a content type (Site Settings > Site Content Type Gallery > Site Content Type > Workflow settings). I then noticed that the strong key file that was being used to sign the workflow assembly wasn't the one I usually use and modified the project settings so that it started using it. After recompiling the project and redeploying the workflow, I started to get an error message: "New instances of this workflow template are currently disallowed". The reason for this message to appear is that a new version version of the workflow had been deployed (same assembly with a different public key token due to the change in the strong key file used to sign the assembly). In these situations, the existing workflow associations made from older versions of the workflow are automatically set to "No New Instances", meaning that new instances of the workflow for those workflow associations are not allowed. The image below shows the Remove Workflow page with my custom workflow association set to "No New Instances". To access this page, go to Site Settings > Site Content Type Gallery > Site Content Type > Workflow settings > Remove Workflows if the workflow is associated with a content type.
To solve the problem, all you have to do is to set the workflow association to "Allow" and new instances of the workflow can again be created.
Introduction
This blog post will show you how to start a workflow programmatically every time an item is updated in a SharePoint list. For those who don’t know, Windows Workflow Foundation (WF) is the new engine for building custom workflows (you can find more information about WF on the official web site). It was released as part of the .NET Framework 3.0 along with Windows Presentation Foundation (WPF) and Windows Communication Foundation (WCF). In the SharePoint world, workflows can be associated both to a list and to a content type. Both options are valid and the choice for one or the other option depends on your needs:
- If you want to run a workflow in a list, independently of the content type of each list item, you should associate the workflow to a list. If you have more than one content type associated to the list, care should be taken in the workflow implementation since each content type can have different columns that may not be present in the other content types;
- If you want to run the workflow on all items of a specific content type, you should associate the workflow to a content type. The workflow will be associated to all list items from that content type in all lists across a site or the whole site collection (depending on the scope of the workflow, typically defined in its deployment feature) that are associated with the content type.
Example
The following example shows how both options can be addressed. This example uses an event handler and starts a workflow every time an item is updated in a SharePoint list.
public class ProgrammaticWorkflowInitiationEventReceiver : SPItemEventReceiver
{
/// <summary>
/// Event handler for item updated event.
/// </summary>
/// <param name="properties"></param>
public override void ItemUpdated(SPItemEventProperties properties)
{
//use this method if the workflow is associated to a list
StartListWorkflow(properties);
//use this method if the workflow is associated to a content type
StartContentTypeWorkflow(properties);
}
/// <summary>
/// Starts a workflow associated to a list programmatically.
/// </summary>
/// <param name="properties"></param>
private void StartListWorkflow(SPItemEventProperties properties)
{
//get list item from event handler properties
SPListItem listItem = properties.ListItem;
using (SPWeb web = listItem.Web)
{
using (SPSite site = web.Site)
{
//obtain an instance of SPWorkflowManager
//which will be later used to start the workflow
SPWorkflowManager manager = site.WorkflowManager;
//get item's parent list
SPList parentList = listItem.ParentList;
//get all workflows that are associated with the list
SPWorkflowAssociationCollection associationCollection =
parentList.WorkflowAssociations;
//lookup and start the worflow
LookupAndStartWorkflow(listItem, manager,
associationCollection, "FDEDCC37-4D8A-44ba-8E22-57B2669A887C");
}
}
}
/// <summary>
/// Starts a workflow associated to a content type programmatically.
/// </summary>
/// <param name="properties"></param>
private void StartContentTypeWorkflow(SPItemEventProperties properties)
{
//get list item from event handler properties
SPListItem listItem = properties.ListItem;
using (SPWeb web = listItem.Web)
{
using (SPSite site = web.Site)
{
//obtain an instance of SPWorkflowManager
//which will be later used to start the workflow
SPWorkflowManager manager = site.WorkflowManager;
//get item's content type
SPContentType contentType = listItem.ContentType;
//get all workflows that are associated with the content type
SPWorkflowAssociationCollection associationCollection =
contentType.WorkflowAssociations;
//lookup and start the worflow
LookupAndStartWorkflow(listItem, manager,
associationCollection, "FDEDCC37-4D8A-44ba-8E22-57B2669A887C");
}
}
}
/// <summary>
/// Lookup and start the workflow.
/// </summary>
/// <param name="listItem"></param>
/// <param name="manager"></param>
/// <param name="associationCollection"></param>
/// <param name="workflowId"></param>
private static void LookupAndStartWorkflow(SPListItem listItem,
SPWorkflowManager manager,
SPWorkflowAssociationCollection associationCollection,
string workflowId)
{
//iterate workflow associations and lookup the workflow to be started
foreach (SPWorkflowAssociation association in associationCollection)
{
//if the workflow association matches the workflow we are looking for,
//get its association data and start the workflow
Guid workflowGuid = new Guid(workflowId);
if (association.BaseId == workflowGuid)
{
//get workflow association data
string data = association.AssociationData;
//start workflow
SPWorkflow wf = manager.StartWorkflow(listItem, association, data);
}
}
}
}
In the previous example, the following actions are being performed:
- The ItemUpdated event in the event handler is being handled. For demonstration purposes, I am assuming that two workflow associations were created, one to a list and another to a content type;
- A call is made to the StartListWorkflow method, which starts the workflow associated with a list;
- A call is made to the StartContentTypeWorkflow method, which starts the workflow associated with a content type.
After making a search to a SharePoint site, it isn't always easy to get a reference to each item returned in the search. This is because we don't know the Web and List where each item is located. The following example shows how this can be achieved:
class SiteCollectionSearch
{
/// <summary>
/// Search items by title
/// </summary>
/// <param name="web"></param>
/// <returns></returns>
private static DataTable Search(SPWeb web)
{
//search items by title
StringBuilder query = new StringBuilder();
query.Append("<Where>");
query.Append("<Contains>");
query.Append("<FieldRef Name='Title' />");
query.Append("<Value Type='Text'>SharePoint</Value>");
query.Append("</Contains>");
query.Append("</Where>");
query.RowLimit = 100;
SPSiteDataQuery query = new SPSiteDataQuery();
query.Webs = "<Webs Scope='SiteCollection' />";
spQuery.Query = query.ToString();
//perform the search
DataTable searchResults = web.GetSiteData(query);
//return results - since ViewFields property is not specified in
//the query, WebId, ListId and Item Id are automatically returned
return searchResults;
}
/// <summary>
/// After a search, obtains each item by its Id.
/// </summary>
/// <param name="list"></param>
private static void GetItemsByID()
{
SPSite site = new SPSite("http://portal");
using (SPWeb web = site.OpenWeb())
{
//search documents by title
using (DataTable docs = Search(web))
{
//iterate results
foreach (DataRow row in docs.Rows)
{
//get webId of the current item
Guid webId = new Guid(Convert.ToString(row["WebID"]));
//get listId of the current item
Guid listId = new Guid(Convert.ToString(row["ListID"]));
//get id of the current item
int itemId = Convert.ToString(row["ID"]);
//get the web where the item is located
using (SPWeb itemWeb = site.AllWebs[webId])
{
//get list using the list id
SPList list = itemWeb.Lists[listId];
SPListItem listItem = null;
try
{
//get item using its id
listItem = list.Items[itemId];
}
catch { }
if (listItem != null)
{
//do something with the item. Example:
//update the title
listItem["Title"] = listItem["Title"]
+ " Updated";
listItem.Update();
}
}
}
}
}
}
}
In the previous example, the following actions are being performed:
- A search is performed to entire site collection for documents containing the word "SharePoint". As a result of that search, a DataTable instance is returned containing the search results;
- Each row of the search results is iterated. For each row, three important values are obtained: the item's ID, ListId and WebId. These are obtained automatically in the result of a SPSiteDataQuery when the ViewFields property is not set;
- The SPWeb instance where the item is located is obtained using the previously obtained WebId;
- The SPList instance where the item is located is obtained using the previously obtained ListId;
- The SPListItem is obtained from the list using the item's ID;
- Finally, something is done with the item. For example, updating its title.
A very common error message that appears when developing in the SharePoint platform is "Value does not all within the expected range" when trying to update an SPListItem that is the result of a query to a SharePoint list. Let's look at the following example:
class SearchListItem
{
/// <summary>
/// Search document by title
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
private static SPListItem SearchDocument(SPList list)
{
//search document by title
StringBuilder query = new StringBuilder();
query.Append("<Where>");
query.Append("<Eq>");
query.Append("<FieldRef Name='Title' />");
query.Append("<Value Type='Text'>My document</Value>");
query.Append("</Eq>");
query.Append("</Where>");
SPView queryView = list.Views["All Documents"];
SPQuery spQuery = new SPQuery(queryView);
spQuery.Query = query.ToString();
spQuery.ViewAttributes = "Scope=\"RecursiveAll\"";
SPListItemCollection items = list.GetItems(spQuery);
if (items != null && items.Count > 0)
return items[0];
return null;
}
/// <summary>
/// Update document.
/// </summary>
/// <param name="list"></param>
private static void UpdateDocumentError(SPList list)
{
//search document by title
SPListItem doc = SearchDocument(list);
//update item in list
doc["Title"] = "My document modified";
doc.Update(); //error "Value does not fall
//within the expected range" is thrown
}
/// <summary>
/// Update document.
/// </summary>
/// <param name="list"></param>
private static void UpdateDocumentSucess(SPList list)
{
//search document by title
SPListItem doc = SearchDocument(list);
//get SPListItem directly from list
SPListItem item = doc.ParentList.GetItemById(doc.ID);
//update item in list
item["Title"] = "My document modified";
item.Update(); //item updated sucessfully
}
}
In the previous example, the following actions are being performed:
- A search is performed within a list for a document named "My Document". As a result of that search, a SPListItem instance is returned containing the document information;
- In the "UpdateDocumentError" method, after the search is performed, the returned item is modified and the list is updated. This action will cause the exception "Value does not fall within the expected range" to be raised;
- In the "UpdateDocumentSucess" method, after the search is performed, we obtain the item directly from the list using the ID of the SPListItem returned from the search (the line "SPListItem item = doc.ParentList.GetItemById(doc.ID);" was added, returning a new SPListItem instance). The new instance is then modified and used to update the item in the list. This way, the item is sucessfully updated.
One situation that you may have experienced is the lack of the "Save site as template" link in the Site Settings page of a MOSS 2007 site even if you have administrative privileges on the site or site collection. This will happen if your site is a Publishing site (either a Publishing site, used primarily for public faced Internet web sites or a Collaboration Portal, used primarily for Intranet Portals). You can check if your site is a Publishing site by checking if you have the Office SharePoint Server Publishing Infrastructure feature activated if you are at the top level of your site collection or if Office SharePoint Server Publishing feature is activated if you are in a sub site. Microsoft has disabled the "Save site as template" for Publishing sites and it is not supported. If you still want to save your publishing site as a template, you can do it by appending "/_layouts/savetmpl.aspx" to the URL of your site. If the URL of your site is
http://intranetportal/site
you just have to go to
http://intranetportal/site/_layouts/savetmpl.aspx
in order for you to save your site as a template. I have already successfully managed to save collaboration portal sites as templates but it might happen that you encounter some problems if you try to save a publishing site as template since this is not a supported procedure. For more information on this, please check this Microsoft Support article.
Introduction
This is the first in a series of blog posts dedicated to Windows Workflow. For those who don’t know, Windows Workflow Foundation (WF) is the new engine for building custom workflows (you can find more information about WF on the official web site). It was released as part of the .NET Framework 3.0 along with Windows Presentation Foundation (WPF) and Windows Communication Foundation (WCF).
Local Service Communication
Workflows are not isolated entities and most of the times need to communicate with the outside world in order to perform some task. There are several ways of communicating with external entities/services, being one of them External Data Exchange and Local Service Communication. Local Service Communication basically allows workflows to communicate with an external service that resides within the workflow host. There are two main activities inside WF that allow communication between a workflow and an external service using Local Service Communication:
- CallExternalMethodActivity – Allows a workflow to call a method from an external service. The workflow and the host establish a contract (using an interface) that defines the way they communicate with each other. Basically the workflow calls a method of a service residing within the host that implements the contract interface. For more details about this activity, click here.
- HandleExternalEventActivity – Besides allowing the workflow to call an external method, it is also possible for the host to send an event to the workflow in order for it to perform some task. This is achieved using the HandleExternalEventActivity that will put the workflow on an idle state until an external event is raised. Just as in CallExternalMethodActivity, the communication between the workflow and the external is based on a contract.
Summary
To build a workflow that uses HandleExternalEventActivity to communicate with a service using Local Service Communication, you’ll need to:
- Define a contract (interface);
- Implement a service based on the defined contract;
- Develop a workflow that uses HandleExternalEventActivity;
- Develop a host for the workflow;
- Add the service to the workflow runtime in the host;
- Start the workflow and handle the workflow WorkflowIdled event in the host;
- Raise the event.
Example
As already explained, the HandleExternalEventActivity allows communication between a workflow and an external service using Local Service Communication based on event notifications. Next, I will present a simple demo that will show a very basic usage of this activity. The demo is based on a simple Approval State Machine Workflow Console Application. The console application will ask the user if he/she wants to approve and based on the answer it will pass the corresponding answer to the workflow through the raise of an event. This is how the workflow looks like:
This workflow will basically handle the external event inside the StartState activity and set the approved or rejected state according to the information supplied by the host in the event. The interface of the service is defined like this:
[ExternalDataExchange]
public interface IApprovalEventService
{
void RaiseSetStateEvent(Guid instanceId, bool approve);
event EventHandler<ApprovalExternalDataEventArgs> ExternalDataEvent;
}
The interface uses the ExternalDataExchange attribute which allows the workflow to use it to communicate with the host using Local Service Communication. The interface defines the event, whose arguments are of type ApprovalExternalDataEventArgs, a type derived from ExternalEventArgs. It also defines a RaiseSetStateEvent method that will be used by the host to raise the event. This method defines an additional Boolean parameter named approve in order to pass the approval information to the workflow. This is how the ApprovalExternalDataEventArgs class looks like:
[Serializable]
public class ApprovalExternalDataEventArgs : ExternalDataEventArgs
{
private bool _result;
public bool Result {
set {
_result = value;
}
get {
return _result;
}
}
public ApprovalExternalDataEventArgs(Guid instanceId, bool approve)
: base(instanceId) {
Result = approve;
}
}
In the constructor, a Boolean approve parameter is used. This parameter is populated by the host when raising the event and is used to set the Result property that will be used later by the workflow. The other parameter used in the constructor is instanceId, which is used to correlate the event with the right workflow instance.
The HandleExternalEventActivity is defined inside the StartState activity and is named handleSetState. Here are the details of the StartState activity:
As shown in the image above, the external event is handled by the HandleExternalEventActivity handleSetState. Then, based on the value that was supplied by the host, approval or rejected activities will be executed (in this case a simple message will be written to the application console). The properties for the HandleExternalEventActivity handleSetState look like this:
There are three properties worth mentioning here:
- e – It will hold the event arguments for the workflow instance. In this case it is set to ExternalDataEvent, a public property in the workflow of type ApprovalExternalDataEventArgs.
- EventName – name of the event that will be handled. Also set to ExternalDataEvent.
- InterfaceType – name of the interface. Set to IApprovalEventService.
The IfElseActivity defined after the HandleExternalEventActivity will evaluate a code condition to determine whether to execute the Approve or Reject activities.
The code condition is based on a CheckState method that will verify the Result property of the workflow ExternalDataEvent property.
// Code condition to evaluate whether to take the 1st branch, YesIfElseBranch
// Since it always returns true, the 1st branch is always taken.
private void CheckState(object sender, ConditionalEventArgs e) {
e.Result = ExternalDataEvent.Result;
}
The two remaining pieces of this demo are the service and the host console application. Here is the definition of the service:
class ApprovalEventService : IApprovalEventService
{
#region IApprovalEventService Members
public void RaiseSetStateEvent(Guid instanceId, bool approve)
{
// create event
EventHandler<ApprovalExternalDataEventArgs> setStateEvent
= this.ExternalDataEvent;
// launch event
if (setStateEvent != null)
setStateEvent(null, new ApprovalExternalDataEventArgs
(instanceId, approve));
}
public event EventHandler<ApprovalExternalDataEventArgs>
ExternalDataEvent;
#endregion
}
The console application code looks like this:
private static ApprovalEventService service;
private static Guid instanceId;
/// <summary>
/// We've decorated the IEventService interface with the
/// ExternalDataExchangeAttribute.
/// The event args class (ApprovalExternalDataEventArgs)
/// derives from ExternalDataEventArgs.
/// The event args class (ApprovalExternalDataEventArgs) is serializable.
/// ApprovalEventService implements IEventService.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
//add ApprovalEventService service to the workflow runtime
AutoResetEvent waitHandle = new AutoResetEvent(false);
ExternalDataExchangeService dataExchange =
new ExternalDataExchangeService();
workflowRuntime.AddService(dataExchange);
service = new ApprovalEventService();
dataExchange.AddService(service);
workflowRuntime.WorkflowCompleted +=
delegate(object sender, WorkflowCompletedEventArgs e)
{
//display workflow completion message
Console.WriteLine("Workflow completed.");
waitHandle.Set();
};
workflowRuntime.WorkflowTerminated +=
delegate(object sender, WorkflowTerminatedEventArgs e)
{
//display error message if an exception is raised
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};
workflowRuntime.WorkflowIdled +=
delegate(object sender, WorkflowEventArgs e)
{
//read the input from keyboard (yes/no) and raise
//the event passing the answer as a boolean parameter
Console.WriteLine("Workflow idled.");
Console.Write("Do you want to approve? (Y/N) ");
string key = Console.ReadLine();
bool approved = key.ToLower() == "y" ? true : false;
Console.WriteLine("Sending event to workflow.");
service.RaiseSetStateEvent(instanceId, approved);
};
//start workflow
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(
typeof(StateMachineCommunicationDemo.SimpleStateMachine));
instanceId = instance.InstanceId;
Console.WriteLine("Starting workflow.");
instance.Start();
//put workflow in an idle state
waitHandle.WaitOne();
//stop runtime
workflowRuntime.StopRuntime();
Console.WriteLine("Press any key.");
Console.Read();
}
}
In the code above, the following actions are performed:
- The service is added to the workflow runtime. In order for the service to be correctly added to runtime, two AddService methods must be executed and in the right order. First, an instance of ExternalDataExchangeService is added to the workflow runtime. Then, an instance of our service (ApprovalEventService) is added to the ExternalDataExchangeService instance.
- The workflow is started
- The workflow is placed in an idle state
- Several delegate functions are defined to handle workflow events. The most important one is the event handler for the WorkflowIdled event. The event handler will be trigger once the workflow is placed in an idle state. Then, the user will be asked the approval question and the event is raised which will cause the HandleExternalEventActivity in the workflow to be executed.
Here is the application’s output in case of approval:
And in case of rejection:

Another related activity is the ListenActivity. For more information, please click here.
Decorreu na passada semana entre os dias 12 e 14 de Março a edição deste ano do
TechDays, o maior evento de tecnologia do país. Este evento juntou mais de 2000 profissionais na área das TI, tendo sido dadas mais de 150 sessões técnicas por 120 oradores, tendo sido claramente a maior edição deste evento. De seguida, deixo o meu testemunho relativamente às sessões a que tive oportunidade de assistir no 3º dia.
COL10 - Como Criar Soluções/Aplicações Utilizando o Open XML
Oradores: Marcos Santos (Microsoft), Susana Guedes (Microsoft) e Bernardo Antunes (Devscope)
Esta sessão abordou o tema do Open XML e de como este pode ser usado no desenvolvimento de aplicações. O Open XML pretende endereçar os seguintes problemas:
- A informação é guardada em muitos formatos diferentes
- Difícil trocar e guardar a informação
- Diferentes aplicações usam formatos de documento de forma diferente
O objectivo passa por uniformizar o formato de documentos usado pelas aplicações de forma a facilitar a interoperabilidade na troca de documentos entre organizações. O Open XML é já o formato usado pelo Office 2007. Para quem não sabe, se renomear um ficheiro .docx como exemplo para .zip pode ver a estrutura interna do ficheiro, basicamente um conjunto de ficheiros xml.
Mais informações e demos de utilização do OpenXML em: http://openxmldeveloper.org e http://www.openxmlcommunity.org/.
ARC05 - Construção de Software+Services com a Plataforma Volta
Oradores: Daniel Fisher e Michael Willers, DevCoach
O tema desta sessão foi muito interessante. "Volta" é o nome de código de um projecto ainda em desenvolvimento por parte da Microsoft cujo objectivo é o de uma forma transparente e sem qualquer esforço de desenvolvimento compilar código de assemblies .NET (em qualquer linguagem .NET compliant) em código Javascript compatível "cross-browser". Detalhando um pouco mais o conceito, a ideia é desenvolver uma aplicação (cliente, serviços) em .NET e por meio de atributos definir se o código corre no cliente ou no servidor (basta incluir o atributo ["RunAtOrigin"]). O "Volta" vai gerar código javascript para a camada de cliente, web services para a camada de servidor e todo o código de "plumbing" (comunicação, serialização, segurança, etc) entre as duas camadas.
Basicamente, a ideia é desenvolver as aplicações em .NET do lado do servidor na nossa linguagem preferida e como output obter uma aplicação AJAX distribuída.
Mais informação pode ser obtida em http://labs.live.com/volta/.
WEB05 - ASP.NET Futures - Dynamic Data Controls and Astoria Services
Orador: Matt Gibbs, Microsoft (Blog: http://blogs.msdn.com/mattgi)
Esta sessão foi apresentada pelo Matt Gibbs que é Development Manager da equipa de ASP.NET da Microsoft e abordou algumas das novidades que irão estar presentes na próxima versão do ASP.NET. A sessão foi dividida em duas partes distintas:
- Dynamic Data Controls
- Astoria Web Data Services
Dynamic Data Controls
Os Dynamic Data Controls permitem a obtenção de forma dinâmica da estrutura da base de dados em tempo real e adaptar a sua apresentação automaticamente de acordo com a mesma. Estes controlos irão estar integrados como extensões na próxima versão do ASP.NET e vêm alterar o ciclo de desenvolvimento de uma aplicação ASP.NET "data-bounded". Antes tínhamos:
- Criação do modelo de BD
- Criação das páginas
- Adição dos controlos às páginas
- Adição lógica de validação
Com estes novos controlos e a com as novas templates de projectos existentes no Visual Studio 2008, teremos:
- Criação do modelo de dados
- Criação automática de um aplicação com base no modelo de dados
- Customização da aplicação (alteração páginas .aspx e controlos .ascx base)
Mais informação sobre Dynamic Data Controls: http://quickstarts.asp.net/Futures/dynamicdatacontrols/default.aspx.
Astoria Web Data Services
Ainda em fase de protótipo (será designado por "ADO.NET Data Services"), a principal ideia é simplificar o acesso a dados através do consumo de serviços com base em URIs (http://quickstarts.asp.net/3-5-extensions/adonetdataservice/ConsumingDataService.aspx) e retornar os dados em formatos JSON (método de serialização mais simples para evitar o "overhead" do XML na troca de informação em web services - http://www.json.org/ muito ligado ao Javascript e AJAX) ou XML. Os serviços poderão ser consumidos tanto por AJAX ou por qualquer outro cliente non-AJAX (serviço WCF, aplicação web, aplicação Windows-Forms, etc).
Mais informação sobre Astoria Web Data Services em http://astoria.mslivelabs.com/ e http://quickstarts.asp.net/3-5-extensions/adonetdataservice/ADONETDataServicesFramework.aspx.
VST03 - Hardcore Debuging e Profiling de Aplicações .NET
Oradores: Filipe Prezado e João Loureiro, Microsoft
Foi mais a curiosidade que me levou a assistir a esta sessão, tendo-se revelado uma sessão bastante interessante, embora com um certo nível de complexidade. Basicamente, foram apresentadas as técnicas usadas pela equipa de suporte técnico da Microsoft para resolução de problemas aplicacionais em produção, tanto em cenários em que detêm o acesso remoto ao servidor como em casos em que tal não sucede. Algumas das ferramentas apresentadas foram: Mdbg, WinDbg (a mais conhecida), SOSAssistant, Hawkeye. Com base nestas ferramentas, foram apresentadas técnicas que permitiram a identificação de "memory leaks", efectuar o memory dump para ficheiro (útil em cenários em que não existe acesso remoto ao servidor) para posterior análise, entre outras. Uma sessão diferente mas interessante.
WEB04: MVC Framework on Future ASP.NET
Orador: Matt Gibbs, Microsoft (Blog: http://blogs.msdn.com/mattgi)
Esta foi a segunda sessão apresentada pelo Matt Gibbs sobre a futura versão do ASP.NET. Desta vez, foi apresentada a framework MVC para ASP.NET que é a implementação da Microsoft desta "pattern". Esta framework basicamente divide uma aplicação em 3 "roles":
- Model (Modelo) – representa os componentes da aplicação que são responsáveis pela manutenção do estado da aplicação (os dados). O caso mais típico é a persistência do estado numa base de dados (ex: uma classe Client que representa os dados de uma tabela Client numa base de dados SQL Server)
- View (Vista) – representa os componentes responsáveis pela camada de apresentação da aplicação
- Controller (Controlador) – representa os componentes responsáveis pela interacção com o utilizador, manipulação do modelo e selecção da vista a utilizar. É o controlador que fica encarregue pela interacção com o utilizador e não a vista, sendo esta apenas usada para apresentação da informação
Um ponto importante a referir é que o MVC não vem substituir o modelo de desenvolvimento ASP.NET tradicional, apresentando-se antes como uma alternativa onde a ideia é haver uma maior separação de responsabilidades entre os componentes da aplicação.
As demos apresentadas não foram tão bem conseguidas quanto estava à espera, tendo ficado por explicar como exemplo a forma como são obtidos ou como são editados/inseridos dados numa base de dados (tipicamente a lógica será colocada no componente "Model").
Mais informação sobre o MVC em http://weblogs.asp.net/scottgu/archive/2007/10/14/asp-net-mvc-framework.aspx e http://weblogs.asp.net/scottgu/archive/2007/11/13/asp-net-mvc-framework-part-1.aspx.
Podem consultar as sessões do dia 1 aqui e do dia 2 aqui.
Decorreu na passada semana entre os dias 12 e 14 de Março a edição deste ano do
TechDays, o maior evento de tecnologia do país. Este evento juntou mais de 2000 profissionais na área das TI, tendo sido dadas mais de 150 sessões técnicas por 120 oradores, tendo sido claramente a maior edição deste evento. De seguida, deixo o meu testemunho relativamente às sessões a que tive oportunidade de assistir no 2º dia.
INT04 - Qual é o Contexto desta Conversação? Activando Conversações Longas em Serviços de Workflow. Serviços "Duráveis"
Orador: José António Silva, Microsoft
Tendo em consideração a natureza dos workflows onde uma instância de workflow pode estar activa durante um período de tempo longo ("long running instance"), é necessário desenvolver os serviços WCF de forma a suportar este tipo de cliente. Esta sessão abordou o tema de serviços "duráveis". Estes são um novo tipo de serviço da .NET Framework 3.5 que permite simplificar a persistência de estado numa "conversação" entre um serviço WCF e um cliente (ex: um workflow). O modelo de persistência do estado de um serviço WCF é em tudo idêntico ao de WF, sendo possível guardar o estado em BD, files system, etc . De forma a tornar um serviço WCF "durável" basta usar o prefixo "Durable" nos atributos da classe do serviço. Exemplo:
[Serializable]
[DurableService]
public class TextComposer : ITextComposer
{
private string CurrentText ;
[DurableOperation]
public string PowerOn(string text)
{
CurrentText = text;
return CurrentText;
}
}
Mais informação sobre "Durable Services" em http://weblogs.asp.net/gsusx/archive/2007/06/14/orcas-durable-services.aspx (post antigo mas com uma boa explicação) e em http://www.microsoft.com/uk/msdn/nuggets/nugget/270/Durable-Services-with-WCF-V35.aspx (screencast).
Algumas tools interessantes para WCF referidas durante a sessão:
Configuration Editor Tool – aplicação que permite que permite a edição das configurações de serviços WCF com uma interface gráfica (StartàRunà SvcConfigEditor). Mais informação em http://msdn2.microsoft.com/en-us/library/ms732009.aspx.
WCF Test Client – aplicação que permite efectuar testes "offline" sobre serviços WCF (StartàRunà WcfTestClient). Mais informação em http://msdn2.microsoft.com/en-us/library/bb552364.aspx.
Service Trace Viewer Tool – aplicação que permite a análise dos logs de mensagens geradas pelo WCF (StartàRunà SvcTraceViewer). Mais informação em http://msdn2.microsoft.com/en-us/library/ms732023.aspx.
ARC01 - Software + Services: The Convergence of SaaS, SOA and Web 2.0
Orador: Beat Schwegler, Microsoft
Esta sessão retratou um tema muito em voga: o Software + Services. A sessão não teve qualquer demo, tendo tido uma componente bastante teórica, tendo sido iniciada com a referência a três conceitos importantes: SaaS (http://msdn2.microsoft.com/en-us/architecture/aa699384.aspx), SOA (http://msdn2.microsoft.com/en-us/architecture/aa948857.aspx) e Web 2.0 (http://twopointouch.com/2006/08/17/10-definitions-of-web-20-and-their-shortcomings/). Foram referidos alguns exemplos de modelos de negócio usados com S+S: Subscription/License Model, Advertisement Base Model (ex: Google). Foram dados ainda alguns exemplos de aplicações S+S: Eve Online (http://www.eve-online.com/), o Amazon S3 (http://www.amazon.com/gp/browse.html?node=16427261) e a British Library. Por fim, foram ainda referidos alguns exemplos concretos de implementação do S+S pela Microsoft:
- Finished Services – Windows Live, Office Online
- Attached Services – XBOX Live
- Building Blocks – BizTalk Services
INT06: Viagem ao Centro da Núvem – O Internet Service Bus (ISB) e os BizTalk Services
Orador: João Pedro Martins a.k.a "Jota", Create IT
A sessão começou de uma forma muito interessante com o Jota a "provocar" a audiência com algumas ideias sobre a forma como será o mundo das aplicações no futuro como a transição de um mundo com "data centers" nas próprias empresas para um em que o "hosting" é feito por grandes empresas com super "data centers" dedicados a fazer o "hosting" de milhares de aplicações. Foi uma forma interessante de cativar desde início a audiência. Neste sentido, foram dadas algumas estatísticas interessantes como a previsão do aumento de número de servidores de hosting da Microsoft de 200000 actuais para 800000 em 2011, indo de encontro à adopção do conceito de Software como um serviço (S+S) com as aplicações a ser alojadas em "hosting" externo e serem expostas como serviços. Os BizTalk Services, são basicamente a visão da Microsoft da forma como as aplicações irão comunicação entre si no futuro, facilitando o desenvolvimento de aplicações orientadas a serviços (SOA). A ideia fundamental dos BizTalk Services é a de permitir a comunicação segura entre as aplicações das organizações através de firewalls.
COL07 - Microsoft Search Server 2008 - Introdução e Apresentação de Novas Funcionalidades
Oradores: Bruno Valente e Luís Calado, Microsoft
O Microsoft Search Server 2008 (MSS 2008) é a versão 2 do já existente "SharePoint Server for Search". As principais diferenças de para a versão anterior são:
- Melhorias significativas na interface de administração
- Pesquisa federada (a maior novidade)
- Melhorias de performance e estabilidade
- Pesquisa "cross-site" mesmo na versão Express (ao contrário do que acontecia antes)
Melhorias nos relatórios
- Duração última indexação e da actual
- Última indexação completa, etc
Vão existir duas versões do produto:
- Search Server 2008 – versão paga que pode ser instalada num cenário de farm. Os clientes MOSS não terão custos adicionais para fazer o upgrade para MSS 2008.
- Search Server 2008 Express – versão gratuita que apenas pode ser instalada num único servidor.
A principal novidade do produto é a pesquisa federada. A ideia passa por permitir a pesquisa sobre outros motores de pesquisa ou aplicações, integrada no SharePoint, sem ter que ter os conteúdos indexados no índice de SharePoint. Alguns dados sobre a pesquisa federada:
- A única desvantagem é a inexistência de "ranking" dos resultados de pesquisa pelo facto de não estarem indexados.
- Cada motor de pesquisa tem que ser configurado no SharePoint como uma localização federada.
- A pesquisa é efectuada com base no standard OpenSearch (http://www.opensearch.org/Home) onde a query de pesquisa é enviada por URL. Ex: http://search.live.com/result.aspx?q=%7bsearchTerms%7d
- Resultados de pesquisa devolvidos em XML
Existem duas web parts "out-of-the-box" para pesquisa federada:
- Federated Results Web Part: mostra todos os resultados de uma localização federada
- Top Federated Results Web Part: mostra os "top x" resultados de uma ou mais localizações federadas
Podem consultar as sessões do dia 1 aqui.
Decorreu na passada semana entre os dias 12 e 14 de Março a edição deste ano do
TechDays, o maior evento de tecnologia do país. Este evento juntou mais de 2000 profissionais na área das TI, tendo sido dadas mais de 150 sessões técnicas por 120 oradores, tendo sido claramente a maior edição deste evento. De seguida, deixo o meu testemunho relativamente às sessões a que tive oportunidade de assistir no 1º dia.
COL03 – Arquitectura e Desenvolvimento de Aplicações que Permitem Pesquisa com MOSS
Orador: Beat Schwegler, Microsoft (Blog: http://blogs.msdn.com/beatsch/)
Esta sessão foi dividida em duas partes:
- Como tornar informação das aplicações pesquisável no MOSS
- Como efectuar pesquisas no MOSS a partir de aplicações e apresentar os resultados
Como tornar informação das aplicações pesquisável no MOSS
Foi dada uma introdução sobre a arquitectura de pesquisa do MOSS e os seus componentes (Content Sources [File, BDs, Exchange,etc], Protocol Handlers [http, ftp, file, BDC, etc], IFilters, Metadata e permissões).
De seguida, foram referidos os requisitos para tornar formatos de ficheiro proprietários pesquisáveis no MOSS, tendo que para tal ser instalado um iFilter de forma a que o motor de indexação do MOSS possa ler o conteúdo dos ficheiros e indexá-los.
Para tornar conteúdos de aplicações LOB pesquisáveis, deve ser usado o BDC. Deve ser criado o ficheiro xml de meta dados (metadata.xml). No ficheiro de meta dados devem ser especificados:
- Entidades
- Métodos - devem ser especificados métodos Finder e SpecificFinder de forma a que os dados possam ser pesquisados.
Como efectuar pesquisas no MOSS a partir de aplicações e apresentar os resultados
Podem ser usados 3 métodos:
Informação adicional: http://msdn2.microsoft.com/en-us/library/bb887531.aspx
ARC04 – Database Design Patterns: Boas Práticas para os Modelos de Dados
Orador: Stephen Forte, Corzen, Inc. (Blog: http://www.stephenforte.net/)
Esta sessão foi muito interessante, das mais interessantes a que assisti. O orador (americano) é um comunicador nato, cativando desde o início a audiência. Durante a sessão foram abordados dois tópicos essenciais:
- Data Model Patterns
- Infrastructure Patterns – Data Partitioning
Data Model Patterns
Existem 3 patterns:
- Transactional Design Pattern – este modelo deve ser usado para as bases de dados operacionais, com elevado nível de transacções (OLTP), tipicamente bases de dados de suporte ao negócio. O modelo de dados deve ser definido para que a base de dados se encontre normalizada. Não devem ser usados índices de forma a evitar perdas de performance nas operações de insert, delete e update.
- Slowly Changing Dimensions Design Pattern (SCD)– esta pattern deve ser usada para bases de dados de reporting e consulta de informação. O seu preenchimento é feito a partir da BD OLTP, devendo a a estrutura da BD ser desnormalizada e as tabelas desenhadas de forma a agregar toda a informação que se pretenda consultar na mesma tabela de forma a simplificar as querys (evitando joins com outras tabelas) e desta forma conseguir-se querys mais rápidas. Com este modelo evita-se que as querys de report sejam efectuadas sobre a BD OLTP. Neste tipo de BDs, podem e devem ser usados índices para acelerar as querys.
- Data Warehouse Design Pattern – este modelo é uma extensão do anterior, tendo um pouco mais de estrutura (existem N tabelas de dimensão [Tempo, Produto, Sexo, etc] e uma tabela de Factos [com uma chave estrangeira para cada tabela de dimensões] onde são guardados os dados a partir da BD OLTP). Este modelo deve ser usado em detrimento do SCD em BDs com volumes massivos de dados.
Existem várias formas para popular as BDs SCD ou DW: por T-SQL, DTS, SSIS, etc.
Foi dado um exemplo concreto da utilização destes modelos: a Amazon.com usa uma BD OLTP para todas as ordens de compra e uma BD SCD para os catálogos de produtos.
Infrastructure Patterns – Data Partitioning
Os patterns seguintes devem ser usados quando se tiver um volume muito grande de informação e as querys à BD começarem a ficar lentas.
Existem 2 patterns:
- Horizontal Partitioning – consiste em dividir os dados em várias tabelas com a mesma estrutura. Exemplos: dados anuais, dados semestrais, etc conforme o volume de dados.
- Vertical Align – consiste em dividir os dados em duas ou mais tabelas, mantendo o número de registos intacto. Numa tabela, são guardados as colunas mais acedidas (ex: nome, email, etc), colocando-se na(s) restante(s) as colunas menos acedidas.
Estas duas patterns podem ser usadas em conjunto.
O particionamento de dados pode também ser feito, separando os dados em vários discos, de forma a que se um disco ficar inoperacional, os outros continuam a funcionar.
COL01 - Gestão de Conteúdos e Usabilidade com Sharepoint (MOSS)
Oradores: Carla Faria e Luís Calado, Microsoft
Esta sessão foi composta por duas partes distintas, apresentadas por cada um dos oradores:
- Acessibilidade na Web em geral - Carla Faria
- Acessibilidade com SharePoint (MOSS) – Luís Calado
Acessibilidade na Web em geral
A Carla Faria é uma especialista em acessibilidade na Web. De seguida, fica um resumo das "guidelines" de acessibilidade apresentadas na sessão:
Acessibilidade com SharePoint (MOSS)
O Luís deixou alguns conselhos práticos para melhorar a acessibilidade dos sites em MOSS:
- Incluir textos alternativos para todos os elementos textuais
- Utilizar tamanhos de letra relativos (usar font-size na unidade ".em") em detrimento de tamanhos fixos (http://www.bigbaer.com/css_tutorials/css_font_size.htm e http://clagnut.com/blog/348/)
- Em WCM, evitar usar Web parts J ou alterar forma de renderização das Web part zones
- Utilizar sempre que possível controlos de publicação MOSS ou web controls ASP.NET sempre que possível em deterimento de web parts
- Incorporação de solução de terceiros para edição de conteúdos (Telerik RAD Editor – W3C WAG level A)
- Começar sempre por desenhar master page mínima
- Validar página para acessibilidade = validar master page + validar page layouts + validar conteúdos
- Nos menus SharePoint, não usar a classe Menu do ASP.NET mas antes renderizar <ul><li>.
Encontra-se em http://www.codeplex.com/aks o Accessibility Toolkit for SharePoint (AKS), um conjunto de templates, master pages, controlos e web parts acessíveis. Este toolkit é instalado através de um solution package (.wsp) e contém alterações à implementação das web part zones. Na próxima versão do AKS vai ser lançado um gestor de conteúdos acessível (Accessible Rich Text Editor [aRTE]). O roadmap do AKS encontra-se disponível em http://blogs.msdn.com/sharepoint/archive/2008/03/12/announcing-accessibility-kit-for-sharepoint-1-1-and-future-roadmap.aspx.
DEV06 - ADO.NET Entity Framework e LINQ To Entities
Orador: Luís Falcão (ISEL)
Nesta sessão foi abordada a ADO.NET Entity Framework (http://msdn2.microsoft.com/en-us/library/aa697427(VS.80).aspx), uma nova framework cujo objectivo é o de aumentar o nível de abstracção no que diz respeito à programação da camada de acesso a dados. Um dos problemas mais comuns no desenho das classes DAL é o esforço associado ao mapeamento entre as classes DAL e a BD. A Entity Framework permite facilitar esta tarefa ao disponibilizar um diagrama integrado no Visual Studio 2008. Usando o Solution Explorer é possível importar todo a estrutura de uma base de dados para um diagrama de classes que é a transformação do modelo de dados num modelo de classes mapeado directamente com a estrutura da BD. Depois é possível definir novas relações entre as classes, definir heranças entre classes (não é possível definir herança ao nível da BD), mapear os dados de uma classe para que estes sejam divididos entre duas ou mais tabelas entre outras funcionalidades.
Na nova API vem incluído um novo .NET provider para Entity Framework (Entity Client) que é o correspondente ao SqlClient (para SQL Server) para actuar sobre as entidades criadas com a Entity Framework.
Ainda se encontra em versão beta e nesta fase só permite gerar o Entity Model a partir da BD, não permitindo ainda criar primeiro o modelo antes e gerar a BD a partir deste.
When developing for the SharePoint platform, developers should be very careful in using the SharePoint API in order to avoid memory leaks in a production SharePoint farm. Roger Lamb has written a great post that shows a series of examples where code can lead to memory leaks and best practices on how to avoid them. Definitely, a must read for all SharePoint developers.
Another suggestion I leave before deploying any custom development into a SharePoint farm, is to use a stress tool on your SharePoint web site. I recommend using Microsoft Web Application Stress Tool and take a good look into the IIS Application Pool Processes (w3wp.exe) memory consumption in order to ensure they remain stable (you should have one w3wp.exe process for each application pool).
It is a common scenario in WCM web sites, to have content page images stored in a column of type Image. SharePoint stores this kind of fields in the content database by storing the image HTML markup, storing an <IMG> tag like in the following example:
<IMG src="http://blogit.create.pt/PublishingImages/picture.GIF" />
When working in context of a page layout, we can easily include image fields by inserting an Image Field control using SharePoint Designer. However, there are situations where we must use the SharePoint Publishing API and there's where the ImageFieldValue class comes in hand. This class represents an <IMG> tag and its properties. A common situation is when you want to get the Image URL from its HTML markup. This example shows how it can easily be achieved by using the ImageFieldValue class:
public
static
string getImageUrl(string imageFieldHTMLMarkup)
{
Microsoft.SharePoint.Publishing.Fields.ImageFieldValue image =
new Microsoft.SharePoint.Publishing.Fields.ImageFieldValue(imageFieldHTMLMarkup);
return image.ImageUrl;
}
More information about the ImageFieldValue class can be found in the MSDN web site.