Monday, January 25, 2016

Parsing SPAuditEntry.EventData

SPAuditEntry contains information about an audited event that is connected to a site collection, site, folder, list, or list item. It provides detailed information about event in EventData property. EventData return XML which is not readable for business users. It contains GUID's.  You can see the detail information about event type and value @ https://msdn.microsoft.com/EN-US/library/office/microsoft.sharepoint.spauditentry.eventdata.aspx

I was working on custom report where EventData needs to be updated with readable data. Following is the method I created to parse the event data.


private string ParseEventData(SPWeb web, SPAuditEntry auditEntry, out string eventName)
{
    XmlDocument xmlEventData = new XmlDocument();
    xmlEventData.LoadXml(string.Format("<EventData>{0}</EventData>", auditEntry.EventData));

    string eventData = "";
    eventName = "";
    try
    {
        int userId;
        int groupId;
        string groupName;
        SPUser user;
        SPGroup group;
        string url;
        string permissionName;
        SPBasePermissions permissions;

        switch (auditEntry.Event)
        {
            case SPAuditEventType.SecGroupCreate:
                groupName = xmlEventData.DocumentElement.SelectSingleNode("/EventData/title").InnerText;
                userId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/user").InnerText);
                user = web.AllUsers.GetByID(userId);

                eventData = string.Format("Group created with name {0} by {1}", groupName, user.Name);
                eventName = "Creation of a user group for a SharePoint site collection";
                break;
            case SPAuditEventType.SecGroupDelete:
                groupId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/groupid").InnerText);
                eventData = string.Format("Group deleted with ID {0}", groupId);
                eventName = "Deletion of a group that is associated with a SharePoint site collection.";
                break;
            case SPAuditEventType.SecGroupMemberAdd:
                if (eventData.Contains("<siteadmin"))
                {
                    groupName = "Site Collection Administrator";
                    userId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/user").InnerText);
                }
                else
                {
                    groupId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/groupid").InnerText);
                    group = web.Groups.GetByID(groupId);
                    groupName = group.Name;
                    userId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/userid").InnerText);
                }

                user = web.AllUsers.GetByID(userId);
                eventData = string.Format("Added {0} to the group {1}", user.Name, groupName);
                eventName = "Addition of a new member to a group that is associated with a SharePoint site collection";
                break;
            case SPAuditEventType.SecGroupMemberDel:

                if (eventData.Contains("<siteadmin"))
                {
                    groupName = "Site Collection Administrator";
                }
                else
                {
                    groupId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/groupid").InnerText);
                    group = web.Groups.GetByID(groupId);
                    groupName = group.Name;
                }
                userId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/user").InnerText);
                user = web.AllUsers.GetByID(userId);
                eventData = string.Format("Removed {0} from group {1}", user.Name, groupName);
                eventName = "Deletion of a member from a group that is associated with a SharePoint site collection";
                break;
            case SPAuditEventType.SecRoleBindBreakInherit:
                url = xmlEventData.DocumentElement.SelectSingleNode("/EventData/url").InnerText;
                eventData = string.Format("Inheritance is break for : {0}", url);
                eventName = "Turning off inheritance of role (that is, permission level) definitions from the parent of the object.";
                break;
            case SPAuditEventType.SecRoleBindInherit:
                url = xmlEventData.DocumentElement.SelectSingleNode("/EventData/url").InnerText;
                eventData = string.Format("Set Inheritance permission level for subsite : {0}", url);
                eventName = "Turning on inheritance of security settings from the parent of the object.";
                break;
            case SPAuditEventType.SecRoleBindUpdate:
                int principalId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/principalid").InnerText);

                var eventRoleName = "";
                try
                {
                    int roleId = Convert.ToInt32(xmlEventData.DocumentElement.SelectSingleNode("/EventData/roleid").InnerText);
                    if (roleId != -1)
                    {
                        SPRoleDefinition eventRole = web.RoleDefinitions.GetById(roleId);
                        eventRoleName = eventRole.Name;
                    }
                }
                catch { }
                string operation = xmlEventData.DocumentElement.SelectSingleNode("/EventData/operation").InnerText.Replace("ensure", "");

                try
                {
                    SPUser eventUser = web.SiteUsers.GetByID(principalId);
                    eventData = operation.Trim() + " " + eventRoleName + " permissions for " + (eventUser.Name.Length > 0 ? eventUser.Name : eventUser.LoginName);
                }
                catch
                {
                    SPGroup eventGroup = web.Groups.GetByID(principalId);
                    eventData = string.Format("{0} {1} permissions for {2}", operation.Trim(), eventRoleName, (eventGroup.Name.Length > 0 ? eventGroup.Name : eventGroup.LoginName));

                }
                eventName = "Changing the permissions of a user or group for the object";
                break;
            case SPAuditEventType.SecRoleDefCreate:
                permissionName = xmlEventData.DocumentElement.SelectSingleNode("/EventData/name").InnerText;
                permissions = (SPBasePermissions)Enum.Parse(typeof(SPBasePermissions), xmlEventData.DocumentElement.SelectSingleNode("/EventData/perm").InnerText);
                eventData = string.Format("{0} permission level created with permissions : {1}", permissionName, permissions);
                eventName = "Creation of a new role (that is, permission level) definition associated with the object";
                break;
            case SPAuditEventType.SecRoleDefModify:
                permissionName = xmlEventData.DocumentElement.SelectSingleNode("/EventData/name").InnerText;
                permissions = (SPBasePermissions)Enum.Parse(typeof(SPBasePermissions), xmlEventData.DocumentElement.SelectSingleNode("/EventData/perm").InnerText);
                eventData = string.Format("{0} permission level updated with permissions : {1}", permissionName, permissions);
                eventName = "Changing a role (that is, permission level) definition associated with an object";
                break;
            case SPAuditEventType.SecRoleDefDelete:
                int permLevel = int.Parse(xmlEventData.DocumentElement.SelectSingleNode("/EventData/id").InnerText);
                eventData = string.Format("Permission Level Delete with ID {0}", permLevel);
                eventName = "Changing a role (that is, permission level) definition associated with an object";
                break;
            default:
                eventName = "";
                break;
        }

    }
    catch
    {
        eventData = auditEntry.EventData;
        eventName = auditEntry.Event.ToString();
    }
    return eventData;
}


