Karine Bosch’s Blog

On SharePoint

Creating fields using CSOM in SharePoint 2013

Recently I had a reader who asked how to to configure a calculated default value like “=YEAR([Today])”. This is not possible within one field as you cannot use [Today] or [Now] within the formula of a calculated field. The only way to solve this is to create 2 columns:

  • A DateTime field with default value Today. You can set this to hidden if you don’t want your users to see it.
  • A calculated field that outputs the year of your DateTime field.

Here is the code snippet:

private static void CreateCalculatedFieldBasedOnToday(ClientContext context)
{
     Web web = context.Web;
     List list = web.Lists.GetByTitle("CSOMFields");
     context.Load(list);
     context.ExecuteQuery();

     // Create a DateTime field that yields to Today
     string schemaTodaysDate = "<Field ID='{297B3AA2-85AD-408D-8346-0B64721C8090}' Type='DateTime' Name='TodaysDate' StaticName='TodaysDate' DisplayName='TodaysDate' Format='DateOnly' Hidden='FALSE' >"
           + "<Default>[Today]</Default></Field>";
     Field todaysDateField = list.Fields.AddFieldAsXml(schemaTodaysDate, true, AddFieldOptions.AddFieldInternalNameHint);
     context.ExecuteQuery();

     // Create a Calculated field that displays the Year of the Today field
     string formula = "<Formula>=YEAR(TodaysDate)</Formula>";
     string schemaCalculatedField = "<Field ID='{446A6933-1751-474D-A407-9EE0250C708B}' Type='Calculated' Name='TodaysYear' StaticName='TodaysYear' DisplayName='Todays Year' ResultType='Number' Decimals='0' Required='TRUE' ReadOnly='TRUE'>" + formula + "</Field>";
     Field calculatedField = list.Fields.AddFieldAsXml(schemaCalculatedField, true, AddFieldOptions.AddFieldInternalNameHint);
     context.ExecuteQuery();
}

Update: Steve Moucheron sent me his code snippet in which he solves in one go:

string fieldXml = "<Field DisplayName='Year' Type='Text'>"
   + "<DefaultFormula>=CONCATENATE(YEAR(Today))</DefaultFormula>"
   + "</Field>";
Field field = list.Fields.AddFieldAsXml(fieldXml, true, 
    AddFieldOptions.defaultValue);
context.ExecuteQuery();

Read more on creating fields using CSOM.

January 13, 2017 Posted by | CSOM, SharePoint Online | Leave a comment

Add or Modify SharePoint 2013 Search Topology using a PowerShell built User Interface

In SharePoint 2013, there is no real user interface to modify the search topology. Well, there is, but you can only use for a single server farm. If you have more servers in your SharePoint farm, you have to do this through PowerShell.

One of my South-African Premier Field Engineer colleagues Scott Stewart developed a tool on top of PowerShell WITH UI to create or modify a search topology.

Read more about it here: “Add or Modify SharePoint 2013 Search Topology using a PowerShell built User Interface

A few screenshots to get you curious 🙂

Search topology tool

Search topology

It looks like a very promising tool! Have fun with it!

May 22, 2015 Posted by | Search, SharePoint 2013 | 1 Comment

Creating fields using CSOM in SharePoint 2013

Recently, I was working on a project where we wanted to create SharePoint fields using the .NET client object model (CSOM). You can easily find examples on the internet on how to create a text field, but we had a lot of trouble to find out how to create a calculated field.

Calculated fields can be created in a number of flavors. The following code snippet generates a calculated field that will show employee data as follows:

Karine Bosch (id 82176)

string formula = "<Formula>=FirstName&amp; \" \" &amp;LastName&amp; \" (id: \" &amp;EmployeeID&amp; \" \"</Formula>"
      + "<FieldRefs>"
      + "<FieldRef Name='FirstName' />"
      + "<FieldRef Name='LastName' />"
      + "<FieldRef Name='EmployeeID' />"
      + "</FieldRefs>";

string schemaCalculatedField = "<Field ID='<GUID>' Type='Calculated' Name='FullName' StaticName='FullName' 
   DisplayName='Full Name' ResultType='Text' Required='TRUE' ReadOnly='TRUE'>" + formula + "</Field>";
