Showing posts with label Code. Show all posts
Showing posts with label Code. Show all posts

Tuesday, January 15, 2013

Event receiver with Elevated Privileged

Scenario:
Client wanted to run the event receiver code with elevated access.

Solution:
Using Elevated Privileged is one option but better options was using System User Token
Code:

/// 
        /// An item is being added.
        /// 
        public override void ItemAdding(SPItemEventProperties properties)
        {
            SPSite site = properties.Web.Site;
            SPUserToken sysToken = site.SystemAccount.UserToken;
            site.Dispose();

            using (var systemSite = new SPSite(properties.SiteId, sysToken))
            {
                using (SPWeb sysWeb = systemSite.OpenWeb(properties.Web.ID))
                {
                       // Code goes here 
                }
            }

Thursday, March 1, 2012

SharePoint Powershell List Items

Scenario: SharePoint automation scripts related to list items.
Code:

#Load SharePoint Snap In
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
function xApproveAll-SPItems
{  
 param ($Url, $ListName) 

$web = Get-SPWeb $Url 
$list = $web.Lists.TryGetList($ListName)

$folders = $list.Folders
foreach ($folder in $folders)
{
     $folder["_ModerationStatus"] = 0
     $folder.Update()
}  

$items = $list.Items
foreach ($item in $items)
{
     Write-Host "Approving : " + $intIndex 
     $item["_ModerationStatus"] = 0
     $item.Update()
}  

 $web.Dispose()
 Write-Host "Approved all the documents successfully" -foregroundcolor Green  
}

function xDeleteAll-SPItems
{  
 param ($Url, $ListName) 

$web = Get-SPWeb $Url
$list = $web.Lists.TryGetList($ListName)
$itemCount = $list.ItemCount
for($intIndex = $itemCount; $intIndex -gt 0; $intIndex--) 
{ 
        Write-Host "Deleting : " + $intIndex 
        $list.Items[$intIndex-1].Delete();
}

 $web.Dispose()

}
$url=Read-Host "Enter site url"
$listName=Read-Host "Enter list library name"

xApproveAll-SPItems $url $listName
xDeleteAll-SPItems $url $listName

Friday, December 9, 2011

Blocking updates for document library

Scenario:
For one of the new requirement we needed to block all updates to documents in document library

1. User should able to upload a new document
2. User should not be able to upload a document with the same file name as existing document
3. User should not be able to edit metadata once it has been entered.

Solution:
Simple event receiver did the trick but as always with some learning / gotta's.

Assuming 2 different situations
1. Simple document library with no extra metadata columns : In this we will need ItemAdded event to update the event once.

2. Document library with extra properties : We dont need ItemAdded event and code can be commented.

Code:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace SKN
{
class EventHandler1 : SPItemEventReceiver
{
public override void ItemAdded (SPItemEventProperties properties)
{
base.ItemAdded(properties);

//DisableEventFiring();
//properties.ListItem.Update();
//EnableEventFiring();
}

public override void ItemUpdating (SPItemEventProperties properties)
{
base.ItemUpdating(properties);

if (!string.Equals(properties.ListItem["Created"], properties.ListItem["Modified"]))
{
properties.Cancel = true;
properties.ErrorMessage = "no update";
}
}
}
}

Sunday, December 4, 2011

SharePoint reading version programatically

Scenario:
Recently doing a new project we needed to display version information of a list but we decided to make it more generic to support both document library and lists. There were lot of learning , I would say.. so I thought of sharing as always

Lesson Learned:
1. List version information + current version information can easily be retrieved using 'SPListItemVersionCollection' class

2. List version doesn't support version comment

3. SPListItemVersionCollection class is sorted by version number

4. Library version information can retrieved using 'SPFileVersionCollection' class but this will not return current version information.

5. Library version does support a version comment if same name file is uploaded. No comment is supported if 'Edit Properties' feature is used.

6. For Library , SPFileVersionCollection class is not guaranteed to be sorted by version number. So additional headache need to be taken to sort them.

7. For Library , Title column information/changes are stored in "vti_title" column.

About Helper Class Code:
Helper class below supports

1. Retrieving versions information for list/library

2. Retrieving all the changes for one particular field only

3. Support returning all versions / only versions which have changes for a particular field.

Helper Class Code:

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace SKN
{
public static class SharePointHelper
{
/// <summary>
/// Adds columns to the table.
/// </summary>
/// <param name="dtVersions">Version History Columns.</param>
public static void AddColumns (DataTable dtVersions)
{
dtVersions.Columns.Add("Source");
dtVersions.Columns.Add("Item");
dtVersions.Columns.Add("ColumnOldValue");
dtVersions.Columns.Add("ColumnValue");

dtVersions.Columns.Add("Version");
dtVersions.Columns.Add("Modified");
dtVersions.Columns.Add("Modified By");
dtVersions.Columns.Add("Comments");
}

/// <summary>
/// Redirection method for list\library
/// </summary>
/// <param name="dtVersions"></param>
/// <param name="web"></param>
/// <param name="listName"></param>
/// <param name="fieldName"></param>
/// <param name="showOnlyChanges">Show only rows which have changes</param>
/// <returns></returns>
public static DataTable GetAllVersions (SPWeb web, string listName, string fieldName, bool showOnlyChanges)
{
DataTable dtVersions = new DataTable();
AddColumns(dtVersions);

SPList spList = web.Lists[listName];
{
if (spList.BaseTemplate == SPListTemplateType.DocumentLibrary)
{
return GetAllLibraryVersions(dtVersions, web, listName, fieldName, showOnlyChanges);
}
else
{
return GetAllListVersions(dtVersions, web, listName, fieldName, showOnlyChanges);
}
}
}

/// <summary>
/// Show all versions of a list
/// </summary>
/// <param name="dtVersions"></param>
/// <param name="web"></param>
/// <param name="listName"></param>
/// <param name="columnName"></param>
/// <param name="showOnlyChanges">Show only rows which have changes</param>
/// <returns></returns>
public static DataTable GetAllListVersions (DataTable dtVersions, SPWeb web, string listName, string columnName, bool showOnlyChanges)
{
SPList spList = web.Lists[listName];
SPListItemCollection listItemColl = spList.Items;

// for each item
foreach (SPListItem listItem in listItemColl)
{
string columnOldValue = "NULL";
bool relavantRow = true;

SPListItemVersionCollection versionColl = listItem.Versions;
for (int i = versionColl.Count - 1; i >= 0; i--)
{
try
{
DataRow dr = dtVersions.NewRow();
dr["Source"] = listName;
dr["Item"] = listItem.Title;

dr["Version"] = versionColl[i].VersionLabel;
dr["Modified By"] = versionColl[i].CreatedBy.User.Name;
dr["Modified"] = GetDateDisplay(web, versionColl[i].Created);

dr["Comments"] = string.Empty;

dr["ColumnOldValue"] = columnOldValue;

if (null != versionColl[i][columnName])
{
dr["ColumnValue"] = versionColl[i][columnName];
}
else
{
dr["ColumnValue"] = "NULL";
}
if (showOnlyChanges)
{
relavantRow = !string.Equals(columnOldValue, dr["ColumnValue"].ToString(), StringComparison.InvariantCultureIgnoreCase);
}
columnOldValue = dr["ColumnValue"].ToString();

if (relavantRow)
{
dtVersions.Rows.InsertAt(dr, 0);
}
}
catch
{
}
}
}

return dtVersions;
}

/// <summary>
/// Show all versions of a document library
/// </summary>
/// <param name="dtVersions"></param>
/// <param name="web"></param>
/// <param name="listName"></param>
/// <param name="columnName"></param>
/// <param name="showOnlyChanges">Show only rows which have changes</param>
/// <returns></returns>
public static DataTable GetAllLibraryVersions (DataTable dtVersions, SPWeb web, string listName, string columnName, bool showOnlyChanges)
{
SPList spList = web.Lists[listName];
SPListItemCollection listItemColl = spList.Items;

foreach (SPListItem doc in listItemColl)
{
string columnOldValue = "NULL";
bool relavantRow = true;
string updatedColumnName = columnName;

if (columnName.Equals("Title"))
{
updatedColumnName = "vti_title";
}

SPFileVersionCollection versionColl = doc.File.Versions;
Dictionary<int, string> myDict = new Dictionary<int, string>();

for (int i = 0; i < versionColl.Count; i++)
{
myDict.Add(i,versionColl[i].VersionLabel);
}

var sortedDict = (from entry in myDict orderby entry.Value ascending select entry).ToDictionary(pair => pair.Key, pair => pair.Value);

foreach(var k in sortedDict)
{
try
{
DataRow dr = dtVersions.NewRow();
dr["Source"] = doc.File.ParentFolder;
dr["Item"] = doc.File.Name;
dr["Version"] = versionColl[k.Key].VersionLabel;
dr["Modified By"] = versionColl[k.Key].Properties["vti_modifiedby"];
dr["Modified"] = GetDateDisplay(web, versionColl[k.Key].Properties["vti_timelastmodified"]);

dr["ColumnOldValue"] = columnOldValue;

if (null != versionColl[k.Key].Properties[updatedColumnName])
{
dr["ColumnValue"] = versionColl[k.Key].Properties[updatedColumnName].ToString();
}
else
{
dr["ColumnValue"] = "NULL";
}

if (showOnlyChanges)
{
relavantRow = !string.Equals(columnOldValue, dr["ColumnValue"].ToString(), StringComparison.InvariantCultureIgnoreCase);
}

columnOldValue = dr["ColumnValue"].ToString();

if (null != versionColl[k.Key].Properties["vti_sourcecontrolcheckincomment"])
{
dr["Comments"] = versionColl[k.Key].Properties["vti_sourcecontrolcheckincomment"];
}

if (relavantRow)
{
dtVersions.Rows.InsertAt(dr, 0);
}
}
catch
{
}
}

DataRow drLatestVersion = dtVersions.NewRow();
drLatestVersion["Source"] = doc.File.ParentFolder;
drLatestVersion["Item"] = doc.File.Name;
drLatestVersion["Modified By"] = doc.File.ModifiedBy;
drLatestVersion["Modified"] = DateTime.Parse(doc["Modified"].ToString()).GetDateTimeFormats()[43];

if (null != doc["Version"])
{
drLatestVersion["Version"] = doc["Version"];
}

if (null != doc["_CheckinComment"])
{
drLatestVersion["Comments"] = doc["_CheckinComment"].ToString().Split('#')[1];
}

// Old Value from stored variable
drLatestVersion["ColumnOldValue"] = columnOldValue;

// Checking for new value
if (null != doc[columnName])
{
drLatestVersion["ColumnValue"] = doc[columnName].ToString();
}
else
{
drLatestVersion["ColumnValue"] = "NULL";
}

if (showOnlyChanges )
{
relavantRow = !string.Equals(columnOldValue, drLatestVersion["ColumnValue"].ToString(), StringComparison.InvariantCultureIgnoreCase);
}

columnOldValue = drLatestVersion["ColumnValue"].ToString();

if (relavantRow)
{
dtVersions.Rows.InsertAt(drLatestVersion, 0);
}
}

return dtVersions;
}

public static string GetDateDisplay (SPWeb web, object p)
{
return SPUtility.FormatDate(web, DateTime.Parse(p.ToString()), SPDateFormat.DateTime).ToString();
}
}
}

Tuesday, November 8, 2011

SP2010 : Validating file name before uploading in code

Scenario:
I wrote another blog post file name and folder name restriction in sharepoint.

and how can you clean a file name cleaning-up-file-names-for-uploading

With a new client we had similar requirement and I discovered a new way to validate filename in SP2010. Also there are many other helpful methods.

Solution:

SPUrlUtility.IsLegalFileName(filename);

Friday, September 30, 2011

Deleting all items from lists

Scenario:
Often during data migration we need to do clean up list data. I had something similar but there were multiple lists and each list with 5000-10000 records.

Solution:
Quick console application did the trick of cleaning all the lists.
>> Create a new console application and add reference to Microsoft.SharePoint.dll
>> Use the below code.

Code: [ Applies To : WSS3.0 / Sharepoint 2010 Foundation ]

using System;
using Microsoft.SharePoint;
using System.Collections.Generic;
using System.Text;

namespace SKN.CleanSPLists
{
class Program
{
static void Main (string[] args)
{
string webUrl = "http://ws2003";
bool deleteFromRecycleBin = true;
List<string> listNames = new List<string>();
listNames.Add("List Title 1");
listNames.Add("List Title 2");
listNames.Add("List Title 3");
listNames.Add("List Title 4");

using (SPSite site = new SPSite(webUrl))
{
using (SPWeb web = site.OpenWeb())
{
foreach (var listName in listNames)
{
SPList list = web.Lists[listName];

Console.WriteLine("Purging list: " + list.Title);
Console.WriteLine("Base Type: " + list.BaseType.ToString());

StringBuilder sbDelete = BuildBatchDeleteCommand(list);
web.ProcessBatchData(sbDelete.ToString());

Console.WriteLine("Done cleaning list : Presss a key to continue with next list.");
Console.WriteLine("");
Console.ReadKey();
}


if (deleteFromRecycleBin)
{
Console.WriteLine("Cleaning Recycle bin");
web.RecycleBin.DeleteAll();
site.RecycleBin.DeleteAll();
Console.WriteLine("Done cleaning Recycle bin");
Console.ReadKey();
}
}
}
}


/// <summary>
/// Builds a batch string with a list of all the items that are to be deleted.
/// </summary>
/// <param name="spList"></param>
/// <returns></returns>
private static StringBuilder BuildBatchDeleteCommand (SPList spList)
{
StringBuilder sbDelete = new StringBuilder();
sbDelete.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch>");
string command = "<Method><SetList Scope=\"Request\">" + spList.ID +
"</SetList><SetVar Name=\"ID\">{0}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar></Method>";

foreach (SPListItem item in spList.Items)
{
sbDelete.Append(string.Format(command, item.ID.ToString()));
}
sbDelete.Append("</Batch>");
return sbDelete;
}
}
}
Article: Batch command , SP List Delete

Wednesday, June 1, 2011

Displaying paged discussion board

Scenario:
We were working on to display item from a discussion list from a paged result set.

Solution:
While I am still not 100% convinced why someone need to fetch all records from 1 to last record of the page you want to display.

Here's the code which worked. Once can use this to work with cache object to make performance faster.

Code:

  public override void RenderControl (System.Web.UI.HtmlTextWriter writer)
{
writer.WriteLine("============================");

if (null != HttpContext.Current.Request.QueryString["pageno"])
{
int pageno = 1;
int.TryParse(HttpContext.Current.Request.QueryString["pageno"].ToString(), out pageno);

SPWeb oWebsite = SPContext.Current.Web;
SPList oList = oWebsite.Lists["Team Discussion"];

SPQuery oQuery = new SPQuery();
oQuery.RowLimit = 2;
oQuery.Query = "<OrderBy Override='TRUE'>" +
"<FieldRef Name='Modified' Ascending='FALSE' /></OrderBy>";

int intIndex = 1;

do
{
writer.WriteLine("Page: " + intIndex );

SPListItemCollection collListItems = oList.GetItems(oQuery);

if (intIndex == pageno)
{
foreach (SPListItem oListItem in collListItems)
{
writer.WriteLine(SPEncode.HtmlEncode(oListItem["Title"].ToString()) );
}

break;
}

oQuery.ListItemCollectionPosition = collListItems.ListItemCollectionPosition;
if (null != collListItems.ListItemCollectionPosition)

intIndex++;

} while (oQuery.ListItemCollectionPosition != null);

writer.Write("=================================");
}

base.RenderControl(writer);
}
Article: SPListItemCollectionPosition

Thursday, May 19, 2011

Discussion board multiple item threaded view

Scenario:
Discussion board has a great 'Threading' view , but limited to display only one time.
We had a requirement to display multiple items in threaded view. I searched a lot but nothing helpful.

Solution:
Wrote a quick console application.

Code:

using System;
using System.Data;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;

namespace SPSandeep.ConsoleApplication
{
class Program
{
public static DataTable dt = null;

static void Main (string[] args)
{
using (SPSite site = new SPSite("http://ws2003/credit"))
{
using (SPWeb web = site.OpenWeb())
{
SPList mainList = web.Lists["Team Discussion"];

foreach (SPListItem folder in mainList.Folders)
{
Console.WriteLine("Folder Name: " + folder.Name);
Console.WriteLine("Folder ID: " + folder.ID);

// Read body of attachment
Console.WriteLine("Body: " + folder.Fields["Body"].GetFieldValueAsText(folder["Body"]));
Console.WriteLine("Threading: " + folder.Fields["Threading"].GetFieldValueAsText(folder["Threading"]).Length);
Console.WriteLine("=============================");

RenderAllReponses(mainList, folder.ID);

Console.WriteLine("=============================");
}
}
}

Console.ReadKey();
}

public static void RenderAllReponses (SPList mainList, int parentID)
{
SPQuery query = new SPQuery();
query.Query = string.Format("<Where><Eq><FieldRef Name='ParentFolderId' /><Value Type='Number'>{0}</Value></Eq></Where>", parentID.ToString());
query.ViewFields = @"<FieldRef Name='ows_ParentFolderId' /><FieldRef Name='Threading' /><FieldRef Name='Title' /><FieldRef Name='ID' /><FieldRef Name='Body' />";
query.ViewAttributes = "Scope='RecursiveAll'";
dt = mainList.GetItems(query).GetDataTable();

if (null != dt)
{
foreach (DataRow listItem in dt.Rows)
{
if (listItem["Threading"].ToString().Length == 56)
{
RenderResponse(listItem, 56);
}
}
}
}

public static void RenderResponse (DataRow listItem1, int length)
{
DataRow[] listItems = dt.Select(string.Format("Threading Like '{0}*' AND LEN(Threading)={1}", listItem1["Threading"], length));

foreach (DataRow item in listItems)
{
Console.WriteLine("Item DisplayName: " + item["Title"]); // Returns Title of Discussion
Console.WriteLine("List ID: " + item["ID"] );
//Console.WriteLine("List Folder ID: " + listItem["ParentFolderId"] + "<BR>"); // Returns ID of Parent Discussion
Console.WriteLine("Body: " + item["Body"]);
Console.WriteLine("Threading: " + item["Threading"].ToString().Length );
int newLength = length + 10;
RenderResponse(item, newLength);
}
}
}
}

Wednesday, May 11, 2011

Making entry for a user in elevated privileges mode

Scenario:
This is quick snippet to show how to make a entry in Custom List on behalf of the user , even in elevated privileged mode.

Code:

 SPContext.Current.Web.AllowUnsafeUpdates = true;

SPUser user = SPContext.Current.Web.CurrentUser;
string weburl = SPContext.Current.Web.Url;

SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(weburl))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["test list"];
SPListItem listItem = list.Items.Add();
listItem["Title"] = string.Format("I am {0}", user.LoginName);
// Modified By
listItem["Editor"] = user;
// Created By
listItem["Author"] = user;
listItem.Update();
}
}
}
);
SPContext.Current.Web.AllowUnsafeUpdates = false;
Update: My good friend Venkat mentioned that in case of update we should not update 'Author'

Tuesday, April 19, 2011

SP2010 : Feedback list

Scenario:
Client wanted a quick feedback link as a part of master page, so that end users can provide feedback. You can use the same idea to provide quick access to other lists.

Solution:
Here's list of things we needed to make this happen.
1. SharePoint list "Feedback" to hold all the feedback.

2. Dynamic link to add new item. We created a new WebControl [ Code below ]

Code:

using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;

namespace SKN
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:FeedbackLink runat=server></{0}:FeedbackLink>")]
public class FeedbackLink : WebControl
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text
{
get
{
String s = (String)ViewState["Text"];
return ((s == null) ? String.Empty : s);
}

set
{
ViewState["Text"] = value;
}
}

protected override void RenderContents(HtmlTextWriter output)
{
try
{
SPWeb web = SPContext.Current.Web;
SPList list = web.Lists.TryGetList("Feedback");

if (null != list)
{
string aRender = "<a class='comment' id=idHomePageNewItem class=ms-addnew onclick='javascript:NewItem2(event, \"/_layouts/listform.aspx?PageType=8&ListId={0}&RootFolder=\");javascript:return false;' href=\"/_layouts/listform.aspx?PageType=8&ListId={0}&RootFolder=\" target=_self>Feedback</a>";
output.Write(string.Format(aRender, list.ID));
}
}
catch {
//handle exception here
}
}
}
}
3. Embedded this control in Master Page, that all.

Monday, March 14, 2011

SP2010 Few code changes for 2007 code

Scenario:
While a lot of my clients are asking for migration to SP2010 from MOSS2007. One important task is to upgrade the code / solutions. So I though of start documenting few code/design change to utilize new APIs.

Design Changes:
1. ContentIterator class for processing large lists ( 5000+ items )
2. Remote Blob storage for very large files ( 50MB+ )
3. Sandbox solutions ( Webparts / Infopath wide code-behind etc. )
4. Client Object Model
5. Powershell based deployments

Code related Changes:


// 1. Easy to validate column exist or not
if(list.Fields.ContainsFieldWithStaticName(columnName))
{
// do something
}

// 2. Easy to get field by internal name
SPField field = list.Fields.GetFieldByInternalName(columnName)

// 3. Code return null in case list is not present
SPList list = web.Lists.TryGetList(listTitle)

// 4. Working with folders is better now
if(folders.TryGetFolder(folderUrl, out folder))
{
// do something
}

// 5. Check if the list has folders other than the root folder
if (!SPFolderHierarchy.ListHasFolders(list))
return;

// 6. Cache support - brand new in SP2010
SPCache.Cache.Put(DataCache, new SPCachedObject("TestKey", new TestCustomClass(), null, new TimeSpan(1, 0, 0)));

SPCache.Cache.Get(DataCache, "TestKey");
SPCache.Cache.Delete(DataCache, "TestKey");

// 7. Client Object Model based utilities
var encodedUrl = SP.Utilities.HttpUtility.ecmaScriptStringLiteralEncode(viewServ);

SP.Utilities.HttpUtility.appendSourceAndNavigateTo(this.newUrl);

// 8. Client Object Model based utlities - 2
this.imageUrl = SP.Utilities.Utility.getImageUrl('sandeepPhoto.jpg');
this.pageUrl = SP.Utilities.Utility.getLayoutsPageUrl('myHome.aspx')




Article:
I will update this post on regular basis with new findings.

Tuesday, March 8, 2011

SharePoint Picture library thumbnail url

Scenario:
While working with picture library we often need Thumbnails for display purpose.
Also depending upon you are accessing it in XSLT or C# code, you need the proper Url to the SharePoint generated thumbnail url.

Solution:
Small snippet here has it

Code:


<a href="{@EncodedAbsUrl}"><img border="0" src="{@EncodedAbsThumbnailUrl}"/></a>

SPListItem["EncodedAbsUrl"]
or
SPListItem["EncodedAbsThumbnailUrl"]


SPListItem[SPBuildInFieldId.EncodedAbsUrl]
or
SPListItem[SPBuildInFieldId.EncodedAbsThumbnailUrl]

Thursday, February 24, 2011

Collection was modified enumeration operation may not execute

Scenario:
I was writing a small SharePoint utility and was in rush for my next meeting.
When I ran my utility to test the first cut. I got the following error.

Error : [System.InvalidOperationException] = {"Collection was modified; enumeration operation may not execute."}

Code was like this

 foreach (SPListItem item in sourceList.Items)
{
if(item["Title"]!="blah")
{
item["Title"] = "blah blah blah ";
// Some more code
item.Update();
}
}
Reason:
While the error message is pretty clear and is not specific to SharePoint, I thought of posting. As you see I am looping through the ListItem Collection and modified the item itself which re-arranged the Collection itself. So next time SharePoint loop to get next item, you will encounter the above error.

Solution:
There can be 'n' solutions for this but this is what I did to fix it.
While looping I didn't modify the item but only copied its ID in a List and added an extra looping to get the items by ID and then modify the Item.

Modified Code:
 List<int> itemIDs = new List<int>();
foreach (SPListItem item in sourceList.Items)
{
if(item["Title"]!="blah")
{
itemIDs.Add(listItem.ID);
}
}

foreach (int i in itemIDs)
{
item["Title"] = "blah blah blah ";
// Some more code
item.Update();
}

Friday, May 21, 2010

SharePoint 2010 Console Application threw FileNotFoundException was unhandled

Scenario:
My colleague called me and asked to look for the error she was getting in her first SharePoint 2010 Console Application.

The Web application at http://demo/ could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.

Solution:
Simple solution, Check to make sure thats Build Target for the Project is x64 and not x86 which is default setting.

1. Right Click the Project Node > Properties
2. Under Build tab > Target should be x64

Also make sure thats there is a site collection at the url you are specifying to create SPSite object

Tuesday, February 16, 2010

Programatically modifying ListView Webparts

Scenario:
I was working on a small POC item, on how to update the listview webparts toolbar and fields after they have been added to the page.

Solution:
Here's the console application I used to test the concept. You might need to move this code as a Application Page Code behind or may be as a Feature Receiver code

Code:

using System;
using System.Reflection;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;

namespace SKN
{
class Program
{
static void Main(string[] args)
{
//This will be text for the link displayed in Summary toolbar for the webparts
string linkText = "Add a new item";

SPSecurity.RunWithElevatedPrivileges(delegate()
{
// get the site in this context
using (SPSite SiteCollection = new SPSite("http://ws2003/sitedirectory/try1"))
{
// get the web in this context
SPWeb myWeb = SiteCollection.RootWeb;

myWeb.AllowUnsafeUpdates = true;

SPLimitedWebPartManager mgr = null;
mgr = myWeb.GetLimitedWebPartManager("default.aspx", System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

foreach (WebPart myWebPart in mgr.WebParts)
{
// Check if the webpart is a listview webpart
if (myWebPart.GetType().ToString().EndsWith("ListViewWebPart"))
{
//Convert the webpart to list view webpart type
ListViewWebPart listWebPart = (ListViewWebPart)myWebPart;

if (listWebPart != null)
{
//Get the list instance
SPList list = myWeb.Lists[new Guid(listWebPart.ListName)];
//Get the view details which is applied to webpart
SPView webpartView = list.Views[new Guid(listWebPart.ViewGuid)];

//Call the EnsureFullBlownXMLDocument for the view
webpartView.GetType().InvokeMember("EnsureFullBlownXmlDocument",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod,
null, webpartView, null);

PropertyInfo nodeProp = webpartView.GetType().GetProperty("Node",
BindingFlags.NonPublic | BindingFlags.Instance);
XmlNode node = nodeProp.GetValue(webpartView, null) as XmlNode;

// Get the handle for the toolbar node
XmlNode toolbarNode = node.SelectSingleNode("Toolbar");
if (toolbarNode != null)
{
toolbarNode.Attributes["Type"].Value = "FreeForm";
XmlAttribute positionNode = toolbarNode.OwnerDocument.CreateAttribute("Position");
positionNode.Value = "After";
toolbarNode.Attributes.Append(positionNode);
toolbarNode.InnerXml = @"<IfHasRights><RightsChoices><RightsGroup PermAddListItems=""required"" /></RightsChoices><Then><HTML><![CDATA[ <table width=100% cellpadding=0 cellspacing=0 border=0 > <tr> <td colspan=""2"" class=""ms-partline""><IMG src=""/_layouts/images/blank.gif"" width=1 height=1 alt=""""></td> </tr> <tr> <td class=""ms-addnew"" style=""padding-bottom: 3px""> <img src=""/_layouts/images/rect.gif"" alt="""">&nbsp;<a class=""ms-addnew"" ID=""idAddNewItem"" href=""]]></HTML><URL Cmd=""New"" /><HTML><![CDATA["" ONCLICK=""javascript:NewItem(']]></HTML><URL Cmd=""New"" /><HTML><![CDATA[', true);javascript:return false;"" target=""_self"">]]></HTML><HTML>" + linkText +@"</HTML><HTML><![CDATA[</a> </td> </tr> <tr><td><IMG src=""/_layouts/images/blank.gif"" width=1 height=5 alt=""""></td></tr> </table>]]></HTML></Then></IfHasRights>";
}

// Get the handle to the ViewFields ( in case you want to change the columns to be displayed )
XmlNode viewFieldsNode = node.SelectSingleNode("ViewFields");
if (viewFieldsNode != null)
{
viewFieldsNode.InnerXml = @"<FieldRef Name='LinkTitle' /><FieldRef Name='Attachments' />";
}

// Update the view
webpartView.Update();
}

}
}
myWeb.AllowUnsafeUpdates = false;
}
});
}
}
}
Enjoy :)

Tuesday, February 2, 2010

Provisioning Lookup based Site Columns

Scenario:
Finally my client asked for this. He wanted few columns with lookup options.

I will not claim that I wrote the code as I got it from Codeplex but I did made some changes to make sure that solution works well in Edit properties page as well as with Document Information Panel.

Solution:
You need to create a feature for this with 2 element manifest files.

First one will create the list instance of the look up list and add options to it

Second one will have the definition of the Site Column you want to provision and will have a List attribute value set to the list title you have provisioned above. This manifest will not be referenced directly by feature but there will be event receiver which will read this element file and provision the columns using Object Model Code.

Feature Name:
ProvisionLookupSiteColumns , in case you change feature name .. make sure to change it in Code below and feature manifest file.

Feature.xml:

<?xml version="1.0" encoding="utf-8"?>
<Feature Id="New_GUID" 
 Title="Lookup Lists and Content" 
 Description="This feature creates all the lookup lists with initial content."
 Scope="Site"
  ImageUrl="/Client/Logo.png"
  Version="1.0.0.0" 
 Hidden="FALSE" 
 ReceiverAssembly="Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0000000000000000" 
         ReceiverClass="ClientName.ProvisionLookupSiteColumns" >
DefaultResourceFile="core" 
 xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="LookupList.xml" />
   </ElementManifests>
  <Properties>
    <Property Key="ColumnDefinitionPath" Value="SiteColumns.xml" />
  </Properties>
</Feature>
LookList.xml:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <ListInstance 
    FeatureId="00bfea71-de22-43b2-a848-c05709900100" 
    TemplateType="100" 
    Title="Security Classification" 
    Url="Lists/SecurityClassification"
    OnQuickLaunch="FALSE">
    <Data>
      <Rows >
          <Row><Field Name="Title">FOR OFFICIAL USE ONLY</Field></Row>
          <Row><Field Name="Title">Confidential</Field></Row>
          <Row><Field Name="Title">Strictly Confidential</Field></Row>
          <Row><Field Name="Title">Unclassified</Field></Row>
      </Rows>
    </Data>
  </ListInstance>
</Elements>
SiteColumns.xml:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  
  <Field
      ID="{New_GUID}"
      Name="SecurityClassification"
      DisplayName="Security Classification"
      Type="Lookup"
      Required="TRUE"
      Description="Information about who can access the resource or an indication of its security status."
      Group="Standard"
      List="Security Classification"
      ShowField="LinkTitleNoMenu"
      UnlimitedLengthInDocumentLibrary="FALSE"
    />
    
</Elements>
ProvisionFeatureReceiver.cs:
using System;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace ClientName
{
    /// <summary>
    /// Creates one or more site columns which gets their data from a SharePoint lists. This is known as a lookup field.
    /// 
    /// Most of the properties of the field(s) are defined in a CAML XML file (located at the value specified 
    /// in the feature property 'ColumnDefinitionPath', however we use the object model to retrieve the GUID of 
    /// the list the field should refer to. Each <Field /> element in the CAML should have a list attribute containing 
    /// the name of the list the column should get data from. The list should exist in the site collection's root 
    /// web (note this could be easily extended). The list GUID is then included in the CAML which is passed to the 
    /// SPWeb.Fields.AddFieldAsXml() method.
    /// 
    /// Note there are some issues surrounding deletion of site columns, see in-line comments for details. Note 
    /// also that the instance of the CAML definition file should stay constant per feature using this technique - 
    /// if new columns are required create a new feature which refers to this FeatureReceiver class, passing 
    /// different parameters from the CAML.
    /// </summary>
    /// <created version="1.0.0.0" by="Chris O'Brien" date="15 April 2007">Sample code</created>
    /// <updated version="1.0.0.1" by="Chris O'Brien" date="11 September 2007">In createLookupColumn(), amended line which 
    /// gets reference to newly-created list to use GetFieldByInternalName() rather than SPWeb.Fields collection. Fix 
    /// suggested by Bruce Sandeman - thanks Bruce!
    /// </updated>

    public class ProvisionLookupSiteColumns : SPFeatureReceiver
    {
        const string FEATURE_FOLDER_NAME = @"Template\Features\ProvisionLookupSiteColumns";
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            // feature is scoped at Site, so the parent is type SPSite rather than SPWeb..
            SPSite site = properties.Feature.Parent as SPSite;
            {
                SPWeb currentWeb = null;
                Guid gRootWebId = Guid.Empty;
                if (site != null)
                {
                    currentWeb = site.RootWeb;
                    gRootWebId = currentWeb.ID;
                }
                else
                {
                    currentWeb = properties.Feature.Parent as SPWeb;
                    gRootWebId = currentWeb.Site.RootWeb.ID;
                }

                using (currentWeb)
                {
                    // read details from CAML..

                    string sFieldElement = null;
                    string sListName = null;

                    string sFilePath = SPUtility.GetGenericSetupPath(FEATURE_FOLDER_NAME);
                    sFilePath += @"\" + properties.Feature.Properties["ColumnDefinitionPath"].Value;

                    XmlTextReader xReader = new XmlTextReader(sFilePath);
                    while (xReader.Read())
                    {
                        if (xReader.LocalName == "Field")
                        {
                            if (xReader.MoveToAttribute("List"))
                            {
                                sListName = xReader.Value;
                                xReader.MoveToElement();
                            }

                            sFieldElement = xReader.ReadOuterXml();

                            // now get reference to list, fix up CAML and create column.. 
                            SPList referencedList = currentWeb.Site.RootWeb.Lists[sListName];

                            string sFinalCaml = replaceListGuidString(sFieldElement, sListName, referencedList);
                            createLookupColumn(currentWeb, sFinalCaml, sListName);
                        }
                    }

                    xReader.Close();

                }
            }
        }

        private string replaceListGuidString(string sFieldElement, string sListName, SPList referencedList)
        {
            string sListWithName = string.Format("List=\"{0}\"", sListName);
            string sListWithGuid = string.Format("List=\"{0}\"", "{" + referencedList.ID + "}");
            return sFieldElement.Replace(sListWithName, sListWithGuid  );
        }

        /// <summary>
        /// Attempt to delete the column. Note that this will fail if the column is in use, 
        /// i.e. it is used in a content type or list. I prefer to not catch the exception 
        /// (though it may be useful to add extra logging), hence feature deactivation/re-activation 
        /// will fail. This effectively means this feature cannot be deactivated whilst the column 
        /// is in use.
        /// </summary>
        /// <param name="column">Column to delete.</param>
        private void attemptColumnDelete(SPWeb web, string sColumnName)
        {
            SPFieldLookup lookupColumn = null;

            // we don't care if there was an exception retrieving the field from the collection, 
            // since if it's not there we can't delete it anyway..
            try
            {
                lookupColumn = web.Fields[sColumnName] as SPFieldLookup;
            }
            catch (ArgumentException argExc)
            {

            }

            if (lookupColumn != null)
            {
                try
                {
                    lookupColumn.Delete();
                }
                catch (SPException SpExc)
                {
                    // consider logging full explanation..

                    throw;
                }
            }
        }

        private void createLookupColumn(SPWeb web, string sColumnDefinitionXml, string sColumnName)
        {
            // delete the column if it exists already and is not yet in use..
            attemptColumnDelete(web, sColumnName);

            SPFieldLookup lookupColumn = null;

            // now create the column from the CAML definition..
            string sCreatedColName = web.Fields.AddFieldAsXml(sColumnDefinitionXml);

            /* COB 11 Sept 2007 - amended next line to use internal name to fix bug when field display name  
             * is different to internal name. */
            // lookupColumn = web.Fields[sCreatedColName] as SPFieldLookup;
            lookupColumn = web.Fields.GetFieldByInternalName(sCreatedColName) as SPFieldLookup;

            lookupColumn.LookupWebId = web.Site.RootWeb.ID;
            lookupColumn.Update();
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            // delete the column if it exists already and is not yet in use..

            // feature is scoped at Site, so the parent is type SPSite rather than SPWeb..
            SPSite site = properties.Feature.Parent as SPSite;
            {
                SPWeb currentWeb = null;
                Guid gRootWebId = Guid.Empty;
                if (site != null)
                {
                    currentWeb = site.RootWeb;
                    gRootWebId = currentWeb.ID;
                }
                else
                {
                    currentWeb = properties.Feature.Parent as SPWeb;
                    gRootWebId = currentWeb.Site.RootWeb.ID;
                }

                using (currentWeb)
                {
                    string sColumnName = null;
                    string sFilePath = SPUtility.GetGenericSetupPath(FEATURE_FOLDER_NAME);
                    sFilePath += @"\" + properties.Feature.Properties["ColumnDefinitionPath"].Value;

                    XmlTextReader xReader = new XmlTextReader(sFilePath);
                    while (xReader.Read())
                    {
                        if (xReader.LocalName == "Field")
                        {
                            if (xReader.MoveToAttribute("Name"))
                            {
                                sColumnName = xReader.Value;
                            }

                            attemptColumnDelete(currentWeb, sColumnName);
                        }
                    }

                    xReader.Close();
                }
            }
        }

        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
        }

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
        }
    }
}

Wednesday, November 4, 2009

Restricting accessing view for admins only

Scenario:
One of the requirement we had was something like this.

Users can submit Feedback forms and should not be able to see other feedback.
But Owners should be able to go to Feedback List and see all the feedbacks.

Solution:
Custom Web Part was the solution. We gave contributor rights to all the users on this list and to avoid them visiting the complete list we added a webpart on top of the AllItems.aspx List.

Code:

using System;
using System.Runtime.InteropServices;

using Microsoft.SharePoint;

namespace ReDirectWebPart
{
public class ReDirectWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
public ReDirectWebPart()
{
}

private const string const_permissionMask = "9223372036854775807";
private string m_permissionMask = const_permissionMask;

// Configuration List Name property.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[Category("Custom Properties")]
[WebDisplayName("Permission Mask")]
[Description("Permission Mask (i.e. FullMask = 9223372036854775807")]
[DefaultValue(const_permissionMask)]
public string PermissionMask
{
get
{
return m_permissionMask;
}

set
{
m_permissionMask = value;
}
}

private const string const_urlToRedirect = "/";
private string m_urlToRedirect = const_urlToRedirect;

// Configuration Url to Redirect property.
[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable(true)]
[Category("Custom Properties")]
[WebDisplayName("Url to Redirect")]
[Description("Url to Redirect")]
[DefaultValue(const_urlToRedirect)]
public string UrlToRedirect
{
get
{
return m_urlToRedirect;
}

set
{
m_urlToRedirect = value;
}
}

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);

SPList list = SPContext.Current.List;

if (!string.IsNullOrEmpty(PermissionMask))
{
if (!list.DoesUserHavePermissions((SPBasePermissions)Enum.Parse(typeof(SPBasePermissions),PermissionMask)))
{
if (string.IsNullOrEmpty(UrlToRedirect))
{
this.Page.Response.Redirect(SPContext.Current.Web.Url);
}
else
{
this.Page.Response.Redirect(UrlToRedirect);
}
}
}
else
{
if (!list.DoesUserHavePermissions(SPBasePermissions.FullMask))
{
if (string.IsNullOrEmpty(UrlToRedirect))
{
this.Page.Response.Redirect(SPContext.Current.Web.Url);
}
else
{
this.Page.Response.Redirect(UrlToRedirect);
}
}
}
}

public override void RenderControl(System.Web.UI.HtmlTextWriter writer)
{
if (string.IsNullOrEmpty(UrlToRedirect) || string.IsNullOrEmpty(PermissionMask))
{
writer.Write("Please configure the webpart with proper custom property values");
}
base.RenderControl(writer);
}
}
}
Enhacements:
1. You can make it generic with what custom properties for what type of permission and the url to redirect. ( Code Updated to include it )

Saturday, October 17, 2009

Content Approval for Folders

Scenario:
We had content approval enabled for one of our list items, since Folder itself is nothing more than ab item in the list, when user creates a folder it was asking for Approval.

Solution:
Users wanted Folders to be auto approved. Item Event Receiver took care of it.

You can reuse this code to approve any specific type of Content Type items.

Code:

public override void ItemAdded(SPItemEventProperties properties)
{

SPWeb web = properties.OpenWeb();

SPList list = properties.ListItem.ParentList;

SPListItem currentItem = properties.ListItem;

string itemCT = currentItem[list.Fields["Content Type"].InternalName].ToString();

if (itemCT == "Folder")
{

DisableEventFiring();

// Set the value to 0 to set it to Approved

currentItem[list.Fields["Approval Status"].InternalName] = "0";
currentItem.SystemUpdate();

EnableEventFiring();

}

Folder Modified Date doesn't get updated

Scenario:
One of the bug in sharepoint folders is that they don't get updated when a new item inside the folder is added / updated. Modified Date of the folder will still show the old time stamp.

Solution:
I quickly wrote an Item Event Handler which can fix the time stamp for the item folder.

Code:

/// <summary>
/// Asynchronous after event that occurs after a new item has been added to its containing object.
/// </summary>
/// <param name="properties">
/// A Microsoft.SharePoint.SPItemEventProperties object that represents properties of the event handler.
/// </param>
public override void ItemAdded(SPItemEventProperties properties)
{
string listItemUrl = properties.ListItem.Url;
string temp = listItemUrl.Remove(listItemUrl.LastIndexOf('/'));
string folderName = temp.Substring(temp.LastIndexOf('/') + 1);

SPQuery query = new SPQuery()
{
Query = string.Format("<Where><And><Eq><FieldRef Name='ContentType' /><Value Type='Text'>folder</Value></Eq><Eq><FieldRef Name='Title' /><Value Type='Text'>{0}</Value></Eq></And></Where>",folderName),
RowLimit = 1
};

SPList list = properties.ListItem.ParentList ;
SPListItemCollection items = list.GetItems(query);

items[0].Update();
list.Update();
}
Note:
Right now this code will fix the Modified date of the folder only when a new item is added , so need to have the same code in Item updated if you want Folder modified date to be changed.

Also please test fully in case you have more than one level of folder structure.I think it will work but I haven't tested it.

Tuesday, October 13, 2009

Copy Data across Lookup Column Fields

Scenario:
Of the common issues with Content Types updates in production is change in field types. OOTB there are few supported field type change but some time not really give you the correct results.

So as a workaround you can add a new field to content type and copy over the data from the old field to the new one.

Along the same lines one of my colleague wanted to copy data across Lookup Column Fields. Source field was a Single Select field and destination was Multi-Select field and there were 100s of sub sites to update.

Solution:
Small utility function did the trick.

Code:

public static void CopyValues()
{

string siteCollectionUrl = "http://localhost";
string sourceFieldDisplayName = "SourceFieldDisplayName";
string destinationFieldDisplayName = "DestinationFieldDisplayName";

using (SPSite site = new SPSite(siteCollectionUrl))
{
foreach (SPWeb web in site.AllWebs)
{
foreach (SPList list in web.Lists)
{
try
{
if (null != list.Fields[sourceFieldDisplayName] && null != list.Fields[destinationFieldDisplayName])
{
string sourceFieldInternalName = list.Fields[sourceFieldDisplayName].InternalName;
string destinationFieldInternalName = list.Fields[destinationFieldDisplayName].InternalName;

foreach (SPListItem item in list.Items)
{

SPFieldLookupValue sourcevalue = new SPFieldLookupValue(item[sourceFieldInternalName].ToString());

SPFieldLookupValueCollection destinationValueCollection = new SPFieldLookupValueCollection();
destinationValueCollection.Add(sourcevalue);

item[destinationFieldInternalName] = destinationValueCollection;

item.Update();
}
list.Update();
}
}
catch
{
}
}
web.Dispose();
}
}
}