Above parsing method works with most of the scenarios. Feel free to add other scenarios in the comment section. I hope this will be useful for someone.

Thursday, July 30, 2015

Using T4 Text Templates In SharePoint To Get Lists & Columns Name

T4 text template is mixture of text block and control logic that can generate the file in Visual Studio. First time I noticed text templates when I was working with Entity Framework. T4 text template is one of the best hidden feature of Visual Studio.  It can help you to generate the file on the fly and automate the task. You can even generate the .net classes. You can get more details about T4 text templates here.

As a developer you need SharePoint column internal names while writing code for various operations. You can get the internal name for a column by browsing to the List Settings > Edit Columns. Column internal name is encoded as querystring "Field" in URL. For example:

/_layouts/15/FldEdit.aspx?List=%7B004B80BD-040C-4B68-8C86-F140F2C4F373%7D&Field=StartDate

I wanted to generated C# utility class using T4 text template which contains Web, Lists and columns related information. Following is the template I use to get Web, Lists and Columns related information while SharePoint development:

<#@ template language="C#" #>
<#@ output extension="cs" #>
<#@ Assembly Name="System.Core" #>
<#@ Assembly Name="Microsoft.SharePoint.Client" #>
<#@ Assembly Name="Microsoft.SharePoint.Client.Runtime" #>
<#@ Import Namespace="Microsoft.SharePoint.Client" #>
<#@ Import Namespace="System.Text" #>
<#@ Import Namespace="System.Text.RegularExpressions" #>
//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using Microsoft.SharePoint.Client;
using System.Text;

namespace Custom.SharePoint
{
    /// <summary>
    /// Custom class like SharePoint SPBuiltinFieldId with you own columns
    /// </summary>
    public static partial class Constants
    {
<#
    string siteUrl = "http://Sharepoint:1111";

    ClientContext clientContext = new ClientContext(siteUrl);
    Web oWebsite = clientContext.Web;
    ListCollection collList = oWebsite.Lists;
   
    clientContext.Load(oWebsite);
    clientContext.Load(collList);
    clientContext.ExecuteQuery();
#>
        /// <summary>
     /// <para>Title : <#= oWebsite.Title #></para>
     /// </summary>
     public static string WebTitle = "<#= oWebsite.Title #>";

     /// <summary>
     /// <para>Id : <#= oWebsite.Id.ToString() #></para>
     /// </summary>
     public static Guid WebId = new Guid("<#= oWebsite.Id.ToString() #>");

     /// <summary>
     /// <para>Url : "<#= oWebsite.Url #>" </para>
     /// </summary>
     public static string WebUrl = "<#= oWebsite.Url #>";
<#