Field fullNameField = demoList.Fields.AddFieldAsXml(schemaCalculatedField, true, AddFieldOptions.AddInternalNameHint);
clientContext.ExecuteQuery();

You can read more on how to create other type of fields using CSOM here.

Tip

To learn how to provision a calculated field, I created this type of field in a blank site through the UI of SharePoint. Then I saved the site as template. This saves the site as a .wsp and stores it in the Solution gallery. From there you can download it and import it in Visual Studio.

Import wsp

Once the .wsp imported, you will notice that all fields, content types, list instances and other elements that make up your site are translated into CAML and features:

Import wsp 2

Click on the elements.xml in the Fields folder to see the definition of all fields.

 

May 14, 2015 Posted by | CSOM, SharePoint 2013 | Leave a comment

New release of CAML Designer available with new functionality!

042813_0652_Newversiono1.png

This weekend we released a new version of the CAML Designer.We solved the bug that occurred when more than one TaxonomyField was selected in the Where clause; but we also include the following new functionality:

– The <Membership> element

– REST snippets including a CAML query

We are very proud with these new additions, and we hope you like it!

 

 The <Membership> element

The <Membership> element already exists for ages but it’s only now that we introduce it in the CAML Designer.

Whenever you select a field of user type in the Where clause, you will now get an additional radio button “Membership”:

User field

When you click the Membership option, you get an additional dropdown where you can select one of the following options:

  • CurrentUserGroups: this will retrieve all tasks that are assigned to groups to which the current user belongs.
  • SPWeb.Groups: this will retrieve all tasks that are assigned to groups.
  • SPWeb.AllUsers: this will retrieve all tasks that have been assigned to users and not to groups.
  • SPWeb.Users: this will retrieve all tasks that have been assigned to users that don’t belong to a specific group but that have been granted access to the site directly.
  • SPGroup: this will retrieve tasks that have been assigned to a specific group.

User field - membership

We’ve also foreseen a small description field that explains the selected option:

User field - membership CurrentUserGroups

When selecting the SPGroup option, another additional dropdown becomes available. This dropdown will list all groups:

User field - membership SPGroup

Selecting a group from this dropdown will retrieve all tasks that have been assigned to the selected group. The CAML query looks like the following:

   <Where>
      <Membership Type='SPGroup' ID='5'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

When you select the CurrentUserGroups option, the CAML query will look as follows:

  <Where>
      <Membership Type='CurrentUserGroups'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

For the SPWeb.Groups option, the CAML query looks like this:

  <Where>
      <Membership Type='SPWeb.Groups'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

For the SPWeb.AllUsers options, the following CAML query is generated:

  <Where>
      <Membership Type='SPWeb.AllUsers'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

And when you select the SPWeb.Users option, you will get the following CAML:

  <Where>
      <Membership Type='SPWeb.Users'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

Credits go to Christoper Clementen with his blog post: http://christopherclementen.wordpress.com/2012/08/27/caml-query-membership-attribute where he clearly explains how the <Membership> element works.

 

REST snippets executing CAML queries

A second new functionality are the REST snippets with CAML queries. In most cases you can use the well known REST requests using the select and filter criteria to retrieve list items. But there are a few cases where such a REST request will return an error. These cases are:

  • filtering on taxonomy fields
  • the <Membership> element

In these cases your REST snippet must include a CAML query. Additionally this REST request needs to be executed as a POST. This is well described in this blog post of CleverWorkarounds.

Such a REST snippet generated by the CAML Designer looks like this:

 

$.ajax({ 
   url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('Developers')/GetItems(query=@v1)?"
        + "@v1={\"ViewXml\":\"<View><ViewFields><FieldRef Name='Title' /><FieldRef Name='FirstName' /><FieldRef Name='Company' /></ViewFields>"
        + "<Query><Where><Eq><FieldRef Name='Technology' /><Value Type='TaxonomyFieldType'>SharePoint</Value></Eq></Where></Query></View>\"}", 
   type: "POST", 
   headers: { 
         "X-RequestDigest": $("#__REQUESTDIGEST").val(), 
         "Accept": "application/json;odata=verbose", 
         "Content-Type": "application/json; odata=verbose" 
   }, 
   success: function (data) { 
      if (data.d.results) { 
         // TODO: handle the data  
         alert('handle the data'); 
      } 
   }, 
   error: function (xhr) { 
      alert(xhr.status + ': ' + xhr.statusText); 
   } 
});

