Many years ago, back when
software shipped with printed manuals, I occasionally dabbled in a bit
of development with Microsoft Access 2.0. Although the developers’
manual that Microsoft provided with Access covered the ins and outs of
the product in great detail, the thing that I found most useful was the
pseudo-class diagram that was printed on the back cover.
In my opinion, there’s no easier way to find your way around a new
object model. Bearing that in mind, the following illustration is my
SharePoint 2010 hierarchy. Of course, the actual object model is far
more complicated, but as a tool, this will help you get up to speed
quickly.
We’ll work through the objects on the diagram to
build an understanding of what each one represents and how you might use
it in development. We’ll use a console project to execute our code
samples. Take the following steps:
In Visual Studio 2010, choose File | New | Project.
In the New Project dialog, select Console Application. Name the new project Chapter2, as shown. Ensure that the framework is set to .NET Framework 3.5. Click OK.
Earlier
I discussed the problems that can arise when debugging and unit testing
SharePoint applications due to the 64-bit nature of the SharePoint
platform. Console Application projects are created with a default build
configuration of x86, meaning that they will be built as 32-bit
assemblies. Since these will not work when targeting SharePoint, we need
to change the default build configuration.
In the Solution Configuration drop-down, select Configuration Manager, as shown next:
From
the Active Solution Platform drop-down, select <New..>, and then
in the New Solution Platform dialog, select x64 as the new platform, as
shown:
Close the Configuration Manager dialog to return to the project.
Add
a reference to Microsoft.SharePoint by choosing Project | Add
Reference, and then select Microsoft.SharePoint from the .NET tab.
Administration and Configuration Classes
The following classes are generally used for
administration and configuration purposes. Many of these are commonly
used when implementing service applications..
SPFarm
It will probably come as no surprise to learn that
the SPFarm object represents the SharePoint farm. Server Object Model
code must be executed on a server that is a member of a SharePoint farm
(or on a single stand-alone server, which is effectively a farm with
only one server in it), and because of this we can obtain a reference to
the SPFarm object that represents the current farm by using code such
as this:
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.ListServersInFarm();
Console.WriteLine("Press enter to exit...");
Console.ReadLine();
}
void ListServersInFarm()
{
Console.WriteLine("Servers in farm:");
foreach (SPServer server in SPFarm.Local.Servers)
{
Console.WriteLine(server.DisplayName);
}
}
}
SPServer
The
SPServer object represents a specific server within a SharePoint farm.
Again, since all Server Object Model code must run on a server within a
SharePoint farm, we can pick up a reference to the current SPServer
object as follows:
void ListServicesOnServer()
{
Console.WriteLine("Services on local server");
foreach (SPServiceInstance service in SPServer.Local.ServiceInstances)
{
Console.WriteLine(service.TypeName);
}
}
SPService
At its most fundamental, SharePoint is a platform for
running services across a farm of servers. These services can include
features such as Web Services, which use IIS to provide web-based
content, or Search Services, which provides search functionality to
other services within the farm.
SPServiceInstance
Since a SharePoint farm may have many servers, each
platform server may have more than one instance. The SPServiceInstance
object represents an instance of a service that is running on a
particular server.
SPWebService
The SPWebService is the parent service that hosts all front-end web sites within a SharePoint farm.
Site Provisioning and Content Access Classes
The following classes are used for programmatically
provisioning sites as well as for accessing data contained within sites,
lists, and libraries. These classes will be commonly used in all
SharePoint development.
SPWebApplication
>As
you saw earlier when we walked through the creation of a SharePoint
site, the web application is the topmost object in the site provisioning
hierarchy. Each web application that’s configured on a SharePoint farm
is represented by an SPWebApplication object in the Server Object Model:
void ListWebApplications()
{
Console.WriteLine("Web applications in farm:");
SPWebService webService = SPFarm.Local.Services.
OfType<SPWebService>().First();
foreach (SPWebApplication app in webService.WebApplications)
{
Console.WriteLine(app.Name);
}
}
SPSite
This is where it gets confusing! The next level in
the site provisioning hierarchy is the site collection. However, within
the SharePoint Object Model, each site collection is represented by an
SPSite object. The SPSite object is one of the primary entry points to
the Server Object Model and will be used frequently in SharePoint
application development.
The following code snippet shows how to create an
SPSite object explicitly. Notice that the SPSite object is defined
within a using block, this
is recommended practice whenever an SPSite object is created.
void ListSitesInSiteCollection()
{
Console.WriteLine("Sites in site collection:");
using (SPSite site = new SPSite("YourSiteCollectionURL"))
{
foreach (SPWeb web in site.AllWebs)
{
Console.WriteLine(web.Title);
web.Dispose();
}
}
}
SPWeb
Continuing with the theme of confusion, within the
model, sites are represented by SPWeb objects. Although SPSite objects
are the primary entry point to the model, picking up references to
objects that we’ll likely be writing code against will require a
reference to an SPWeb object. The following code snippet shows how to
obtain a reference to the root site in a site collection:
void ListListsInRootWeb()
{
Console.WriteLine("Lists in site collection root site:");
using (SPSite site = new SPSite("YourSiteCollectionURL "))
{
using (SPWeb root = site.RootWeb)
{
foreach (SPList list in root.Lists)
{
Console.WriteLine(list.Title);
}
}
}
}
As
well as explicitly creating SPWeb objects, references to the current
SPWeb object can often be obtained from other sources. For example, when
you’re writing code that runs in the context of a web page, the static
SPContext.Current property provides a reference to the current SPWeb
object, as this code snippet shows:
SPList list = SPContext.Current.Web.Lists.TryGetList(ListName);
if (list == null)
{
//do stuff
}
SPList
Most SharePoint content is stored within lists or
document libraries. Within the Server Object Model, both lists and
document libraries are represented by an SPList object. Although not
included in our diagram, document libraries are also represented by
SPDocumentLibrary objects. The SPDocumentLibrary class is derived from
the SPList class and provides additional functionality that is
appropriate for document libraries. Other classes are derived from
SPList and represent specific types of list; for more information, see http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splist.aspx.
SPListItem
As mentioned, most content within a SharePoint site
is accessed via an SPList object. Each item in a list or library is in
turn represented by an SPListItem object that is accessed via the
SPList.Items collection. The SPList class and the SPListItem class will
feature heavily in practically all development on the SharePoint
platform.
SPFile
Although almost all content is represented by an
SPListItem object, where the content in question is a file, the
SPListItem object only represents the metadata for the file. For
example, if we create a document library and upload a Word document, the
SPListItem object that represents the document will contain only the
document title as well as a few additional system-generated metadata
fields. To perform work on the document, we need to use an SPFile object
as shown next.
void ListRootWebMasterPages()
{
Console.WriteLine("Master Page files in site collection root site:");
using (SPSite site = new SPSite("YourSiteCollectionURL"))
{
using (SPWeb root = site.RootWeb)
{
SPList masterPages = root.Lists.TryGetList("Master Page Gallery");
if (masterPages != null)
{
SPListItemCollection items = masterPages.Items;
foreach (SPListItem fileItem in items)
{
SPFile file = fileItem.File;
Console.WriteLine(file.Name + "\t" + string.Format("{0:###,### →
bytes}",file.Length));
}
}
}
}
}
SPFolder
Most
user-generated content within SharePoint sites is stored in lists and
document libraries, and these document libraries can also contain
folders that operate in the same way as folders in the file system. As
well as folders that are used for organizing user content, other folders
contain files that are used by the SharePoint platform itself. These
files often contain configuration files for platform elements such as
content types.
The following code snippet shows how to enumerate
folders within a SharePoint site. Folders used for organizational
purposes have an attached DocumentLibrary object, whereas system folders
do not.
void ListRootWebFolders()
{
Console.WriteLine("Files in site collection root site:");
using (SPSite site = new SPSite("YourSiteCollectionURL"))
{
using (SPWeb root = site.RootWeb)
{
listFolders(root.Folders);
}
}
}
void listFolders(SPFolderCollection folders)
{
foreach (SPFolder folder in folders)
{
Console.Write(folder.Name + "\t");
if (folder.DocumentLibrary != null)
{
Console.WriteLine("Corresponding library: " + folder.DocumentLibrary.Title);
}
else
{
Console.WriteLine(string.Empty);
}
listFolders(folder.SubFolders);
}
}
Saving Changes Using the Server Object Model
Behind
the scenes, SharePoint, like many enterprise applications, stores all
data within a database. Many of the objects that we’ve seen are actually
an in-memory copy of the state of a particular component, and as a
result, changing properties on the object affects only the in-memory
copy and not the underlying database. To commit object changes to the
database, the Update method should be called, as this snippet shows:
void UpdateDescription()
{
Console.WriteLine("Lists in site collection root site:");
using (SPSite site = new SPSite("YourSiteColectionURL"))
{
using (SPWeb root = site.RootWeb)
{
root.Description = "My New Description";
root.Update();
}
}
}
Best Practice Guidelines
We’ve covered most of the commonly used objects in
the Server Object Model. However, you should bear in mind a few caveats
when using these objects.
IDisposable
Probably the most important thing to remember is that
some of the objects that we’ve covered here implement the IDisposable
interface, as you can see from the hierarchical diagram shown earlier.
There is a very good reason for the objects to implement this interface
specifically: these objects hold a reference to an SPRequest object,
which in turn holds a reference to a COM component. The SharePoint
platform uses the COM component to communicate with SQL Server. By
implementing IDisposable, these objects can explicitly close the
connection to the database and properly clean up the COM component when
the .NET Framework objects are no longer required.
So what can we do to ensure that objects are disposed
of properly? As a general rule, best practice is to wrap all
IDisposable objects in a using block; you can see this technique used in
the earlier examples. However, there are exceptions to this rule. On
many occasions, IDisposable
objects are passed into a function or are otherwise automatically
created by the SharePoint platform. For example, the following code
samples use a reference to an IDisposable object that was created by the
platform:
private void UseSPContext(string myList)
{
SPList list = SPContext.Current.Web.Lists.TryGetList(myList);
if (list == null)
{
//Do Something
}
}
When creating event handlers, the properties parameter contains a reference to the SPWeb object that has raised the event:
public override void ItemUpdating(SPItemEventProperties properties)
{
string title = properties.Web.Title;
}
For situations in which the IDisposable object is
created elsewhere, it is not appropriate to dispose of it explicitly
since this could cause problems elsewhere.
Performance
A few common coding practices can lead to performance
problems when you’re developing SharePoint applications. In addition to
the IDisposable issues, which are by far the most common, most other
problems relate to the proper use of data access.
You’ve seen how the SPList and SPListItem classes can
be used to retrieve and represent data from a SharePoint content
database. However, the SPListItem object is relatively heavyweight and
as a result, if we retrieve the contents of a list that contains many
items, the resource implications are significant.
The following code sample shows how we can use the
SPQuery object to restrict the number of rows returned and then page
through the items in a list.
SPQuery query = new SPQuery();
query.RowLimit = 20;
do
{
SPListItemCollection items = myList.GetItems(query);
//Use the items
query.ListItemCollectionPosition = items.ListItemCollectionPosition;
} while (query.ListItemCollectionPosition != null);
Another common coding pattern that can cause performance issues is demonstrated in this code snippet:
SPList masterPages = root.Lists.TryGetList("Master Page Gallery");
if (masterPages != null)
{
foreach (SPListItem fileItem in masterPages.Items)
{
SPFile file = fileItem.File;
Console.WriteLine(file.Name);
}
}
Although this code works properly and would probably
be our first choice when iterating through a collection of list items,
behind the scenes, the implementation to the SPList object makes this a
common cause of performance problems. Each time the Items collection is
referenced, the underlying SPWeb object makes a call to the SQL database
to retrieve the list of items. So if we imagine a list with 2000 items,
iterating through the list using this code would generate 2000 database
calls with each one returning 2000 rows. If a few users were performing
the same actions at the same time, you can see how this quickly would
become a major performance drain.
Thankfully, the problem is easy to fix:
SPList masterPages = root.Lists.TryGetList("Master Page Gallery");
if (masterPages != null)
{
SPListItemCollection items = masterPages.Items;
foreach (SPListItem fileItem in items)
{
SPFile file = fileItem.File;
Console.WriteLine(file.Name);
}
}
By assigning the Items property to a
SPListItemCollection variable and then using that as the target of our
iteration, we’re generating only a single database query when the
SPListItemCollection is assigned.
Error Handling
I’ve left error
handling and boundary checking code out for the sake of brevity. Of
course, in real-world code, we’d add these things and create suitable
unit tests to validate their functionality. To make it possible for us
to filter SharePoint specific errors in try/catch blocks, all SharePoint
exceptions are derived from the SPException class.
Earlier we looked at Sysinternals DebugView as a tool
to assist in debugging problems in server-side code. Although we could
use this as an error logging tool, SharePoint provides a better
way to achieve the same result. Using code similar to the following
sample, we can write error logging entries to the SharePoint Unified
Logging Service (ULS) logs:
try
{
//some code
}
catch (Exception ex)
{
SPDiagnosticsCategory myCat=new SPDiagnosticsCategory("A new category",
TraceSeverity.Monitorable,
EventSeverity.Error);
SPDiagnosticsService.Local.WriteEvent(1, myCat,
EventSeverity.Error,
"My custom message",
ex.StackTrace);
}