Wednesday, October 31, 2007

SharePoint: Does the user have permissions?

A common task in SharePoint programming is writing security code. One of the great things about the object model is that it is security trimmed, so you can usually just ask for items that the user has permissions for. However, there may be items that the user can access but the user still doesn't have access to perform a specific task-- which is a great reason to check for permissions before attempting an operation. I'm pretty sure I've blogged about this before... but I've heard this question several times lately.
To check for permissions on an item, the SPSecurableItem interface defines 2 methods for checking security. The DoesUserHavePermissions method returns a bool speccifying if the user can access the item, where the CheckUserHasPermissions method will throw a security exception, which causes a 401 http status if the current SPSite's CatchAccessDeniedException property isn't set to false. Also note that you call these using the SPBasePermissions value which specifies the task you want to check permissions for-- and you don't use the overloaf
The following sample shows how to check permissions on the SPWeb level:
SPWeb web = SPContext.Current.Web ;if (web.DoesUserHavePermissions(SPBasePermissions.ViewListItems){ // do something, like Enumerate lists}
The SPList is also an ISecurableObject, which means that you can apply the same principlesto check permissions on lists. To check the user’s permission to view list items within aspecific list, call the list’s DoesUserHavePermissions method as follows:foreach(SPList list in web.lists){ if (list.DoesUserHavePermissions(SPBasePermissions.ViewListItems)) { /* Process the list */ }}
Likewise, the same method is available in other objects, such as the SPListItem class, whichcan be used to ensure that the user has permissions to the item or document:foreach(SPListItem item in list.Items){ if (item.DoesUserHavePermissions(SPBasePermissions.ViewListItems)) { {/* Process the list item */ } }
You can also check if the anonymous user has access to an item like this, in the case where the current user is anonymous:if ((list.AnonymousPermMask64 & SPBasePermissions.ViewListItems) == SPBasePermissions.ViewListItems)
{ // Do something here... }
You can also get the subwebs for the calling user using the method, which will return a security trimmed collection of webs:
SPContext.Current.Web.GetSubwebsForCurrentUser();

What We’ve Learned about AJAX Code in SharePoint

In “Inside WSS” we did a chapter on using ASP.NET AJAX technologies within SharePoint. There are a few things we’ve learned since writing the book that we’d like to clear up, and a few ASP.NET techniques that we’d recommend avoiding in favor of more stable methods, as well as some additional tips and tricks for coding AJAX applications in the portal. We’ve been proving these technologies both in the classroom and through commercial applications for close to a year now, refined some methods and established the supportable development story. I'll add this to an official errata for the book, but these tips will be useful to any ajax developer working in SharePoint.
First off, we would not recommend using the WebResource and ScriptResource technologies. We talk about this in the Web Parts chapter as well as the Ajax chapter, but in commercial applications we’ve decided to deploy resources to the “_layouts” vdir which is easier to debug and avoids a bug with the SharePoint runtime, in which the call to compiled script resources sometimes fails when accessed by a non-administrator. This can be a difficult bug to track down in your application, especially if you develop as an administrative account on your dev box (but, something you can totally avoid when using Windows server 2008 as your dev box!) What we do recommend and what is fully supported is deploying resources to a folder for your application in the Layouts application (http://localhost/_layouts/[your app name].)
So instead of the following code:

string xsl = this.Page.ClientScript.GetWebResourceUrl(typeof(FeedListWebPart),@"LitwareAjaxWebParts.Resources.OPML.xslt");this.XsltUrl = SPContext.Current.Web.Url + @"/" + xsl;

We would simply use:

this.XsltUrl = @"/_layouts/litware/rss.xslt";
ASP.NET AJAX ScriptReferences are simplified as well. Instead of compiling in the resource and adding the required assembly attribute before adding the script reference by fully qualified type name, we recommend adding a script reference with the URL:

this.AtlasScriptManager.Scripts.Add(new ScriptReference("/_layouts/Litware/ListViewWebPart.js"));
Another point of failure we've found is the ASP.NET AJAX JavaScript proxy generator. We've found that in most scenarios, the reference to foo.asmx/js fails when the calling user is not the administrator. So instead of creating a ServiceReference with the ASP.NET Script Manager, we instead save the asmx/js endpoint to a JavaScript file that we deploy with our other scripts. When calling web services that use the site context, it is also necessary to set the proxy's url before each call, or you'll be calling the webservice from the root web site's context. Because of this, we include the spWebUrl property in the SharePoint Ajax Toolkit, which writes the current SPWeb's Url to the window.spWebUrl property. To do this, use the set_path method of your web service proxy, like this, immediately before the call:

Litware.WikiWebService.set_path(window.spWebUrl+'/_vti_bin/Litware/WikiWebService.asmx');
Another thing that we’ve done in the SharePoint Ajax Toolkit is add a cache key, which is generated on each build. You can then append this cache key to script resources, or even output it as JavaScript to use on the client, to ensure that scripts and xslt resources are cached (and NOT cached) properly between builds. You really don’t want to have to tell your users to clear their cache after each load—that’s just embarrassing! The following code will generate an arbitrary string to append in a query for each url, which will cause the browser to cache the resource uniquely per build.
public readonly static string CacheKey = Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.GetHashCode().ToString(CultureInfo.InvariantCulture);
You would then use this in code like this: this.XsltUrl = @"/_layouts/litware/rss.xslt?" + Utility.CacheKey;
We've also added many refinements to the SharePoint AJAX Toolkit at NewsGator in our product lifecycles, so be sure to grab the latest code from www.codeplex.com/sharepointajax.

Copying a SQL Server Database to Another Environment

A couple of weeks ago I was troubleshooting a performance problem with the variations feature in MOSS 2007 and I needed to copy the content database to another environment for further analysis and testing. An easy (an unobtrusive) way to "snapshot" a database and copy it to another environment is to create a backup with the COPY_ONLY option:
BACKUP DATABASE [WSS_Content]
TO DISK = N'H:\WSS_Content.bak'
WITH NOFORMAT, NOINIT
, NAME = N'WSS_Content-Full Database Backup'
, SKIP, NOREWIND, NOUNLOAD, STATS = 10
, COPY_ONLY
From SQL Server 2005 Books Online:
Taking a backup normally changes the database, in turn affecting other backups and how they are restored. Sometimes, however, a backup must be taken for a special purpose that should not affect the overall backup and restore procedures for the database.

A data backup is normally a base backup for one or more differential backups taken after it. Microsoft SQL Server 2005 introduces support for creating copy-only backups, which do not affect the normal sequence of backups. Therefore, unlike other backups, a copy-only backup does not impact the overall backup and restore procedures for the database.
In other words, by using the COPY_ONLY option I avoided screwing up the scheduled differential backups on the database.
However, there are a couple of issues with this approach:
You cannot specify the COPY_ONLY option through the UI in SQL Server Management Studio, but this is no big deal -- you can start by configuring most of the backup options using the UI, script the action to generate the corresponding SQL, and then add the COPY_ONLY option as shown above
You cannot restore a backup created using the COPY_ONLY option through the UI in SQL Server Management Studio; in the Restore Database dialog, when you select the From device option and then specify the backup file previously created with the COPY_ONLY option, no backup sets are displayed
The second problem was puzzling to me. After specifying my backup file, when I attempted to change to the Options page, I encountered the following error:
You must select a restore source.
When I first encountered this problem, I thought I had a corrupt backup file. However, by once again reverting to SQL instead of the UI, I was able to verify the backup was, in fact, valid:
RESTORE FILELISTONLY
FROM DISK = N'E:\NotBackedUp\Temp\WSS_Content.bak'
To restore from a COPY_ONLY backup, use a command similar to the following:
RESTORE DATABASE [WSS_Content_TEST]
FROM DISK = N'E:\NotBackedUp\Temp\WSS_Content.bak'
WITH FILE = 1
, MOVE N'WSS_Content'
TO N'E:\Microsoft SQL Server\MSSQL.1\MSSQL\Data\WSS_Content_TEST.MDF'
, MOVE N'WSS_Content_Log'
TO N'L:\Microsoft SQL Server\MSSQL.1\MSSQL\Data\WSS_Content_TEST_Log.LDF'
, NOUNLOAD, STATS = 10

How many ways to represent True and False

I was recently serializing data from both the database and user input, and it made me reflect on how many ways you can represent a boolean value as a literal string. For an English-language app (no globalization), here are several ways to represent a boolean:

TRUE / FALSE - converting from a literal string.
T / F - users who only want to enter the first character.
Yes / No - non-technical users who want "friendly" terms.
Y / N - again, users who want to only enter the first character.
1/0 - A bit, such as how SQL Server stores booleans.

And of course, the first three options can be case insensitive and trimmed white space (i.e. "tRUe" becomes "TRUE").

I had talked about using Convert.ToString to convert different objects to string, so I'd initially look at it's related method: Convert.ToBoolean. But one quickly sees that that won't handle all the cases (and with good reason).
The only literal string it takes from this group is "true"/"false". For example, it would handle converting the integer 1, but not the literal string "1".
Having a single function that just converts these different inputs to a boolean is a nice convenience.
Here's a sample:
public static bool ConvertToBoolean(string strVal)
{
if (string.IsNullOrEmpty(strVal))
return false;
strVal = strVal.ToUpper().Trim();
if (strVal == "TRUE" strVal == "T" strVal == "1" strVal == "YES" strVal == "Y") return true; else if (strVal == "FALSE" strVal == "F" strVal == "0" strVal == "NO" strVal == "N")
return false;
else
throw new ArgumentException("Cannot convert '" + strVal + "' to Boolean.");}

Adding an Event to a User Control (Code Sample)

I had talked about how to trigger an event from a UserControl.
This ability has many benefits, such as with refactoring. For example, suppose a UserControl is hosted on many different pages, and each page requires that the control have slightly different validation that incorporates values from the host page. One way to do this is to have the UserControl call a validation method on the host page.
Here's a code snippet you can download that shows how to have a UC call a method on its parent. The idea is to add an event member to the control, and hook it up with a delegate. (I had initially seen this technique from an article on MSDN several years ago).
This specific example has four files:
A UserControl - RecordNav.ascx and RecordNav.ascx.cs
A host page - HostRecordNav.aspx and HostRecordNav.aspx.cs
The UserControl contains an event "UpdateDate" and the host page adds a method to handle the event: RecordNav1_UpdateData.
RecordNav1.UpdateData += new AspxDemo_Events_RecorNav.UpdateDataEventHandler(RecordNav1_UpdateData);

The difference between array and ref array

Sometimes you'll want to pass an object (like an array) into a method, and have that method update the object. For an array, the common ways to do this are using the ref keyword, or modifying a member of an array. It's easy to confuse these two approaches because if you're just updating a member, they appear to have the same affect. However they're actually fundamentally different - passing in an array by ref lets you modify the array reference itself, such as changing it to a new array with a new length. The code snippet below illustrates this:


#region Normal Array [TestMethod]
public void TestMethod1()
{ //Normal array changes individual member
string[] astr = new string[]{"aaa"};
ModifyArray1(astr);
Assert.AreEqual("bbb", astr[0]);
} [TestMethod]

public void TestMethod2()
{ //Non-ref array doesn't change array itself
string[] astr = new string[] { "aaa" };
ModifyArray2(astr); Assert.AreEqual(1, astr.Length);
Assert.AreEqual("aaa", astr[0]);
}
public static void ModifyArray1(string[] astr)
{
astr[0] = "bbb";
}
public static void ModifyArray2(string[] astr)
{ astr = new string[] { "ccc", "ccc" };
} #endregion

#region Ref Array [TestMethod]
public void TestMethodRef1()
{
string[] astr = new string[] { "aaa" };
ModifyArrayRef1(ref astr);
Assert.AreEqual("bbb", astr[0]);
}
[TestMethod]
public void TestMethodRef2()
{ //Ref array can change the array itself, like giving it a new length
string[] astr = new string[] { "aaa" };
ModifyArrayRef2(ref astr);
Assert.AreEqual(2, astr.Length);
Assert.AreEqual("ccc", astr[0]);
}
public static void ModifyArrayRef1(ref string[] astr)
{
astr[0] = "bbb";
}
public static void ModifyArrayRef2(ref string[] astr)
{ astr = new string[] { "ccc", "ccc" };
} #endregion

Save the Grid Item

foreach(DataGridItem dgItem in dg_ProposedExp.Items)
{
Label Sno=(Label)dgItem.Cells[0].FindControl("Lbl_Sno");
Label Description =(Label)dgItem.Cells[1].FindControl("Lbl_Description");
Label Unit = (Label)dgItem.Cells[2].FindControl("Lbl_Unit");
Label Qty = (Label)dgItem.Cells[3].FindControl("Lbl_Qty");
Label Rate = (Label)dgItem.Cells[4].FindControl("Lbl_Rate");
Label Amount = (Label)dgItem.Cells[5].FindControl("Lbl_Amount");
PE_save="Insert into Proposed_Expenditure values('"+IE_ID+"','"+Sno.Text+"','"+Description.Text+"','"+Unit.Text+"','"+Qty.Text+"','"+Rate.Text+"','"+Amount.Text+"')";
SqlCommand sqlcmd_dg=new SqlCommand(PE_save,sqlcon_save);
sqlcmd_dg.ExecuteNonQuery();
}

Save Grid to the Data Table

private void DataSave(DataTable Dt)
{
if (!(ViewState["__Data"]==null))
{
ViewState["__Data"] = Dt;
}
else
{
ViewState.Add("__Data", Dt);
}
}
public bool DataExists()
{
if(!(ViewState["__Data"]==null))
return true;
else
return false;
}
private DataTable DataRetrieve()
{
DataTable Dt ;
if ((ViewState["__Data"] == null))
{
return datb;
}
else
{
Dt = ((DataTable)(ViewState["__Data"]));
return Dt;
}
}

get the value from dataset

int j=0;
while(j
{
DateTimeFormatInfo dateTimeFormatInfo = new DateTimeFormatInfo();
//DateTimeFormatInfo dateTimeFormatInfo1 = new DateTimeFormatInfo();
string fromdate=dateTimeFormatInfo.GetAbbreviatedMonthName((Convert.ToDateTime(ds.Tables[0].Rows[j][0]).Month)).ToString();
string todate=dateTimeFormatInfo.GetAbbreviatedMonthName((Convert.ToDateTime(ds.Tables[0].Rows[j][1]).Month)).ToString();
string year=Convert.ToDateTime(ds.Tables[0].Rows[j][1].ToString()).Year.ToString();
string AppraisalPeriod=String.Concat(String.Concat (fromdate,todate),year);
string tempDistinctAppraisalRecord = AppraisalPeriod + "$" + ds.Tables[0].Rows[j][0].ToString() + "$" + ds.Tables[0].Rows[j][1].ToString();
distinctAppraisalRecord.Add(tempDistinctAppraisalRecord);
//Label3.Text=AppraisalPeriod;
appdroplist.Items.Add(AppraisalPeriod);
j++;
}
appdroplist.DataBind();

To Upload Documents in C#

if (Charge_To == "CISCO")
{
if((Upload.PostedFile != null)&& (Upload.PostedFile.ContentLength > 0))
{
strFile_Name=Upload.PostedFile.FileName;
strFile_Extn=System.IO.Path.GetExtension(strFile_Name);
File_Name = IE_ID + strFile_Extn;
upload_loc = Server.MapPath("Upload") + "...." + File_Name; //@"http://localhost/PurchaseOrder/Upload/";
Web_loc = ConfigurationSettings.AppSettings["FilePath"].ToString() + File_Name;
try
{
Upload.PostedFile.SaveAs(upload_loc);
//Response.Write("The File has been uploaded");
}
catch(Exception ex)
{
Response.Write("Error: " + ex.Message);
}
StringBuilder buildUpload=new StringBuilder();
buildUpload.Append("insert into PO_Upload values('"+IE_ID+"','"+File_Name+"','"+Requested_by+"','"+Requested_Date+"','"+Web_loc+"')");
SqlCommand sqlcmdUpload=new SqlCommand(buildUpload.ToString(),sqlcon_save);
sqlcmdUpload.ExecuteNonQuery();
}
}