    foreach (List oList in collList)
    {
        if(!oList.Hidden)
        {
            var listName = Regex.Replace(oList.Title, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
            clientContext.Load(oList.RootFolder);
            clientContext.ExecuteQuery();
#>
  /// <summary>
  /// <para>ID : <#= oList.Id.ToString() #></para>
  /// <para>Type : <#= oList.BaseType #> </para>
  /// <para>Title : <#= oList.Title #></para>
        /// <para>Url : "<#= oWebsite.Url #><#= oList.RootFolder.ServerRelativeUrl #>" </para>
  /// </summary>
  public class <#= listName #>
  {
   /// <summary>
   /// <para>Title : <#= oList.Title #></para>
   /// </summary>
   public static string Title = "<#= oList.Title #>";

   /// <summary>
   /// <para>Id : <#= oList.Id.ToString() #></para>
   /// </summary>
   public static Guid Id = new Guid("<#= oList.Id.ToString() #>");

   /// <summary>
   /// <para>ServerRelativeUrl : "<#= oList.RootFolder.ServerRelativeUrl #>" </para>
   /// </summary>
   public static string ServerRelativeUrl = "<#= oList.RootFolder.ServerRelativeUrl #>";

   /// <summary>
   /// <para>Url : "<#= oWebsite.Url #><#= oList.RootFolder.ServerRelativeUrl #>" </para>
   /// </summary>
   public static string Url = "<#= oWebsite.Url #><#= oList.RootFolder.ServerRelativeUrl #>";

<# 
            FieldCollection fields = oList.Fields;
            clientContext.Load(fields);
            clientContext.ExecuteQuery();
#>
   public class Fields
   {
<#
            foreach (Field field in fields)
            {
                if (!field.FromBaseType)
                {
#>
    /// <summary>
    /// <para>Title : <#= field.Title #></para>
    /// <para>InternalName : <#= field.InternalName #></para>
    /// <para>Id : <#= field.Id.ToString() #></para>
                /// <para>Type : <#= field.TypeAsString #></para>
    /// </summary>
    public static Guid _<#= field.InternalName #> = new Guid("<#= field.Id.ToString() #>");

<#
                }
            }
#>
      }
     }
<#
        }
    }
#>
    }
}


Above template generates the following class:

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using Microsoft.SharePoint.Client;
using System.Text;

namespace Custom.SharePoint
{
    /// <summary>
    /// Custom class like SharePoint SPBuiltinFieldId with you own columns
    /// </summary>
    public static partial class Constants
    {
        /// <summary>
        /// <para>Title : HemantTest</para>
        /// </summary>
        public static string WebTitle = "Web Title";

        /// <summary>
        /// <para>Id : e8ee6e88-45d2-41f7-b234-f68602fc2f31</para>
        /// </summary>
        public static Guid WebId = new Guid("e8ee6e88-45d2-41f7-b234-f68602fc2f31");

        /// <summary>
        /// <para>Url : "http://Sharepoint:1111" </para>
        /// </summary>

