Troubleshooting SharePoint 2010 : Tools & Techniques


Troubleshooting is needed to develop and maintain complex systems where the symptoms of a problem can have many possible causes. One such complex system is SharePoint 2010.

Troubleshooting SharePoint requires logical, systematic search for the source of a problem so that it can be solved, and so the SharePoint Farm or Solution can be made operational again. If you are experiencing problems with a SharePoint farm or  solution, you can use below tools and techniques to find the root cause and fix it.

1. Enable Debugging in Web.config.

You can make below changes in web.config of your web application to see the actual exceptions\errors on the page itself . If you want to debug application pages you may need to the changes in web.config at 14\TEMPLATE\LAYOUTS also.

  • Turn on the call stack (CallStack=”true”)
  • Disable custom errors in Visual Studio (<customErrors mode=”Off” />)
  • Enable compilation debugging (<compilation debug=”true”>)

The resulting web.config looks like :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>
...
<SharePoint>
<SafeMode MaxControls="200"
CallStack="true"
DirectFileDependencies="10"
TotalFileDependencies="50"
AllowPageLevelTrace="false">
...
</SafeMode>
...
</SharePoint>
<system.web>
...
<customErrors mode="Off" />
...
<compilation debug="true">
...
</compilation>
...
</system.web>
...
</configuration>

2. Visual studio F5 Debugging

It is the most common for of debugging used by developers. Just set a break point in the code and attach to the worker process(w3wp) for your web application.If you have too many web application you can run below command (in command prompt) to find the w3wp Process ID.

%windir%\system32\inetsrv\appcmd.exe list wp

If you want to see the exact set of steps to do Visual Studio debugging, check here

Please note that to debug Timer jobs and waiting Workflows you need to attach to OWSTIMER process instead on w3wp.

3. Trace(ULS) Logs

 Trace Logs are text files and can be opened with any tool that works with text files. The default location of trace logs is  in the LOGS directory of the SharePoint root (also called the 14 Hive) at C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14 . The naming format for the trace log files is machinename-YYYYMMDD-HHMM.log, in 24-hour time.

[ Note : By default, a new log file is created every 30 minutes. You can change it using  PowerShell with the Set-SPDiagosticConfig command. For example, to set for every two hours use :  Set-SPDiagnosticConfig Set-SPDiagnosticConfig -LogCutInterval 180]

Trace logs can give you important information to debug a particular issue. The trace logs have the following columns:

  • Timestamp — Date and time of the event
  •  Process — Process that generated the log entry
  •  TID — Thread ID
  •  Area — Area of the event
  •  Category — The category of the Area of the event
  •  Message — Actual error message
  •  Correlation — The correlation ID

Correlation ID

The correlation ID is a globally unique identifier GUID that is assigned to each conversation a user or process has with SharePoint. When an error occurs, an administrator can use the correlation ID to track down all of the events in the trace logs that pertain to the conversation where the problem occurred.

Correlation ID

An error with Correlation ID displayed to user

Every error\event\information recorded in the trace logs has a correlation ID. You can open trace log in Notepad or ULS Viewer and search for the Correlation ID. Then you can find the actual problem related to the error.

The correlation IDs can be used to find error details in the trace logs, Windows Event Logs and even the SQL trace. The correlation ID is considered by many administrators to be one of the best new features in SharePoint 2010.

You can also View and filter log events by using Windows PowerShell using Get-SPLogEvent

4. Event Throttling

In SharePoint 2010, You can choose to monitor specifically events which you think are the problem areas. Go to Central Administration>Monitoring>Reporting>Configure diagnostic logging to view\change the settings.

Using Event Throttling settings you can also control the severity of events captured in the Windows event log and the trace logs. As the severity decreases, the number of events logged will increase.

Also, you have option to ” Restore to default” settings once you are done with troubleshoot. Any event category which is selected appears bold. For example, If BCS is selected to diagnose, below is how it appears when you view the screen second time.

Event Throttling

Event Throttling Screen

[Note : Use the Verbose setting only when needed. For Verbose-level events,  the system will log every action that SharePoint  takes. Verbose-level logging can quickly use drive space and affect drive and server performance. You can use verbose-level logging to record a greater level of detail when you are making critical changes and then re-configure logging to record only higher-level events after you make the change]

Options for Event log

Level Definition
None No logging occurs.
Critical This message type indicates a serious error that has caused a major failure in the solution.
Error This message type indicates an urgent condition. All error events should be investigated.
Warning This message type indicates a potential problem or issue that might require attention. Warning messages should be reviewed and tracked for patterns over time.
Information Information messages do not require any action, but they can provide valuable data for monitoring the state of your solution.
Verbose This event log level corresponds to lengthy events or messages.

Options for Trace log

Level Definition
None No trace logs are written.
Unexpected This level is used to log messages about events that cause solutions to stop processing. When set to log at this level, the log will only include events at this level.
Monitorable This level is used to log messages about any unrecoverable events that limit the solution’s functionality but do not stop the application. When set to log at this level, the log will also include critical errors (Unexpected level).
High This level is used to log any events that are unexpected but which do not stall the processing of a solution. When set to log at this level, the log will include warnings, errors (Monitorable level) and critical errors (Unexpected level).
Medium When set to this level, the trace log includes everything except Verbose messages. This level is used to log all high-level information about operations that were performed. At this level, there is enough detail logged to construct the data flow and sequence of operations. This level of logging could be used by administrators or support professionals to troubleshoot issues.
Verbose When set to log at this level, the log includes messages at all other levels. Almost all actions that are performed are logged when you use this level. Verbose tracing produces many log messages. This level is typically used only for debugging in a development environment.

5. Developer Dashboard

SharePoint 2010 introduces a new developer dashboard that allows you to see all the calls made on a page on the page itself. Those calls can be ones that SharePoint is making or they can be your custom code. By looking at the call stack, response times, and utilization, you can quickly uncover where your code is performing poorly and try to fix it.

Using Developer Dashboard you can track down whether the problem is bad  code, bad database calls the code makes, or coding errors that cause excessive CPU, disk, or memory utilization.  It gives you information for below counters:

  • Thread execution time
  • Number, duration, call stack information and query text of each SQL Server query generated by the page
  • Number, duration, and call stack information of each WCF call
  • URL or timer job name
  • Current user
  • Execution start time
  • Any of the preceding statistics for code enclosed by SPMonitoredScope
Developer Dashboard in SharePoint 2010

Developer Dashboard on a SharePoint 2010 page

You can turn on the Developer Dashboard on Demand using any one of below ways. After getting enabled, the Developer Dashboard appears  on any page that use the default master page or any page using a custom master page with the Dashboard control is included.

PowerShell:

$contentService =[Microsoft.SharePoint.Administration.SPWebService]::ContentService
$dashboardSetting = $contentService.DeveloperDashboardSettings
$dashboardSetting.DisplayLevel = [Microsoft.SharePoint.Administration.SPDeveloperDashboardLevel]::OnDemand
$dashboardSetting.TraceEnabled = $true;
$dashboardSetting.Update()

Stsadm

stsadm -o setproperty -pn developer-dashboard -pv ondemand

Object Model

SPWebService cs = SPWebService.ContentService;
cs.DeveloperDashboardSettings.DisplayLevel = SPDeveloperDashboardLevel.OnDemand;
cs.DeveloperDashboardSettings.Update();

Monitoring Your Own Code with the Dashboard using SPMoniteredScope()
By default, the developer dashboard display some information about custom code, but the information is about the calls the code is making, not the source code itself. For example, if you ’ ve added web part to the page, and you accidentally put in some poor code that takes a long time to render. If you looked at the developer dashboard, you would see the time it took to add your web part to the page and how long the pre – rendering or rendering for your web part took. However, you wouldn ’ t see how long your code took to run, unless you calculated this off the total rendering time of the page and how long it took SharePoint to do its operations.

So, to add your own monitored code sections to the developer dashboard, you need to implement the SPMonitoredScope class in your custom code. You can wrap all your custom code that runs in SharePoint using the SPMonitoredScope class.

using (new SPMonitoredScope("My Code"))
{
Thread.Sleep(5000);
}

6. Other Useful Tools for Troubleshooting

SPDisposeCheck

We have to call the Dispose() method on  SPSite and SPWeb objects otherwise we get memory leaks in your application. SPDisposeCheck can scan your code and identify where objects that are not disposed.

Internet Explorer\Chorme  Developer Tools

