Monthly Archives: December 2013

Fix: Content Approval always Modified By System Account

If you found this article, it means you know how to setup OOB SharePoint Approval Workflow and Content Approval and just realize the issue where Modified By always be System Account.

For those of you who wants to know how to set approval and content approval; or find issue where content approval not approved after approval workflow completed; there is a great article to show you how to use a workflow to manage content approval for a library.

Issue

Content Approval issue

Resolution

1. Update SharePoint OOB Approval Workflow Start Option > Clear Start this workflow to approve publishing a major version of an item.

ContentApproval2

2. Update SharePoint OOB Approval Workflow Enable Content Type Option > Update the approval status after the workflow is completed (use this workflow to control content approval).

ContentApproval3

3. Implement ItemUpdated event receiver to start approval workflow manually if user check in major version.

public override void ItemUpdated(SPItemEventProperties properties)
{
 base.ItemUpdated(properties);
 int iBefore, iAfter;
 iBefore = (int.TryParse(properties.BeforeProperties["vti_level"].ToString(), out iBefore) ? iBefore : -1);
 iAfter = (int.TryParse(properties.AfterProperties["vti_level"].ToString(), out iAfter) ? iAfter : -1);
 if (iAfter != iBefore)
 {
  StartWorkflow(properties.ListItem, "Templates Approval Workflow");
 }
}

public void StartWorkflow(SPListItem listItem, string wfName)
{
 SPSecurity.RunWithElevatedPrivileges(delegate()
 {
  using (SPSite site = new SPSite(listItem.Web.Site.ID))
  {
   using (SPWeb web = site.OpenWeb(listItem.Web.ID))
   {
    SPList listTemplate = web.Lists.TryGetList(listItem.ParentList.Title);
    if(listTemplate != null)
    {
     SPListItem currItem = listTemplate.Items[listItem.UniqueId];
     SPWorkflowAssociationCollection associationCollection = listTemplate.WorkflowAssociations;
     SPWorkflowAssociation approvalWorkflow = associationCollection.GetAssociationByName(wfName, CultureInfo.CurrentCulture);

     if(approvalWorkflow != null)
      site.WorkflowManager.StartWorkflow(currItem, approvalWorkflow, approvalWorkflow.AssociationData, true);
    }
   }
  }
 });
}

4. Implement WorkflowCompleted event receiver.

public override void WorkflowCompleted(SPWorkflowEventProperties properties)
{
 base.WorkflowCompleted(properties);

 PublishItemBasedOnWorkflowStatus(properties);
}

private void PublishItemBasedOnWorkflowStatus(SPWorkflowEventProperties properties)
{
 SPSecurity.RunWithElevatedPrivileges(delegate()
 {
  using (SPSite site = new SPSite(properties.WebUrl))
  {
   using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
   {
    SPListItem workflowHistoryItem = GetWorkflowHistoryItem(web, properties);
    if (workflowHistoryItem == null)
     return;

    SPListItem item = GetItemForThisWorkflow(properties);

    //Provide association name as approval workflow name
    string associationName = "Approval";

    //This code runs for each workflow available in the site
    //so just make sure you are not intrupting other workflow (for instance your custom one, collect feedback etc)
    //and make sure it approve or reject for your specific libraries
    MarkItemApproveOrRejectBasedWorkflowStatus(item, web, associationName, properties);
   }
  }
 });
}

private void MarkItemApproveOrRejectBasedWorkflowStatus(SPListItem item, SPWeb web, string associationName, SPWorkflowEventProperties properties)
{
 SPListItem latestApprovalTask = GetLatestApprovalTask(properties, web);
 string status = (latestApprovalTask != null ? latestApprovalTask["WorkflowOutcome"].ToString() : string.Empty);

 //we are only interested in OUR WORKFLOW ASSOCIATION (i.e. configured in Settings List)
 //if it is different, then it returns empty string
 if (string.IsNullOrEmpty(status))
  return;

 item.ModerationInformation.Status = SPModerationStatusType.Denied;
 if (status.Equals("Approved"))
  item.ModerationInformation.Status = SPModerationStatusType.Approved;

 item.Update();
}

private SPListItem GetLatestApprovalTask(SPWorkflowEventProperties properties, SPWeb web)
{
 SPListItem item = null;
 string caml = @"y{0}";

 SPQuery query = new SPQuery();
 query.Query = string.Format(caml, properties.InstanceId);
 query.RowLimit = 1;
 SPList list = web.Lists["Workflow Tasks"];
 SPListItemCollection items = list.GetItems(query);
 if (items.Count > 0)
  item = items[0];

 return item;
}

private SPListItem GetItemForThisWorkflow(SPWorkflowEventProperties properties)
{
 SPListItem itemRelated = null;
 using (SPWeb web = new SPSite(properties.WebUrl).OpenWeb())
 {
  //Get the workflow instance Id
  String wfInstanceId = properties.InstanceId.ToString();

  //Assume "Tasks" list manages all the workflow tasks
  SPList listRelated = web.Lists[properties.ListId];
  SPQuery query = new SPQuery();
  query.Query = String.Format("{0}", wfInstanceId);
  SPListItemCollection itemColl = listRelated.GetItems(query);
  if (itemColl.Count > 0)
   itemRelated = itemColl[0];
 }
 return itemRelated;
}

private SPListItem GetWorkflowHistoryItem(SPWeb web, SPWorkflowEventProperties properties)
{
 string caml = string.Format(@"{0}2", properties.InstanceId.ToString("B"));

 SPList workflowHistory = web.Lists.TryGetList(SPResource.GetString("DefaultWorkflowHistoryListName", new object[0]));
 SPQuery query = new SPQuery();
 query.Query = caml;
 SPListItemCollection items = workflowHistory.GetItems(query);

 if (items.Count >= 1)
  return items[0];

 return null;
}

