Using SharePoint Search feature in ASP.Net Application


Using SharePoint Search feature in ASP.Net Application


Integrating ASP.Net application in to MOSS 2007 to utilize SharePoint Enterprise Search Feature can be done by using Business Data Search.

We can enable full text search for our .Net application in following steps:


  • Create Business Data Catalog metadata model for our .Net application, which basically defines contract between MOSS and the .Net business application data. The BDC contract includes data connection information, business entities, and methods to access business data. Additionally you may specify security information.

  • Import BDC metadata model (XML file from step above) using SharePoint SSP Admin
    site and create a business data catalog (BDC). We can also define full
    & incremental crawl schedule for your business data.

  • Define search scope to include our BDC crawl data. This makes crawled business data to
    searchable.

  • Once SharePoint crawl business data, SharePoint automatically create profile web pages. Profile pages are used by MOSS to display Business data details when a result row in search result page is clicked.


MSDN suggested Procedure to configure Business Data

  1. For line-of-business applications, register the application in the Business Data Catalog and set the number of connections.

  2. Create a content source for the business application data.

  3. Crawl the content source for the application data to add its properties as new crawled properties in the portal site schema.

  4. Select the relevant properties in the Configure Search section of the Business Data Catalog and map them to managed properties for search.

  5. Create ACLs for business data types in the Business Data Catalog.

  6. Crawl the content source for the business data source again to update managed properties and ACLs.

  7. Create search scopes for business data.

  8. Configure keywords for business data.

Business data search results can be displayed in four distinct ways:


  • When a search scope that includes business data is used to search, the search
    results include business data results. A business data search scope is not
    provided by default, but administrators can create search scopes that include
    business data.

  • Users can refine initial search results by selecting the option to search business
    data sources.

  • Users can select a business data tab provided in the Search Center
    site, and the search will include all business data for the tab. A tab for all
    business data is not included by default, but administrators can add tabs for
    searching all business data or for searching data from specific business
    applications.

  • If the search term in a standard search query matches a keyword phrase selected by an administrator, the business data result appears in recommended results.

We can search the .Net business data in following ways


  • SharePoint portal
  • Expose it as SharePoint list
  • Create Business Data Web Parts and
  • Use SharePoint Search Web service and create your own custom search application



Note: The integration of business data is available only in the enterprise version of Office SharePoint Server 2007.Deployments of the standard version of Office SharePoint Server 2007 cannot register business data applications or search for business data by using enterprise search.






MOSS Best Practices & Coding Guidelines

MOSS Best Practices & Coding Guidelines



1. Common development Issues in SharePoint objects:

A Bad SharePoint code can be one of the key factors for degrading the performance. We should be careful on the following areas when we write the custom code…

· Disposing of SharePoint objects

· Caching data and objects

· Writing code that is scalable

1.1. Disposing ­­­SharePoint objects

We all know that SPSite class and SPWeb class objects are created as managed objects. However, these objects use unmanaged code and memory to perform the majority of their work. Unfortunately the managed part of the object is small; the unmanaged part of the object is much larger. We should not rely on the garbage collector to release objects from memory automatically. Because the smaller managed part of the object does not put memory pressure on the garbage collector, the garbage collector does not release the object from memory in a timely manner. The object's use of a large amount of unmanaged memory can cause some of the unusual behaviors.

1.1.1. Problems in not disposing SharePoint object

In the SharePoint object model, the Microsoft.SharePoint.SPSite and Microsoft.SharePoint.SPWeb objects are created in managed code as a small wrapper (approximately 2 KB in size). This wrapper then creates unmanaged objects, which can average approximately 1–2 MB in size. If your code resembles the following code example, and if you assume that the SPWeb.Webs collection has 10 subsites, a total of 10 items are created, each with an average of 2 MB of memory (for a total of 20 MB).

Sample code

public void GetNavigationInfo()
{

SPWeb oSPWeb = SPContext.Web;
// .. Get information oSPWeb for navigation ..
foreach(SPWeb oSubWeb in oSPWeb.GetSubWebsForCurrentUser())
{
// .. Add subweb information for navigation ..
}
}

Table 1. Best and worst case memory usage as number of users increases

Users

Best Case

Worst Case

10

100 MB

200 MB

50

500 MB

1000 MB

100

1000 MB

2000 MB

250

2500 MB

5000 MB

The following unusual behaviors can happen because of non-disposing objects

· Frequent recycles of the Microsoft Windows SharePoint Services application pool, especially during peak usage

· Application crashes that appear as heap corruption in the debugger

· High memory use for Microsoft Internet Information Services (IIS) worker processes

· Poor system and application performance

1.1.2. Coding Techniques to follow:

Power of Using Clause:

Using clause automatically disposes the SharePoint objects. It implements the IDisposable interface.

String str;
using(SPSite oSPsite = new SPSite("http://server"))
{

using(SPWeb oSPWeb = oSPSite.OpenWeb())

{
str = oSPWeb.Title;
str = oSPWeb.Url;
}

}