The latest browsers Internet Explorer 8 and Chrome includes tools to debug the  HTML, script, and Cascading Style Sheets (CSS) . IE 8 also includes a JavaScript profiler that shows you the performance of your script code. With these tools, you can track down any issues in your client – side code.
Visual Round Trip Analyzer

The Visual Round Trip Analyzer (VRTA) visualizes how long it takes a client to talk to a server — information that you can then use to understand if you are making excessive round trips, if your code is slowing down the pages (for example, because of loading many small JavaScript or CSS files), or if there are network issues causing any problems between your application and the server.

Fiddler\HTTPWatch
Fiddler and HTTPWatch are a web debugging proxy that logs all HTTP traffic between your computer and the Internet. They allows you to inspect all HTTP traffic and view your incoming and outgoing data. It is an essential tool to help you understand what server variables are coming back from your server, what the payload is, how many calls your client – side code took, and other factors that provide insight into
your applications.

7. Troubleshooting few common problems in SharePoint Solutions  : http://msdn.microsoft.com/en-us/library/ee231594.aspx

8. Troubleshooting SharePoint Packaging and Deployment : http://msdn.microsoft.com/en-us/library/ee330922.aspx

, , , ,

Leave a Comment

SharePoint Tag Cloud using Deep refiners in Fast Search


SharePoint 2010 offers good enhancements and capabilities over the previous versions. If you exploit them wisely you can produce great solutions.

Recently, I wanted to have a Tag cloud to show the most heavily used tags\keywords in the pages. The important part of the requirement is that the Tag Cloud should provide aggregate view of the tags applied to pages at different scopes like:

  • All sites in a Site Collection
  • All site collections in a Web Application
  • All Web applications in a Farm

Also, the tags should be hyperlinks which would take user to page with list of all pages with similar tags.

I thought to use Managed Metadata feature of SharePoint 2010 that provides efficient way to manage Tags. However, later I discovered that the Tag cloud web part provided by SharePoint 2010 can only be used for Social Tags and not for Managed Metadata columns.

I also found a Tag cloud at Codeplex that uses Managed Metadata: http://metadatawebpart.codeplex.com/ . However this web part is not able to meet all of my requirements.

After exploring all the available options, I decided to design my own solution to meet all of the above requirements.

The solution is based on Deep Refiners in Fast Search.

FAST Search provides the capability of result refining on metadata associated with all results.
Moreover, FAST Search shows counts for each of the refiners.

The Refinement is also available in SharePoint 2010 Search with the capability of refining results by the most important metadata. However, only FAST Search provides the capability of result refining on metadata associated with all results  called Deep Refinement.

Below is a screenshot showcasing deep refiners showing facets for all items found in search:

FAST Search Deep Refiners

FAST Search Deep Refiners

So, the Refinement panel on a Fast Search results page, more or less, acts like a Tag cloud.

However, you cannot use the panel as standalone web part since it works in collaboration with other Search web parts. So there is need to programmatically access the get the deep refiners and display them as Tags where the font size or weight of a tag in is determined by its refinement count.

Before we access the deep refiners in code, There are some prerequisite steps to follow :

Note : You should have Fast Search installed and configured with Search Service applications running.

1 . Create Managed Property in Fast Search Administration that maps the Managed Metadata column (It can be any other type of column as well if you want. I used Managed Metadata as it provides auto fill and more control on tags)

2. Set a managed property as  deep refiner: You can do it via PowerShell or Fast Search Administration UI.

For PowerShell, here are the details.

For UI, You can do it via ‘Fast Query Service Application’ management interface as below:

Fast Search Managed Property

Fast Search Managed Property

3. Do a Full Crawl.

Now we are ready to get refiners in code. We can use Keyword query class like I have demonstrated in another post with some changes as below :

1. Set the result provider to Fast Search

 keywordQuery.ResultsProvider = SearchProvider.FASTSearch;

2. Specify the refiners using KeyworQuery.Refiners property

//Tags is the name of the managed property mapped to managed metadata column
keywordQuery.Refiners = "Tags";

This property is used to set the set of refiners to be returned in a search result. The value for each refiner is a format string that specifies the name of a refiner and optionally a set of refinement options for this refiner. No refiners will be returned with the query result if the Refiners property is empty.

3. Specify that the results set should contain refined results matching the search query.

keywordQuery.ResultTypes = ResultType.RefinementResults;

4. Specify a query to search all items. The below query look for all list items. You can provide custom query to restrict the scope of search.

keywordQuery.QueryText = "contentclass:STS_ListItem";

Below is the complete code that you can use in a console application to test the result :