Reference
http://blog.mmasood.com/2012/12/approval-worfklow-showing-system.html

Advertisements

LINQ VS SPQUERY to Retrieve List Items

Nowadays, I’m getting used to query list using Linq instead of creating SPQuery object and use CAML to filter and retrieve list items. I did that because I’m lazy to generate the CAML query using CAML Designer. Below is example on how you retrieve list items using Linq and SPQuery

SPQuery

private SPListItem GetLatestResultForStudent(SPList listStudentResult, string name)
{
 string caml = string.Format(@"<Where><Eq><FieldRef Name='Title' /><Value Type='Text'>{0}</Value></Eq></Where><OrderBy><FieldRef Name='Created' Ascending='FALSE' /></OrderBy>", name);
 SPQuery query = new SPQuery();
 query.Query = caml;
 SPListItemCollection studentResults = listStudentResult.GetItems(query);

 if (studentResults.Count >= 1)
 {
  return studentResults[0];
 }
 return null;
}

LINQ

private SPListItem GetLatestResultForStudent(SPList listStudentResult, string name)
{
 SPListItem latestStudentResult = listStudentResult.Items.Cast<SPListItem>().Where(strslt => strslt["Title"].ToString() == name).OrderBy(apv => apv["Created"]).Last();
 return latestWorkflowCompleted;
}

Performance: CAML win !

What you guys think in terms of reading CAML query and LINQ where clause? I think both needs sometime to get used to. But recently I found technet article to explain both performance. One of the response is “In Linq, Code is converted to CAML under the hood resulting in a slight performance hit

Determine Check In Major / Minor Version in Item Updating / Updated Event Receiver

Recently, I need to determine whether user check in major version to the file in document library. I found a good article from Morten Marquad& to do it. The way to determine is to read before and after properties of SharePoint field “vti_level“. I just want to re-iterate it here, so I don’t miss it.

The value of “vti_level” after and before properties would be as per table below and these matrix ONLY WORK IN ItemUpdating and ItemUpdated event receiver. When I tried it in ItemCheckedIn event receiver, it gives you different values.

Event iBefore iAfter
Check in major version 255 2
Check in minor version 255 255
public override void ItemUpdating(SPItemEventProperties properties)
{
 base.ItemUpdating(properties);
 int iBefore = (int.TryParse(properties.BeforeProperties["vti_level"].ToString(), out iBefore) ? iBefore : -1);
 int iAfter = (int.TryParse(properties.AfterProperties["vti_level"].ToString(), out iAfter) ? iAfter : -1);

 // If check in major version
 if (iAfter != iBefore)
 {
  // do your code here ...
 }
}

Reference
http://mqsharepoint.blogspot.com/2010/01/identify-publish-major-version.html

Filtering in Business Connectivity Services from WCF Service

I have found few excellent article to define BCS Filter coming from SQL Database as per below.

But I can not find “Filter Parameter” in second step of BCS Read List operation. Also, I have difficulties to find good article on how to add filtering in BCS from WCF Service. Hence, I posted this article.

There are couple of great articles to build WCF Service that can be consumed by BCS below. It is a good starting point:

Issues

Probably some of you have the same problem or familiar with below error messages:

No Filter to select in Find drop down box.

BCS1Could not find item that is exists.

bcs21

 

Could not validate item that is exists.

bcs3

 

Resolution

1. Update your WCF Service to retrieve all records service contract to accept filter parameter (in my scenario ActiveOnly and NameFilter)

2. In SharePoint Designer > Remove existing Read List operation

3. In SharePoint Designer > Create new Read List operation from updated WCF service contract

4. Read List operation step 1, enter Operation Name and Display Name and click Next

BCS4

5. Read List operation step 2, set ActiveOnly default value to true and configure filter to NameFilter parameter, then click Next

BCS5BCS6

6. Read List operation step 3, set Id as Map to Identifier and show AffiliateName in picker, then click Finish

BCS7BCS8

7. Create new list from External Content Type & Ensure the list retrieving records

BCS9

8. Create / Update your list to add / update External data column to new External Content Type list

9. Ensure it has “Name” in filter drop down box and could validate existing item works

BCS10

bcs11

bcs12

Overlooked Information Management Policy settings and configuration

In this article, I would like to show you about the Information Management Policy settings and configuration that might have been overlook and ends up spending unnecessary time to figure it out.

If you want to know what things Information Management Policy can do, you could find it in Plan for information management policy in SharePoint Server 2013 technet article.

If you want to know how to setup Infomation Management Policy in Library, there are couple of good articles from googling like Retention Policy for a document library in SharePoint 2010.

I have configure simple Information Management Policy inside my Document Library as per below:

Move To Recycle Bin

Then, if you have done both and wondering why it is NOT working, there are couple of things you need to check:

1. Activate Library and Folder Based Retention

  • Go to Root Site Settings
  • Select Site Collection Features
  • Activate Library and Folder Based Retention

2. Set Information management policy and Expiration policy job definition to Daily (by default it set to Weekly)

  • Go to SharePoint Central Administration Site
  • Select Monitoring > Review Job Definition
  • Look for Information management policy job definition for your desired web application
  • Click it and update Recurring Schedule to Daily and click OK
  • Look for Expiration policy job definition for your desired web application
  • Click it and update Recurring Schedule to Daily and click OK
  • You could also Run Now both job definitions to see whether your policy run successfully

InformationRetentionPolicy1

InformationRetentionPolicy2

%d bloggers like this: