Thursday, February 9, 2012

BreakRoleInheritance in SharePoint:

If we want to unique permissions to SharePoint objects (Lists, document libraries, folders etc..) we use BreakRoleInheritance. It takes a Boolean parameter which is either true or false.
BreakRoleInheritance(true) - means copy all the permissions from the parent and then maintain its own unique permissions for all the future changes on that object.
BreakRoleInheritance(false) - means do not copy any permissions from the parent and maintain its own unique permissions for all the future changes on the object.
For some reason BreakRoleInheritance(false) was not working and was throwing an error. I am not the only person to complain about this, but the same with many developers on the Google. Below is the final code which worked for me.

//Code to fix BreakRoleInheritance(false) issues
//with BreakRoleInheritance(true) permissions are inherited and then removed one by one.
//It is responsible for locking Permissions table for long time and potentially resulting
//into the deadlock
oWeb.AllowUnsafeUpdates = true;
SPFolder oPermissionFolder = oList.RootFolder.SubFolders.Add(FolderName);
oPermissionFolder.Item.BreakRoleInheritance(false);
oFolderItem = oPermissionFolder.Item;
oFolderItem["Title"] = false.ToString() + ";1";

if(!oWeb.AllowUnsafeUpdates)
oWeb.AllowUnsafeUpdates = true;

int raCount = oFolderItem.RoleAssignments.Count;
for (int i = raCount - 1; i >= 0; i--)
{
oFolderItem.RoleAssignments.Remove(i);
}

//oFolderItem.Update();
oFolderItem.SystemUpdate();

Override SharePoint SaveButton to function as Save As Draft button:

There was a requirement for an application to save a list item as draft and then submit the list item as final once all the required information is available. SharePoint save button can be overridden to start a custom workflow if required. Remember save as draft and save Buttons are different.



<%@ Register TagPrefix="WebControl" Assembly="XXXXXX, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c2b3ca69d93d7c67" namespace="XXXXXX"%>

In the custom list form
<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator=" " runat="server">
<Template_RightButtons>
<WebControl:SubmitButton ID="SubmitButton1" Text="Submit" WorkflowKey="Server Request" runat="server" />
<WebControl:SaveDraftButton ID="SaveDraftButton1" text="Save Draft" runat="server"/>
<SharePoint:GoBackButton runat="server"/>
</Template_RightButtons>
</wssuc:ToolBar>

Code for Save as Draft button:
namespace XXXXXXX
{
[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true), AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class SaveDraftButton : SaveButton
{
protected override bool SaveItem()
{

bool setPermissions = ListItem.Name == null;
bool success = base.SaveItem();
if (setPermissions)
{
EventMethods.SetItemInitialPermissions(ListItem);
}

return success;
}

[SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
protected override bool OnBubbleEvent(object source, EventArgs e)
{
bool flag;
string redirectUrl;
base.OnBubbleEvent(source, e);
flag = this.SaveItem();
redirectUrl = base.RedirectUrl;
SPUtility.Redirect(redirectUrl, SPRedirectFlags.UseSource, this.Context);
return flag;
}
}
}

Sunday, March 27, 2011

Upload files to SharePoint 2007 from Network Share


To upload files to SharePoint document library from a network share we need to create a SharePoint timer job. I was written a SharePoint timer job which iterates through all the folders of the network share and uploads all the files present in the folders to a SharePoint document library. For your convenience of debugging I will be posting a console application code below. Once you feel everything is working as expected please convert it to a SharePoint timer Job.
namespace CopyDocumentsFromSharedFolderToSharePoint {
class Program
{
const string NetworkPath = "\\\\ServerName\\Reports";
const string SharePointPath = "http://SiteAddress/sites/Reports/Staging Reports/TEST/";
const double maxFileSize = 52428800;//50MB in Bytes 50*1024*1024
static void Main(string[] args)
{
ProcessFolder(NetworkPath);
Console.ReadKey();
}
private static void ProcessFolder(string startingPath)
{
int iterator = 0;
List<string> dirList = new List<string>();
dirList.Add(startingPath);
string parentFolder = startingPath;
// Every new folder found is added to the list to be searched. Continue until we have
// found, and reported on, every folder
while (iterator < dirList.Count)
{
parentFolder = dirList[iterator]; // Each FileTreeEntry wants to know who its parent is
try
{
foreach (string dir in Directory.GetDirectories(dirList[iterator]))
{
dirList.Add(dir);
}
foreach (string filename in Directory.GetFiles(dirList[iterator]))
{
FileInfo file = new FileInfo(filename);
int returnCode = validateFileUploadToSharePoint(file);
if (returnCode == 0)
{
UploadFileToSharePoint(filename, (SharePointPath + file.Name));
}
Console.WriteLine(file.Name);
}
}
// There are two *acceptable* exceptions that we may see, but should not consider fatal
catch (UnauthorizedAccessException ex1)
{
}
catch (PathTooLongException ex2)
{
}
catch (DirectoryNotFoundException ex3)
{
}
catch (Exception ex4)
{
}
iterator++;
}
}
private static int validateFileUploadToSharePoint(FileInfo file)
{
//ensure folder path exists
//Check if file size exceeds 50MB
if (file.Length > maxFileSize)
{
//file length is greater then 50MB
return 2;
}
return 0;
}
protected static void UploadFileToSharePoint(string UploadedFilePath,string SharePointPath)
{
WebResponse response = null;
try
{
// Create a PUT Web request to upload the file.
WebRequest request = WebRequest.Create(SharePointPath);
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = "PUT";
// Allocate a 1 KB buffer to transfer the file contents.
// You can adjust the buffer size as needed, depending on
// the number and size of files being uploaded.
byte[] buffer = new byte[1024];
// Write the contents of the local file to the
// request stream.
using (Stream stream = request.GetRequestStream())
using (FileStream fsWorkbook = File.Open(UploadedFilePath,FileMode.Open, FileAccess.Read))
{
int i = fsWorkbook.Read(buffer, 0, buffer.Length);
while (i > 0)
{
stream.Write(buffer, 0, i);
i = fsWorkbook.Read(buffer, 0, buffer.Length);
}
}
// Make the PUT request.
response = request.GetResponse();
File.Delete(UploadedFilePath);
}
catch (Exception ex)
{
throw ex;
}
finally
{
response.Close();
}
}
}
}
References:
http://www.scottleckie.com/2009/07/iterating-through-a-bunch-of-folders-and-files/
http://blogs.msdn.com/b/erikaehrli/archive/2009/06/30/how-to-upload-files-to-sharepoint-server-2007-from-asp-net-web-applications.aspx
http://msdn.microsoft.com/en-us/library/dd902097.aspx

Best Object Model Coding Practices for SharePoint 2007


I was working on performance tuning a SharePoint 2007 application and these are the common bad coding practices which developers make unintentionally.
Never Use SPList.Items.GetItemById(ID):
Refer this link for further details
http://www.3guysonsharepoint.com/?p=197
SPWeb.EnsureUser Vs. SpWeb.AllUsers:
The best way to check if the user exists / have access to the site / web is this way.
SPWeb.AllowUnsafeUpdates = true;
SPUser user = webInUserContext.EnsureUser("ad\\C001234");
SPWeb.AllowUnsafeUpdates = false;
Don't forget to use AllowUnsafeUpdates if the logged in user only has read/contribute permissions, otherwise you will get "Access Denied". It is not necessary to use AllowUnsafeUpdates if you are running code with elevated privileges or impersonation.
http://blog.qumsieh.ca/2008/06/26/spweb-siteusers-vs-spweb-users/
This is not a good way.
SPUser user = webInUserContext.AllUsers["ad\\C001234"];
Windows SharePoint Services 3.0 SDK
SPWeb.AllUsers – Gets the collection of user objects that represents all users who are either members of the site or who have browsed to the site as authenticated members of a domain group in the site.
SPWeb.SiteUsers – Gets the collection of all users that belong to the site collection.
SPWeb.Users – Gets the collection of user objects that are explicitly assigned permissions on the Web site.
SPWeb.EnsureUser - Checks whether the specified login name belongs to a valid user of the website, and if the login name does not already exist, adds it to the website.
Best way of Accessing SharePoint Groups:
I think this is the best way of accessing SharePoint groups if we know the name of the group.
SPGroupCollection groupColl = newWeb.SiteGroups.GetCollection(new String[] { "Readers", "Contributers" });
This one gets SharePoint groups only which you want where as below one gets all the SharePoint Groups
SPGroupCollection groupColl = newWeb.SiteGroups;
Efficient way of adding list item to SharePoint list:
Refer the link below
http://msdn.microsoft.com/en-us/library/bb687949(office.12).aspx
public static class Extensions
{ /*efficient way of adding list item to SharePoint list
* http://msdn.microsoft.com/en-us/library/bb687949(office.12).aspx */
public static SPListItem AddItem(this SPList list)
{
const string EmptyQuery = "0";
SPQuery q = new SPQuery { Query = EmptyQuery };
return list.GetItems(q).Add();
}
}
Usage…
SPListItem item = list.AddItem();
List item query elapsed time:
I got this message in the SharePoint ULS log.
01/21/2011 10:30:02.00 w3wp.exe (0x1320) 0x1944 Windows SharePoint Services Database 8sli Monitorable List item query elapsed time: 4508 milliseconds, Additional data (if available): Query HRESULT: 0 List internal name, flags, and URL: {A134D627-5580-49FD-883C-9D1325CE6CD3}, flags=0x0000000020801088, URL="http://SiteURL/Lists/Tickets/AllItems.aspx" Query XML: "<Query><OrderBy><FieldRef Name="Modified" Ascending="FALSE"/></OrderBy></Query>" SQL Query: " SELECT TOP 101 t1.[Type] AS …….
I was wondering what this message in the SharePoint ULS log means, finally figured out that this message is logged whenever we try to load a SharePoint List View which has large number of rows and which does not use default view sorting order (List Item ID)
I our case our SharePoint List had 1,000,00 rows in it and the AllItems View is being sorted by Last modified descending.
Select Distinct or Unique values from SharePoint List column:
Refer the link below
http://blog.visualstudioteamsystem.com/post.aspx?item=33
if (web.ServerRelativeUrl == "/")
{
ticketsList = (SPList)web.GetList(ticketsListURL);
}
else
{

ticketsList = (SPList)web.GetList(string.Concat(web.ServerRelativeUrl, ticketsListURL));
}
SPField field = ticketsList.Fields.GetFieldByInternalName("CreatedByHistorical");
object[,] values;
uint numberValues = ticketsList.GetDistinctFieldValues(field, out values);
for (int i = 0; i < numberValues; i++)
dropDownListEnterers.Items.Add(values.GetValue(0, i).ToString());

Wednesday, January 12, 2011

Locks / Critical Sections in Multi – WFE Environment

While we are dealing Deadlocks in SharePoint 2007 case which I have blogged here
http://bharatreddybasani.blogspot.com/2010/10/deadlocks-in-sharepoint-2007_29.html
with Microsoft, One of the Microsoft representative made this statement regarding placing Locks / Critical sections in multi-WFE environments.
In multi-WFE env, this lock may not work properly, because the objLock is different instance on each WFE.
I was surprised to learn about it. I do not have a solid proof to prove the above statement. To eliminate transactions being deadlocked in multi - Web front end environments, this was the workaround given by Microsoft representative. Below statement is in the context of SharePoint.

Another portion of the workaround that you may not be familiar with is to add a property key/value pair in the SPWeb object so that the values can be queried from all servers. It is actually quite easy to use it and you can see it from here. http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.allproperties.aspx. On another note, you do not have to use the SPWeb property bag to store the value. You can also use a SharePoint list at the root site level to contain both the flag and URLs mentioned in the steps below. Or you can also use a custom DB to store those. All in all, there are several options for storing these values, you can choose the one that fits your need the best.

Friday, October 29, 2010

Deadlocks in SharePoint 2007

One of our custom developed SharePoint application contains code for creating SharePoint sites from a template (stp file) and assigning custom permissions to the created site, lists and document libraries inside that site. Since we have very heavy volume of users using this custom developed SharePoint application, we ran in to deadlock issues on the SharePoint database server, whenever multiple sites are being created at the same time and permissions are being assigned on sites, Lists and document libraries. The following deadlock messages appear on the SharePoint logs whenever multiple users create sites at the same time in the fraction of seconds. Sometimes application fails at the time of site creations and sometimes at the time of assigning permissions.

Execution process goes as follows: Sites are created on the fly through code and permission's are only assigned to the current logged in user, so that current logged in user can carry out his process of uploading documents. A background thread is created to assign permission's to rest of the users for the created site.

Error Message in the front end:

The URL "/sites/1234abcd" is invalid. It may refer to a nonexisting file or folder, or refer to a valid file or folder that is not in the current web.

Notes: In this case site is half created and the site collection under which this half created site is present is not accessible. If we try to login in to site collection it will crash and display one of these messages.

Value does not fall in the expected range.

Template Selection – on the user interface

Group not found.

Deadlock error In the ULS Log:

Unexpected query execution failure, error code 1205. Additional error information from SQL Server is included below. "Transaction (Process ID 110) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction." Query text (if available): "{?=call proc_CreateWeb(?,?,?,?,?,?,?,?,?,?,?,?,?,?)}"


Error Message in the front end:

Operation aborted (Exception from HRESULT: 0x8000404 (E_ABORT))

Notes: in this case site gets creates successfully but permissions are not properly assigned on the lists and document libraries.

Deadlock error In the ULS Log:

Unexpected query execution failure, error code 1205. Additional error information from SQL Server is included below. "Transaction (Process ID 111) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction." Query text (if available): "{?=call proc_SecAddPrincipalToRole(?,?,?,?,?,?)}"


We are working with Microsoft to resolve this issue. I am eagerly waiting to hear the resolution from Microsoft.

Update: (01/11/2011) We were able to reproduce this deadlocks on the Microsoft development environment. Microsoft did not accept that this is a defect in the SharePoint 2007. The contact person, whom we dealt with, told us that they have no right to accept that it is a defect in SharePoint 2007, he only told that he could take this behavior to the notice of SharePoint product group and it is up to the product group to decide if this is the defect with SharePoint 2007. After a while he came back to us, telling that SharePoint product group is not ready to release a hot fix for this issue since SharePoint 2010 is already released. They also told that if they have to release a hot fix it is going to be a major change in the SharePoint 2007 and would not like to take this up at this point since it could affect other parts of SharePoint 2007 which are working well.

They also told that we had this deadlock problem since we are putting lot of stress on SharePoint API. BreakRoleInheritance is a very heavy operation and since we are breaking inheritance, assigning custom permissions a lot on multiple objects on our site creation process we are getting this problem. These deadlocks are being caused on SQL server database and not in C# code. As per my analysis this is a problem in the way stored procedures are written in SQL server database and not something which is caused by putting lot of stress on SharePoint API. So as a final result to resolve this problem, they have proposed a couple of workarounds which did not eliminate deadlocks completely. So we had to change the design of the application to eliminate deadlocks up to some extent.

We and Microsoft had also done the same test on SharePoint 2010 and found that this problem had been eliminated to great extent in SharePoint 2010. Below is the email from Microsoft representative.

I have just finished porting the sample code from MOSS2007 to SPS2010 and finished the testing. I tested with 6 concurrent site creation requests each 0.5 seconds apart. Where it failed in my MOSS2007 environment under those test conditions, it finished successfully on my SPS2010 environment. I took a brief look at the user assignments at the newly created sites and the document libraries and all the user permissions are assigned. I have also found that there were some updates made to the stored procedures from MOSS2007 to SPS2010 targeted to improve performance of the stored procedures. Although I did not see any changes that were made specifically to address the deadlock issues around breakroleinheritance, my test results and findings for updates to the SPs is showing a very positive conclusion that the deadlock issue you are seeing in MOSS2007 has been largely alleviated in SPS2010.

Despite my conclusions above, I would still advise you to perform more extensive stress testing if you should decide to migrate to SPS2010 to resolve this deadlock behavior.

Background:
Microsoft had already released a hot fix for this issue, but it doesn’t really help. Below is the link to it.
http://support.microsoft.com/kb/932056

Using SharePoint Custom Application pages as SharePoint List Forms

I was surprised to know that I can develop a SharePoint custom application page that can be used as one of the SharePoint list forms (NewForm.aspx, EditForm.aspx and DispForm.aspx). This can be done by writing a custom list definition. The code snippet is as follows.

<XmlDocuments>
<XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url"&gt;

<FormUrls
xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">

<Display>_layouts/ProjectName
/Ticket List Forms/TicketsDisplayForm.aspx </Display>

<Edit>_layouts/ProjectName/Ticket List Forms/TicketsEditForm.aspx</Edit>

<New>_layouts/ProjectName/Ticket List Forms/TicketsNewForm.aspx</New>

</FormUrls>

</XmlDocument>

</XmlDocuments>

Where TicketsDisplayForm.aspx, TicketsEditForm.aspx and TicketsNewForm.aspx are SharePoint custom application pages deployed to layouts folder.

If we place this snippet in the list definition,and when we click on "New Item" button on the list it will open SharePoint custom application page "TicketsDisplayForm.aspx". In this case we have to handle all the operations like inserting list items in to the list by ourself. You might me wondering why I have to do this? I had to do this since everyone on this list has read-only permission and List item insertion is done by using impersination. We have to take this route since the system is designed to work on role based.