Make sure that the following objects are properly disposed either by using Using Clause or by uinsg Dispose method:

a. SPSite.OpenWeb - creates new items and should be disposed of.

b. SPSite.RootWeb and SPWeb.ParentWeb - creates new objects and assign them to a local member variable.

c. SPSite() constructors

a. SPSite (Guid) Initializes a new instance of the SPSite class based on the specified GUID for a site collection.

b. SPSite (String) Initializes a new instance of the SPSite class based on the specified absolute URL.

c. SPSite (Guid, SPUrlZone) Initializes a new instance of the SPSite class based on the specified site collection GUID and URL zone.

d. SPSite (Guid, SPUserToken) Initializes a new instance of the SPSite class based on the specified site collection GUID and user token.

e. SPSite (String, SPUserToken) Initializes a new instance of the SPSite class based on the specified absolute URL and user token.

f. SPSite (Guid, SPUrlZone, SPUserToken) Initializes a new instance of the SPSite class based on the specified site collection GUID, URL zone, and user token.

d. SPSiteCollection Class

e. SPSiteCollection.Add method - It creates and returns a new SPSite object

f. SPSiteCollection [ ] Index Operator - It returns a new SPSite object for each access

Bad Practice:

a. SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin();
SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites;
SPSite oSPSite = aSites.Add( ... );
... Process the site info ...
oSPSite.Dispose();
oSPGlobalAdmin.Dispose();

b. SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin();
SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites;
foreach(SPSite oSPSite in aSites)
{
BuildTableRow(oDisplayTable, "Site", oSPSite.Url);
}
oSPGlobalAdmin.Dispose();

Good Practice:

a. int i;
SPSite oSPSite;
SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin();
SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites;
for(i = 0;i < aSites.Count;i++)
{
oSPSite = aSites[i];
BuildTableRow(oDisplayTable, "Site", oSPSite.Url);
oSPSite.Dispose();
}
oSPGlobalAdmin.Dispose();

b. int i;
SPGlobalAdmin oSPGlobalAdmin = new SPGlobalAdmin();
SPSiteCollection aSites = oSPGlobalAdmin.VirtualServers[0].Sites;
for(i = 0;i < aSites.Count;i ++)
{
using(SPSite oSPSite = aSites[i])
{
BuildTableRow(oDisplayTable, "Site", oSPSite.Url);
}
}
oSPGlobalAdmin.Dispose();

g. SPSite.AllWebs Property (SPWebCollection) - SPSites.AllWebs.Add Method

h. SPSite.OpenWeb

i. SPSite. SelfServiceCreateSite Methods - Creates a SPWeb object and return it to the caller

j. SPSite.LockIssue, SPSite.Owner, and SPSite.SecondaryContact Properties - All these properties reference data from the top-level Web site and use the SPSite.RootWeb property

k. SPSite.RootWeb property – This property determines whether a member variable is assigned with a non-null value. If the member variable is null, a new SPWeb object is created by calling SPSite.OpenWeb method.

l. SPWeb Objects

m. SPWeb.ParentWeb Property

n. Area.Web property - It returns a new SPWeb object each time it is accessed

o. WebPartPage.RootWeb Property - The WebPartPage.RootWeb property is similar to the SPSite.RootWeb property in that the first time the property

1.1.3. Dispose Vs Close:

Though Dispose and close dose the same thing, Microsoft recommends to use Displose for the following reason…

· SPWeb and SPSite objects implement the IDisposable interface, and the standard .NET Framework process calls the Dispose method to free from memory any resources associated with the object.

· Future releases of your code are ensured to be called properly.

1.2. Caching Data and Objects

Many of us are using Microsoft .NET Framework caching objects (for example, System.Web.Caching.Cache) to help make better use of memory and increase overall system performance. But, many objects are not "thread safe" and caching those objects can lead to application crashes and unexpected or unrelated user errors.

1.2.1. Caching SharePoint Objects That Are Not Thread Safe

We are trying to increase performance and memory usage by caching SPListItemCollection objects that are returned from queries. In general, this is a good practice but the SPListItemCollection object contains an embedded SPWeb object that is not thread safe and should not be cached. For example, assume the SPListItemCollection object is cached in thread A. Then, as other threads try to read it, the application can fail or behave strangely because the object is not thread safe.

1.2.2. Not Using Thread Synchronization

Sometimes we are not aware that we are running in a multi-threaded environment (by default, Internet Information Services is multi-threaded) or how to manage that environment. The following code example shows the caching Microsoft.SharePoint.SPListItemCollection objects.

Sample code

public void CacheData()
{
SPListItemCollection oListItems;
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if(oListItems == null)
{
oListItems = DoQueryToReturnItems();
Cache.Add("ListItemCacheName", oListItems, ..);
}
}

In the previous code example, the problem is that if the query to get the data takes 10 seconds, our could have many users hitting that page at the same time, all running the same query and trying to update the same cache object at the same time. This can cause performance issues because the same query might be running 10, 50, or 100 times and can cause crashes because multiple threads are trying to update the same object at the same time, especially on multi-process, hyper-threaded computers. To fix this, we must change the code as follows.

Sample Code

public void CacheData()
{
SPListItemCollection oListItems;
lock(this)
{
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if(oListItems == null)
{
oListItems = DoQueryToReturnItems();
Cache.Add("ListItemCacheName", oListItems, ..);
}
}
}

It is possible to increase performance slightly by placing the lock inside the if(oListItems == null) code block. When we do this, we do not need to suspend all threads while checking to see if the data is already cached. Depending on how long it takes the query to return the data, there is still the possibility that more than one user might be running the query at the same time.

1.2.3. Caching an object which is thread safe

Sample code: Using DateTable object which is thread safe

public void CacheData()
{
DataTable oDataTable;
SPListItemCollection oListItems;
lock(this)
{
oDataTable = (DataTable)Cache["ListItemCacheName"];
if(oDataTable == null)
{
oListItems = DoQueryToReturnItems();
oDataTable = oListItems.GetDataTable();
Cache.Add("ListItemCacheName", oDataTable, ..);
}
}
}

1.3. Writing Code That Is Scalable

Writing scalable code is very important when we handle multiple users at the same time.

1.3.1. A few things that we need to take into consideration when asking how to make code more scalable

· Is the data static (seldom changes), somewhat static (changes occasionally), or dynamic (constantly changing)?

· Is the data the same for all users, or does it change? For example, does it change depending on the user who is logged on, the part of the site being accessed, or the time of year (seasonal information)?

· Is the data easily accessible or does it require a long time to return the data? For example, is it returning from a long-running SQL query or from remote databases that can have some network latency in the data transfers?

· Is the data public or does it require a higher level of security?

· What is the size of the data?

· Is the SharePoint site on a single server or on a server farm?

A good example of this is creating custom navigation information for all sites and subsites on each page or as part of a master page. For example, if we have a SharePoint site on a corporate intranet and each department has its own site with many subsites, our code might resemble the following.

public void AddAllWebs(SPWeb oSPWeb)
{
foreach(SPWeb oSubWeb in oSPWeb.Webs)
{
//.. Code to add items ..
AddAllWebs(oSubWeb);
oSubWeb.Dispose();
}
}

While the previous code disposes of objects properly, it still causes problems because the code is going through the same lists over and over. For example, if we have 10 site collections and an average of 20 sites or subsites per site collection, we would iterate through the same code 200 times. For a small number of users this might not cause bad performance. But, as we add more users to the system, the problem gets worse. Table 2 shows this.

Table 2. Iterations increase as the number of users increase

Users

Iterations

10

2000

50

10000

100

200000

250

500000

The above code executes for each user that hits the system, but the data remains the same for everyone. The impact of this can vary depending on what the code is doing. The above code creates a lot of performance overhead.

There are several ways we can make our code more scalable and handle multiple users.They are...

· Caching Raw Data

· Building Data Before Displaying It

· Caching for a Single Server or Server Farm

1.3.2. Caching Raw Data

we can cache our data by using the System.Web.Caching.Cache object. This object requires that we query the data one time and store it in the cache for access by other users.

If our data is static, we can set up the cache to load the data once and not expire until the application is restarted or to load once a day to ensure data freshness. We can create the cache item when the application starts, when the first user session starts, or when the first user tries to access that data.

If our data is somewhat static, we can set up the cached items to expire within a certain number of seconds, minutes, or hours after it is created. This enables us to refresh your data within a timeframe that is acceptable to our users. Even if the data is cached for only 30 seconds, under heavy loads we will still see an increase of performance because we are running the code only once every 30 seconds instead of multiple times a second for every user hitting the system.

Be sure to take into consideration the issues outlined previously in Caching Data and Objects.

1.3.3. Building Data before Displaying It

We should think how our cached data will be used. If this data is used to make run-time decisions, putting it into a DataSet or DataTable object might be the best way to store it. We can then query those objects for the data to make run-time decisions. If the data is being used to display a list, table, or formatted page to the user, consider building a display object and storing that object in the cache. At run time, we need only to retrieve the object from the cache and call its render function to display its contents. We could also store the rendered output, but this can lead to security issues and the cached item could be quite large, causing a lot of page swapping or memory fragmentation.

1.3.4. Caching for a Single Server or Server Farm

Depending on how our SharePoint site is set up, we might have to address some caching issues differently. If our data must be the same on all servers at all times, then we must ensure that the same data is cached on each server. One way to ensure this is to create the cached data and store it on a common server or in an SQL database. Again, we must consider how long it takes to access the data and any security issues of the data being stored on a common server.

2. Things to check before check in the code

To ensure that our SharePoint system performs at its best, we need to check the following points

  • Does our code properly dispose of SharePoint objects?
  • Does our code cache objects properly?
  • Does our code cache the correct types of objects?
  • Does our code use thread synchronization when necessary?
  • Does our code work as efficiently for 1000 users as it does for 10 users?