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.

JavaScript for disabling menu options under SharePoint List Actions menu

Below JavaScript can be used to disable menu options under SharePoint List Actions menu.

<script type="text/javascript">

function GetElementByText(tagName, title)

{

var a = document.getElementsByTagName(tagName);

for (var i=0; i < a.length; i++)

{

if (a.item(i).text)

{

if (a.item(i).text == title)

{

return a.item(i);

}

}

}

return null;

}

function hideMenuItems()

{

/*var o = GetElementByText("ie:menuitem","New Item");

if (o)

{

o.disabled = true;

}*/

var o = GetElementByText("ie:menuitem","Edit in Datasheet");

if (o)

{

o.disabled = true;

}

var o = GetElementByText("ie:menuitem","View RSS Feed");

if (o)

{

o.disabled = true;

}

var o = GetElementByText("ie:menuitem","Alert Me");

if (o)

{

o.disabled = true;

}

}

_spBodyOnLoadFunctionNames.push("hideMenuItems");

</script>

Saturday, September 11, 2010

Overriding SharePoint List Item Delete Functionality

There was a requirement which says

  • There are two types of users for this list Owners and Contributors/members SharePoint groups.
  • Contributors SharePoint group should not have delete permissions for deleting list items permanently
  • Contributors should be able to perform soft delete on the list items i.e marking an item as deleted and hiding that item from the users
  • Contributors should be able to perform soft delete only on the items which are created today. They should not be able to delete items which are created in the past.

To implement the above requirement we need to create a special permission set by excluding the delete items and delete versions permissions. Now apply this permission set to the desired SharePoint group in which we are going to place all the contributors. All the users in this SharePoint group do not have permission to delete SharePoint List items. For detailed step-by-step procedure for implementing this can be found here

To perform soft delete on the SharePoint list items we are going to override Display Form of SharePoint list and place a Delete button beside close button as shown below.

To override the SharePoint Display Form follow the below steps:

  • Create new rendering template and please it in the ControlTemplates folder
  • Override the "OnClick" event of Asp.net Button Class
  • Register this template in the SharePoint custom list definition schema

Create new rendering template and please it in the ControlTemplates folder:

All the SharePoint list forms (NewForm.aspx, EditForm.aspx, DisplayForm.aspx) uses rendering template called "ListForm" which is present in the file "DefaultTemplates.ascx" in the following path.

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\CONTROLTEMPLATES\DefaultTemplates.ascx

This is the code which I am referring to

<SharePoint:RenderingTemplate ID="ListForm" runat="server">

<Template>

<SPAN id='part1'>

<SharePoint:InformationBar runat="server"/>

<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator="&nbsp;" runat="server">

<Template_RightButtons>

<SharePoint:NextPageButton runat="server"/>

<SharePoint:SaveButton runat="server"/>

<SharePoint:GoBackButton runat="server"/>

</Template_RightButtons>

</wssuc:ToolBar>

<SharePoint:FormToolBar runat="server"/>

<TABLE class="ms-formtable" style="margin-top: 8px;" border=0 cellpadding=0 cellspacing=0 width=100%>

<SharePoint:ChangeContentType runat="server"/>

<SharePoint:FolderFormFields runat="server"/>

<SharePoint:ListFieldIterator runat="server"/>

<SharePoint:ApprovalStatus runat="server"/>

<SharePoint:FormComponent TemplateName="AttachmentRows" runat="server"/>

</TABLE>

<table cellpadding=0 cellspacing=0 width=100%>

<tr>

<td class="ms-formline">

<IMG SRC="/_layouts/images/blank.gif" width=1 height=1 alt=""></td></tr></table>

<TABLE cellpadding=0 cellspacing=0 width=100% style="padding-top: 7px">

<tr><td width=100%>

<SharePoint:ItemHiddenVersion runat="server"/>

<SharePoint:ParentInformationField runat="server"/>

<SharePoint:InitContentType runat="server"/>

<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&nbsp;" runat="server">

<Template_Buttons>

<SharePoint:CreatedModifiedInfo runat="server"/>

</Template_Buttons>

<Template_RightButtons>

<SharePoint:SaveButton runat="server"/>

<SharePoint:GoBackButton runat="server"/>

</Template_RightButtons>

</wssuc:ToolBar>

</td></tr></TABLE>

</SPAN>

<SharePoint:AttachmentUpload runat="server"/>

</Template>

</SharePoint:RenderingTemplate>

If we look more closely into the above code we see the outline structure of the list form which includes Toolbar, Save Button, Cancel Button Attachments etc.. So how are the actual list data columns being generated? They get generated on the fly based in the columns in the list by one more rendering template called "ListFieldIterator" with this code of line

<SharePoint:ListFieldIterator runat="server"/>

Since we should not modify the out of box template we should create a new rendering template an user control (.ascx) file and place it in the control templates folder. Let's name the user control as "FaxLogDisplayFormTemplate.ascx". Below is the code that should be present in the FaxLogDisplayFormTemplate.ascx user control.

<%@ Control Language="C#" AutoEventWireup="false" %>

<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" %>

<%@ Register TagPrefix="SPHttpUtility" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
Namespace="Microsoft.SharePoint.Utilities" %>

<%@ Register TagPrefix="wssuc" TagName="ToolBar" Src="~/_controltemplates/ToolBar.ascx"
%>

<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" Src="~/_controltemplates/ToolBarButton.ascx" %>

<%@ Register TagPrefix="FaxLogDeleteButton" Namespace="FaxLog" Assembly="FaxLog, Version=1.0.0.0, Culture=neutral, PublicKeyToken=53432c81e5329871"%>

<sharepoint:renderingtemplate id="FaxLogDisplayForm" runat="server">

<Template>

<!-- Custom Display Form -->

<SPAN id='part1'>

<SharePoint:InformationBar runat="server"/>

<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator="&nbsp;" runat="server">

<Template_RightButtons>

<SharePoint:NextPageButton runat="server"/>

<FaxLogDeleteButton:FaxLogDeleteButton
class="ms-ButtonHeightWidth" ID="ButtonDeleteTop" Text="Delete" OnClientClick="return confirm('Are you sure you want to delete?')" ToolTip="ButtonDeleteTop" runat="server" />

<SharePoint:GoBackButton runat="server"/>

</Template_RightButtons>

</wssuc:ToolBar>

<SharePoint:FormToolBar runat="server"/>

<TABLE class="ms-formtable" style="margin-top: 8px;" border=0 cellpadding=0 cellspacing=0 width=100%>

<SharePoint:ChangeContentType runat="server"/>

<SharePoint:FolderFormFields runat="server"/>

<SharePoint:ListFieldIterator runat="server"/>

<SharePoint:ApprovalStatus runat="server"/>

<SharePoint:FormComponent TemplateName="AttachmentRows" runat="server"/>

</TABLE>

<table cellpadding=0 cellspacing=0 width=100%><tr>

<td class="ms-formline">

<IMG SRC="/_layouts/images/blank.gif" width=1 height=1 alt=""></td></tr></table>

<TABLE cellpadding=0 cellspacing=0 width=100% style="padding-top: 7px"><tr><td
width=100%>

<SharePoint:ItemHiddenVersion runat="server"/>

<SharePoint:ParentInformationField runat="server"/>

<SharePoint:InitContentType runat="server"/>

<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&nbsp;" runat="server">

<Template_Buttons>

<SharePoint:CreatedModifiedInfo runat="server"/>

</Template_Buttons>

<Template_RightButtons>

<FaxLogDeleteButton:FaxLogDeleteButton class="ms-ButtonHeightWidth" ID="ButtonDeleteBottom" Text="Delete" OnClientClick="return confirm('Are you sure you want to delete?')" ToolTip="ButtonDeleteBottom" runat="server" />

<SharePoint:GoBackButton runat="server"/>

</Template_RightButtons>

</wssuc:ToolBar>

</td></tr></TABLE>

</SPAN>

<SharePoint:AttachmentUpload runat="server"/>

</Template>

</sharepoint:renderingtemplate>

If we observed the above highlighted code we have placed the custom delete button which we have developed beside the actual SharePoint close Button. Below shows the Delete Button code

Override the "OnClick" event of Asp.net Button Class:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.UI.WebControls;

using System.Web.UI;

using System.Web;

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

namespace FaxLog

{
public class FaxLogDeleteButton : Button

{
protected override void OnClick(EventArgs e)

{
string todaysDateTemp = DateTime.Today.GetDateTimeFormats()[66];
string todaysDate = todaysDateTemp.Substring(0, todaysDateTemp.ToString().IndexOf(' ')).ToString();
SPList list = SPContext.Current.List;
string redirectURL = list.DefaultViewUrl;
int id = SPContext.Current.ItemId;
SPListItem item = SPContext.Current.ListItem;
string createdDateTemp = Convert.ToDateTime(item["Created"].ToString()).GetDateTimeFormats()[66];
string createdDate = createdDateTemp.Substring(0, createdDateTemp.ToString().IndexOf(' ')).ToString();
if (createdDate == todaysDate)

{
using (SPWeb web = SPControl.GetContextSite(Context).OpenWeb())

{

web.AllowUnsafeUpdates = false;

item["Delete Flag"] = "1";

item.Update();

web.AllowUnsafeUpdates = false;

}

}
this.Context.Response.Redirect(redirectURL);

}

}

}

