Monday, February 3, 2014

Using Elevated Privileges efficiently


Utility methods:
public static SPUserToken GetSystemToken(SPSite site)
{
bool cade = SPSecurity.CatchAccessDeniedException;
SPSecurity.CatchAccessDeniedException = false;
SPUserToken token = null;
try
{
token = site.SystemAccount.UserToken;
}
catch (UnauthorizedAccessException)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite elevatedSite = new SPSite(site.ID))
{
token = elevatedSite.SystemAccount.UserToken;
}
});
}
finally
{
SPSecurity.CatchAccessDeniedException = cade;
}
return token;
}

public static SPUserToken GetUserToken(SPSite site, SPWeb web, string userLoginName)
{
bool cade = SPSecurity.CatchAccessDeniedException;
SPSecurity.CatchAccessDeniedException = false;
SPUserToken token = null;
try
{
token = web.GetUserToken(userLoginName);
}
catch (UnauthorizedAccessException)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite elevatedSite = new SPSite(site.ID))
{
using (SPWeb elevatedWeb = elevatedSite.OpenWeb(web.ID))
{
token = elevatedWeb.GetUserToken(userLoginName);
}
}
});
}
finally
{
SPSecurity.CatchAccessDeniedException = cade;
}
return token;
}

Using the above methods:
SPSite contextSite  = SPContext.Current.Site;
SPWeb contextWeb = SPContext.Current.Web;
SPUserToken authorUserToken = Utility.GetUserToken(contextSite, contextWeb, hfAuthorLoginName.Value);
using (SPSite elevatedSite = new SPSite(contextSite.ID, authorUserToken))
{
     using (SPWeb elevatedWeb = elevatedSite.OpenWeb(contextWeb.ID))
     {    }
}



Wednesday, March 28, 2012

Custom Form for SharePoint List - FieldRenderingControl

I was working on a feature to enable bulk edit of list items..For this purpose, I wanted to develop a custom page in which all the list fields/columns should be rendered for the user to submit his data..

I thought I might have to build the form rendering logic myself..i.e, render a text box for text fields, drop down for choice fields etc..

But to my pleasant surprise, I chanced upon the "FieldRenderingControl" property of SPField, and it turned out that the complete rendering logic could be achieved with just few lines of code, as shown below:

SPList list = web.Lists[new Guid(listID)];
foreach (SPField field in list.Fields)
{
     BaseFieldControl 
renderingControl = field.FieldRenderingControl;
     
renderingControl .ID = field.Id.ToString().Replace("-", "_");
     
renderingControl .ControlMode = SPControlMode.New;
     
renderingControl .ListId = list.ID;
     
renderingControl .FieldName = field.InternalName;
     
renderingControl .RenderContext = SPContext.GetContext(HttpContext.Current, list.DefaultView.ID, list.ID,   web);

      myPanel.Controls.Add(renderingControl); //Add the control dynamically to a panel on page
}

This is it.. The above code will render all the fields in the list with the appropriate edit controls..

Note: You can use the following condition, within the above loop to hide read-only and non-editable fields:

if (field.ShowInNewForm.GetValueOrDefault(true) 
                                && field.Hidden == false 
                                && field.ReadOnlyField == false
                                && field.FieldRenderingControl != null
                                && (field.ShowInEditForm ?? false || field.CanBeDisplayedInEditForm))
{
      // Render the fields
}

Tuesday, March 27, 2012

Auto-closing SharePoint pop-up window after exporting to Excel

Recently I was working on a utility to export SharePoint list view data to a CSV file.. The logic was to open an application page using SharePoint's dialog framework, do the export logic in page-load of that application page and finally close the pop-up..

I thought closing the pop-up after completion would be fairly simple. I thought I can just use

Response.Write("<script type='text/javascript'>window.frameElement.commitPopup();</script>");

after process completion. But here was the challenge..To save the CSV data, I had to use the following code:


HttpContext context = HttpContext.Current;
context.Response.Clear();
context.Response.Write(csvData.ToString());
context.Response.ContentType = "text/csv";
context.Response.AppendHeader("Content-Disposition", "attachment; filename=" + fileName + ".csv");
context.Response.Flush();
context.Response.End();



The problem was, after changing the content type of Response to "text/csv", it was not possible to execute any JavaScript from server code to close the pop-up window..

After trying many options, I was about to give up when I found this method definition on MSDN..
SP.UI.ModalDialog.commonModalDialogClose(dialogResult, returnVal);
Closes the most recently opened modal dialog with the specified dialog result and return value.
Now, the solution was simple.. All I had to do was to set a cookie in the server side after process completion, and in the parent page, run a timer job every few seconds in JavaScript to check for the same cookie, and if found, delete the cookie, stop the timer and close the pop-up..

Note: To process the cookie in JavaScript I used this great library, because of which it turned out to be just a single line of code..
http://code.google.com/p/cookies/

Here is the complete code..

Javascript to open and close the pop-up:

function OpenPopUp() {
    var oUrl =  'http://mysite/_layouts/SPExcelExport/SPExcelExport.aspx;
    var options = {
        title: 'Excel Export',
        allowMaximize: false,
        showClose: true,
        width: 300,
        height: 90
    };
    SP.UI.ModalDialog.commonModalDialogOpen(oUrl, options, null, null);//Open pop-up
    var millisecondsToWait = 2000; //Run every 2 seconds to check for cookie
    var intrvl = setInterval(function () {
        if (jaaulde.utils.cookies.get('csvexportcookie') != null) { //check if cookie is found
            jaaulde.utils.cookies.del('csvexportcookie'); //Delete the cookie
            SP.UI.ModalDialog.commonModalDialogClose(1, 1); //Close the pop-up
            clearInterval(intrvl);//Clear timer 
        }
    }, millisecondsToWait);
};

Server side code in the pop-up page:


HttpCookie oCookie = new HttpCookie("csvexportcookie", "completed"); //Set the Cookie
Response.Cookies.Add(oCookie);

HttpContext context = HttpContext.Current; //Write the CSV data to current response
context.Response.Clear();
context.Response.Write(csvData.ToString());
context.Response.ContentType = "text/csv";
context.Response.AppendHeader("Content-Disposition", "attachment; filename=" + fileName + ".csv");
context.Response.Flush();
context.Response.End();

That was it.. Problem solved!

Wednesday, September 21, 2011

Problems with "AfterProperties" in list event receivers

This is to summarize the inconsistent behaviors I encountered when working with "AfterProperties" in "ItemUpdating" event handler on a custom SharePoint list in SP 2010.

1) AfterProperties will be NULL when ItemUpdating event is called because of a code-update to list:

This is something that is not documented, and lead me into a lot of trouble:

Consider this scenario:

My SharePoint list had a column named "Email".

I wrote an "item-updating" event handler that will update a few other child lists with updated Email, when it was altered in the parent list..(to which the event handler was attached)

Here is the code:

string oldEmail = (properties.ListItem["Email"] == null) ? "" : properties.ListItem["Email"].ToString();

string newEmail = (properties.AfterProperties["Email"] == null) ? "" : properties.AfterProperties["Email"].ToString();

if(oldEmail != newEmail )
{
     //Update the other child lists with "newEmail"
}

I tested this code by editing the list item and updating the Email and the change was correctly propagated to  the other lists. 

After a few days we noticed that a lot of items in those child lists, had empty email values.

Reason:

When the item in parent list is edited directly using the SharePoint interface, then both properties.ListItem["Email"] and properties.AfterProperties["Email"] has the expected values.

But, when the list is being updated by code, and the code does not update the "Email" (for example, the code just updates "First Name" in the list)  then properties.AfterProperties["Email"] will be NULL in the event receiver code. AfterProperties will have the correct value, only when Email is also updated through code.

This was not the case when editing was done through SharePoint UI.( i.e If I edit that item and change only the "First Name", then AfterProperties["Email"] had the correct value..)

Because of this my event receiver had reset Email in all the child lists with empty values.

Resolution:

Unfortunately, the only resolution available was to check whether "AfterProperties" is NULL and in that case assume that the list item is updated through code somewhere else, and do nothing..

2) Dealing with "Lookup" columns:

If you are handling a "Lookup" column using "AfterProperties", then be careful. It looks like a bug where "AfterProperties" contains only the "ID" part of look-up and not the whole look-up value.

Consider this example, where I am checking whether a look up column value has been altered:

SPFieldLookupValue oldLookupValue = new SPFieldLookupValue(properties.ListItem["LookupColumn"].ToString());

SPFieldLookupValue newLookupValue = new SPFieldLookupValue(properties.AfterProperties["LookupColumn"].ToString());

if(oldLookupValue != newLookupValue)
{
     //look up column value has changed
}



The above code looks correct, but simply would not work in "ItemUpdating" event.

Reason:
Value of properties.ListItem["LookupColumn"].ToString() will have the correct look up value like "3;#karthik", but the value of properties.AfterProperties["LookupColumn"].ToString() will have only "3" (just the look up ID).

Resolution:
Here is the workaround that I implemented to get around this issue:

string oldLookupValue = (properties.ListItem["LookupColumn"] == null) ? "" : properties.ListItem["LookupColumn"].ToString();

string newLookupValue = (properties.AfterProperties["LookupColumn"] == null) ? "" : properties.AfterProperties["LookupColumn"].ToString();

int oldLookupID = 0;
int newLookupID = 0;


if (!string.IsNullOrEmpty(oldLookupValue))//Get the old Lookup id
{
     SPFieldLookupValue oldLookup = new SPFieldLookupValue(oldLookupValue);
      oldLookupID = oldLookup.LookupId;
}
if (!string.IsNullOrEmpty(newLookupValue))//Get the new Lookup id
{
     newLookupID = Convert.ToInt32(newLookupValue);
}

if (oldLookupID != newLookupID)//Lookup has been updated
{
     //value has changed

}

Another solution would be to use string parsing methods to compare just the ID part.

Wednesday, June 22, 2011

Event receiver, item permissions and form submission error

I had to open a SharePoint list for anonymous users to add items. The solution was to allow anonymous users to have "add" permissions on the list, design an InfoPath form for them to add the data, and fire an event receiver on the item added event to remove permissions for anonymous users on the item.. (In our case, anonymous users should not be able to see any item that was added to this list).

When I implemented this solution, InfoPath form would throw the error "Item was not found" after form submission. However the item was added successfully and the event receiver had also done its job.

Reason
The reason is pretty obvious. The event receiver had fired immediately after the item was added and removed all permissions on the list item, before InfoPath could complete its work. So, I thought delaying the event receiver by 10 seconds or so using "Thread.Sleep" would solve the issue. But that was not the case. I also observed a strange behavior where the InfoPath form itself waited for 10 seconds before throwing the same error again.

Resolution
The resolution was to execute the event receiver code in a separate thread as shown below:


         public override void ItemAdded(SPItemEventProperties properties)
        {
            EventFiringEnabled = false;
            try
            {
                Thread setPermissionsOnItem = new Thread(RunCodeInThread);
                setPermissionsOnItem.Start(properties);
            }
            catch (Exception ex)
            {
                //Log exception here
            }
            EventFiringEnabled = true;
        }

        private void RunCodeInThread(object objProperties)
        {
            Thread.Sleep(10 * 1000); //Halt for 10 secs to allow infopath form to complete
            try
            {
                SPItemEventProperties properties = (SPItemEventProperties)objProperties;
                // do the event receiver job here
            }
            catch (Exception ex)
            {
                //Log exception here
            }
        }

This solved the problem. The InfoPath form completed its processing immediately and the event receiver also worked fine. (A delay of 10 seconds is too much in most cases. You can reduce it considerably, but be sure to test it. I tried executing this code, without using Thread.Sleep and it worked 8 out of 10 times on an average)

Wednesday, December 15, 2010

Unable to load DLL 'FileTracker.dll' error in Visual Studio 2010

I got this error, when I tried to build a project in Visual Studio 2010 (professional).
System.DllNotFoundException: Unable to load DLL 'FileTracker.dll': The specified module could not be found.
This build error occurred only in projects in which resource files were used.

Resolution:
1) Open "Microsoft.Common.targets" file located at "C:\Windows\Microsoft.NET\Framework\v4.0.30128".
2) Locate the following line "<TrackFileAccess Condition="'$(TrackFileAccess)' == ''">true</TrackFileAccess>"
3) Change the value to "false"
4) Do the same for the file found in "Framework64" folder also.

Note:
If we have to get rid of this problem in all the machines where it would be deployed, we can edit the ".csproj" file of our project, to include the following lines:


<PropertyGroup>
      <TrackFileAccess>false</TrackFileAccess>
</PropertyGroup>



Make sure that this is at the the bottom of the file just before the closing project tag (</Project>).
Doing so, will however slow down the build time, during deployment.


SharePoint 2010 installation on Windows 7 - Product Config Wizard fails in step 2 with System.FormatException

Today I was trying to install SharePoint 2010 enterprise on my Windows 7 laptop. The product configuration wizard was failing with the following error in Step 2 [creating configuration database].


The exception was:

System.FormatException: Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).   at System.Guid..ctor(String g)   at Microsoft.SharePoint.Administration.SPFarm.GetInstalledProductIds()
Reason:
The following registry key had an "invalid" value "0" for the registry name {90140000-110D-0000-1000-0000000FF1CE} :
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server extensions\14.0\WSS\InstalledProducts
Solution:


This is the key that is used to determine the current edition (Foundation, Standard,Enterprise..) of SharePoint installed. The correct value of this should be:



88BED06D-8C6B-4E62-AB01-546D6005FE97 - For Enterprise trial
D5595F62-449B-4061-B0B2-0CBAD410BB51 - For Enterprise licensed
B2C0B444-3914-4ACB-A0B8-7CF50A8F7AA0 - For Standard trial
3FDFBCC8-B3E4-4482-91FA-122C6432805C - For Standard licensed

Changing this solved the problem.