When filtering on membership, the REST snippet will look like this:

$.ajax({ 
   url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('Tasks')/GetItems(query=@v1)?@v1={\"ViewXml\":\"<View><Query><Where><And><Neq><FieldRef Name='Status' /><Value Type='Choice'>Completed</Value></Neq><Membership Type='SPWeb.Groups'><FieldRef Name='AssignedTo' /></Membership></And></Where></Query></View>\"}", 
   type: "POST", 
   headers: { 
         "X-RequestDigest": $("#__REQUESTDIGEST").val(), 
         "Accept": "application/json;odata=verbose", 
         "Content-Type": "application/json; odata=verbose" 
   }, 
   success: function (data) { 
      if (data.d.results) { 
         // TODO: handle the data  
         alert('handle the data'); 
      } 
   }, 
   error: function (xhr) { 
      alert(xhr.status + ': ' + xhr.statusText); 
   } 
}); 

May 22, 2014 Posted by | CAML Designer, SharePoint 2013 | 3 Comments

Where are the SharePoint assemblies?

In SharePoint 2013, the SharePoint assemblies are located in the \Windows\Microsoft.NET\assembly\GAC_MSIL\ directory:

SharePoint 2013 assemblies

All other SharePoint files are located in what we call the 15 hive: \Program Files\Common Files\Microsoft Shared\Web Server Extensions\15.

When inspecting IIS, you will notice a change in the virtual directories: the _layouts folder still points to the 14 hive (containing the SharePoint 2010 files), while there is an additional sub folder that points to the 15 hive (containing the SharePoint 2013 files).

SharePoint virtual folders

 

When navigating through the file system, you can see that there is still a 14 hive, containing folders like TEMPLATE, TEMPLATE\CONTROLTEMPLATES and TEMPLATE\LAYOUTS. When you edit one of the controls in the CONTROLTEMPLATES folder, you will notice that they still reference the old SharePoint v14 assemblies. If you deploy your SharePoint 2010 solutions and features to your SharePoint 2013 farm, it is in this 14 hive that you will find your files.

But there is no ISAPI folder anymore in the 14 hive, nor are the assemblies in the Global Assembly Cache. Calls to the old assemblies are redirected to the SharePoint v15 assemblies. This redirection is defined in special policy files located in the \Windows\Microsoft.NET\assembly\GAC_MSIL\ directory:

SharePoint policy files

 

April 19, 2014 Posted by | SharePoint 2013 | 2 Comments

New release of CAML Designer available!

042813_0652_Newversiono1.png

My friend Andy Van Steenbergen and I worked very hard to prepare a new release of the CAML Designer. We didn’t aim at adding new functionality; we rather preferred to solve a number of bugs. You can download the new version on the BIWUG site under the Download tab.

We also have the impression that the server object model works much slower than the client object model. Therefore we switched the default work mode to client object model.

There are also a number of bugs that were reported but that are not included in this release:

  • The <Membership> element is missing
  • For complex queries, the CAML query can be included in the REST call
  • Taxonomy fields: only top-level of multi-select field was visible

The reason why these bugs are not solved yet, is because these bugs are not that simple to solve and we didn’t want to wait much longer with this release because of the fixes that are already in it. We will try to solve the remaining bugs as soon as possible.

We hope you enjoy the improvements. If you would encounter bugs or have a good idea on how we could further improve the CAML Designer, please don’t hesitate to report it to camlfeedback@biwug.be  or leave a note on this blog. Together we can make it a better tool.

Show hidden fields

In some cases you want to build a filter based on a value in a hidden field. By default the CAML Designer doesn’t show hidden fields. To solve this issue we added the check box “Show hidden fields” to the user interface just above the list treeview. By default the hidden fields are not displayed, but you can click this check box to get them displayed.

Show hidden fields

Long Display Names

One of our users als reported that there were issues with long display names. It would ask a lot of redesign to have this properly displayed so we chose to add a tooltip that shows the complete display name. We hope that this suits your needs.

Long display names

Boolean fields

There was also a problem with querying boolean fields. This issue is now solved:

  <Where>
      <Eq>
         <FieldRef Name='VeryTall' />
         <Value Type='Boolean'>1</Value>
      </Eq>
   </Where>

I tested and retested, and this query returns rows:

Boolean field query result

User and UserMulti fields

There were a number of problems when filtering on a User field or UserMulti field. These have been solved. Now, you can choose between filtering on the current user or on a specific user.

User field - current user

If you choose to filter on the current user, your query will look as follows:

   <Where>
      <Eq>
         <FieldRef Name='AssignedTo' />
         <Value Type='Integer'>
            <UserID />
         </Value>
      </Eq>
   </Where>

If you choose to filter on a specific user then you can enter or the name of the user or its ID. If you enter the name of a user, your query looks as follows.

   <Where>
      <Eq>
         <FieldRef Name='AssignedTo' />
         <Value Type='User'>Karine Bosch</Value>
      </Eq>
   </Where>

If, at the other side, you want to filter on the user ID, the following query is generated, indicating that you are passing an ID:

   <Where>
      <Eq>
         <FieldRef Name='AssignedTo' LookupId='True' />
         <Value Type='Integer'>4</Value>
      </Eq>
   </Where>

The FileRef field

There was also a problem when trying to filter on FileRef. When you now select the FileRef field on the Where tab, you get a lookup displaying all folders in the selected document library.

FileRef field

When you select a folder, the query is built as follows:

   <Where>
      <Eq>
         <FieldRef Name='FileRef' />
         <Value Type='Lookup'>/Shared Documents/Folder 2</Value>
      </Eq>
   </Where>

REST snippet for lookup fields

Based on a blog post of Andrew Connell “Applying Filters to Lookup Fields with the SharePoint 2013 REST API“, we also updated how the REST snippet for lookup fields is generated:

$.ajax({
   url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('Cities')/Items?$expand=Country/Title&$filter=Country/Title eq 'France'",
   type: "GET",
   headers: {"accept": "application/json;odata=verbose"},  
   success: onDataReturned; 
   error: onError; 
   function onDataReturned(data){ // TODO: handle the data }; 
   function onError(err) { // TODO: handle the error }
});

Taxonomy Fields

We also added functionality to retrieve list data based on the ID of a taxonomy field. The difficulty here is that a term has a name and an guid in the term store. You can retrieve list data based on a term name but this is dangereous as term names can change over time.

Taxonomy Field

   <Where>
      <Eq>
         <FieldRef Name='Technology' />
         <Value Type='TaxonomyFieldType'>SharePoint</Value>
      </Eq>
   </Where>

When a term is used to tag a list item, this term is stored in a hidden list on site collection level. At this time, the term gets an ID, which is stored internally as WssId.

This release of the CAML Designer now offers the possibility to query on this ID:

Taxonomy Field by ID

The query instantly changes as follows:

   <Where>
      <Eq>
         <FieldRef Name='Technology' LookupId='True' />
         <Value Type='Integer'>1</Value>
      </Eq>
   </Where>

It will retrieve the same list items as when you would have queried on the term “SharePoint”.

The Test tab

The Test tab displays now a label that indicates the number of rows that are returned by the result set. Once the number becomes visible, you know that the retrieval has finished. This is handy when your query does not return a result and no grid becomes visible. The number zero will indicate that the query is finished but that no results have been returned.

March 17, 2014 Posted by | CAML Designer, SharePoint 2013 | 4 Comments

SharePoint 2013 SP1 is available for download now!

The release of SP1 for SharePoint 2013 has been announced on February 25th. It is available for download from the Microsoft Download Center.

My colleague Tom Van Gaever did a nice job in listing all issues that have been solved in SP1.

You can find the official documentation here.

 

March 5, 2014 Posted by | SharePoint 2013 | Leave a comment

Declaring a taxonomy field that allows multiple values

There are two different types of taxonomy fields:

  • TaxonomyFieldType: This defines a taxonomy field to store a term to tag the list item.
  • TaxonomyFieldTypeMulti: This defines a taxonomy field that allows users to tag the list item with more than one term from the same term set.

When using CAML, you can declare taxonomy fields as follows:

  <Field  ID="{CB736B25-C980-4688-B076-1C5315B20092}" 
          Name="ProductCategory" DisplayName="Product Category" 
          Type="TaxonomyFieldType"
          ShowField="Term1033" 
          EnforceUniqueValues="FALSE" 
          Group="XYZ Site Columns" 
          SourceID="http://www.xyz.be/v1" />

  <Field  ID="{6E8C115D-43DE-4AD9-9DE4-27F5D4C008F4}" 
          Name="ProductCategories" DisplayName="Product Categories" 
          Type="TaxonomyFieldTypeMulti"
          ShowField="Term1033" 
          EnforceUniqueValues="FALSE" 
          Group="XYZ Site Columns" 
          SourceID="http://www.xyz.be/v1" />

This will create your field of the correct type but it will not be mapped to a term set yet. This cannot be done through CAML, but only through code, ideally in the feature receiver:

   field.SspId = termStore.Id;
   field.TermSetId = termSet.Id;
   field.AnchorId = Guid.Empty;

These properties mean:

  • TermSetId: This is the Guid of the term set to which the field is mapped. Users will only be able to tag the item with term(s) from this term set.
  • AnchorId: If the entry point for tagging is a term within the term set, the anchor id needs to be set to the guid of this term. If the entry point for tagging is any term within the term set, this property does not need to be set.
  • SspId: This is the Guid of the term store to which the term set belongs.

You would think that, when you create a field of type TaxonomyFieldTypeMulti, you would be able to store more than one term, but unfortunately that’s not the case.

When you inspect the field in the user interface, you see that the check box Allow multiple values is not checked.

It’s only after having checked this property that you are able to store more than one term using the TaxonomyFieldValueCollection. Unfortunately there is no attribute in CAML to set this checkbox, so you will have to solve this in your feature receiver by setting the AllowMultipleValues property:

if (field.TypeAsString == "TaxonomyFieldTypeMulti")

      field.AllowMultipleValues = true;

As of then you will be able to store multiple terms in the taxonomy field.

December 20, 2013 Posted by | SharePoint 2010, SharePoint 2013 | 9 Comments

Programmatically provisioning a TaxonomyField of type TaxonomyFieldTypeMulti

I had a hard time finding out how I could save multiple terms to a field of TaxonomyFieldTypeMulti using the SharePoint server object model.

The following code worked out for me:

        private static void SetTaxonomyValue(SPListItem item, Term term, string fieldName)

        {

            if (term != null && item != null && item.Fields[fieldName] != null)

            {

                if (item.Fields[fieldName].TypeAsString == "TaxonomyFieldType")

                {

                    TaxonomyField taxonomyField = item.Fields[fieldName] as TaxonomyField;

                    TaxonomyFieldValue taxvalue = new TaxonomyFieldValue(taxonomyField);

                    taxvalue.TermGuid = term.Id.ToString();

                    taxvalue.Label = term.Name;

                    item[fieldName] = taxvalue;

                }

                else if (item.Fields[fieldName].TypeAsString == "TaxonomyFieldTypeMulti")

                {

                    TaxonomyField taxonomyField = item.Fields[fieldName] as TaxonomyField;

                    TaxonomyFieldValueCollection taxvalues = 

item[fieldName] as TaxonomyFieldValueCollection;

                    if (taxvalues == null)

                        taxvalues = new TaxonomyFieldValueCollection(taxonomyField);

                    TaxonomyFieldValue taxvalue = new TaxonomyFieldValue(taxonomyField);

                    taxvalue.TermGuid = term.Id.ToString();

                    taxvalue.Label = term.Name;

                    taxvalues.Add(taxvalue);

                    item[fieldName] = taxvalues;

                }

            }

        }

 

When the taxonomy field is of type TaxonomyFieldTypeMulti, terms are stored as a TaxonomyFieldValueCollection. The trick is that you first have to test whether you already have a term available in the collection or not. If not, you have to initialize the collection, otherwise you will get a “null reference” exception when you try to add a term to the collection.

December 19, 2013 Posted by | SharePoint 2010, SharePoint 2013 | Leave a comment

Programmatically provisioning a term set for navigation

I’ve been experimenting with Managed Metadata and one of the things that I’ve been doing is automating the provisioning a navigation term set using the SharePoint server object model.

In your code you will have to add a reference to the Microsoft.SharePoint.Taxonomy.dll.

First you have to find the correct instance of the term store:

   TaxonomySession session = new TaxonomySession(site, true);

   TermStore termStore = session.TermStores[mmsName];

Then you have to find the correct group to which you want to add your term set. In SharePoint 2010 you could only create groups from within the Central Administration and the term sets in these groups could be accessed by any site collection with the correct permissions.

   termGroup = termStore.Groups[groupName];

But in SharePoint 2013 you can also have a group on site collection level. To retrieve this group you have to use a new method:

   termGroup = termStore.GetSiteCollectionGroup(site);

If you want the term set to be used for navigation, you have to add your term set to the local group:

   TermSet termSet = termGroup.CreateTermSet(termsetName);

And you also have to configure that this term set is going to be used for navigation:

   NavigationTermSet navigationTermSet = NavigationTermSet.GetAsResolvedByWeb(termset, site.RootWeb,

                    StandardNavigationProviderNames.CurrentNavigationTaxonomyProvider);

   navigationTermSet.IsNavigationTermSet = true;

Good to know that the NavigationTermSet class is defined in the Microsoft.SharePoint.Publishing.Navigation namespace of the Microsoft.SharePoint.Publishing.dll.

And then you can start creating the hierarchy of terms that is needed for your site navigation:

   Term term = termSet.CreateTerm(termName, 1033);

In the meantime you can also set the friendly URL.

   NavigationTerm navigationTerm = navigationTermset.Terms.Where(

nt => nt.Id == term.Id).FirstOrDefault();

   navigationTerm.FriendlyUrlSegment.Value = termUrl;

You could also set other properties like the Navigation Hover Text, the Simple Link Url or the Target Url. You could also define whether the term should be excluded for global navigation.

But I had a hard time finding how to change the Navigation Node Title programmatically. It seems that there is no property foreseen for this. I tried to set the Title property of the NavigationTerm but this is a read-only property and reflects the name of the term itself.

When working in the user interface, you can also see that you change the Navigation Node Title, which is set to the name of the term by default:

In Reflector I found that NavigationTerm derives from NavigationTermSetItem and that it has a method named SetCustomizableStringCustomValue(Enum, string) which is not publically accessible. The protected enum is defined as follows:

There you can see that there is indeed a property Title that could map the Navigation Node Title.

I found 2 methods on the Term class that I thought I could use: SetCustomProperty(string, string) and SetLocalCustomProperty(string, string). But both methods created a custom property (which is then visible on the Custom Properties tab).

My friend Tom Van Gaever, who works as a PFE at Microsoft Belgium, pointed out to me that you can set custom properties using the SetLocalCustomProperties method and that properties like the Navigation Node Title have a special name:

  • _Sys_Nav_Title: the Navigation Node Title
  • _Sys_Nav_FriendlyUrlSegment: the Friendly Url, also represented by the FriendlyUrlSegment property of the NavigationTerm class.
  • _Sys_Nav_TargetUrl: the target URL, also represented by the TargetUrl property of the NavigationTerm class.
  • _Sys_Nav_TargetUrlForChildTerms: the target URL for child terms, also represented by the TargetUrlForChildTerms property of the NavigationTerm class.
  • _Sys_Nav_CatalogTargetUrl: the catalog target URL, also represented by the CatalogTargetUrl property of the NavigationTerm class.
  • _Sys_Nav_CatalogTargetUrlForChildTerms: the catalog target URL for child terms, also represented by the CatalogTargetUrlForChildTerms property of the NavigationTerm class.

And that was the key to my solution. By executing the SetLocalCustomProperty method on the _Sys_Nav_Title property, I was able to set the Navigation Node Title:

   term.SetLocalCustomProperty("_Sys_Nav_Title", "core business");

If you use that same method with a property name that does not exist, it will create a custom property (which is then visible on the Custom Properties tab); just like the SetCustomProperty method does.

Save your changes to the term store when you’re finished:

   termStore.CommitAll();

December 13, 2013 Posted by | SharePoint 2013 | 9 Comments