Register this template in the SharePoint custom list definition schema:

Now once we complete our development we need to register this custom rendering template which we have developed in our custom list definition as follows

<XmlDocuments>

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

<FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms"&gt; <New>FaxLogNewForm</New>

<Display>FaxLogDisplayForm</Display>

</FormTemplates>

</XmlDocument>

</XmlDocuments>
So the final folder structure looks like this…



Tuesday, August 31, 2010

Security Trimming of Static Links on SharePoint Quick Launch

SharePoint Out Of Box features which comes with team site/Blank site does not provide security trimming of static links on quick launch bar, Whereas this feature is present for all publishing sites. To implement this feature for Team/Blank sites I have decided to write a web part for quick launch bar which reads xml form the custom property of the web part and renders the links. XML contains the required security trimming of static links. Once this web part is ready I would disable the quick launch bar in the master page through SharePoint designer and place this web part there. I am cashing this XML since the operation to convert an XML to dataset is costly.

XML:

<?xml version="1.0" encoding="utf-8"?>

<Home>

<Menu text="Tickets" url="/Lists/Tickets/AllItems.aspx" Roles="Approvers,Enterers,Administrators">

<SubMenu text="New Ticket" url="/_layouts/ProjectName/Ticket List Forms/CaymanTicketsNewForm.aspx" Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="1st Approval" url="/_layouts/ProjectName/Workflow/FirstApproval.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="2nd Approval" url="/_layouts/ProjectName/Workflow/SecondApproval.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="My Tickets" url="/_layouts/ProjectName/Workflow/MyTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="My Action List" url="/_layouts/ ProjectName /Workflow/MyActionList.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

</Menu>

<Menu text="Ticket Status" url="/Lists/Tickets/AllItems.aspx"
Roles="Approvers,Enterers,Receivers,Administrators">

<SubMenu text="Pending" url="/_layouts/ProjectName/Workflow/PendingTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Cancelled" url="/_layouts/ProjectName/Workflow/CancelledTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Completed" url="/_layouts/ProjectName/Workflow/CompletedTickets.aspx"
Roles="Approvers,Enterers,Receivers,Administrators"></SubMenu>

</Menu>

<Menu text="Delete" url="/Lists/Tickets/AllItems.aspx" Roles="Approvers,Administrators">

<SubMenu text="Deletion of Completed Transactions" url="/_layouts/ProjectName/Workflow/DeleteTickets.aspx" Roles="Approvers,Administrators"></SubMenu>

</Menu>

<Menu text="Reports" url="/_layouts/viewlsts.aspx" Roles="Approvers,Enterers,Administrators">

<SubMenu text="Daily Tickets" url="/_layouts/ProjectName/Reports/DailyTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Today's Tickets" url="/_layouts/ProjectName/Reports/TodaysTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Daily By Enterer" url="/_layouts/ProjectName/Reports/DailyTicketsByEnterer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Monthly By Enterer" url="/_layouts/ProjectName/Reports/MonthlyTicketsByEnterer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Monthly By Customer" url="/_layouts/ProjectName/Reports/MonthlyTicketsByCustomer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Outstanding By Customer"
url="/_layouts/ProjectName/Reports/OutstandingByCustomer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Today's Transactions"
url="/_layouts/ProjectName/Reports/TodaysTransactions.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="End Of Day Download Report"
url="/_layouts/ProjectName/Reports/EndOfDayDownloadReport.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

</Menu>

<Menu text="Administrator" url="/_layouts/viewlsts.aspx" Roles="Administrators">

<SubMenu text="Customers"
url="/Lists/Customers/AllItems.aspx" Roles="Administrators"></SubMenu>

<SubMenu text="Currency" url="/Lists/Currency/AllItems.aspx" Roles="Administrators"></SubMenu>

<SubMenu text="Portfolio" url="/Lists/Portfolio/AllItems.aspx" Roles="Administrators"></SubMenu>

<SubMenu text="Type" url="/Lists/Type/AllItems.aspx" Roles="Administrators"></SubMenu>

</Menu>

</Home>

Web Part Code:

public class SampleQuickLaunch : WebPart

{

#region Variable Decleration
private Utility _oUtility;
private string quickLaunchXML = string.Empty;
private DataSet quickLaunchDS;
private DataView dvSubHeading;

#endregion

#region Constants
const string administratorsSPGroupName = "Administrators";
const string approversSPGroupName = "Approvers";
const string enterersSPGroupName = "Enterers";
const string receiversSPGroupName = "Receivers";

#endregion

[WebBrowsable(true),
WebDisplayName("Quick Launch XML"),
WebDescription("Enter Quick Launch XML"),
Personalizable(PersonalizationScope.Shared)]
public string QuickLaunchXML

{
get { return this.quickLaunchXML; }
set { this.quickLaunchXML = value; }

}
protected override void RenderContents(System.Web.UI.HtmlTextWriter output)

{
this.EnsureChildControls();
base.RenderContents(output);
try

{
string role = string.Empty;
string workflowApprovalSteps = string.Empty;
SPWeb web = SPContext.Current.Web;

createUtilityObject();
string administratorsBranchSPGroupName = web.Title + " " + administratorsSPGroupName;
string enterersBranchSPGroupName = web.Title + " " + enterersSPGroupName;
string approversBranchSPGroupName = web.Title + " " + approversSPGroupName;
string currentUserLoginName = web.CurrentUser.LoginName;
if (_oUtility.IsUserInSharePointGroup(web, administratorsBranchSPGroupName,currentUserLoginName))

{//user will get Admin menu

role = "Administrators";

}
else if (_oUtility.IsUserInSharePointGroup(web, approversBranchSPGroupName, currentUserLoginName))

{//user will get Approver menu

role = "Approvers";

}
else if (_oUtility.IsUserInSharePointGroup(web, enterersBranchSPGroupName, currentUserLoginName))

{//user will get enterer menu

role = "Enterers";

}
else if (_oUtility.IsUserInSharePointGroup(web, receiversSPGroupName, currentUserLoginName))

{//user will get receiver menu

role = "Receivers";

}
if (HttpContext.Current.Cache["Menu"] == null)

{
if (!string.IsNullOrEmpty(this.QuickLaunchXML))

{

quickLaunchDS = ConvertXMLToDataSet(this.QuickLaunchXML);
/*XML for the quick launch menu is getting cached. So this is the same XML which will be used for all the branchs in ProjectName.

* if you want to use a different XML for each branch please prefix the xml with the branch name and then cache it.*/
HttpContext.Current.Cache.Add("Menu", quickLaunchDS, null, DateTime.MaxValue,
TimeSpan.FromMinutes(20), System.Web.Caching.CacheItemPriority.Normal, null);

}

}
else

{

quickLaunchDS = (DataSet)HttpContext.Current.Cache["Menu"];

}
if (role != "Receivers")

{
using (SPWeb myWeb = SPControl.GetContextSite(Context).OpenWeb())

{

workflowApprovalSteps = _oUtility.getNumberOfWorkflowSteps(myWeb);

}

}
if (quickLaunchDS != null && quickLaunchDS.Tables[0].Rows.Count > 0)

{
string strRootURL = web.Url;

output.RenderBeginTag("div class=ms-quicklaunchouter");

output.RenderBeginTag("div class=ms-quicklaunch style=\"width:100%; border: none;\"");

output.RenderBeginTag("table cellpadding=0 cellspacing=0 class=ms-quicklaunch width=100%");

output.RenderBeginTag("tr");

output.RenderBeginTag("td class=ms-quicklaunchheader style=\"border: none;\"");

output.RenderBeginTag("a class=ms-navitem style=\"border-style:none;font-size:1em;font-family:Tahoma;\" href=" + strRootURL + "/_layouts/viewlsts.aspx");

output.Write("View All Site Content");

output.RenderEndTag();//anchor

output.RenderEndTag();//td

output.RenderEndTag();//tr
for (int i = 0; i < quickLaunchDS.Tables[0].Rows.Count; i++)

{
if (quickLaunchDS.Tables[0].Rows[i][3].ToString().Contains(role))

{
//Menu heading

output.RenderBeginTag("tr");

output.RenderBeginTag("td");

output.RenderBeginTag("table class=ms-navheader cellpadding=0 cellspacing=0 border=0 width=100%");

output.RenderBeginTag("tr");

output.RenderBeginTag("td style=width:100%;");
string strTD = "a class=ms-navheader style=\"border-style:none;font-size:1em;font-family:Tahoma;\" href=";

output.RenderBeginTag(strTD + strRootURL + System.Web.HttpUtility.UrlPathEncode(quickLaunchDS.Tables[0].Rows[i][2].ToString()));

output.RenderBeginTag("b");

output.Write(quickLaunchDS.Tables[0].Rows[i][1]);

output.RenderEndTag();//bold

output.RenderEndTag();//anchor tag

output.RenderEndTag();//td

output.RenderEndTag();//tr

output.RenderEndTag();//table

output.RenderEndTag();//td

output.RenderEndTag();//tr
string headingKey = quickLaunchDS.Tables[0].Rows[i][0].ToString();

dvSubHeading = quickLaunchDS.Tables[1].DefaultView;

dvSubHeading.RowFilter = "Menu_Id = '" + headingKey + "'";
//Sub Menu
if (dvSubHeading.Count > 0)

{
for (int x = 0; x < dvSubHeading.Count; x++)

{
if (dvSubHeading[x][2].ToString().Contains(role))

{
if (dvSubHeading[x][0].ToString() == "1st Approval" && workflowApprovalSteps == "1")

{
//do not add 1st approval link

}
else

{

output.RenderBeginTag("tr");

output.RenderBeginTag("td style=\"width:100%;border-style:none;border-width:0px;\"");

output.RenderBeginTag("table class=ms-navitem width=100% border=0 cellpadding=0 cellspacing=0");

output.RenderBeginTag("tr");

output.RenderBeginTag("td style=\"width:100%;border-style:none;border-width:0px;\"");

output.RenderBeginTag("a class=ms-navitem style=\"border-style:none;font-size:1em;\" href=" + strRootURL + System.Web.HttpUtility.UrlPathEncode(dvSubHeading[x][1].ToString()));

output.Write(dvSubHeading[x][0].ToString());

output.RenderEndTag();//anchor

output.RenderEndTag();//td

output.RenderEndTag();//tr

output.RenderEndTag();//table

output.RenderEndTag();//td

output.RenderEndTag();//tr

}

}

}

}

}

}

output.RenderEndTag();//table

output.RenderEndTag();//div

output.RenderEndTag();//div

}

}
catch (System.Exception ex)

{
PortalLog.LogString(ex.StackTrace);
throw new SPException(ex.Message);

}
finally

{

}

}

#region Methods
private DataSet ConvertXMLToDataSet(string xmlData)

{
StringReader stream = null;
XmlTextReader reader = null;
try

{
DataSet xmlDS = new DataSet();

stream = new StringReader(xmlData);
// Load the XmlTextReader from the stream

reader = new XmlTextReader(stream);
//this is costly operation... chk for alternative.

xmlDS.ReadXml(reader);
return xmlDS;

}
catch (System.Exception ex)

{
PortalLog.LogString(ex.StackTrace);
throw new SPException(ex.Message);

}
finally

{
if (reader != null) reader.Close();

}

}// Use this function to get XML string from a dataset
private void createUtilityObject()

{
if (_oUtility == null)

{

_oUtility = new Utility();

}

}

public bool IsUserInSharePointGroup(SPWeb currentWeb, string groupName, string username)

{
bool reachedMax = false;
bool userIsInGroup = false;
// return false if there are invalid inputs
if (String.IsNullOrEmpty(username) String.IsNullOrEmpty(groupName))
return false;
// The Run with elevated privileges needs read permission
// on active directory and the ability to run directory code.
SPSecurity.RunWithElevatedPrivileges(delegate

{
// Get the Site Collection
using (SPSite site = new SPSite(currentWeb.Site.ID))

{
// Get the web
using (SPWeb web = site.OpenWeb())

{
// Find the group
SPGroup group = site.RootWeb.SiteGroups[groupName];
string upperCaseUserName = username.ToUpper();
// Get ad users in the groups. Since MOSS does not support nested groups
// this will always be a collection of AD users and groups
foreach (SPUser user in group.Users)

{
// Check if this is a Group
if (!user.IsDomainGroup)

{
// Verify if the user name matches the user name in group
if (user.LoginName.ToUpper().Equals(upperCaseUserName))

{
// if a match is confirmed, return from the method.
// There is no need to continue

userIsInGroup = true;
break;

}

}
else

{
// If the AD entitiy is a User Group, then check for users in that group
if (IsUserInADGroup(web, user.LoginName, username, out reachedMax))

{

userIsInGroup = true;
break;

}

}

}

}

}

});
return userIsInGroup;

}

#endregion

}