using (SPSite site = new SPSite("http://sp2010site"))
            {
                SearchServiceApplicationProxy SSAProxy = (SearchServiceApplicationProxy)SearchServiceApplicationProxy. GetProxy(SPServiceContext.GetContext(site));
                KeywordQuery keywordQuery = new KeywordQuery(SSAProxy);       
                keywordQuery.ResultsProvider = SearchProvider.FASTSearch;
                keywordQuery.QueryText = "contentclass:STS_ListItem";
                keywordQuery.ResultTypes = ResultType.RefinementResults;            
                keywordQuery.EnableFQL = false;
                keywordQuery.StartRow = 0;
                keywordQuery.RowLimit = 100000;
                keywordQuery.KeywordInclusion = KeywordInclusion.AllKeywords;
                keywordQuery.Refiners = "Tags";
                ResultTableCollection searchResults = keywordQuery.Execute();
                if (searchResults.Exists(ResultType.RefinementResults))
                {
                    ResultTable searchResult = searchResults[ResultType.RefinementResults];
                    Console.WriteLine(searchResult.TotalRows);
                    DataTable result = new DataTable();
                    result.TableName = "SearchResults";
                    result.Load(searchResult, LoadOption.OverwriteChanges);
                    DataSet ds = new DataSet();
                    ds.Tables.Add(result);
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(ds.GetXml());
                    XmlNodeList nodeList = xmlDoc.DocumentElement.SelectNodes("/NewDataSet/SearchResults[RefinerName='tags']");                   
                    foreach (XmlNode myNode in nodeList)
                    {
                        if (myNode != null)
                            Console.WriteLine(myNode.SelectSingleNode("RefinementName").InnerText + "(" + myNode.SelectSingleNode                            ("RefinementCount").InnerText+")");
                    }
                    Console.Read();

In above code, The RefinementName gives you the value of tag and RefinementCount gives you the count i.e. how many times the tag is used

Below is the output in console application :

Tag Cloud in Console

Tag Cloud in Console

 

Now, you can transform the above code to a webpart.

You can decide on font size of a tag based on the RefinementCount : Greater RefinementCount , higher the font size. You can also use below formula to calculate font size on fly :

s_i = \left \lceil \frac{f_{\mathrm{max}}\cdot(t_i - t_{\mathrm{min}})}{t_{\mathrm{max}}-t_{\mathrm{min}}} \right \rceil for t_i > t_{\mathrm{min}}; else s_{i}=1

  • s_i: display fontsize
  • f_{\mathrm{max}}: max. fontsize
  • t_i: count
  • t_{\mathrm{min}}: min. count
  • t_{\mathrm{max}}: max. count

Now, You need to link the Tag to the page which has a list of pages with similar tag. You can use out of box search page for this or again build your own with a webpart using Keyword query.

In a tag cloud,  you can dynamically create  link for  Tags like below :

<a href="/search.aspx?k=Tags:Microsoft"><font size=15>Microsoft<font></a>,<a href="/search.aspx?k=Tags:Yahoo"><font size=12>Yahoo<font></a>

When a user click the Tag  named “Microsoft” for example , the search page will present results with Keyword Query “Tags:Microsoft”  i.e. All pages or list items tagged with keyword “Microsoft”.

So, you have a Tag cloud which you can fine tune according to your need. The use of  Search to find Tags provides good performance even if you aggregate huge data .

Enjoy!

, , ,

1 Comment

Visual Studio 2010 : Error occurred in deployment step ‘Activate Features’


Problem : You suddenly see the below error in Visual studio 2010 while deploying SharePoint 2010 solution:

Error occurred in deployment step ‘Activate Features’: Feature with Id ‘#featureid#’ is not installed in this farm, and cannot be added to this scope

Cause : This error generally comes when a new WFE server is added to the SharePoint farm.

Resolution : Remove WFE or manually install the solution or features.

Leave a Comment

Event Receiver enhancements in SharePoint 2010


With SharePoint 2010, there are some good enhancements in Event Receivers.

  1. New Events
  2. New Registration technique
  3. Synchronous After-Events
  4. Custom error pages and redirection
  5.  Impersonation enhancements (SPEventPropertiesBase.OriginatingUserToken)

Lets discuss one by one.

1. New Events

As part of SharePoint 2010, there are six new events that you can take advantage of. These events allow you to capture creation and provisioning of new webs and the creation and deletion of lists.
 Event Name  Event Description  Event Receiver Class
WebAdding
A synchronous event that happens before the web is added. Some URL properties may not exist yet for the new site, since the new site does not exist yet.
SPWebEventReceiver
WebProvisioned
A synchronous or asynchronous after-event that occurs after the web is created. You make the event synchronous or asynchronous by using the Synchronization property and setting it to synchronous or Synchronous. This is located under the Receiver node in the elements.xml file for your feature.
SPWebEventReceiver
ListAdding
A synchronous event that happens before a list is created.
SPListEventReceiver
ListAdded
A synchronous or asynchronous after-event that happens after a list is
created but before being it is presented to the user.
SPListEventReceiver
ListDeleting
A synchronous event that happens before a list is deleted.
SPListEventReceiver
ListDeleted
A synchronous or asynchronous after-event that happens after a list is
deleted.
SPListEventReceiver

2. New Event Registration technique using the <Receivers/> tag

SharePoint 2010 has added a new registration mechanism for registering your event receivers, using the <Receivers> XML block.

<Receivers
  ListTemplateId = "Text"
  ListTemplateOwner = "Text"
  ListUrl = string
  RootWebOnly = TRUE | FALSE
  Scope = Site | Web>
</Receivers>

With this new capability, you can register your event receiver at the site collection level by using the new Scope attribute and setting it either to  Site or Web, depending on the scope that you want for your event receiver.

  • SPSite-Level Binding

In SharePoint 2010, you have the ability to deploy an event receiver not only at the web level, but also at the Site Collection level. In fact, all of the event receivers except the SPEmailEventReceiver can be deployed at the Site Collection level, so you can share their behavior across all the websites within that Site Collection. For instance, you can share at the Site Collection level, a list-level event receiver that checks list provisioning and/or custom fields using a unique deployment step.

  • Event Binding by List Template

Also, In SharePoint 2010 we can bind an event receiver to a list template, using  <ListTemplateId>  and then apply that event receiver to every list instance based on that ListTemplateId.

Below example displays a feature element that installs an event receiver for a list.

<?xml version=”1.0” encoding=”utf-8”?>
<Elements xmlns=”http://schemas.microsoft.com/sharepoint/”>
<Receivers ListTemplateId=”104”>
<Receiver>
<Name>News Update Event Receiver</Name>
<Type>ItemUpdating</Type>
<Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
<Class>AmitKumawat.SP2010.EventReceivers.NewsItemUpdateReceiver</Class>
<Data>Contains a string that is used to pass parameters to the receiver via the ReceiverData property</Data>
<SequenceNumber>1002</SequenceNumber>
</Receiver>
</Receivers>
</Elements>

The <Data> tag of the Receiver element allows you to define a custom configuration text that will be provided to the event receiver via the ReceiverData property of the SPEventPropertiesBase base class, whenever the receiver is invoked. Note that the length of the content inside Data element is limited to 255 chars.

Another important one is the ListUrl attribute, which allows you to scope your receiver to a particular list by passing in the relative URL.

Note: ListUrl works only if feature has Scope=”Web”. In case the feature has Scope=”Site”, event receiver is fired for every list, ListUrl is ignored.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListUrl="Lists/Tasks">
    <Receiver>
      <Assembly>MyReceiverAssembly, Version=1.0.0.0, Culture=Neutral,
        PublicKeyToken=12e5e5525fb3d28a</Assembly>
      <Class>MyReceiverAssembly.MyReceiverClass</Class>
      <Type>ItemAdded</Type>
      <Name>My ItemAdded Event Receiver</Name>
      <Synchronization>Synchronous</Synchronization>
      <SequenceNumber>1000</SequenceNumber>
    </Receiver>
  </Receivers>
</Elements>

You can tell SharePoint to just have the receiver work on the root site by using the RootWebOnly attribute on the <Receivers> node.

3.  Synchronous After-Events

We now have  support for synchronous after – events, such as listadded , itemadded , or webprovisioned .

In SharePoint, all the Before events are executed synchronously within the same process and thread of the current user request, while all After events are executed asynchronously, on a background thread and potentially in a process different from the current user request.

SharePoint 2010 Events Pipeline

Important Bullet Points:

  • Synchronous event receivers are called in sequential order based on the sequence number specified during event binding. This applies to both Before and After synchronous events.
  • Asynchronous After event receiver threads are initiated in sequential order based on the sequence number. However, there is no guarantee that they will finish in that same order.
  • An asynchronous After event can start at any time after its associated user action is performed. It may start before, at the same time as, or after the Web request is completed.

To make the After events, such as ItemUpdated, synchronous, you need to set the Synchronization property either through the SPEventReceiverDefinition object model if you are registering your events programmatically or by creating a node in your <Receiver> XML that sets the value to Synchronous or Asynchronous.

The property can take one of the following values:

  • Default : Before events are synchronous; After events are asynchronous.
  • Synchronous : The current event is executed synchronously.
  • Asynchronous The current event is executed asynchronously.

To configure this property, you can define it in the feature element XML file or in Code

<?xml version=”1.0” encoding=”utf-8”?>
 <Elements xmlns=”http://schemas.microsoft.com/sharepoint/”>
 <Receivers ListTemplateId=”105”>
<Receiver>
 <Name>News Update Event Receiver</Name>
 <Type>ItemUpdating</Type>
 <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
 <Class>AmitKumawat.SP2010.EventReceivers.NewsItemUpdateReceiver</Class>
 <Data>Contains a string that is used to pass parameters to the receiver via the ReceiverData property</Data>
 <SequenceNumber>1002</SequenceNumber>
 <Synchronization>Synchronous</Synchronization>
 </Receiver>
</Receivers>
 </Elements>

Another option is to configure the Synchronization property in code, as shown below:

using (SPSite site = new SPSite(“http://sp2010site/”)) 
{
using (SPWeb web = site.OpenWeb()) 
{
NewsItemEventReceiverConfiguration config = new NewsItemEventReceiverConfiguration();

SPList list = web.Lists[“Contacts”];
var newReceiver = list.EventReceivers.Add();
Assembly asm = Assembly.LoadFrom(
@”..\..\AmitKumawat.SP2010.EventReceivers.dll”);
newReceiver.Assembly = asm.FullName;
newReceiver.Class = asm.GetType(
“AmitKumawat.SP2010.EventReceivers.NewsItemEventReceiver”).FullName;
newReceiver.Name = “News Receiver”;
newReceiver.Type = SPEventReceiverType.ItemUpdated;
newReceiver.SequenceNumber = 110;
newReceiver.Data = XmlSerializationUtility.Serialize<NewsItemEventReceiverConfiguration>(config);
newReceiver.Synchronization =SPEventReceiverSynchronization.Synchronous;
newReceiver.Update();
}
}

4. Custom Error Pages

In Sharepoint 2007, you can cancel events and return an error message to the user,which provides limited interactivity and is not much helpful to the user beyond what the error message says.

In Sharepoint 2010 , you can cancel the event  and redirect the user to a custom error page that you create. This allows you to have more control of what the users see, and you can try to help them figure out why their action is failing.

However, Please note that the custom error pages and redirection will only work for  synchronous Before-Events(ending with ..ing), so you cannot achieve this for  synchronous After-Events such as ListAdded . Also, this will only work when the client is a Browser.

The way to implement custom error pages is to set the Status property on your property bag for your event receiver to SPEventReceiverStatus.CancelWithRedirectUrl , set the RedirectUrl property to a relative URL for your error page, and set the Cancel property to true .

 properties.Cancel = true;
 properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
 properties.RedirectUrl = "/_layouts/mycustomerror.aspx";

5.  Impersonation enhancements.

By default, all events in SharePoint run under the context of the user who raised the event.

 Generally, this is okay, but there may be certain times when you want to let a user perform actions on lists or libraries that the current user does not have permissions to do. In most cases, you would use SPSecurity.RunwithElevatedPrivileges method. However, you may want to revert to the originating user on some operations.

With 2010, the event property bag contains the OriginatingUserToken ,UserDisplayName , and UserLoginName, which you can use to revert to the original user.

Below is an example  that perform some tasks by using the OriginatingUserToken to impersonate the originating user.

public override void ItemUpdated(SPItemEventProperties properties) {
using (SPSite site = new SPSite(properties.SiteId,
properties.OriginatingUserToken)) {
using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl)) {
// Some tasks here impersonating the originating user token
}
}
base.ItemUpdated(properties);
}


Technorati Tags :

, , ,

Leave a Comment

Develop in Asp.net or SharePoint ?


I encountered an interesting question on Sharepoint.StackExchange : SharePoint 2010 custom application or ASP.Net web application ?

I feel many developers, when they move to SharePoint from ASP.NET, think over  the same question many times. Below are my thoughts ( I answered the same at SSE as well).  What do you think ?  :)

SharePoint is an ASP.NET application on steroids. Almost everything that works in ASP.NET(3.5) will work in SharePoint as well.

Having said that, the decision to choose between ASP.NET and SharePoint depends on N no of factors …. Requirements, Budget, Skills, Infrastructure, Corporate politics ;-) to name a few.

At the developer level, I think decision can be made on the basis of requirements only ( after they come to you finally). Also, If you are new to SharePoint chances are high that you would like to develop in ASP.NET :-)

Some points worth to consider:

1) See how complex your database model can be.

It is easy to define and manage data structures in lists as they don’t require intervention of a database admin. However, If your application has complex DB design, It may not be feasible using SharePoint. Check out this as well Using SharePoint Lists vs. Database Tables

2) Identify the Features your application will support

SharePoint provide many features out-of-box for which you may have to code in ASP.NET. If your application need revolve around below, SharePoint is worth to consider:

  • Search
  • Information Portals
  • Business Intelligence and Dashboards
  • Simple Workflows
  • Content Management
  • Social networking ( Blogs, Wikis, Communities, People Search)
  • MS Office Integration
  • Surfacing External Data Systems
  • Future Integration with other Systems

3) Cross Cutting Concerns

The majority of web applications contain common functionality that spans layers and tiers. This functionality typically supports operations such authentication, authorization, caching, communication, exception management, logging and instrumentation, and validation.

SharePoint provides effective way for managing common crosscutting concerns, such as authentication and authorization, storage, scale, provisioning of services, and governance controls.The value in managing crosscutting concerns in SharePoint is that you don’t have to continually reinvent the wheel.

It may be boring to write crosscutting concerns in ASP.NET :-)

SharePoint 2010 and the associated tools promote the increased productivity and accelerated development developers are looking for.

Leave a Comment

Hiding Menu Items in the ECB from SharePoint List Items


Edit Control Block (ECB)  is the context menu that is displayed for all items in Lists and Document Libraries. This post focus on editing or hiding existing ECB menu items.

If you want to add new custom actions or menu items to the ECB menu, You can do it by:

1) Using  the element <CustomAction Location=”EditControlBlock” > in a Feature [recommended way].

2) Create a javascript function named Custom_AddListMenuItems and add it to a page or master page. For e.g.

//This function is called automatically from core.js in-built functions like AddListMenuItems(), AddDocLibMenuItems() etc...
function Custom_AddListMenuItems(m, ctx) {   
    //This function creates a new ECB item.
    CAMOpt(m, "Test ECB item", "alert(I am created from script!!');", "/_layouts/images/testECB.GIF");
    //This function creates a seperator line.
    CAMSep(m);

}

If you want to hide or delete an existing menu item which is provided out-of-box by SharePoint, we cannot do it by using a feature. The out-of-box actions or menu items in ECB are rendered by using a JavaScript file, which is core.js [Note : Modifying the core.js file is not supported or recommended]. So there are two ways we can hide existing menu items :

  • Create a copy of core.js and edit it to remove the ECB menu items we don’t want.
  • Use JQuery to hide the ECB menu items without editing core.js
We will discuss both the ways below ( I personally prefer no:2 as it is much easier )
 
Copy and Edit Core.js

Important:Please do not edit existing core.js

Following below steps  to create a customcore.js file and refer it in the master page.

  1. Copy the core.js file from its default location ( For SP2010 : %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\LAYOUTS\1033; For SP2007:%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\1033)  place it in the same folder, and rename as customcore.js file.
  2. Remove unwanted ECB menu items by editing the appropriate functions like CreateMenuEx()(for SP2007 core.js), BuildMenu()(for SP2010 core.js), AddVersionMenuItems(), AddDocLibMenuItems() etc. For more details on the functions please see the section ”ECB menu items in core.js” below.
  3. In the  master page, add the following line to render the customcore.js file [Note: core.jsis also required]:
    <!--It is important to refer core.js even if you create and refer customcore.js-->
    <SharePoint:ScriptLink language="javascript" name="core.js" Defer="true" runat="server"/>
    <SharePoint:ScriptLink language="javascript" name="customcore.js" Defer="true" runat="server"/>
  4. Save and publish the master page.

             

The  JavaScript function CreateMenu() (for SP2007) or BuildMenu() (for SP2010)  in core.js creates the menu items when you hover or click to edit a list item.

//This function is used to create ECB menu in SP2010
function BuildMenu(a) {
    ULSrLq: ; var b = CMenu(currentItemID + "_menu");
    if (!b) return; else if (a.isVersions)
    //Add ECB menu item if versioning is enabled in a list or document library(For eg. "Check Out","Version History")
    AddVersionMenuItems(b, a);
    else if (a.listTemplate == 121)
   //Add ECB menu item for solution catalog
    AddSolutionsCatalogMenuItems(b, a);
    else if (a.listBaseType == 1)
   //Add menu item for items in document library (For eg. "Edit Document")
    AddDocLibMenuItems(b, a);
    else if (a.listTemplate == 200)
    //Add menu item for items in meetings
    AddMeetingMenuItems(b, a);
    else
   //Add menu item for items in standard List (For eg. "View Item","Edit Item")
   AddListMenuItems(b, a);
    InsertFeatureMenuItems(b, a);
    return b
}

//This function is used to create ECB menu in SP2007
function CreateMenu(e)
{
	if (!IsContextSet())
		return;
	var ctx=currentCtx;
	if (e==null)
		e=window.event;
	var srcElement=e.srcElement ? e.srcElement : e.target;
	if (itemTable==null || imageCell==null ||
		(onKeyPress==false &&
		 (srcElement.tagName=="A" ||
		  srcElement.parentNode.tagName=="A")))
		return;
	return CreateMenuEx(ctx, itemTable, e);
}

The  CreateMenu() function in SP2007 core .js calls  CreateMenuEx() which calls respective functions to create ECB menu items for list items in a library, meeting or custom list.

function CreateMenuEx(ctx, container, e)
{
 if (container==null)
 return;
 IsMenuShown=true;
 document.body.onclick="";
 var m;
 m=CMenu(currentItemID+"_menu");
 if (!m)
 return;
 else if (ctx.isVersions)
 //Add menu item if versioning is enabled in a list or document library(For eg. "Check Out","Version History")
 AddVersionMenuItems(m, ctx);
 else if (ctx.listBaseType==1)
 //Add menu item for items in document library (For eg. "Edit Document")
 AddDocLibMenuItems(m, ctx);
 else if (ctx.listTemplate==200)
 //Add menu item for items in meetings
 AddMeetingMenuItems(m, ctx);
 else
 //Add menu item for items in standard List (For eg. "View Item","Edit Item")
 AddListMenuItems(m, ctx);
 InsertFeatureMenuItems(m, ctx);
 currentEditMenu=m;
 container.onmouseout=null;
 OMenu(m, container, null, null, -1);
 itemTable=GetSelectedElement(container, "TABLE");
 m._onDestroy=OutItem;
 e.cancelBubble=true;
 return false;
}

We can edit the JS functions(and inner functions) called above to remove the ECB menu items.We can comment the lines of code  which create menu items. These lines are  :

 
menuOption=CAMOpt(m, strDisplayText, strAction, strImagePath, null, 700);
menuOption.id="ID_[**action**]";
 

The CAMOpt() function creates a new ECB menu item.We can comment the lines which call it to create a specific ECB menu item. Below are few examples. To remove “Check Out” ECB menu item, we can comment the below lines in AddCheckinCheckoutMenuItem() function:

// menuOption=CAMOpt(m, strDisplayText, strAction, strImagePath, null, 700);
// menuOption.id="ID_Checkout";




To remove “Delete” ECB menu item from a list item, we can comment the below lines in AddListMenuItems() function:



//menuOption=CAMOpt(m, strDisplayText, strAction, strImagePath, null, 300);
//menuOption.id="ID_DeleteItem";

  

Use JQuery to hide the ECB menu items
The ECB menu html which is created on fly by CreateMenu() looks like below. This can be manipulated by JQuery as well.

<!--the menu id is formed by Item Id + "_menu" -->
<menu id="268_menu" class="ms-SrvMenuUI">
<span type="option" text="View Properties" onmenuclick="STSNavigate('/Documents/Forms/DispForm.aspx?ID=268')" sequence="200" id="ID_ViewProperties"></span>
<span type="option" text="Edit Properties" onmenuclick="STSNavigateWithCheckoutAlert('/Documents/Forms/EditForm.aspx?ID=268',1,'0','\u002fDocuments\test.pdf','http://sp2007site')" iconsrc="/_layouts/images/edititem.gif" iconalttext="" sequence="220" id="ID_EditProperties"></span>
<span type="option" text="Manage Permissions" onmenuclick="NavigateToManagePermsPage('http://sp2007site', '{A8073644-42BB-46C2-9CEC-33371585C51C}','268')" iconsrc="/_layouts/images/manageperm.gif" iconalttext="" sequence="250" id="ID_MngPerms"></span>
<span type="option" text="Delete" onmenuclick="DeleteDocLibItem('/_vti_bin/owssvr.dll?CS=65001&amp;Cmd=Deletesequence="310" id="ID_DeleteDocItem"></span>
<span type="submenu" text="Send To" iconalttext="" sequence="400" id="ID_Send"><span type="option" text="Other Location" onmenuclick="STSNavigate('http://sp2007site/_layouts/copy.aspx?SourceUrl=%2FDocuments%2F090505%2Dtest%2Epdf&amp;')" iconsrc="/_layouts/images/sendOtherLoc.gif" iconalttext="" id="ID_OtherLocation"></span>
<span type="separator"></span>
<span type="option" text="E-mail a Link" onmenuclick="javascript:navigateMailToLinkNew('http://sp2007site/Documents/090505-test.pdf')" iconsrc="/_layouts/images/gmailnew.gif" iconalttext="" id="ID_SendToEmail"></span>
<span type="option" text="Create Document Workspace" onmenuclick="STSNavigate('http://sp2007site/_layouts/createws.aspx?list={A8073644-42BB-46C2-9CEC-33371585C51C}&amp;item=268&amp;RootFolder=%2FDocuments')" iconalttext="" sequence="1140" id="ID_CreateDWS"></span>
<span type="option" text="Download a Copy" onmenuclick="STSNavigate('http://sp2007site/_layouts/download.aspx?SourceUrl=%2FDocuments%2F090505%2Dtest%2Epdf&amp;') iconalttext="" id="ID_DownloadACopy"></span>
</span>
<span type="separator"></span>
<span type="option" text="Check Out" onmenuclick="CheckoutDocument('http://sp2007site', '%2FDocuments%2F090505%2Dtest%2Epdf', '.3')" iconsrc="/_layouts/images/checkout.gif" iconalttext="" sequence="700" id="ID_Checkout"></span>
<span type="option" text="Version History" onmenuclick="NavigateToVersionsAspx('http://sp2007site', 'list={A8073644-42BB-46C2-9CEC-33371585C51C}&amp;ID=268&amp;FileName=%2FDocuments%2F090505%2Dtest%2Epdf')" iconsrc="/_layouts/images/versions.gif" iconalttext="" sequence="800" id="ID_Versions"></span>
<span type="option" text="Workflows" onmenuclick="STSNavigate('http://sp2007site/_layouts/Workflow.aspx?ID=268&amp;List={A8073644-42BB-46C2-9CEC-33371585C51C}&amp;Source=http%3A%2F%2Fsp2007site%2FDocuments%2FForms%2FAllItems%2Easpx')" iconsrc="/_layouts/images/workflows.gif" iconalttext="" sequence="900" id="ID_Workflows"></span>
<span type="separator"></span>
<span type="option" text="Alert Me" onmenuclick="NavigateToSubNewAspx('http://sp2007site', 'List={A8073644-42BB-46C2-9CEC-33371585C51C}&amp;ID=268')" iconalttext="" sequence="1100" id="ID_Subscribe"></span>
<span type="separator"></span>
</menu>

The below jquery snippet hides the “Check Out” ECB menu item at mouseover on the div(class=ms-MenuUIPopupBody) that is created on fly everytime a ECB menu is constructed.

$(document).ready(function(){ 	       

	  $('.ms-MenuUIPopupBody').live('mouseover', function() {         

           $('#ID_Checkout').parent().hide();
           $('#ID_Checkout').remove();         

         }); 

});

1 Comment

Using KeywordQuery Class in SharePoint Query Object Model


In order to develop custom search web parts or applications that support ‘search-by-keyword’ scenario, SharePoint Query object model exposes KeywordQuery Class.

Using KeywordQuery Class, We can construct keyword search queries for :

  • SharePoint Foundation Search
  • SharePoint Server Enterprise Search
  • FAST Search Server 2010

This class exposes QueryText property which accepts query(keywords)  in Keyword query syntax or KQL (Keyword Query Language). Using KQL, search terms are passed directly to the Search Service.

KQL allows keywords, phrases, and Managed Property names to create sophisticated search queries.In addition, KQL supports Boolean operators and wild cards.

Some Keyword Query Examples 

  1. Moss AND Sharepoint: Returns items containing both “Moss” and “Sharepoint”
  2. Lastname:K* :  Returns people whose last name starts with K
  3. AverageRating>1  : Returns items whose average rating is greater than one
  4. Finance +isDocument:1  : Returns documents that are related to Finance
  5. Finance +Author:”Amit Kumawat” : Returns items authored by Amit Kumawat
  6. Finance Scope:”Local SharePoint Sites” Scope:”Internet Sites” NOT Scope:”Shared Folder”
Using Scopes in KeywordQuery
The example 6. above is worth to take a look. It shows how we can include scopes in keywordquery.
So, it will search for keyword “Finance”  in scopes ”Local SharePoint Sites” and ”Internet Sites”  but not ”Shared Folder“.

 

In SharePoint Server 2010, KQL  also supports :

  • Prefix matching for free-text and property restriction queries
  • Property operators with non-text fields

For more details on forming queries based on KQL, Please refer Keyword Query Syntax Reference

In below example, We create a custom web part that uses KeywordQuery Class to  perform search. Create a new SharePoint project in Visual Studio 2010 and add reference to below dlls (from Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI):

  • Microsoft.Office.Server.dll
  • Microsoft.Office.Server.Search.dll.

[Note: If you are working with SharePoint Foundation, you need to use another namespace, Microsoft.SharePoint.Search.Query, that contains a subset of the SharePoint Server functionality. It comprises the query object model with reduced functionality and is found in Microsoft.SharePoint.Search.dll.]

In the project, add a new web part and use below code sample :
using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Data;
using Microsoft.Office.Server.Search.Query;
using Microsoft.Office.Server.Search.Administration;

namespace AmitKumawat.SP2010.Samples.CustomSearchWebPart
{
    [ToolboxItemAttribute(false)]
    public class CustomSearchWebPart : WebPart
    {
       protected Button btnSearch;
       protected TextBox txtSearchBox;
       protected Label lblResults;
       protected GridView gridSearchResults;

        protected override void CreateChildControls()
        {

            txtSearchBox = new TextBox();
            this.Controls.Add(txtSearchBox);

            lblResults = new Label();
            this.Controls.Add(lblResults);

            btnSearch = new Button();
            btnSearch.Text = "Find";
            btnSearch.Click += new EventHandler(btnSearch_Click);
            this.Controls.Add(btnSearch);

        }

        void btnSearch_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(txtSearchBox.Text))
            {
                lblResults.Text = "Please enter words to search for";
                return;
            }

            ExecuteKeywordQuery(txtSearchBox.Text);

        }

        void ExecuteKeywordQuery(string keywordQueryText)
        {
          //Get a proxy for Search Service Application(SSA). The SSA serves search queries.
             SearchServiceApplicationProxy SSAProxy = (SearchServiceApplicationProxy)SearchServiceApplicationProxy.
                GetProxy(SPServiceContext.GetContext(SPContext.Current.Site));
            KeywordQuery keywordQuery = new KeywordQuery(SSAProxy);
            //keyword search using default provider. We can use "FAST" or "SharepointSearch" to be specific.
            keywordQuery.ResultsProvider = SearchProvider.Default;
            keywordQuery.QueryText = keywordQueryText;
            keywordQuery.ResultTypes |= ResultType.RelevantResults;

            ResultTableCollection searchResults = keywordQuery.Execute();

            if (searchResults.Exists(ResultType.RelevantResults))
            {
                ResultTable searchResult = searchResults[ResultType.RelevantResults];
                DataTable result = new DataTable();
                result.TableName = "SearchResults";
                result.Load(searchResult, LoadOption.OverwriteChanges);
                PopulateResultsGrid(result);
            }
        }

        private void PopulateResultsGrid(DataTable resultsTable)
        {
            gridSearchResults = new GridView();
            gridSearchResults.DataSource = resultsTable;
            gridSearchResults.DataBind();
            Controls.Add(gridSearchResults);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            writer.Write("<table width='100%'><tr><td>");
            txtSearchBox.RenderControl(writer);
            writer.Write("</td><td>");
            btnSearch.RenderControl(writer);
            writer.Write("</td></tr><tr><td colspan='2'>");
            lblResults.RenderControl(writer);
            writer.Write("</td></tr><tr><td colspan='2'>");
            if(gridSearchResults!=null)
            gridSearchResults.RenderControl(writer);
            writer.Write("</td></tr></table>");
        }

    }
}

If you want to use a specific SSA , you can get reference of it by using its name as:

SearchQueryAndSiteSettingsServiceProxy settingsProxy = SPFarm.Local.ServiceProxies.GetValue<SearchQueryAndSiteSettingsServiceProxy>();
 // Get the SSA proxy by name
SearchServiceApplicationProxy SSAProxy = settingsProxy.ApplicationProxies.GetValue<SearchServiceApplicationProxy>("Search Service Application 1");

In your SharePoint farm, You must create and configure a Search service application(SSA) before using Search. You can follow steps here to create SSA.

, ,

4 Comments

RDBMS Features in SharePoint 2010 : Referential Integrity,Unique Keys and Joins


SharePoint lists are, by and large, similar to database tables in structure and behavior. Like Tables, Lists support different data types and can have triggers (event receivers) that fire on events such as creating, updating or deleting items. Also, lists can be configured to filter, sort or group items based on column values.

Also, SharePoint lists support the concept of view like in database and can be queried by CAML. The columns in the list can be indexed for better performance while querying.

In SharePoint 2010, the lists are much more enhanced to incorporate important aspects of RDBMS like :

  1.  Referential Integrity
  2.  Unique Keys
  3.  Joins

1.  Referential Integrity with improved lookup columns

In previous versions of SharePoint (2003, 2007), the  lists lacked an important aspect of RDBMS – Referential integrity- that is a measure of data consistency in a database. If you have a lookup between two lists and you want to have Referential Integrity, previous SP versions won’t provide it. The lookup columns could be used just to get their data from other list columns.

Now, SharePoint 2010 provides the mechanism to enforce Referential integrity in our data model. Also, it can block or cascade your deletes between lists automatically.

Just like foreign key constraints in RDBMS, SharePoint 2010 allows you to configure restrict delete and cascade delete rules on lookup column relationships.

Cascade delete : When we delete a list item, SharePoint automatically delete all items that have references to that item by look up column. It can be used to enforce parent-child relationships.

Restrict delete : With this rule, SharePoint don’t allow you to delete a list item that is referenced by a lookup column in another list. It can be used to enforce peer-to-peer relationships.

[Note: SharePoint is not going to be a replacement for SQL Server with this functionality. If you have a very complex data model, SQL Server is an obvious choice]

An additional improvement on lookup columns is that now we can retrieve more columns(Projected Columns) from the related list.

Configuring the Delete Behavior on a Lookup Field 

We can configure list relationship rules through the user interface when we create look up column.

Look Up Column Delete Behavior

We can also programmatically configure delete behavior in a feature receiver class. Since the list relationship is specific to individual list instances, we cannot configure it declaratively in column definitions or content types.Programmatically setting the delete behavior can be useful if you use features to deploy the lists and columns.

SPFieldLookup is class that represents lookup columns. This class has a RelationshipDeleteBehavior enum property that enables us to specify the delete behavior for the column.It can be set to one of the following SPRelationshipDeleteBehavior enumeration values:

  • SPRelationshipDeleteBehavior.Cascade. Setting this value deletes all items if the lookup column refers to an item that no longer exists in the related list.
  •  SPRelationshipDeleteBehavior.Restrict. Setting this value prohibit the users from deleting items in the related list if those items are referred to in the lookup column values.

For example, we can use below methods in a feature receiver class to restrict or cascade delete.

 
private void RestrictDelete(SPWeb web)
        {
            SPList list = web.Lists["MyList"];
            SPField field = list.Fields["TestNameLookUp"];
            SPFieldLookup fieldLookup = (SPFieldLookup)field;

            //For unique values of column in items
            fieldLookup.EnforceUniqueValues = true;
            //For indexing column
            fieldLookup.Indexed = true;

            fieldLookup.RelationshipDeleteBehavior = SPRelationshipDeleteBehavior.Restrict;
            fieldLookup.Update();

        }

     private void CascadeDelete(SPWeb web)
        {
            SPList list = web.Lists["MyList"];
            SPField field = list.Fields["TestNameLookUp"];
            SPFieldLookup fieldLookup = (SPFieldLookup)field;
            //For unique values of column in items
            fieldLookup.EnforceUniqueValues = true;
            //For indexing column
            fieldLookup.Indexed = true;         

            fieldLookup.RelationshipDeleteBehavior = SPRelationshipDeleteBehavior.Cascade;
            fieldLookup.Update();

        }

Programmatically Find Lookups and Related Lists

We can use the GetRelatedFields method to return a SPRelatedFieldCollection collection. We can retrieve properties, such as the LookupList that the column  is related to,  the relationship behavior when some item is deleted from the list and also some other useful information.

Below is the example

private void GetRelatedColumnsAndListsInfo(SPWeb web)
        {
            SPList list = web.Lists["MyCustomersList"];

            //Get related columns
            SPRelatedFieldCollection relatedFields = list.GetRelatedFields();

            foreach (SPRelatedField relatedField in relatedFields)
            {
                //Get Lookup list
                SPList relatedList = relatedField.LookupList;
                Console.WriteLine(relatedField.ListId + " " +   relatedField.FieldId);
                Console.WriteLine("List Name: " + relatedList.Title + " Relationship Behavior: " + relatedField.RelationshipDeleteBehavior.ToString());

            }

        }

Controlling the cascade delete limits and time outs

At web appilcation level, We can have control over the Cascade deletes  using SPWebApplication.CascadeDeleteMaximumItemLimit and SPWebApplication.CascadeDeleteTimeoutMultiplier.

  • CascadeDeleteMaximumItemLimit allows you to specify the maximum  number of cascaded items that can be deleted. By default, this value is 1000 items.
  •  CascadeDeleteTimeoutMultiplier  allows you to specify the timeout(in secs)  for the operation. The default value is 120 seconds.
 private void SetCascadeDeletes(SPWebApplication webApp)
        {
            //sets max cascade delete limit to 2000
            webApp.CascadeDeleteMaximumItemLimit = 2000;           

            //sets the time out to 5 mins
            webApp.CascadeDeleteTimeoutMultiplier = 300;

            webApp.Update();
        }

2.  Unique Keys (Columns)

SharePoint 2010  lists have the ability to ensure uniqueness for the values in your columns. As the unique column  is guaranteed to have a unique value, We can  it as an  index to make look ups faster.

Unique Column

3. List Joins

SharePoint lists support joins like database lists.  SharePoint can perform left and inner joins but not right joins.

  • By inner join, we can combine the values from the lists\tables.
  • By left join or left outer join , anything that appears in the left list\table  will be returned in the result set even if it does not exist in the right list\table.

We can perform a join between two lists on a lookup field by setting the Joins property on our SPQuery object with the join we want to perform.

Apart from the Joins property, we must also specify a value for the ProjectedFields property. This property gets other required columns from the lookup list. We can alias the column by using the Name attribute and specify the column name in the ShowField attribute. After getting results, we have to use the SPFieldLookupValue  to display the values for projected columns.

In the below example, we are joining on the Customers list, where the Customers.customer = Orders.Customer

private void PerformListJoin(SPWeb web)
        {

            SPList OrderList = web.Lists["Orders"];
            SPQuery CustomerQuery = new SPQuery();
            CustomerQuery.Joins =
            " <Join Type='INNER' ListAlias='Customers'> " +
            " <Eq> " +
            " <FieldRef Name='Customer' RefType='Id' /> " +
            " <FieldRef List='Customers' Name='ID' /> " +
            " </Eq> " +
            " </Join> ";

            StringBuilder ProjectedFields = new StringBuilder();
            ProjectedFields.Append(" <Field Name='CustomerTitle'  Type='Lookup' List='Customers' ShowField='Title' /> ");
            ProjectedFields.Append(" <Field Name='CustomerAddress'Type='Lookup' List='Customers' ShowField='CustomerNum' /> ");

            CustomerQuery.ProjectedFields = ProjectedFields.ToString();
            SPListItemCollection Results = OrderList.GetItems(CustomerQuery);

            foreach (SPListItem Result in Results)
            {
                SPFieldLookupValue CustomerTitle = new SPFieldLookupValue(Result["CustomerTitle"].ToString());
                SPFieldLookupValue CustomerAddress = new SPFieldLookupValue(Result["CustomerAddress"].ToString());

                Console.WriteLine(Result.Title + " " + CustomerTitle.LookupValue + "  " + CustomerAddress.LookupValue);
            }
        }

Technorati Tags : , ,

, , , ,

3 Comments

Custom Security Trimming in Sharepoint 2010 Search Results : ISecurityTrimmer2 Interface


In my previous post I demonstrated custom security trimming in Moss (SharePoint 2007). This post focuses on implementing custom security trimming in SharePoint 2010.

In general, SharePoint 2010 provides security trimming out-of-box for the content which is indexed from WSS 3.0, SharePoint 2010, windows file system or any location where objects or files have ACL associated with them. An ACL (access control list) is a list of permissions attached to a file or folder and it determines which users are granted access to files and what operations are allowed on given files.

SharePoint stores the ACL information while crawling the content. When a user performs search, this information is used to filter search results at run time. However, this is not the case for every type of content …like non-SharePoint websites. For some cases, like one here, It does not happen even if  the content is in SharePoint.

For such  extreme ( sorry for overloading the term many times :) ) cases where specific business rules need to be applied before search results are displayed to user, SharePoint 2010 provides ISecurityTrimmer2 Interface (I am not sure why it is 2 and not 1) defined in Microsoft.Office.Server.Search.Query namespace.

Just like ISecurityTrimmer in Moss, following three steps required to use a custom security trimmer for SharePoint 2010 Enterprise Search:

  1. Implement the ISecurityTrimmer2 interface
  2. Deploy the custom security trimmer
  3. Register the custom security trimmer
  4. Debug the custom security trimmer(optional)

Implementing the ISecurityTrimmer2 interface

In VS2010, Create an empty SharePoint project as a farm solution and add reference to

  • Microsoft.Office.Server.Search.dll (14\ISAPI\Microsoft.Office.Server.Search.dll)
  • Microsoft.IdentityModel.dll (C:\Program Files\Reference Assemblies\Microsoft\Windows Identity Foundation\v3.5\Microsoft.IdentityModel.dll) [Note: This reference is only required in case of Claims Mode Authentication.  By default, SharePoint 2010 site have Classic Mode Authentication (Integrated Windows Authentication) and so you will not need it (most probably).Also, You should have Windows Identity Foundation SDK installed. Download and install WindowsIdentityFoundation-SDK-3.5.msi  .Take care to download V3.5 and not V4]

Create a custom class and implement ISecurityTrimmer2. There are two methods in the interface we need to implement:

1.  ISecurityTrimmer2.Initialize () : In this method, we can provide or retrieve values for configuration properties for the trimmer.

Two parameters which are passed into the method are:

  • NameValueCollection Object: It contains the properties configured for the security trimmer when it is registered (using stsadm) with the Search service application.
  • SearchServiceApplication Object: It is the Search service application with which security trimmer is registered.

In ISecurityTrimmer2.Initialize () method, we can retrieve values for some properties which were configured at the time of registering the security trimmer using stsadm. For example, to optimize performance, you may want to limit the amount of crawled urls to be processed by your custom security trimmer. For that we need to retrieve CheckLimitProperty. Below is the code you write to do that.

           //to provide default configuration limit for search results on which security trimming is applied
            private int intResultsCheckLimit = 150;
            public void Initialize(NameValueCollection trimmerProperties, SearchServiceApplication searchApplication)
            {
                //retrieve the value of CheckLimitProperty if it is configured while registering the trimmer using stsadm and use it if found.
                if (trimmerProperties["CheckLimitProperty"] != null)
                {
                    intResultsCheckLimit = Convert.ToInt32(trimmerProperties["CheckLimitProperty"]);
                }
            }

2.  ISecurityTrimmer2.CheckAccess() :  This is  the  method where security trimming is applied to the search results.  The CheckAccess()  can execute multiple times for one search query, depending on the number of results that are passed to the security trimmer.

This method has some differences between Moss and SharePoint 2010. In SharePoint 2010 we get an IIdentity and in MOSS we  use WindowsIdentity.GetCurrent()(For Windows Authentication) or HttpContext.Current.User (For Forms Authentication).

 Three parameters which are passed into the method are:

  • IList<String> documentCrawlUrls : It is collection of URLs for each content item from the search results that match the crawl rule.
  • IDictionary<String, Object> sessionProperties : This object is the collection of configuration properties and it is used (by developers like us) to track or preserve custom information across multiple CheckAccess() method calls for the same search query
  • IIdentity passedUserIdentity : The identity of the user who issued the query. We can also use IIdentity as mentioned instead of passedUserIdentity parameter : IIdentity userIdentity = System.Threading.Thread.CurrentPrincipal.Identity;

This ISecurityTrimmer2.CheckAccess() method returns a BitArray which is a collection of bit values,  represented as boolean, where true indicates that the bit is on (1) and false indicates the bit is off (0).

If the bit has a value of 1, the corresponding crawl URL is displayed in the Search results; otherwise the crawl URL is not be displayed in the Search results.

For a content source that is configured to crawl external websites using http protocol, the documentCrawlUrl  is the exact url of the a page crawled.

For a content source that is configured to crawl SharePoint sites, the documentCrawlUrl may take below forms depending on the protocol:

  • sps://wssv2site/site$$$category/bucketid=6/itemid={7c436fba-8618-4558-a3d0-709f29fb3d8e} [ This is generally when a WSS 2.0 site is crawled]
  • sts3://spsite/siteurl=/siteid={f208bff2-a0dd-4f8d-9dac-9bb3dceb4114}/weburl=/webid={567c4e3d-0c7b-450b-bfc9-f8a4ee60bc94}/listid={bf8a9b92-3bdb-483b-91a5-76b63de5a575}/folderurl=/itemid=182 [ This is when a SP2010 site is crawled]

It is important that we know the format of documentCrawlUrls because we will use that information in our security trimming logic, like I did here for draft items

public BitArray CheckAccess(IList<String> documentCrawlUrls, IDictionary<String, Object> sessionProperties, IIdentity passedUserIdentity)
            {
                BitArray retArray = new BitArray(documentCrawlUrls.Count);
                IIdentity userIdentity = System.Threading.Thread.CurrentPrincipal.Identity;

//For claims, use IClaimsIdentity. It is defined in Microsoft.IdentityModel.Claims; 
//IClaimsIdentity claimsIdentity = (IClaimsIdentity)passedUserIdentity

                for (int x = 0; x < documentCrawlUrls.Count; x++)
                {
                    string currentDocUrl = documentCrawlUrls[x];
                    /* Add code here to check if user associated with userIdentity can access documentCrawlUrls[x]. If so: retArray[x] = true; If not: retArray[x] = false; */
                }
                return retArray;
            }

Deploy and Register the Custom Security Trimmer

1)      Deploy the assembly to GAC. [Note: If you have multiple Web front-end (WFE) servers, you must deploy to GAC on all WFEs].

2)      Creating a Crawl Rule for the Custom Security Trimmer.

Before you register the custom security trimmer, we need to create the crawl rule for the content that the custom security trimmer applies to.To create the crawl rule Open SharePoint 2010 Central Administration, and then navigate to the Application Management page.

3)      Register the Custom Security Trimmer

Using PowerShell :In SharePoint 2010 Management Shell, run below command (Replace class , assembly,rulepath  and Search Service Application details with yours)

New-SPEnterpriseSearchSecurityTrimmer -SearchApplication "Search Service Application 1" -typeName "SP2010.Samples.CustomSharepoint2010SecurityTrimmer, SP2010.Samples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f4496ec7e0f80a75" -RulePath http://wssv2site/* -id 1
//Code

Using PluggableSecurityTrimmerManager : Using PluggableSecurityTrimmerManager Class, we can programmatically access already registered security trimmers and also we can register new trimmers.

4)       Do a full crawl the Content Source. The content source in this example is configured to crawl external web sites as below.

The complete code will look like :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Server.Search.Query;
using Microsoft.Office.Server.Search.Administration;
using System.Collections.Specialized;
using System.Collections;
using System.Security.Principal;
using Microsoft.IdentityModel.Claims;

namespace SP2010.Samples.CustomSharepoint2010SecurityTrimmer
{
    public  class CustomSharepoint2010SecurityTrimmer : ISecurityTrimmer2
        {
            //to provide default configuration limit for search results on which security trimming is applied
            private int intResultsCheckLimit = 150;

            public void Initialize(NameValueCollection trimmerProperties, SearchServiceApplication searchApplication)
            {

                //retrieve the value of CheckLimitProperty if it is configured while registering the trimmer using stsadm and use it if found.
                if (trimmerProperties["CheckLimitProperty"] != null)
                {
                    intResultsCheckLimit = Convert.ToInt32(trimmerProperties["CheckLimitProperty"]);
                }
            }

            public BitArray CheckAccess(IList<String> documentCrawlUrls, IDictionary<String, Object> sessionProperties, IIdentity passedUserIdentity)
            {
                BitArray retArray = new BitArray(documentCrawlUrls.Count);

                //Check if the search results obtained are under limit (150 or that from trimmerProperties["CheckLimitProperty"]) when the current search query is executed
                if (!this.CheckLimit(sessionProperties, documentCrawlUrls.Count))
                {
                    throw (new PluggableAccessCheckException("Reached Limit!This Security trimmer will not be applied"));
                }

                IIdentity userIdentity = System.Threading.Thread.CurrentPrincipal.Identity;

 //For claims, use IClaimsIdentity. It is defined in Microsoft.IdentityModel.Claims; 
 //IClaimsIdentity claimsIdentity = (IClaimsIdentity)passedUserIdentity

                for (int x = 0; x < documentCrawlUrls.Count; x++)
                {
                    string currentDocUrl = documentCrawlUrls[x];
                    /* Add code here to check if user associated with userIdentity can access document 
 or page with url documentCrawlUrls[x]. If so: retArray[x] = true; If not: retArray[x] = false; */
                }
                return retArray;
            }

            private bool CheckLimit(IDictionary<String, Object> sessionProperties, int documentCrawlUrlsCount)
            {
                Object currentCount;
                //Use of sessionproperties to store and preserve checkcount across multiple CheckAcess() calls
                sessionProperties.TryGetValue("currentCheckCount", out currentCount);
                if (currentCount == null)
                {
                    sessionProperties["currentCheckCount"] = documentCrawlUrlsCount;
                    return (true);
                }
                int currentCountInt = Convert.ToInt32(currentCount);
                currentCountInt += documentCrawlUrlsCount;

                sessionProperties["currentCheckCount"] = currentCountInt;
                if (currentCountInt <= intResultsCheckLimit)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

}

Security trimmer  or ISecurityTrimmer2 account :

Got to Central Administration >Configure service accounts.

Check the account for “Service Application Pool – SharePoint Web Services Default”. This pool hosts the Search Service Application. It is not recommended not to manage this app pool account yourself because it hosts other services as well which may get affected.

Important to read : Performance Implications of implementing custom security trimmer

A custom security trimmer has affect on performance of the Search, so we should go for it only if the out-of-box security trimming functionality does not meet our requirements.

Also, It is recommend to  limit the number of access checks the trimmer performs for a single query. Without a limit, the CheckAccess method is called many times until the document that the user has access to is returned. In this scenario, performance would be much better if the number of access checks are limited(to 150 for example). The access checks are stopped after the limit is exceeded and a message is presented to the user to refine his query for better results.

We can track the number of items checked and  throw a PluggableAccessCheckException exception if limit exceeds.We can pass a string  message  for the user to the constructor of the PluggableAccessCheckException class. The Search then returns this text instead of any results in response.

Technorati Tags :

,

5 Comments

Sharepoint 2010 Sandbox Solutions : You see it a new feature ?


“Amit! SharePoint 2010 has many new features. I found Sandbox Solutions…a cool new feature !!” –exclaimed my friend… who started his career as a SharePoint developer. If you are a pure SharePoint developer, chances are high that you may feel the same :) .

The concept of ‘Sandbox’ existed in .NET right from the beginning.  SharePoint in nothing but a .net application on steriods. With Sharepoint 2010, it is good that we can exploit the concept of “Sandbox” like we were(and we are) able to do in .NET

A sandbox is an environment where every aspect of security is controlled by the host.The host is a separate process which can be a windows service, console application or IIS website or SharePoint 2010 Sandbox Worker Process (which runs sandbox solutions in SharePoint).

Before going into a sandbox, let me talk a bit about of Permissions in .NET. Permissions are the different powers or abilities which CLR can grant or deny to .NET code. The .NET Framework has a set of permissions to control access to system which you’ll find  in the System.Security.Permissions namespace.

In .NET, Permissions provide a layer of security (apart from operating system security) in two ways:

  1. Sandboxing: Limiting the type of operations that partially trusted .NET assemblies can perform. Sandboxing uses code access permissions.
  2. Authorization: Limiting who can do what. Authorization uses identity and role permissions.

To understand how a Sandbox work, we should also know about code access security (CAS) in order to :

  •  Write assemblies that will run in a limited permissions environment (a sandbox).
  •  Create your own hosting environment, like Sandbox Worker Process (SPUCWorkerProcess.exe) we have in SharePoint 2010, those sandboxes other assemblies.

How Code Access Security Is Applied to Sandbox

When you run a .NET exe from the Windows shell or command prompt, it runs with unrestricted permissions. This is called full trust.

If you execute an assembly via another hosting environment – such as a SQL Server CLR integration host or Sandbox Worker Process (SPUCWorkerProcess.exe), the host decides what permissions to give your assembly ( a sandbox web part for example).

To be technically correct, a host does not apply restricted permissions to your assembly. Rather, it creates an application domain with restricted permissions, and then loads your assembly into that sandboxed domain. This means that any assemblies that load into that domain run in that same sandbox with the same permissions.

There are two exceptions, however:

• Assemblies registered in the GAC like Farm level solutions in SharePoint

• Assemblies that a host has nominated to fully trust. We know that Sandbox solutions can use Proxy, SPProxyOpertion, which is fully trusted and allows us to extend sandbox solutions capability in SharePoint 2010.

Code Access Security Restrictions for Sandbox Solutions in SharePoint

The sandboxed solutions are enforced by a restrictive code access security policy. This restricts sandboxed solutions to use a specific subset of the Microsoft.SharePoint namespace and access to external resources or databases.

The Web.config file in the 14\Usercode folder contains the CAS policies that apply to sandboxed solutions in SharePoint.

Permission Restrictions

Apart from code access security policy , there are restrictions on the actions that you can perform from sandbox solution as the sandbox worker process uses an account with a limited permission set.

Because sandbox solutions run in a partial-trust environment, any assembly that contains method to be called from the sandbox must include the attribute AllowPartiallyTrustedCallersAttribute.

 

Creating a sandbox solution in .NET

Imagine you want write an application that allows your friends to install third-party plugins. One example is Visual studio- It allows you to install plugins like SPDisposeCheck. Right?

Most likely you’d want to prevent plug-ins from exploiting your privileges as a trusted application, so as not to destabilize your application or the user’s machine.  The purpose of Sandbox solutions in SharePoint 2010 is also the same – to prevent code from destabilizing the SharePoint farm. And the best way to achieve this is to run each plug-in in its own sandboxed Application domain.

This is what the Sandbox Worker Process (SPUCWorkerProcess.exe) does. For every sandbox solution, it creates a separate application domain which is, of course, subject to execution and access constraints.

For this example, we’ll assume a plug-in is a .NET exe called  sandboxplugin.exe. Here’s the complete code, for the host program:

using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;
class Program
{
static void Main()
{
string sandboxPluginPath = @"c:\sandboxfolder\sandboxplugin.exe";

//Creating  restricted permissions for plugin to execute
PermissionSet ps = new PermissionSet (PermissionState.None);
ps.AddPermission (new SecurityPermission (SecurityPermissionFlag.Execution));
ps.AddPermission (new FileIOPermission (FileIOPermissionAccess.PathDiscovery |
FileIOPermissionAccess.Read, plugInPath));
ps.AddPermission (new UIPermission (PermissionState.Unrestricted));

AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;

//Creating a new application domain for plugin
AppDomain sandbox = AppDomain.CreateDomain ("sbox", null, setup, ps);

//Any problem in running the plugin will not destablize your host application.
sandbox.ExecuteAssembly(sandboxPluginPath);
AppDomain.Unload (sandbox);
}
}

Technorati Tags : ,

, ,

Leave a Comment

Follow

Get every new post delivered to your Inbox.