Chapter 20: Incorporating ASP.NET 2.0 Applications
This article is an excerpt from Professional SharePoint 2007 Web Content Management Development: Building Publishing Sites with Office SharePoint Server 2007 by Andrew Connell from Wrox (ISBN 978-0-470-22475-5, copyright Wrox 2008, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.
As covered in this book and many others, Windows SharePoint Services 3.0 (WSS) can be used to host not only traditional collaboration sites, but also content-centric sites. However, many organizations may require some sort of functionality or custom application to be embedded within a SharePoint site. For instance, a SharePoint Publishing site may need to have a newsletter subscription and management application or an event registration system. In past versions of WSS 3.0 it was not very easy to incorporate custom applications into a site.
Thankfully, WSS 3.0 greatly expands on the number of options available to developers to build custom applications in SharePoint sites, both collaboration and Publishing sites. This chapter covers different techniques and options developers can utilize to incorporate custom ASP.NET 2.0 applications into SharePoint sites. In addition, many common questions that come up when building applications in SharePoint are covered in this chapter, such as when to store data in SharePoint lists compared to a custom database and how to customize the navigation.
While incorporating custom ASP.NET 2.0 applications into SharePoint sites is one option, another option is to use WSS 3.0 as the application development platform. In this case, the application is built on top of SharePoint, rather than being integrated into an existing collaboration or publishing site.
One thing not included in this chapter is a large number of code snippets and screenshots demonstrating the different techniques. That's because the techniques have been covered extensively throughout other chapters in the book and repetition does not add value. Therefore, this chapter contains references to other chapters throughout the book. Similarly, this chapter does not walk through the development of a custom application, as each application is very different and has unique business requirements. Think of this chapter as more of an overview of different options and possibilities when creating custom applications in SharePoint sites.
Before reading the rest of this chapter it would be helpful to adopt a particular mindset regarding the development of SharePoint sites (if it is not already apparent). SharePoint development is unlike traditional ASP.NET 2.0 development in the sense that instead of building large applications, developers build many smaller components and integrate them into a larger solution. For example, creating a custom list or content type with advanced custom workflows and event receivers involves building many little components, rather than a single large component. The sum of the components yields a much larger and valuable component than the individual pieces.
Contents
Each Component Adds More Value
Advantages to Using SharePoint As an Application Development Platform
Incorporating Applications into SharePoint Sites
Data Storage Options
Application Configuration Options
Utilizing SharePoint Components in Custom Applications
Summary
Each Component Adds More Value
Before jumping into custom application development it helps to take a closer look at all of the components that make up a SharePoint site and how they can be utilized in an application. This also helps when you are deciding whether or not SharePoint is the right platform for building applications on top of the WSS 3.0 platform. As explained in Chapter 2, "Windows SharePoint Services Development Primer," WSS 3.0 is built on top of ASP.NET 2.0, and Office SharePoint Server (MOSS) 2007 is built on top of WSS 3.0. Because the SharePoint architecture is additive to the underlying frameworks, every component's features and capabilities are available throughout the stack. This chapter covers the different major aspects of these components and why they are significant when building custom applications within SharePoint sites. The material provided in the following sections is by no means exhaustive, but covers many of the most significant components as they pertain to custom application development.
What ASP.NET 2.0 Brings to the Table
Chapter 2 details the primary components in ASP.NET 2.0 that are heavily leveraged within WSS 3.0. While all the components in ASP.NET 2.0 can be leveraged in SharePoint sites in the same manner, SharePoint provides some added value in certain areas, including the following:
All pages within a section of the site can be configured to use the same master page, and site administrators are provided with a Web interface for selecting and changing the master page used for the site, rather than setting it on a page-by-page basis. For more information on master pages in SharePoint, refer to Chapter 7, "Master Pages and Page Layouts."
The plumbing required by the Web Part framework, such as the WebPartManager control and various zones, is all provided OOTB in SharePoint. This is not the case in ASP.NET 2.0 sites, as developers need to ensure that all pages leveraging Web Parts have an instance of the WebPartManager control, and create the necessary Web Part zones to host Web Parts, a catalog zone to provide a list of Web Parts to add to the page, and an editor zone to enable users to modify the properties of the Web Parts on the page. For more information on Web Parts, refer to Chapter 11, "Web Parts."
Although user and server controls can be used in SharePoint sites in the same way they are used in ASP.NET 2.0 sites, SharePoint takes it a step further. Using delegate controls, developers can create WSS 3.0 Features that enable site owners to dynamically inject and replace functionality and content very easily, complete with a built-in undo mechanism. For more information on delegate controls, refer to Chapter 7.
The addition of Windows Workflow Foundation (WF) to the .NET 3.0 Framework enabled developers to create episodic and reactive programs such that while waiting for some event or state, the programs are serialized to a persisted state and are therefore not subject to server reboots or resource issues, as are traditional .NET applications. Applications utilizing WF must host the workflow runtime and provide the necessary services, such as the persistence service that handles serialization and deserialization of the workflow instance. SharePoint provides all the necessary plumbing to host the workflow runtime and persistence service, removing these burdens from developers. It also provides a human element by facilitating different types of forms and associating workflows with items, documents, and content types. For more information on WF in SharePoint, refer to Chapter 12, "Leveraging Workflow."
When it comes to building a custom application that either integrates into an existing SharePoint site or uses SharePoint as the foundation, developers can also take advantage of additional ASP.NET 2.0 components. The membership provider model enables developers to use any authentication store desired in a custom application. The abstraction of the particulars of each authentication mechanism at the ASP.NET 2.0 level dramatically simplifies the integration of custom applications at the SharePoint level.
All applications require some sort of navigation. The navigation provider model included in ASP.NET 2.0 enables developers to cleanly separate the data portion of the navigation from the rendering implementation, enabling teams to purchase full-featured third-party navigation components that easily snap into existing projects with very little, if any, custom code. SharePoint fully leverages the navigation provider model, covered in detail in Chapter 8, "Navigation," and even includes a few site map data sources that do most of the work in generating the navigation hierarchical structure.
While ASP.NET 2.0 provides a significant number of components that can be utilized by developers in custom applications, SharePoint provides much more. The next two sections provide an overview of the various components that both WSS 3.0 and MOSS 2007 add to the developer's proverbial toolbox.
What WSS 3.0 Brings to the Table
The previous section already mentioned a few of the additional WSS 3.0 benefits, such as how master pages are implemented and how the plumbing of the Web Part framework and Workflow Foundation are already provided out of the box (OOTB). However, the list does not stop there! Almost all custom applications store and retrieve data from tables in a database. Each of these tables usually requires some sort of administrative interface. This means that developers are left with the task of creating the CRUD (Create, Read, Update, and Delete) admin pages. Fortunately, when SharePoint lists are used to store such data, all these pages are included OOTB with each list.
Virtually all custom applications also require some sort of security model. Some users act as administrators to maintain and manage the application, while others fall into various buckets of roles such as general users, power users, managers, and so on. WSS 3.0 includes a robust and granular security model that leverages the ASP.NET 2.0 membership provider model. This model enables administrators to add and remove users and groups to a site using a familiar Web interface. Administrators can also configure sections of the site either to inherit the same permissions from its parent or to break inheritance and create a unique permission configuration at a site, list/library, or even list item level (including folders). In addition to the robust security model, WSS 3.0 also provides full auditing of all events, although this must be enabled and managed using custom code, as shown in Listing 20-1.
Listing 20-1: Enabling all auditing events on a site collection
SPSite siteCollection = new SPSite("http://foo");
siteCollection.Audit.AuditFlags = SPAuditMaskType.All;
siteCollection.Audit.Update();
Many content-centric applications demand some level of search. WSS 3.0 provides a simple built-in search capability. If more robust search functionality is required, developers can look to MOSS 2007 or Microsoft Search Server 2008 Express. In addition, many applications need to share their data with other applications or provide data feeds for some process. WSS 3.0 makes this task very easy, as it includes several Web services that enable users to manage SharePoint sites, as well as read and write data to SharePoint lists using the lists.asmx Web service.
Another powerful tool in application development that WSS 3.0 offers is WSS solution packages (WSPs). Covered in depth in Chapter 4, "SharePoint Features and the Solution Framework," WSPs make deployment of new or existing code and custom files a simple task, even in the largest load-balanced farm environment. Deployment of custom code and files in a traditional load-balanced ASP.NET 2.0 site requires the use of additional software packages or sophisticated scripts—or just traditional XCOPY deployment. WSPs dramatically simplify this task for SharePoint developers.
The navigation provider model mentioned in the previous section is a valuable component included in ASP.NET 2.0. WSS 3.0 fully leverages this model and includes two small additional pieces to the navigation puzzle. The included site map providers and data sources build the navigation hierarchical structure based on the structure of the site. When building the navigation structure, SharePoint factors in the current permissions of the objects to which the navigation nodes refer and compares them to the current user's permission rights, omitting anything to which the user does not have access. In addition, developers can add and remove items from the navigation programmatically.
Sometimes the requirements of a custom application demand some sort of plug-in support. Again, WSS 3.0 includes something to facilitate this requirement: the Feature framework! SharePoint Features can be used not only to deploy and add new functionality, but also to provide a plug-in style capability. While WSS 3.0 offers quite a bit in the area custom application development, MOSS 2007 offers even more!
What MOSS 2007 Brings to the Table
Just like WSS 3.0, MOSS 2007 includes quite a few features that can be utilized in custom applications. Some of these may or may not make sense in custom applications, such as utilizing the Publishing Features or Excel Services (granted, the custom application may have some reporting capabilities that utilize Excel Services—this is just an example). However, other components included in MOSS 2007 can provide significant value in a custom application. This section takes a look at some of those components. First, building off the previous section, MOSS includes a much more robust search capability than what WSS 3.0 provides. For example, the different components in SharePoint search (indexing and queries) can be isolated to specific application servers in MOSS, whereas in WSS 3.0 all servers contain all server roles. For more information on search in SharePoint refer to Chapter 13, "Search."
Another component of MOSS that can be very useful in custom application development is Forms Services. This addition to the latest release of the SharePoint platform enables developers and business users to author rich electronic forms using Office InfoPath 2007 and to consume those forms from the server. The forms are rendered either using the InfoPath 2007 client or as a typical Web form. Virtually all applications require some type of form for data collection or editing. Using InfoPath 2007, business users and/or developers can quickly build sophisticated forms that can be deployed to a MOSS 2007 server running Forms Services, and configure the forms to be rendered in the browser.
Many custom applications need to interface with existing applications deployed within an organization. These can be as big as CRM or ERP systems or as small as a homegrown time-keeping system. When one application needs to interface with another application, this is done either using direct calls to the database or by going through some middle business layer such as Web services. This can prove to be a maintenance nightmare because developers need to keep track of multiple database connection strings or Web service URLs, as well as the credentials needed to impersonate specific application accounts in order to access these resources.
In MOSS 2007, Microsoft introduced a new component called the Business Data Catalog (BDC) that helps with these challenges. Developers first create an application definition, which is an XML file containing the connection information and credentials used to connect to the source (a database or Web service); entities; and relationships. The application definition can be loosely thought of as an object relationship mapper (ORM). With the application definition created, SharePoint is then aware of the external application. This does not mean that data is copied and consumed in SharePoint; rather, SharePoint simply knows how to connect and retrieve data. Custom applications can utilize the BDC application definitions however they need to. The advantage to using the BDC to connect to another system is that all the connection information is maintained in one place by an administrator. In addition, the connection information can specify using a specific account, pass the current user's credentials through to the target system, or leverage SharePoint's single sign-on capability.
The list of features described in this section is by no means exhaustive—MOSS 2007 offers numerous capabilities, many of which were left off this list, such as the capability to create policies and manage auditing configuration via the browser. Only the capabilities that provide the biggest value for the majority of applications were included here. Even with all the components and added value that both WSS 3.0 and MOSS 2007 bring to the table when building custom applications, additional benefits should be considered when evaluating SharePoint as a potential application development platform.
Advantages to Using SharePoint As an Application Development Platform
The previous sections covered the many components that SharePoint adds to a developer's toolbox when creating custom applications that either integrate into existing SharePoint sites or utilize SharePoint as the foundation. However, it is not only the components that should be considered when evaluating SharePoint as an application development platform. Other factors should also weigh in to the decision process.
First, SharePoint development is much more along the lines of building many smaller components and integrating them. This is very different from traditional ASP.NET 2.0 development whereby the entire application is typically built from scratch (aside from some store-bought or reusable libraries). Typically, this results in less custom code, which in turn results in less chance for defects and bugs, as well as less code to write and maintain. Instead, more of the code is written and supported by Microsoft. This point cannot be discounted or overlooked, as it is quite significant.
Second, as discussed earlier, many of the things that all custom applications need are provided OOTB by SharePoint, including navigation, search, personalization and customization capabilities, self-service, a security model, and a plug-in framework. As with any evaluation period when deciding on an application development platform, weighing the advantages against the disadvantages, as well as the capabilities, the next step is to determine the available options when it comes to implementation. The next section covers the various implementation options.
Incorporating Applications into SharePoint Sites
After evaluating the components provided by ASP.NET 2.0, WSS 3.0, and MOSS 2007, and understanding how they can assist in the development of a custom application, the next step is to devise an implementation plan. This is the stage in the process where most developers get confused and perplexed: How do you do it? Typically, three different techniques can be adopted, none of which are mutually exclusive. Many custom applications require a combination of two, if not all three, of the techniques depending on the application business requirements.
Implementing One or More Web Parts
The most obvious of the three options is to create one or more Web Parts for the application. In this case the developer would create a Web Part that housed the business logic and user interface of the application. Most commonly, the developer needs to account for the state of the application, including all postbacks.
When the application requires multiple Web Parts, deployment and configuration can start to get a little tricky. If the Web Parts reside on the same page, developers can utilize Web Part connections to pass data back and forth between them; but if the Web Parts live on different pages, then the deployment instructions need to include provisions ensuring that the application manager configures each Web Part to point to the other's page. How will the data be shared between the two Web Parts? Utilizing ASP.NET 2.0 session state in a SharePoint site can be quite challenging, so should data be passed around on the query string? These are the types of questions that should be raised in the implementation stage of the process when considering this approach.
For anything other than the simplest applications, those where everything resides on a single page, using Web Parts can prove problematic and a maintenance nightmare. For example, consider an entire application built within a single user or server control in an ASP.NET 2.0 site. For something on a par with a conference registration system, this can end up being a significantly challenging task. However, for something as simple as a newsletter registration and management applet, it would be quite easy.
Provisioning Site Pages
The second option is to create traditional ASP.NET 2.0 pages (*.ASPX) and provision them into SharePoint sites using a WSS 3.0 Feature. This approach is much more flexible than the Web Part approach because developers can provision multiple pages at once and control all the configuration settings in the deployment, or provisioning, process. This approach is not very obvious to most developers, yet interestingly enough it is the option that would be the most familiar. ASP.NET 2.0 developers are used to creating ASPX pages in a traditional ASP.NET 2.0 site, and the technique is very much the same. However, there are a few subtle differences.
First, the page directive should use one of the SharePoint-provided master page tokens (covered in Chapter 7) in the MasterPageFile attribute, although it is not required (developers can point to a specific master page if so desired). Second, the page directive should also contain meta:progid="SharePoint.WebPartPages.Document"
. This attribute is needed if the provisioned page is opened through the site using SharePoint Designer 2007.
Developers often get tripped up when managed code is needed in the page. For example, many developers don't think SharePoint supports code=behind files, but in fact all pages in SharePoint sites can utilize code-behind files. Unfortunately, the developer experience in SharePoint is still a bit lacking in tools such as Visual Studio, so some manual work is needed. When managed code is needed on a site page, the code should always be placed in a code-behind file, never as inline code within the ASPX file. The reason for this goes back to the safe mode parser discussed in Chapter 2. When a page instance is uncustomized, the request is not passed through the safe mode parser. However, when the page becomes customized using something such as SharePoint Designer 2007, the request is run through the safe mode parser.
One of the things the safe mode parser does is prohibit the execution of inline script on a page. This includes code surrounded by the <script runat="server></server>
tags, as well as any event handlers declared in the controls, such as <asp:button OnClick="SomeHandler()" />
. To wire up event handlers for controls such as buttons, wire the events up in the code-behind file—specifically, by overriding the OnInit() method on the System.Web.UI.Page class. The use of inline script frequently trips up developers because a page runs just fine until it is customized, and then a cryptic error is returned. The safe approach is to avoid inline script at all costs.
In order to use code-behind files in site pages, create a new class that inherits from System.Web.UI.Page. The assembly containing this class should be signed, deployed to the host Web application's \bin directory or the global assembly cache, and registered as a safe control (<SafeControl />
) in the Web application's web.config file. Next, the ASPX page needs to be made aware that it has an associated class. This is done by adding the fully qualified name of the class and the assembly (also known as the five-part name, i.e., [namespace.type], [four-part assembly strong name]) in the Inherits attribute in the page directive, as shown here:
<%@ Page Inherits="SitePage, WROX.ProMossWcm.Chapter20, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3be73eb52598ff2e" Language="C#" MasterPageFile="~masterurl/default.master" %>
The process of provisioning site pages is almost identical to the technique of provisioning master pages and page layouts using Features, as demonstrated in Chapter 7 (refer to the code in Listing 7-3). The only difference is that the files are not provisioned into document libraries like master pages and page layouts; rather, they are provisioned into the site. This is done by setting the Type attribute on the <File />
node to Ghostable
instead of GhostableInLibrary
:
<File Url="SomePage.aspx" Type="Ghostable" />
When does it make sense to use this technique in custom applications? One scenario might be when an application's pages need to support customization, such as utilizing Web Parts, or the application should be available only to a particular site or subsite. What about applications that need to be used across all site collections, such as creating an application for people within an organization to request a new SharePoint team site? The next section covers this scenario.
Application Pages
The previous section detailed the technique of provisioning pages into a SharePoint site. This is quite handy when an application needs to be deployed on a site-by-site basis, but what if the application should be available across the entire farm? This is where the last technique of leveraging application pages comes in. Application pages are those pages that reside within the http://[site]/_layouts
URL. This is a virtual directory that all sites share. It points to a special folder located at [..]\12\TEMPLATE\LAYOUTS
.
There are a few significant differences between application pages and site pages. First, because application pages are accessed via a virtual directory and pulled straight from the file system, they cannot be customized using something like SharePoint Designer 2007. Second, because they cannot be customized, these files are not subject to the safe mode parser and therefore they can contain inline script. Third, all applications use a single master page, application.master. This is covered in more detail in Chapter 7.
Application pages, just like site pages, can also utilize code- behind files. If a code-behind file is used in an application page, the class should not inherit from System.Web.UI.Page but from Microsoft.SharePoint.WebControls.LayoutsPageBase.
Data Storage Options
Almost every custom application needs to store data in some manner. In a traditional ASP.NET 2.0 application, data is usually stored in a relational database such as SQL Server. This is achieved in SharePoint using one of two options: SharePoint lists or an external database.
SharePoint Lists
The ASP.NET 2.0 developer's first instinct is to store data used by a custom application in a database. Before doing so, however, developers should consider using SharePoint's internal store constructs: lists! Utilizing lists has many advantages over using a database in a SharePoint application. SharePoint lists are similar in many ways to a database table. Both have columns and rows, although the terminology is used a bit differently. Database tables also have triggers, a way for developers to add business logic before and after an action is committed on the table. SharePoint lists have a similar concept called event receivers, which also support pre- and post-logic processing on actions.
All the CRUD pages are provided OOTB to perform inserts, updates, deletes, and selects on the contents. In addition, all the content in the lists can be indexed and searched using the OOTB search functionality provided in SharePoint.
Other common requirements in custom applications are included with SharePoint lists that would usually require custom development. The following list touches on some of the more popular ones:
Versioning and content approval—Many applications require historical data to be retained or some sort of one-stage approval. This can be easily enabled on a list with a simple radio button toggle on the list's settings page.
Really Simple Syndication (RSS)—All SharePoint lists can be configured to expose their contents via an RSS feed.
Exporting report views to Office applications—Business users often need to work with the data provided in a report in an external application such as Office Excel or Access. Whereas this would require custom development within an ASP.NET 2.0 application, it is provided OOTB with SharePoint lists.
One thing SharePoint lists do not have or support OOTB that is easy to do in relational databases is implement referential integrity between two different tables (lists). While this can be achieved in SharePoint using event receivers and custom field types and controls, it does require some extra custom development.
Another advantage that lists have over database tables is that developers do not have to worry about connection information. All the connection information is provided by SharePoint as long as the list is in the same site collection. When working with a database, developers need to deal with the location and credentials necessary to connect to the database. Refer to Chapter 6, "Site Columns, Content Types, and Lists," for information on reading and writing to SharePoint lists.
A major difference between SharePoint lists and database tables is the capacity and storage of the data. Whereas database tables can scale to contain hundreds of thousands of records, if not millions, the performance of SharePoint lists starts to degrade at those levels. Specifically, the performance of a SharePoint container begins to degrade as the number of items in the container approaches 2,000. The primarily reason for the performance degradation centers around the view architecture when rendering the contents of the list.
An easy workaround to this issue is to group the contents in a list into containers. For example, if a list contains 8,000 items, group the items into a handful of folders such that each container has fewer than 2,000 items. This does not mean that lists cannot contain more than 2,000 items. For one thing, this is not a hard limit. Second, there are other ways to retrieve the data from the list.
Note
For more information on working with lists containing more than 2,000 items, refer to the TechNet white paper "Working with Large Lists in Office SharePoint Server 2007" (www.andrewconnell.com/go/270). This paper contains test results demonstrating the various methods of reading and writing data to SharePoint lists. It also provides good guidance on how and when to create indexes on fields in SharePoint lists to improve performance.
While custom applications can certainly store their data within SharePoint lists, it does not always make sense to do so, in which case an external database should be used.
External Database
After reading the previous section, you are likely wondering when a developer should not use SharePoint lists, and instead use a database to store the data used in a custom application. The answer is quite simple: when using SharePoint lists does not make sense. For instance, does the application store vast amounts of data (tens of thousands of records or more) or must it absolutely contain a relational model? If so, then SharePoint lists may not be the way to go. Instead, consider using an external database for the application.
The database can reside on the same SQL Server that hosts the SharePoint farm and content databases. Just make sure that the custom application tables are not added to any of the SharePoint databases, as this is not supported. Using an external database in SharePoint is almost no different from using one in an ASP.NET 2.0 application. All the same rules apply. First a connection must be established using ADO.NET and then queries are issued to either create, retrieve, update, or delete data in the database tables.
The only aspect that most ASP.NET 2.0 sites are not affected by in SharePoint deals with code access security. By default, SharePoint sites start out using a very restrictive policy called WSS_Minimal. This policy does not allow database connections to be made from third-party assemblies. In order to create a connection to a database, either the trust level must be bumped up to WSS_Medium or a custom policy must be created that grants the assembly and type the necessary permission: System.Data.SqlClient.SqlClientPermission
.
Application Configuration Options
Virtually every custom application requires a place to store configuration information. Such configuration information may include Web service URLs, database connection data, as well as other application-specific configuration information. When building ASP.NET 2.0 applications, this configuration data can be stored in a custom database table or in configuration files, the most common being the web.config file. How should configuration data be handled in a custom application incorporated into or based on the SharePoint framework? Developers have a few options.
The configuration data can still remain in the web.config file, but this approach should be used with great care. Keep in mind that all sites (as well as site collections) in the Web application will have access to these settings, which might not be desirable for things such as database connection strings and login credentials. Another option is to store the configuration in a custom database or a special SharePoint list. The list can be secured quite easily to keep users from viewing the information, yet the application can use elevated privileges via the SPSecurity.RunWithElevatedPrivledges() method to retrieve values from the list. Refer to Chapter 15, "Authentication and Authorization," for more information on the SPSecurity.RunWithElevatedPrivledges() method.
There are two other options unique to SharePoint that ASP.NET 2.0 does not provide. One, many SharePoint objects contain a generic property bag that is exposed as a simple StringDictionary (via the Microsoft.SharePoint.Utilities.SPPropertyBag object). Two, you can use the hierarchical object store within a SharePoint farm. The latter approach involves creating a new object, adding it to the farm's store, and assigning it a unique identifier for easy retrieval later.
To use the hierarchical object store, developers first must create a class that inherits from Microsoft.SharePoint.Administration.SPPersistedObject. This class provides the necessary plumbing to create and remove an object from the store. It serializes or deserializes all the public fields decorated with the Microsoft.SharePoint.Persisted attribute to XML and stores it in the configuration database. This class must contain a default constructor (one with no parameters) in order to be serializable and override a constructor on the SPPersistedObject class to give the object a name, specify the object's parent, and optionally a unique identifier. The code in Listing 20-2 contains a sample object that could be used to store database connection information.
Listing 20-2: Database connection information stored in a farm's hierarchical store
using System;
using Microsoft.SharePoint.Administration;
namespace WROX.ProMossWcm.Chapter20 {
public class Database : SPPersistedObject {
[Microsoft.SharePoint.Administration.Persisted]
public string _server = string.Empty;
[Microsoft.SharePoint.Administration.Persisted]
public string _database = string.Empty;
[Microsoft.SharePoint.Administration.Persisted]
public string _username = string.Empty;
[Microsoft.SharePoint.Administration.Persisted]
public string _password = string.Empty;
public Database () { }
public Database (string name, SPPersistedObject parent, Guid id) :
base(name, parent, id) { }
}
}
With the object created, the next step is to create an instance of the object and store in the configuration database. This is demonstrated in Listing 20-3 using a Feature receiver to add and remove the object upon Feature activation and deactivation.
Listing 20-3: Adding and removing objects from the hierarchical store
using System;
using Microsoft.SharePoint;
namespace WROX.ProMossWcm.Chapter20 {
public class HierarchicalDataStoreFeatureReceiver : SPFeatureReceiver {
private const string LITWARE_DB_CONFIG_KEY = "08F7E568-3184-4D94-A559-8E91DB39F858";
public override void FeatureInstalled (SPFeatureReceiverProperties properties) {}
public override void FeatureUninstalling (SPFeatureReceiverProperties properties) {}
public override void FeatureActivated (SPFeatureReceiverProperties properties){
Database db = new Database("LitwareDB",
properties.Definition.Farm,
new Guid(LITWARE_DB_CONFIG_KEY));
db.Server = "LitwareServer01";
db.DatabaseName = "Foo";
db.Username = "Admin";
db.Password = "pass@word1";
db.Update();
}
public override void FeatureDeactivating (SPFeatureReceiverProperties properties) {
Database db = properties.Definition.Farm.GetObject(
new Guid(LITWARE_DB_CONFIG_KEY));
db.Delete();
}
}
}
To retrieve the object from the store in the application, simply obtain a reference to it using the same method demonstrated in the FeatureDeactivating() method in Listing 20-3:
Guid dbid = new Guid("08F7E568-3184-4D94-A559-8E91DB39F858");
Database db = SPContext.Site.WebApplication.Farm.GetObject(dbid);
Thankfully, as demonstrated in this section, developers are provided with many different options when a custom application incorporated into or built on the SharePoint platform needs configuration information.
Utilizing SharePoint Components in Custom Applications
Aside from all the components provided in ASP.NET 2.0, when SharePoint is selected as the application development platform there are a few SharePoint components that warrant a bit more discussion. This section describes the customization options for SharePoint navigation, utilizing the grids that Microsoft uses throughout SharePoint (SPGridView), and leveraging permission levels.
SharePoint Navigation
SharePoint's navigation data sources and providers are already set up to build the navigation based on the structure of a site and site collection. Thus, if a custom application is based completely on SharePoint sites and lists, no custom code needs to be written. The only customization that might be required is to modify the navigation control to configure how many levels of dynamic flyouts are desired.
However, a custom application may provision custom pages with Features that need to be linked in the navigation, or there may be some other need to customize the navigation. In these cases, developers may want to customize navigation items. The first impulse is usually to create a custom navigation provider or data source that will achieve this, but it isn't necessary. Menus can be augmented using the Feature schema—specifically, <CustomAction />
, <CustomActionGroup />
, and <HideCustomAction />
. Another option is to add items to the navigation when the pages are provisioned or the application is installed using the SharePoint API. The API enables developers to interact with the top navigation area, SPWeb.Navigation.TopNavigationBar, or the left-hand navigation area, SPWeb.Navigation.QuickLaunch. The code in Listing 20-4 contains a Feature receiver that adds a new navigation item to the top navigation control in a SharePoint site.
Listing 20-4: Feature receiver adding a navigation node to the top navigation control
public override void FeatureActivated (SPFeatureReceiverProperties properties) {
// get a reference to the current site's top navigation
SPWeb site = properties.Feature.Parent as SPWeb;
if (site == null)
throw new SPException("Error obtaining reference to the parent SPWeb within FeatureActivated event handler.");
SPNavigationNodeCollection topNavigation = site.Navigation.TopNavigationBar;
// create new drop down menu for our new pages
SPNavigationNode newNode = new SPNavigationNode("Some Link", "SomeFolder/SomePage.aspx", false);
// add the new menu to the end of the top nav bar
topNavigation[0].Children.AddAsLast(newNode);
site.Update();
}
Leveraging SPGridView
Almost all applications need to display data. SharePoint lists display data in a special grid that is based on the ASP.NET 2.0 GridView control: Microsoft.SharePoint.WebControls.SPGridView. Although developers building custom applications can create their own grid controls, they should consider using the grid that SharePoint uses. The primary benefit is to inherit the same look and feel as the rest of a SharePoint site. When the SharePoint grid is used in a custom application, it uses the same CSS classes the other grids SharePoint uses.
One major difference exists when using SharePoint's SPGridView control over the ASP.NET 2.0 GridView control: Developers must set the AutoGenerateColumns property to false and explicitly bind the columns as shown in Listing 20-5. This listing retrieves data from a list containing sessions for a conference, adds them to an ADO.NET DataTable, creates the columns in the grid, and then binds the DataTable to the grid.
Listing 20-5: Utilizing SPGridView
private void BindConferenceDayToGrid
(SPList sessionList, SPGridView gridView, string conferenceDay) {
// run query for day 1 sessions
SPQuery query = new SPQuery();
query.Query = String.Format("<Where><Eq><FieldRef Name=\"StartTime\" /><Value Type=\"DateTime\">{0}</Value></Eq></Where><OrderBy><FieldRef Name=\"StartTime\" /></OrderBy>", conferenceDay);
SPListItemCollection results = sessionList.GetItems(query);
DataTable table;
table = new DataTable();
table.Columns.Add("TimeSlot", typeof(string));
table.Columns.Add("AdminTrack", typeof(string));
table.Columns.Add("CustomizationTrack", typeof(string));
table.Columns.Add("DeveloperTrack", typeof(string));
DataRow row;
string presenter;
foreach (SPListItem result in results) {
row = table.Rows.Add();
row["TimeSlot"] = string.Format("{0} - {1}",
Convert.ToDateTime(result["StartTime"].ToString()).ToString("h:mm tt"),
Convert.ToDateTime(result["EndTime"].ToString()).ToString("h:mm tt"));
row["AdminTrack"] = string.Empty;
presenter = result["Presenter"] == null ? "unknown" : result["Presenter"].ToString();
switch (result["Track"].ToString()) {
case "Customization":
row["CustomizationTrack"] = string.Format("{0} - by: {1}",
result["Title"].ToString(),
presenter);
break;
case "Developer":
row["DeveloperTrack"] = string.Format("{0} - by: {1}",
result["Title"].ToString(),
presenter);
break;
}
}
// bind to the gridview
SPBoundField boundField;
boundField = new SPBoundField();
boundField.HeaderText = "Sessions";
boundField.DataField = "TimeSlot";
boundField.ItemStyle.HorizontalAlign = HorizontalAlign.Center;
boundField.ItemStyle.Wrap = false;
gridView.Columns.Add(boundField);
boundField = new SPBoundField();
boundField.HeaderText = "Admin Track";
boundField.DataField = "AdminTrack";
gridView.Columns.Add(boundField);
boundField = new SPBoundField();
boundField.HeaderText = "Customiation Track";
boundField.DataField = "CustomizationTrack";
gridView.Columns.Add(boundField);
boundField = new SPBoundField();
boundField.HeaderText = "Developer Track";
boundField.DataField = "DeveloperTrack";
gridView.Columns.Add(boundField);
gridView.AutoGenerateColumns = false;
gridView.DataSource = table.DefaultView;
gridView.DataBind();
}
As Listing 20-5 demonstrates, working with the SPGridView control is very similar to working with the ASP.NET 2.0 GridView control. The primary differences are related to the source of the data and binding columns to the grid.
Creating and Managing Custom Security Roles
As previously covered, SharePoint includes a very robust and granular permission model that custom applications can take advantage of. Part of this architecture includes permission levels, which are groups of permission rights. Permission rights are granted to behaviors such as opening a page, adding items to a list, reading items in lists, and so forth. Permission levels are used to group one or more permission rights together. Then, administrators grant permissions to users and groups by assigning them permission levels, rather than permission rights. Administrators have the option to use the provided permission levels to create custom ones through the user browser interface. Unfortunately, custom permission rights cannot be created; administrators are limited to the ones provided by Microsoft.
Developers can still use permission levels within a custom application if the application has special security requirements. For example, consider a conference registration system as the custom application. People attending the conference should be provided a self-service registration system. Some users—registration agents, for instance—need the capability to modify registrations. One way to address this is to create a custom permission level through code (when a Feature is activated, for instance) and then create a custom event receiver that is attached to the registrations list. This event receiver would check whether the user is attempting to update or delete a registration record. If they have not been granted the special permission level created by the Feature, they should not be permitted to make the change.
The code in Listing 20-6 demonstrates creation of a permission level through code.
Listing 20-6: Creating permission levels in code
private void CreatePermissionLevel (SPWeb site) {
SPRoleDefinition registrator = new SPRoleDefinition();
registrator.Name = "Registration Agent";
registrator.Description = "Users with this permission level can modify registrations.";
// assign no rights... used only by name
registrator.BasePermissions = SPBasePermissions.EmptyMask;
site.RoleDefinitions.Add(registrator);
}
The next step, shown in Listing 20-7, is to create the event receiver that will check whether the user has been assigned the permission level.
Listing 20-7: Event receiver checking for a custom permission level
using System;
using Microsoft.SharePoint;
namespace WROX.ProMossWcm.Chapter20 {
public class RegistrationListItemReceiver : SPItemEventReceiver {
private const string REGISTRATOR_PERMISSION_LEVEL = "Conference Registrator";
public override void ItemUpdating (SPItemEventProperties properties) {
this.DisableEventFiring();
using (SPSite siteCollection = new SPSite(properties.WebUrl)) {
using (SPWeb site = siteCollection.OpenWeb(properties.RelativeWebUrl)) {
if (!IsUserAllowedToAddRegistrations(site)) {
properties.Status = SPEventReceiverStatus.CancelWithError;
properties.Cancel = true;
properties.ErrorMessage = "Current user does not have permission to manage registrations. Only users assigned the permission level <b>" + REGISTRATOR_PERMISSION_LEVEL + "</b> can do this.";
}
}
}
EnableEventFiring();
}
public override void ItemDeleting (SPItemEventProperties properties) {
this.DisableEventFiring();
using (SPSite siteCollection = new SPSite(properties.WebUrl)) {
using (SPWeb site = siteCollection.OpenWeb(properties.RelativeWebUrl)) {
if (!IsUserAllowedToAddRegistrations(site)) {
properties.Status = SPEventReceiverStatus.CancelWithError;
properties.Cancel = true;
properties.ErrorMessage = "Current user does not have permission to manage registrations. Only users assigned the permission level <b>" + REGISTRATOR_PERMISSION_LEVEL + "</b> can do this.";
}
}
}
EnableEventFiring();
}
private bool IsUserAllowedToAddRegistrations (SPWeb site) {
SPRoleDefinition registratorRole = site.RoleDefinitions[REGISTRATOR_PERMISSION_LEVEL];
// if not found, there's an error with the setup
if (registratorRole == null)
throw new SPException("Permission level '" + REGISTRATOR_PERMISSION_LEVEL + "' not found. This permission level is created by a Feature. Recreate a new permission level using this same name.");
return site.AllRolesForCurrentUser.Contains(registratorRole);
}
}
}
Note that when the permission level was created, no rights were specified:
registrator.BasePermissions = SPBasePermissions.EmptyMask;
That's because this permission level has a very specific use. This is a good technique to follow; otherwise, it could be inadvertently adding rights to users.
Summary
This chapter covered the various options available to developers in incorporating custom applications into SharePoint sites or building custom applications on top of the SharePoint platform. Developers should consider using SharePoint as an application development platform because it brings so much to the table that would normally have to be created from scratch. Things such as navigation, a granular security model, workflow integration, data storage and CRUD pages, a site provisioning engine, and personalization capabilities—all of this is provided OOTB with the free version of SharePoint: WSS 3.0. Adding MOSS 2007 simply adds additional components that can be utilized in a custom application such as electronic forms (Forms Services) or hooks into other applications (BDC).
Additional Resources
Professional SharePoint 2007 Web Content Management Development: Chapter 14: Authoring Experience Extensibility