        public static string WebUrl = "http://Sharepoint:1111";
....
/// <summary> /// <para>ID : 0aa3da1b-b1a4-42b6-9136-008f0d462b80</para> /// <para>Type : GenericList </para> /// <para>Title : TestList</para> /// <para>Url : "http://Sharepoint:1111/Lists/TestList" </para> /// </summary> public class TestList { /// <summary> /// <para>Title : TestList</para> /// </summary> public static string Title = "TestList"; /// <summary> /// <para>Id : 0aa3da1b-b1a4-42b6-9136-008f0d462b80</para> /// </summary> public static Guid Id = new Guid("0aa3da1b-b1a4-42b6-9136-008f0d462b80"); /// <summary> /// <para>ServerRelativeUrl : "/Lists/TestList" </para> /// </summary> public static string ServerRelativeUrl = "/Lists/TestList"; /// <summary> /// <para>Url : "http://Sharepoint:1111/Lists/TestList" </para> /// </summary> public static string Url = "http://Sharepoint:1111/Lists/TestList"; public class Fields { /// <summary> /// <para>Title : Field_SingleLineText</para> /// <para>InternalName : Field_SingleLineText</para> /// <para>Id : 386a0aff-9acb-4e86-a619-b56fdfc8124d</para> /// <para>Type : Text</para> /// </summary> public static Guid _Field_SingleLineText = new Guid("386a0aff-9acb-4e86-a619-b56fdfc8124d"); /// <summary> /// <para>Title : Field_MultiLineText</para> /// <para>InternalName : Field_MultiLineText</para> /// <para>Id : 497e52a5-e87b-46c3-857e-b15effc46659</para> /// <para>Type : Note</para> /// </summary> public static Guid _Field_MultiLineText = new Guid("497e52a5-e87b-46c3-857e-b15effc46659"); .... /// <summary> /// <para>Title : Taxonomy Catch All Column</para> /// <para>InternalName : TaxCatchAll</para> /// <para>Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611</para> /// <para>Type : LookupMulti</para> /// </summary> public static Guid _TaxCatchAll = new Guid("f3b0adf9-c1a2-4b02-920d-943fba4b3611"); } } } }

Now you can using intellisense to get List, Column Id .







Simply add "SharePointObjects.tt" file to your project in Visual Studio. Update the Site url and save. It will generate the C# class.

Tuesday, May 5, 2015

CAML Queries For SharePoint Fields


Collaborative Application Markup Language (CAML) is an XML based markup language to define queries against list data. You can use CAML query to get the filtered, grouped or sorted data from SharePoint list.  CAML query can be used to retrieve SharePoint data in SharePoint object model, Web Service as well as PowerShell.
Following is the basic structure of CAML query:


Followings are the operators to use under Where element:

Logical Operators

And Or are the logical operators you can use to filter the data on multiple fields.

Comparison Operators


Operator Meaning
Eq = (Equal To)
Neq <> (Not Equal To)
Lt < (Less Than)
Gt > (Greater Than)
Geq >= (Greater Than Or Equal To)
Leq <= (Less Than Or Equal To)
Contains Like
IsNull Null
IsNotNull Not Null
BeginsWith Beginning with the word
DateRangesOverlap Compare the dates in a recurring event with a specified
DateTime value, to determine whether they overlap


Order/Group Operators


Operator Meaning
OrderBy Specify the sort order. Query can be sort by multiple fields.
GroupBy Specify the grouping for data. Data can be group by multiple fields

Followings are few snippets of CAML query to filter on the different types of fields in SharePoint. 

Text, Choice, Number, Currency, Boolean


Replace the DataType in the query. Followings are the datatypes:
DataType Field
Text Single line of text
Choice Choice field (menu to choose from)
Number Number field (1, 1.0, 100)
Currency Currency field ($, ¥, €)
Boolean Yes/No field (check box)
1 = Yes
0 = No


Date and Time


DateTime datatype can be used with multiple options. It can work with provided Date/DateTime or time part Today

Using Today



Lookup




Person


By ID


By Name



Taxonomy




Thursday, April 23, 2015

Preserve properties after overriding document

The value from SharePoint columns for Office documents is actually stored in the document. When user updates the column values in the SharePoint document library(Edit Form), the values are updated in document itself. Columns included in the content type are represented as properties in the content type schema stored in the document. They are identified in the document Management node of the properties element in the schema. These document properties map to document library columns, represented by Field elements in the content type definition stored in the document library.

When user overrides document with different properties in SharePoint document library then it overwrites the current values.  Following are the ways to preserve the old column values:


  • Checkout and update document content
    Checkout the document and open it from SharePoint. Update the document content with new data and check in.
  • Edit document and update the properties
    You can open the document in Client(Word, Excel etc) and update the the properties. After that upload the document to SharePoint.


SharePoint does not provide any event to check if document is overrode. One of my client wanted to preserve the old values after override the document. I came up with following code to preserve the values. Its not full proof. It only works with new document for which properties are not set or the document is not uploaded to SharePoint previously. Same time Title field should be mandatory field. This code fires on ItemUpdated list item event. Following is the code:


Above code helped me to preserve the properties in case user is uploading new document without setting properties. I hope this will help someone. Add comment if anyone knows other workaround.

Some useful JavaScript methods and objects in Sharepoint

SharePoint provides some useful methods and objects in JavaScript. Followings are some methods and objects which I use frequently in my client side code:

SP.ScriptUtility

SP.ScriptUtility provides methods and an emptyString field. It contains following methods:


  • isNullOrEmptyString - Checks if the specified text is null, an empty string or undefined
  • isNullOrUndefined - Checks if the specified object is null or undefined.
  • isUndefined - Checks if the specified object is undefined.
  • truncateToInt - Returns largest integer value that is less than or equal to the specified number if the number is greater than 0 else returns the smallest integer value that is greater than or equal to the number.

SP.Guid

SP.Guid.newGuid().toString() return new guid.

_spPageContextInfo

_spPageContextInfo provides following properties:
  • pageListId - Get Current List Guid
  • pageItemId - Get id for current item. 
  • serverRequestPath - Get current server requested relative path. 
  • siteAbsoluteUrl - Get current site absolute URL
  • siteServerRelativeUrl - Get current site relative url
  • userId - Get current user ID
  • webTitle - Get current web Title
  • webAbsoluteUrl - Get current web absolute Url
  • webServerRelativeUrl - Get web server relative url. For exmaple "/teams/SITE_NAME"

escapeProperly(str)

escapeProperly function returns the encoded value for provided string.

unescapeProperly(str)

unescapeProperly function returns decoded string for provided encoded string.

Cookies

GetCookie(str)

GetCookie function returns the value for the cookie. It returns null in case cookie is not available.

DeleteCookie(str)

DeleteCookie function remove the cookie by setting expiry to minimum DateTime. 

SetCookie(str, str)

SetCookie function adds session cookie. But this is not very useful function as it adds value as true or false.  Its only useful if you want to set a flag. Second parameter is optional. If you pass second parameter then value of cookie will be "true" else its "false".

JSRequest

You can use JSRequest object to get the FileName, PathName and QueryString. To use the JSRequest object, you must initialize it using EnsureSetup method.


String.format(str, arguments)

You can apply formatting to string using format method.


GetUrlKeyValue

GetUrlKeyValue is very useful function when you want to get the querystring values. It can return the querystring value from current url or provided url. Followings are the ways you can use GetURLKeyValue method:

GetUrlKeyValue(str)

It will return the querystring value for provided parameter name from current url. 

GetUrlKeyValue(str, bool)

It will return the encoded or decoded querystring value for provided parameter name from current url on the basis of second parameter. 

GetUrlKeyValue(str, bool, str)

It will return the encoded or decoded querystring value for provided parameter name from given url.


STSHtmlEncode(str)

STSHtmlEncode function ecodes the provided HTML string.

STSHtmlDecode(str)

STSHtmlDecode function decodes the provided string into HTML.

Wednesday, April 15, 2015

Creating SharePoint Columns Programatically Using C#

As a SharePoint developer you can use SharePoint API to create columns programmatically in Lists or Document Libraries. You can set the different properties for the column. Followings are few snippets to create SharePoint column programmatically using C#.

Single line of text

Represents a column that contains a single line of text.


Multiple lines of text

Represents a column that contains a multiple line of text.

Choice (menu to choose from)

Represents a column that contains choices. It can be single select or multiple select. For single select Choice field renders Dropdown list or Radio button list and for multiple select it renders Checkbox list.

Number (1, 1.0, 100)

Represents a column that contains numbers. You can use DisplayFormat property to set the allowed number of decimal.

Currency ($, ¥, €)

Represents a column that contains currency. You can use DisplayFormat property to set the allowed number of decimal. You can use CurrencyLocaleId property to set the currency symbol. You can find full list of Locale Ids here

Date and Time

Represents a column that contains a DateTime value. You can use DisplayFormat property to set the date format.

Lookup (information already on this site)

Represents a column that contains a information from other List/Document Library from current site.

Yes/No (check box)

Represents a column that contains Boolean value.

Person or Group

Represents a column that contains a user or group information.

Hyperlink or Picture

Represents a column that contains a Url information. It can be Hyperlink or Picture Url.

Calculated (calculation based on other columns)

Represents a column that contains a calculated value based on other columns.

Managed Metadata

Represents a column that contains information from Term Store.

Tuesday, March 31, 2015

Processing message for long code operations in SharePoint

As a SharePoint Developer you may need to show spinning wheel or Processing sign while lengthy code operations. You have probably seen this while creating new web application or site collection in Central Administration. You may need to show the processing messages for server side or client side long operation code. Showing processing message improves the user experience. SharePoint provides some nice ways to show processing message while long running code operations.

Server side code


On Server side, if you are executing some code that takes longer to process then you can use SPLongOperation Class to show processing request message. SPLongOperation would require redirecting the user to the destination page. Destination page could be same page or confirmation page. Following is the sample code to get the processing message for server side code:

Once long code is executed, you can end the operation by calling End() method. If you are calling SPLongOperation in modal popup then call EndScript() method to close the current dialog box. For example:


Client side code


For client side long operations, SharePoint provides following options to show wait screen dialog:

SP.UI.ModalDialog.showWaitScreenSize(title, message, callbackFunc, height, width) Method

Displays a wait/loading modal dialog with the specified title, message, height and width. Height and width are defined in pixels. Cancel button is shown. If user clicks it, the callbackFunc is called.

SP.UI.ModalDialog.showWaitScreenWithNoClose(title, message, height, width) Method

Displays a wait/loading modal dialog with the specified title, message, height and width. Height and width are defined in pixels. Cancel/close button is not shown.
Following is the code example to show the wait dialog on client side: