Web Parts


The primary task of the SharePoint developer is to build reusable components for business users. Business users in turn will take these components and build applications, customizing the application for the particular business need and personalizing the application for their own working style. This development mindset is in contrast to typical Windows or Web software that is deployed as a complete unit. With WSS technologies, the application is always evolving as business users employ deployed components to build their own applications and workspaces. Web Parts are used within the site to enable further collaboration or integration within the site context.

Web Parts are the fundamental building blocks for SharePoint’s user interface, and with them we can build and integrate many different types of applications. The built-in Web Parts serve as good design examples when writing your own. For example, the Data View Web Part can be configured to point at any data source and display the data in many different ways. In the same way, all of the Web Parts that ship with WSS are generic and built for reuse. Although you may build more specific applications with Web Parts, it is important to be as generic as possible to allow the greatest flexibility for your customer, the business user, who can then configure the Web Part for a specific use.

image from book
Built-in Web Parts

It’s important to note that there are many Web Parts that might meet your business requirements out of the box with WSS, and there are even more available with Microsoft Office SharePoint Server. It’s not always necessary to write code to integrate your application into SharePoint sites, especially for business data. There are many Microsoft Web Parts that can be customized and deployed using only Web Part Description XML files, or used within your own Web Parts as WebControls. The following Web Parts are just a few of those included with WSS:

  • Content Editor Web Part   Use this to display static HTML content using a WYSIWYG editor or to link to a text file.

  • Data View Web Part   Use this to display database, XML, or SharePoint list data with rich design support through Microsoft SharePoint Designer.

  • List View Web Part   Use this to display view list content for any list in the SharePoint site.

  • Image Web Part   Use this to display an image.

  • Members Web Part   Use this to display members of the site.

  • Page Viewer Web Part   Use this to display an existing Web page in an IFrame.

image from book

Web Part Fundamentals

A Web Part is a class that inherits from the WebPart class defined in the System.Web.UI.WebControls.WebParts namespace inside the System.Web assembly. The Web Part is a special type of Web control that is deployable inside of a Web Part Zone control after the initial page has been created and deployed. A Web Part is loosely coupled with the page that it is deployed on, assuming the Web Part infrastructure is in place. (See Chapter 3, “Pages and Design,” for more information on creating custom Web Part pages.) Web Parts interact with the Web Part Manager control, which itself adds and maintains the instances of Web Parts that are added to Web Part zones either at page design time or at run time. The Web Part Manager acts as a director for the page, adding Web Parts to the Web Part zones from the personalization database during the page’s initialization. You can build Web Parts applications outside of SharePoint in traditional ASP.NET applications, but the Web Part framework built into WSS 3.0 contains a rich set of additional functionality including the dynamic SharePoint site model, templating framework, security framework, and Web Part management using the WSS Web Part Gallery. Figure 4-1 shows the inheritance model between SharePoint’s Web Part Zone and Web Part Manager controls and the ASP.NET Web Part framework classes. Note that even though SharePoint contains specific implementations of the Web Part Zone and Web Part Manager, you will use the ASP.NET Web Part class as the base class for your Web Part applications.

image from book
Figure 4-1: SharePoint’s implementation of the Web Part framework

The Web Part Manager class that SharePoint uses is SPWebPartManager, which is a bridge between the page’s Web Part Zone objects and the content database. When you add a Web Part to the page, you are actually adding a serialized instance of the Web Part to the content database. You can also declare default Web Parts in pages added to Modules in Features, as shown in the previous chapter. To write the simplest Web Part, create a class that derives from System.Web.UI.WebControls.WebParts.WebPart. To render simple text or HTML to the page, simply override the RenderContents method by using the same syntax that is used when creating a simple WebControl for an ASP.NET application, as shown in Listing 4-1.

Listing 4-1: A very simple Web Part

image from book
  namespace LitwareWebParts {   // A very simple webpart   public class HelloWebPart : System.Web.UI.WebControls.WebParts.WebPart {     protected override void RenderContents(System.Web.UI.HtmlTextWriter writer) {       writer.Write(string.Format("Hello, {0}!", this.Page.User.Identity.Name));     }   } } 
image from book

Web Parts render inside of chrome. Chrome refers to the common user interface elements, such as a formatted title bar and borders around the Web Part’s body that the framework adds to controls. The chrome adds a bit of style to your Web Part in a way that is consistent with the user interface of the application. The rendering of chrome is handled by the application framework-when this Web Part is deployed in WSS, the resulting display includes the title of the Web Part as well as a drop-down menu with customization options. The Web Part chrome is responsible for rendering common properties and styles as well as menu and administration user interface elements. For an example of default chrome added to the HelloWebPart example, see Figure 4-2. Note that chrome is rendered by the Web Part framework only when Web Parts are deployed within Web Part zones, and it is not rendered when the Web Part is deployed declaratively as a Web control.

image from book
Figure 4-2: Even the simplest Web Part renders chrome.

Because the chrome is rendered by the application framework (in our case WSS), applications built from multiple Web Parts are all easily maintained by users of the portal without additional training costs or learning curves. After users learn how to add or remove Web Parts and customize them using SharePoint’s Tool Pane, they will be able to use your application deployed as a Web Part as well.

Web Control Basics

Because Web Parts are WebControl classes, a basic understanding of the Web control is fundamental to programming Web Parts. Web controls are also the UI components that you will be using within your Web Part solutions, whether you write your own or use common ASP.NET or SharePoint Web controls such as the SPGridView control. Figure 4-3 displays the Web Part class model-the Web Part derives from the Panel Web control and implements the IWebEditable interface, which enables deployment and customization with the Web Part framework. You also can use Web Parts as controls, including using Microsoft’s WSS Web Parts in your own custom Web Part.

image from book
Figure 4-3: The ASP.NET Web Part class model

Note 

Web Parts for SharePoint deployment derive from the System.Web.UI.WebParts.WebPart class. Although in ASP.NET 2.0 you can add a User Control (.ascx file) as a Web Part (using the GenericWebPart wrapper), the GenericWebPart is not deployable to SharePoint. Therefore, SharePoint is more restrictive than ASP.NET 2.0 in that you cannot directly use User Controls as Web Parts. Instead, you must use an indirect technique in which you write wrapper Web Parts to dynamically load and host User Controls. Read about the Smart Part (http://www.SmartPart.info) made famous by Jan Tielens if you want to see just how far you can take this idea.

There are a few methods that are crucial to understand in the control life cycle. OnLoad is used to initialize the control but is not intended for loading data or other processing functionality. OnPreRender is used to initiate any long-running processes such as database retrieval or Web service calls by initiating asynchronous calls. The Page class also fires a PreRenderComplete event after all of the page’s prerender tasks are completed. If you are using controls for your display, the only method you’ll need to implement for UI components is CreateChildControls. Controls added to the controls collection of the Web control will render from the framework, and so there is typically no need to override any render methods unless you need to directly use the HtmlTextWriter class. If you do need to render your own controls, you can do so in the RenderContents method, which will get rendered inside of the Web control chrome. You may want to render your own controls in order to display them in a table or other HTML layout. To receive postback data from an included control, you will need to create a private reference of your control and create it in the CreateChildControls method. Before accessing the control, you can ensure it is created by first calling the EnsureChildControls method. Table 4-1 lists the Web Part life cycle in general order of execution, including the standard methods and events to use when building Web Parts. There are some methods, however, that are not guaranteed to execute in order. These include CreateChildControls, EnsureChildControls, and methods marked with the ConnectionConsumer and ConnectionProvider attributes. Before accessing the properties of any composite controls, the EnsureChildControls method must be called to prevent null reference exceptions.

Table 4-1: The Web Part Life Cycle
Open table as spreadsheet

Method/Event

Description

OnInit

Handles initialization of the control.

OnLoad

Handles the Load event.

CreateChildControls

Creates any child controls that are used as part of a composite control.

EnsureChildControls

Ensures that CreateChildControls has executed. Use this to ensure that a control you are referencing exists before accessing its data.

OnPreRender

Handles or initiates tasks such as data loading that must complete before the control can render. Asynchronous page tasks should be started from this method.

Page.PreRenderComplete

The page fires the PreRenderComplete event after all controls have completed their OnPreRender methods and the page has completed asynchronous tasks.

Render

Renders the entire control, including the outer tags and Web Part chrome.

RenderContents

Renders the contents of the control only, inside of the outer tags and style properties.

SharePoint Development Versus ASP.NET Development

Because SharePoint applications run in a virtualized site context using the content database, it is important to note several significant differences from traditional ASP.NET development, as well as some ASP.NET Framework requirements that may not be quite obvious. Web Parts that are added to pages within Web Part zones exist entirely in the content database, which means that they are processed through SharePoint’s Safe Mode Parser (see below). Web Parts are also added to the page later in the page’s life cycle than declarative controls. Web Part applications written for WSS should exist entirely in a compiled assembly, without additional dynamically compiled code files in the app_code directory as traditional ASP.NET 2.0 applications permit. Web Part applications should be as autonomous as possible, and they should contain all of the required resources such as images and scripts compiled into the assembly as resources and included through the Client Script Manager. Finally, Web Part applications for WSS will run under specific security settings and should be deployed through SharePoint Solution packages to correctly specify trust settings for deployment. While in development, you will most likely need to escalate the trust level in the site’s web.config file to WSS_Medium or Full. We will talk about trust level and security policy in more detail in Chapter 9, “Solutions and Deployment.”

Tip 

Security Exceptions that result from running under a Code Access Security policy that is too restrictive are a common point of failure in Web Part applications.

image from book
The Safe Mode Parser

Code deployed through the Content Database is run through the Safe Mode Parser. SharePoint’s Safe Mode Parser ensures that only code that has been configured as trusted can be dynamically deployed to the SharePoint server. Without the Safe Mode Parser, users could write and deploy malicious code in dynamic Web pages created as documents, posing a threat to the data integrity and security of the SharePoint site. To deploy code that does not run through the Safe Mode Parser requires physical access to the Web Server’s file system, such as when one is deploying a custom application page, as described in Chapter 2. The Safe Mode Parser guarantees that code executed within the SharePoint application has been allowed by the server administrator.

image from book

Web Parts were first introduced in WSS 2.0 as the next version of Microsoft’s Dashboard technology. ASP.NET 2.0 includes a new version of the Web Part framework that can run outside of WSS using standard ASP.NET components. The WSS 3.0 Web Part framework is built on this same Web Part framework. To write a Web Part for SharePoint, you will derive from the ASP.NET Web Part class System.Web.UI.WebControls.WebParts.WebPart. WSS 3.0 includes the Web Part class Microsoft.SharePoint.WebPartPages.WebPart for backward compatibility purposes, and this Web Part should be used only when migrating existing code from WSS 2.0. This Web Part derives from the ASP.NET WebPart class and includes multiple compatibility layers to simplify code migration. But it should not be used for new development. Table 4-2 lists the ASP.NET Web Part properties, methods, and components, along with the backward-compatible property, method, or component in the SharePoint.WebPartPages namespace. Another difference to note is that ASP.NET Web Parts and SharePoint backward-compatible Web Parts have different serialization formats, making a change in the base class a breaking change for deployed parts.

Table 4-2: WSS Backward-Compatibility Comparison Table
Open table as spreadsheet

ASP.NET Web Parts

SharePoint Backward Compatibility

WebBrowsableAttribute

BrowsableAttribute

WebDisplayName

FriendlyName

WebDescription

Description

Personalizable

WebPartStorage

PersonalizationScope

Storage

EditorPart

ToolPart

EditorPartCollection

ToolPart[]

CreateEditorParts()

GetToolParts()

RenderContents()

RenderWebPart()

SetPersonalizationDirty()

SaveProperties

Developing Web Parts for WSS 3.0

To develop a Web Part application for WSS, create a new class library by using Visual Studio 2005 and add a reference to System.Web. To use the SharePoint site object model (including security and authorization), add a reference to the primary WSS assembly Microsoft .SharePoint.dll and Microsoft.SharePoint.Security.dll. To deploy your Web Part assembly with a strong name to the bin directory, you will also need to include the assembly attribute AllowPartiallyTrustedCallers. This attribute must be included once in the assembly and is typically included in the AssemblyInfo code file.

 [assembly: System.Security.AllowPartiallyTrustedCallers]

To create your first Web Part, simply create a class that derives from WebPart and override the CreateChildControls method. Because the ASP.NET Framework will automatically render the controls in the collection, adding controls and handlers in this method is usually all you need to do to build your user interface. In the following examples, we will create a simple RSS Viewer Web Part along with an RSS feed picker Web Part and a custom Editor Part. The code in Listing 4-2 demonstrates the simplest “HelloWorld” Web Part, which we will eventually turn into our RSS Viewer Web Part. Even though your final Web Part will be complex, it is important to test your Web Part as it is developed, beginning with the simple shell.

Listing 4-2: The first steps in Web Part development

image from book
  Starting Out: An RSS Web Part using System; using System.Web; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; namespace LitwareWebParts {     public class RssViewWebPart : WebPart {         protected override void CreateChildControls() {             base.CreateChildControls();             this.Controls.Add(new LiteralControl("Hello, world!"));         }     } } 
image from book

To deploy the Web Part to your WSS Site, you must then register the control as safe in web.config’s SafeControls node, as described in Chapter 3. Assuming the project name LitwareWebParts, the following entry in the SafeControls node in web.config will register the assembly and namespace as safe.

 <SafeControl Assembly="LitwareWebParts" Namespace="LitwareWebParts"     TypeName="*" Safe="True" />

Note that for this example, we are not strong-naming the assembly, and therefore we can use the simple name of the assembly. When you strong-name the assembly, you should always register the SafeControl entry using the full four-part assembly name.

Next, you will need to compile the assembly dynamic-link library (DLL) and copy it to your SharePoint Web application’s bin directory. Many developers like to do this as a post-build event in Visual Studio, or as a custom-build task using MSBuild, as in the following example.

 xcopy /Y *.* C:\Inetpub\LitwareWebApp\bin\*.*

After adding the SafeControls entry, the next step is to test your Web Part by adding it to the gallery and a Web Part page. There are many ways to deploy a Web Part, but during development you will do this manually most of the time. To add your Web Part to the gallery, navigate to the following URL on your WSS site: http://localhost/_catalogs/wp. This gallery is also available from each top level site’s Site Settings page. From this gallery, click the New button to get to the page http://localhost/_layouts/NewDwp.aspx. This page will list all Web Parts that are available either in the bin directory or global assembly cache and that are registered in this Web application’s Safe Controls entries. If you don’t see your Web Part on this page, take some time to troubleshoot. For example, ensure that your Web Part is public, derives from the WebPart class, is correctly registered as safe with the full namespace and proper assembly name, and is deployed to the right directory. If your Web Part assembly is signed, be sure that the correct strong name is referenced in the SafeControls entry. Figure 4-4 displays the new Web Part page where you can create Web Part Gallery entries using the Web interface.

image from book
Figure 4-4: The New Web Parts page enables you to discover Web Parts that have been marked as safe and to add these Web Parts to the Web Part Gallery for the current site collection.

Tip 

The Web Part Gallery is a special type of document library that includes XML files that contain serialized Web Parts. Web Parts that are serialized using the new ASP.NET format have a .webpart extension, whereas backward-compatible Web Parts are serialized using the older WSS 2.0 format and have a .dwp extension.

When you select a Web Part on the New Web Parts page and click the Populate Gallery button, WSS generates a new entry in the site collection’s Web Part Gallery. After this, the Web Part will be available to all pages, and you can access it with the Add A Web Part option from the Edit Page command. After the Web Part is added to the page, you can export it to a .webpart XML file. This is especially useful if you want to set default properties for deployment. It is this XML content that you can use when adding your Web Part to a site or feature definition’s pages, as well as the XML that will be saved in the content database to serialize your Web Part. You also will want to add this XML file to your Visual Studio project and source control repository so that you can create a feature to automatically import these Web Part Gallery entries. Listing 4-3 displays an example Web Part file from our basic RSS View Web Part.

Listing 4-3: An example Web Part description file

image from book
  An Example Web Part File <webParts>   <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">     <metaData>       <type name="LitwareWebParts.RssViewWebPart" />       <importErrorMessage>Cannot import this Web Part.</importErrorMessage>     </metaData>     <data>       <properties>         <!-- standard Web Part properties -->         <property name="ChromeType" type="chrometype">Default</property>         <property name="Title" type="string">Litware RSS View Web Part</property>         <property name="Description" type="string">Use to ... </property>         <property name="CatalogIconImageUrl" type="string">           /_layouts/images/msxmll.gif         </property>         <property name="AllowConnect" type="bool">True</property>         <property name="ExportMode" type="exportmode">All</property>         <!-- custom Web Part properties -->         <property name="XmlUrl" type="string">           http://blogs.msdn.com/MainFeed.aspx?Type=AllBlogs         </property>       </properties>     </data>   </webPart> </webParts> 
image from book

Creating a Feature for Importing Web Parts

When you develop Web Parts for distribution, we recommend that you create a feature designed to import your .webpart files into the Web Part Gallery. Because there is only one Web Part Gallery per site collection, a feature created to import .webpart files should be scoped at the site collection level instead of being scoped at the site level. Following is an example of the feature.xml file for a feature named LitwareWebParts that is part of the Visual Studio project that accompanies this chapter.

 <Feature   Id=""   Title="Chapter 4: A demo feature to deploy Litware Web Parts"   Description="Demo from Inside Windows SharePoint Services (Pattison/Larson)"   Hidden="FALSE"   Scope="Site"   ImageUrl="actionssettings.gif"   xmlns="http://schemas.microsoft.com/sharepoint/">   <ElementManifests>     <ElementManifest Location="elements.xml"/>   </ElementManifests> </Feature>

When it’s time to deploy a feature for importing Web Parts, the actual .webpart files will need to be copied to the file system of the front end Web server. Therefore, you should copy your .webpart files inside the feature directory itself. In the LitwareWebParts feature, we have followed a common Microsoft convention and placed our .webpart files inside the feature directory in an inner directory named DWP.

After you have copied your .webpart files into the feature directory, update the elements.xml file by adding File elements within a Module element, as in the following example. This will copy the files into the Web Part Gallery of the current site collection during feature activation. Note that when targeting the Web Part Gallery, a Module element should be defined with a list type of 113, a Url value of "_catalogs/wp" and a RootWebOnly setting of true.

 <Elements xmlns="http://schemas.microsoft.com/sharepoint/">   <Module Name="LitwareWebParts" Path="dwp"           List="113" Url="_catalogs/wp" RootWebOnly="true">     <File Url="HelloWebPart.webpart" Type="GhostableInLibrary" >       <Property Name="Group" Value="Litware Web Parts" />     </File>     <File Url="RssViewWebPart.webpart" Type="GhostableInLibrary" >       <Property Name="Group" Value="Litware Web Parts" />     </File>     <File Url="FeedListWebPart.webpart" Type="GhostableInLibrary" >       <Property Name="Group" Value="Litware Web Parts" />     </File>   </Module> </Elements>

In the listing of the elements.xml file you have just seen, you should notice that the File element for each .webpart file contains an inner Property element that adds a Group property with a value of "Litware Web Parts." The Group property value must be set inside the File element in this fashion. This can be a bit confusing at first, because you cannot assign a Group property value inside the .webpart file itself as you can with other Web Part properties, such as Title and Description. However, adding a Group property value is helpful to users, because it enables your Web Parts to appear in custom groups in the standard WSS Add Web Parts dialog box, as shown in Figure 4-5.

image from book
Figure 4-5: After you use a feature to import a Web Part to the Web Part Gallery, a user can add it easily to Web Part pages.

You have just seen how to create a feature to import .webpart files into the Web Part Gallery, but this does not complete the story of how you deploy Web Parts into a production environment. You will have to wait until Chapter 9 to learn more, when we introduce solution packages and fill in the rest of the pieces. As you will see, a solution package can automatically deploy Web Part assembly DLLs and accompanying features to import their .webpart files. A solution package also can be used to deploy other related Web Part files, such as User Controls, and to add the required SafeControl entries to all the necessary web.config files on the various front-end Web servers in the farm. We also will use the Solution Package in Chapter 9 for deploying required Code Access Security policies and configurations required for our Web Parts to run in a partially trusted environment. Further changes to the web.config file can be made with Feature receivers deployed in the solution using the SPWebConfigModification class.

Debugging Web Parts

Debugging Web Parts is an essential skill, but fortunately, it is not too different from debugging traditional ASP.NET applications. The main difference is that you will use Visual Studio to attach to the w3wp.exe process running on Windows Server rather than pressing F5. To attach to the process using Visual Studio 2005, press Ctrl+Alt+P or choose Attach To Process from the Debug menu. When developing and debugging Web Parts for WSS, it is important to run Windows Server and have WSS running on your development machine with Visual Studio installed. A post-build event can be used to copy the .dll and .pdb files to the bin directory, after which you can attach the debugger. There are many things that can go wrong in a Web Part application for SharePoint, and stepping through the code as it runs is an everyday task.

Tip 

If a server environment is not available as your desktop operating system, Microsoft Virtual PC or Microsoft Virtual Server can be a viable alternative for development tasks, although you will notice a significant performance hit.

Customization and Personalization

Customization and personalization of Web Parts enable your Web Parts to be reused for multiple business applications. Customization refers to a change that is shared by all users of the Web Part instance, whereas personalization is specific to the individual user’s Web Part instance. For example, the site owner may choose a specific news feed to display on a site’s home page. This would be a customization, because the change would be shared by all users of the site. Individual users on the site may choose different rendering formats according to their preferences, which would be an example of personalization. Personalization applies to individual users and is persisted on a per-user basis.

Tip 

The performance cost of personalization in an application is higher than that of customization and should be planned for accordingly.

To add a new custom property, just add a public property and the Personalizable attribute. The WebBrowsable, WebDisplayName, WebDescription, and Category attributes also will be useful in administering the property from the Web Part’s Editor Parts. These attributes are defined in the System.Web.UI.WebControls.WebParts and System.ComponentModel namespaces.

If you create a property and apply the WebBrowsable attribute, the Web Part framework will automatically display this property in a generic Editor Part when the user selects Modify Web Part. An Editor Part is a special type of control that is used only to edit Web Part properties that support customization and personalization. The following demonstration adds a persistent Web Part property named XmlUrl.

 private string xmlUrl; [ Personalizable(PersonalizationScope.Shared),   WebBrowsable(true),   WebDisplayName("Feed Url"),   WebDescription("Set your RSS feed's XML URL here!"),   Category("Configuration")] public string XmlUrl {     get { return xmlUrl; }     set { xmlUrl = value; } }

The Personalizable attribute is the one that instructs the Web Part Manager to persist the XmlUrl property value in the Web Part framework. In WSS, this will be persisted in the content database. Because the personalization scope is set to Shared, the XmlUrl property supports customization on a site-wide basis by the site owner, but it does not support per-user personalization. The other attributes applied to this property specify it should be shown in the generic Editor Part with a specific display name, a description for a tooltip, and a category group. Without any additional code, the XmlUrl property is exposed to the SharePoint tool pane through a default Editor Part.

Next, let’s create a second property named HeadlineMode that supports per-user personalization. We will accomplish this by using a personalization scope of User instead of Shared. This is done so that our Web Part can enable each user to switch back and forth between two different supported rendering modes. The HeadlineMode property will be based on the following user-defined enumeration named RenderMode.

 public enum RenderMode {   Full,   Titles }

Once we have defined the RenderMode enumeration, we can define a new personalizable property along with a private field to track its value with the following code.

 private RenderMode headlineMode = RenderMode.Full; [Personalizable(PersonalizationScope.User),   WebBrowsable(true),   WebDisplayName("Headline Mode"),   WebDescription("Do you want to only display headlines?"),   Category("Configuration")] public RenderMode HeadlineMode {   get { return headlineMode; }   set { headlineMode = value; } }

Both the XmlUrl property and the HeadlineMode property have been defined with the WebBrowsable attribute. Using this approach, you will not be required to create a custom Editor Part, because WSS will display these properties automatically in the generic Web Part editor, enabling users to change their values. For example, when a site owner selects Modify Shared Web Part on this Web Part, the task pane will display these two properties in the generic Editor Part shown in Figure 4-6. Note that the property based on the RenderMode enumeration is rendered as a drop-down list.

image from book
Figure 4-6: Properties defined with the WebBrowsable attribute are exposed automatically in the standard WSS Editor Part.

One more thing to keep in mind is that the RenderMode property is defined with a personalization scope of User, whereas the XmlUrl property is defined with a personalization scope of Shared. When a user goes into personalization mode using the Personalize This Page menu item from the top-level welcome menu, the user will not be able to see or edit the XmlUrl property. It will only be displayed for editing in Shared Mode and not User Mode.

Creating Custom Editor Parts

For some Web Parts, you will find that it is sufficient to define persistent properties with the WebBrowsable attribute so that WSS automatically displays them for editing in the generic Editor Part. However, this will not be sufficient for all scenarios. There will be times when you will want to create your own custom Editor Parts for the tool pane. This section will step through what’s required to accomplish this.

To create a custom Editor Part, you must create a new class that inherits from the EditorPart class defined inside the System.Web.UI.WebControls.WebParts namespace. In our example, we will create a new EditorPart-derived class named RssViewEditorPart to serve as a custom Editor Part of the RssViewWebPart class.

Note that in addition to creating the new RssViewEditorPart class, we must also make a few changes to the RssViewWebPart class. First, we will modify the Personalizable properties so that they do not appear in the generic Editor Part. This can be done by applying the WebBrowsable attribute with a value of false.

 [Personalizable(PersonalizationScope.Shared),   WebBrowsable(false)] public string XmlUrl {   get { return xmlUrl; }   set { xmlUrl = value; } }

Next, you must override the CreateEditorParts method within the Web Part class named RssViewWebPart to instruct WSS to load your custom Editor Part instead of or in addition to the standard Editor Parts that are normally displayed for Web Parts.

When you override CreateEditorParts, you must work with collection objects of type EditorPartCollection. Once created, an EditorPartCollection object is immutable, but EditorPartCollection has a constructor that takes an existing EditorPartCollection and an ICollection of EditorParts. Therefore, you use the approach of creating a new EditorPartCollection object that contains the standard Editor Parts in addition to your custom Editor Part.

When writing the code to create and initialize an EditorPart object, it is essential to set the ID property. If you forget to do this, you will get an ambiguous exception with very little debugging detail. The following example demonstrates the syntax for overriding the CreateEditorParts method and providing a custom Editor Part that gets loaded along with the standard Editor Parts that are normally displayed to the user.

 public override EditorPartCollection CreateEditorParts() {   List<EditorPart> editorParts = new List<EditorPart>(1);   EditorPart part = new RssViewEditorPart();   part.ID = this.ID + "_rssViewEditor";   editorParts.Add(part);   EditorPartCollection baseParts = base.CreateEditorParts();   return new EditorPartCollection(baseParts, editorParts); }

Now let’s look at the details of creating the RssViewEditorPart class, which involves creating a new public class that derives from the EditorPart class. As in the case of creating a WebPart class, you add controls to an Editor Part by overriding the CreateChildControls method. When you intialize the controls in an Editor Part, it is common to use the current Web Part property values. The Web Part that is being edited by your Editor Part is available using a property named WebPartToEdit.

There are two important abstract methods to implement in the EditorPart class: they are named ApplyChanges and SyncChanges. These methods will set properties of the Web Part or set properties of the Editor Part from the Web Part. The ApplyChanges method will take input from the custom Editor Part and apply it to properties in the Web Part. The SyncChanges method will take settings from the Web Part and apply them to Editor Part input control values. When loading the Editor Part, it is the SyncChanges method (not OnLoad or CreateChildControls) that will apply the initial values to the Editor Part. Listing 4-4 demonstrates a simple custom Editor Part for the RSS View Web Part. For larger applications, you will want to create Editor Parts based on interfaces for common Web Part properties. Note that within the Editor Part, you will need to check the personalization scope to enable or disable edits based on the current personalization scope. The user either will be editing shared Web Part customizations or Web Part personalizations, in which case the WebPartManager’s PersonalizationScope will be set to PersonalizationScope.User.

Tip 

When implementing a custom Editor Part for a property, you should also remove the WebBrowsable attribute from the property to remove the property from the default Editor Parts.

Listing 4-4: A custom Editor Part for updating RssViewWebPart property values

image from book
  Example Editor Part: RssViewEditorPart public class RssViewEditorPart : EditorPart {   TextBox txtXmlUrl;   RadioButtonList lstHeadlineMode;   protected override void CreateChildControls() {     Controls.Add(new LiteralControl("Feed Url:<br/>"));     txtXmlUrl = new TextBox();     txtXmlUrl.Width = new Unit("100%");     txtXmlUrl.TextMode = TextBoxMode.MultiLine;     txtXmlUrl.Rows = 3;     // mark XmlUrl Textbox read-only in personalization mode     if (WebPartManager.Personalization.Scope == PersonalizationScope.User)       txtXmlUrl.Enabled = false;     this.Controls.Add(txtXmlUrl);     Controls.Add(new LiteralControl("Headline Model:<br/>"));     lstHeadlineMode = new RadioButtonList();     lstHeadlineMode.Items.Add(RenderMode.Full.ToString());     lstHeadlineMode.Items.Add(RenderMode.Titles.ToString());     this.Controls.Add(lstHeadlineMode);   }   public override void SyncChanges() {     this.EnsureChildControls();     RssViewWebPart sourcePart = (RssViewWebPart)this.WebPartToEdit;     string SelectedMode = sourcePart.HeadlineMode.ToString();     lstHeadlineMode.Items.FindByText(SelectedMode).Selected = true;     txtXmlUrl.Text = sourcePart.XmlUrl;   }   public override bool ApplyChanges() {     this.EnsureChildControls();     RssViewWebPart targetPart = (RssViewWebPart)this.WebPartToEdit;     targetPart.XmlUrl = txtXmlUrl.Text;     if(lstHeadlineMode.SelectedValue.Equals("Full"))       targetPart.HeadlineMode = RenderMode.Full;     else       targetPart.HeadlineMode = RenderMode.Titles;     return true;   } }  
image from book

Figure 4-7 shows the custom Editor Part that provides the user with a richer editing experience via a multiline TextBox and a RadioButtonList control. You should also take note that the implementation of the custom Editor Part has been written to distinguish between different modes in which the personalization scope is either Shared or User. When the Editor Part determines that the user is editing the Web Part with a personalization scope of User, it sets the Enabled property of the TextBox named txtXmlUrl to false so that the user cannot edit its value. The key observation is that when you create a custom Editor Part, you are responsible for knowing which properties support customization but not personalization and making sure you don’t allow users to make edits to these properties while in User personalization mode.

image from book
Figure 4-7: A custom Editor Part permits you to take over the user interface in the task pane that enables users to customize and personalize your persistent Web Part properties.

Asynchronous Web Part Processing

Web Part applications usually are deployed on pages with multiple Web Parts. Furthermore, you will not know how many and what type of Web Parts are running on the page, and in some cases several “expensive” Web Parts may coexist on the same page. Because of this, it is important that Web Parts process their data as efficiently as possible. For example, a database call, an LDAP call, and several Web Service calls in multiple Web Parts can add up to a slow page. Rather than having the page load at the combined processing time of all operations, asynchronous page processing lets the page load in the time of the longest operation (or the longest operation that has timed out). Although asynchronous page tasks are not exclusively a Web Part technology, they are perhaps most applicable in Web Part applications due to the composite nature of Web Part pages.

With ASP.NET 2.0, the Page class can register asynchronous tasks by using the RegisterAsyncTask method, in which multiple processes can occur at the same time. This not only decreases the page load time but also frees up the worker process threads of IIS, which are a limited resource on your server. Because they are limited resources, we don’t want to tie them up while waiting on long-running operations, but instead we want to return them for other uses when we’re not using them. With asynchronous page tasks, the IIS worker thread is returned to the thread pool (where it can serve other requests if needed) while the page is waiting for the asynchronous tasks to complete. In the example RSS View Web Part, an asynchronous task can be used to fetch the remote XML data, which will free the IIS worker thread while it is waiting for a response from the remote server. Before initiating the remote call, it is also a good idea to verify that the page is not in design mode, which can be done with a simple check of the Web Part Manager’s Display Mode. Classes that implement the IAsyncResult design pattern work well with asynchronous tasks and are a good design pattern for your own middle tier code. Some examples of these classes include ADO.NET database classes, Web Requests, and Web Service Proxies. Listing 4-5 demonstrates the asynchronous task for retrieving remote data using the WebRequest class. Listing 4-6 is a simple XSLT file for an RSS Web Part application that will transform the output by using the XslCompiledTransform class. This file is compiled in as an embedded resource in this example, with the full name LitwareWebParts.Resources.RSS.xslt. (It is available as a resource stream by using the WebPartResources utility class in Listing 4-13.) You also will find an alternate XSLT file for transforming the feed into linked titles only when set to RenderMode.Titles in this chapter’s code samples.

Listing 4-5: An Asynchronous RSS Web Part using Page.RegisterAsyncTask

image from book
  RSS Viewer with Page.RegisterAsyncTask using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; using System.Reflection; namespace LitwareWebParts {   public class RssViewWebPart : WebPart, IWebEditable {     // xmlUrl and RenderMode properties omitted for clarity     private Stream xmlResponseStream = null;     private WebRequest xmlReq;     // Handle any prerender tasks including async operation initiation     protected override void OnPreRender(EventArgs e) {       base.OnPreRender(e);       if (string.IsNullOrEmpty(this.xmlUrl))         return;       // Check to see if we're in design mode and if so skip request       if (this.WebPartManager.DisplayMode.AllowPageDesign) {         this.Controls.Add(new LiteralControl("No display while in design mode."));         return;       }       try {         Uri xmlUri = new Uri(this.xmlUrl);         xmlReq = WebRequest.CreateDefault(xmlUri);         xmlReq.Credentials = CredentialCache.DefaultCredentials;         xmlReq.Timeout = 10000; // 10 seconds timeout         this.Page.RegisterAsyncTask(             new PageAsyncTask(new BeginEventHandler(BeginXmlRequest),                               new EndEventHandler(EndXmlRequest),                               new EndEventHandler(XmlRequestTimeout),                               null, true)             );       } catch (System.Security.SecurityException) {         this.Controls.Add(           new LiteralControl("Permission denied - set trust level to WSS_Medium."));       }     }     IAsyncResult BeginXmlRequest(object src, EventArgs args,                                  AsyncCallback callback, object state) {       return this.xmlReq.BeginGetResponse(callback, state);     }     void XmlRequestTimeout(IAsyncResult ar) {       Label timeoutLabel = new Label();       timeoutLabel.Text = string.Format(           "The request timed out while waiting for {0}.", this.XmlUrl);       this.Controls.Add(timeoutLabel);     }     void EndXmlRequest(IAsyncResult ar) {       WebResponse response = this.xmlReq.EndGetResponse(ar);       this.xmlResponseStream = response.GetResponseStream();     }     protected override void RenderContents(HtmlTextWriter writer) {       base.RenderContents(writer);       if (string.IsNullOrEmpty(this.xmlUrl) || this.xmlResponseStream == null)         return;       XslCompiledTransform transform = new XslCompiledTransform();       string xslt;       if (this.HeadlineMode == RenderMode.Full)         xslt = @"Resources.RSS.xslt";       else         xslt = @"Resources.RssTitles.xslt";       string resourceName = @"LitwareWebParts" + xslt;       using (Stream res =           Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {           using (XmlTextReader stylesheet = new XmlTextReader(res)) {             transform.Load(stylesheet);           }       }       try {         using (XmlReader reader = new XmlTextReader(this.xmlResponseStream)) {           XmlTextWriter results = new XmlTextWriter(writer.InnerWriter);           transform.Transform(reader, results);           reader.Close();         }       } catch (Exception ex) {         writer.Write(ex.Message);         if (this.xmlResponseStream != null) {           this.xmlResponseStream.Close();           this.xmlResponseStream.Dispose();         }       }     }   } } 
image from book

Listing 4-6: A simple XSLT resource for transforming RSS XML

image from book
  RSS XSLT Transform <?xml version='1.0' encoding='UTF-8'?> <xsl:stylesheet   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   xmlns:dc="http://purl.org/dc/elements/1.1/"   version="1.0" >   <xsl:output omit-xml-declaration="yes" method="html" encoding="utf-16" />   <xsl:template match='/rss'>     <h3>       <xsl:value-of select='channel/title'/>     </h3>     <xsl:apply-templates select='channel/item' />   </xsl:template>   <xsl:template match='item'>     <div style='margin:6px;'>       <strong>         <a href='{link}'>           <xsl:value-of select='title'/>         </a>       </strong><br/>       <xsl:value-of select='description' disable-output-escaping='yes' />       <xsl:text disable-output-escaping='yes'>&amp;nbsp;</xsl:text>       <a href='{link}'>Read the full item</a>.<br />       <xsl:if test='dc:creator'>         <strong>Author: </strong><xsl:value-of select='dc:creator' />         <br/>       </xsl:if>       <strong>Published Date: </strong><xsl:value-of select='pubDate' /><br/>       <font color='gray'>         <xsl:for-each select='category'>           <xsl:value-of select='.' /> |         </xsl:for-each>       </font>       <br />     </div>   </xsl:template>   <xsl:template match='category'>     <xsl:value-of select='.'/> |   </xsl:template> </xsl:stylesheet> 
image from book

Listing 4-13: A utility class for resource access

image from book
  Getting Resources from the Assembly using System; using System.Reflection; using System.IO; internal static class WebPartResources {   internal static string GetNamedResource(object reference, string fileName) {     Assembly thisApp = Assembly.GetExecutingAssembly();     string resource = null;     string resourceName = string.Format(@"{0}.{1}.{2}",                                         thisApp.GetName().Name,                                         reference.GetType().Namespace,                                         fileName);     using (Stream resStream = thisApp.GetManifestResourceStream(resourceName)) {       using (StreamReader reader = new StreamReader(resStream)) {         resource = reader.ReadToEnd();       }       resStream.Close();     }     return resource;   }   internal static Stream GetNamedResourceStream(object reference, string fileName) {     Assembly thisApp = Assembly.GetExecutingAssembly();     string resourceName = string.Format(@"{0}.{1}",                                         reference.GetType().Namespace,                                         fileName);     return thisApp.GetManifestResourceStream(resourceName);   } } 
image from book

image from book
RSS and Syndicated Data

RSS (Really Simple Syndication) is a standard XML syndication format for news feeds, blogs, WSS lists, and enterprise data streams. Although it is a great way to incorporate external data into your portal, it is also a security risk that you should limit to trusted sites. Because the external HTML and script can be rendered in your portal page, which is a trusted site for most of your users, be careful what feeds you enable.

RSS is also a format that fits well with list data. Because SharePoint stores its data in lists, RSS is a natural fit, and it is a built-in component for WSS 3.0. The application page /_layouts/listfeed.aspx will serve any list as a security trimmed RSS feed when provided with the list’s GUID as a parameter.

RSS can also be extended with common extensions such as Simple List Extensions and custom SharePoint extensions so that remote clients and aggregators can handle specific list types as strongly typed lists. For more information on the RSS specification, see http://blogs.law.harvard.edu/tech/rss.

image from book

Web Part Building Blocks

When creating your Web Parts, it’s generally a good design principle to aggregate controls, resources, and business logic classes. You’ve already seen the use of resources using an XSLT file embedded as an assembly resource for the RSS Web Part. You also may want to use resources for static HTML, JavaScript, or images. In addition, you may want to use User Controls if you have existing applications, as well as using XML data sources such as RSS feeds or Web Service endpoints. In the next section, we will look at additional Web Part building blocks for composite Web Part applications.

Using User Controls in Web Parts

Sometimes you may have an existing Web page application that needs to be ported to SharePoint. It is relatively simple to do this by converting the pages to User Controls. Although User Controls aren’t recommended for all Web Part applications, they can be a quick way to get an ASP.NET application deployed into your SharePoint Web sites. User Controls deployed in SharePoint either must inherit from the UserControl class or from a UserControl-derived class that is available to the Web application (through either the bin directory or the GAC) and must exist in the same IIS Web application. Web Part code then can load the control and add it to your Web Part using the method Page.LoadControl. Since your User Control will exist on the file system and not in the content database, it can contain inline code if needed, since it isn’t processed through the Safe Mode Parser. Note that you cannot deploy User Controls through the content database. Listing 4-7 demonstrates a User Control Host Web Part. You only have to load the control and add it to the controls collection, and the Control framework will render it.

Listing 4-7: An example Web Part that loads a User Control

image from book
  User Control Host Web Part using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; namespace LitwareWebParts {   public class UserControlHost : WebPart {     protected Control userControl;     protected override void CreateChildControls() {       this.Controls.Clear();       string userControlPath =              @"/_controltemplates/Litware/LitwareUserControl.ascx";       this.userControl = this.Page.LoadControl(userControlPath);       this.Controls.Add(this.userControl);     }   } } 
image from book

Tip 

Although User Controls can be deployed anywhere within the IIS Web application (on the physical file system), the preferred deployment path for WSS 3.0 is a subdirectory of the _ControlTemplates directory, located in %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES. You could build a custom Editor Part to let users choose from available User Controls in a specific directory.

Using SharePoint’s SPGridView Control

SharePoint has some very useful controls defined in the Microsoft.SharePoint.WebControls namespace that extend common ASP.NET controls with the SharePoint functionality and look and feel. Perhaps the most useful Web control is SPGridView. The SPGridView control extends the ASP.NET GridView class with SharePoint’s style declarations and has additional support for WSS data sources such as Lists and Cross-Site Queries using the SPDataSource class. In our next example, we’ll utilize the SharePoint site context to determine a list of feed URLs within the SharePoint site and its child sites. We’ll expose the feed list through the SPGridView, from which we will later add a connection to our RSS View Web Part. The SPGridView control also can be bound to an SPDataSource object, which can wrap a list as a data source. For this example, we will use a DataTable object constructed of lists from aggregated SharePoint sites. Because this example lists available feeds, we will skip lists that are not news-based, such as the catalogs, galleries, and categories lists. Inspect the code in Listing 4-8 to see how the SPGridView control is populated using data about specific lists inside the current site.

Listing 4-8: A feed list Web Part using the SPGridView control

image from book
  Feed List Web Part Using SPGridView public class FeedListWebPart : WebPart {   private SPGridView listsView;   private DataTable dt;   protected override void CreateChildControls() {     List<SPList> lists = new List<SPList>();     AddLists(lists, SPContext.Current.Web);     listsView = new SPGridView();     listsView.AutoGenerateColumns = false;     this.Controls.Add(listsView);     BoundField colTitle = new BoundField();     colTitle.DataField = "Title";     colTitle.HeaderText = "Title";     listsView.Columns.Add(colTitle);     BoundField colXml = new BoundField();     colXml.DataField = "ItemCount";     colXml.HeaderText = "Item Count";     listsView.Columns.Add(colXml);     CommandField colSelectButton = new CommandField();     colSelectButton.HeaderText = "Action";     colSelectButton.ControlStyle.Width = new Unit(75);     colSelectButton.SelectText = "Show RSS";     colSelectButton.ShowSelectButton = true;     listsView.Columns.Add(colSelectButton);     listsView.SelectedIndexChanged += new EventHandler(view_SelectedIndexChanged);     if (!this.Page.IsPostBack) {       dt = new DataTable();       dt.Columns.Add("Title");       dt.Columns.Add("ItemCount");       dt.Columns.Add("XmlUrl");       dt.Columns.Add("ID");       foreach (SPList list in lists) {         DataRow dr = dt.NewRow();         dr["Title"] = list.Title;         dr["ItemCount"] = list.ItemCount.ToString();         dr["ID"] = list.ID;         string url = this.Page.Request.Url.GetLeftPart(UriPartial.Authority)         + SPUtility.MapWebURLToVirtualServerURL(             list.ParentWeb,             string.Format("{0}/_layouts/listfeed.aspx?List={1}",             list.ParentWebUrl, list.ID.ToString()));         dr["XmlUrl"] = url;         dt.Rows.Add(dr);       }       listsView.DataKeyNames = new string[] { "XmlUrl" };       listsView.DataSource = dt;       listsView.DataBind();     }   }   void view_SelectedIndexChanged(object sender, EventArgs e) {     GridViewRow row = listsView.SelectedRow;     this.xmlUrl = listsView.SelectedValue.ToString();   }   private void AddLists(List<SPList> lists, SPWeb web) {     foreach (SPList list in web.Lists)       if (list.AllowRssFeeds && list.EnableSyndication &&               list.BaseTemplate != SPListTemplateType.Categories &&               list.BaseTemplate != SPListTemplateType.ListTemplateCatalog &&               list.BaseTemplate != SPListTemplateType.MasterPageCatalog &&               list.BaseTemplate != SPListTemplateType.WebPageLibrary &&               list.BaseTemplate != SPListTemplateType.WebPartCatalog &&               list.BaseTemplate != SPListTemplateType.WebTemplateCatalog &&               list.BaseTemplate != SPListTemplateType.UserInformation &&               list.DoesUserHavePermissions(SPBasePermissions.ViewListItems))         lists.Add(list);     foreach (SPWeb subweb in web.Webs) {       if (web.DoesUserHavePermissions(SPBasePermissions.ViewListItems))         AddLists(lists, subweb);     }   } } 
image from book

Web Part Verbs

A Web Part Verb is an action that is rendered in the Web Part menu by the Web Part framework as part of the chrome that is rendered around the control. The action can call a client-side function or a server-side handler. To add Web Part Verbs as menu items, override the Verbs property of the Web Part. The Verbs property returns a read-only WebPartVerbCollection, so you will need to merge a collection of Verbs with the base.Verbs property to create a new WebPartVerbCollection. Listing 4-9 adds Verbs for both a server-side transfer and client-side JavaScript opener to the RSS View Web Part that enables the user to navigate to the source RSS feed. The resulting menu display is shown in Figure 4-8.

Listing 4-9: An example Web Part Verb implementation

image from book
  Using Web Part Verbs for Custom Actions namespace LitwareWebParts {   public class RssViewWebPart : WebPart, IWebEditable {     public override WebPartVerbCollection Verbs {       get {         List<WebPartVerb> verbs = new List<WebPartVerb>();         if (!string.IsNullOrEmpty(this.XmlUrl)) {           WebPartVerb verb1 = new WebPartVerb(this.ID + "_ClientSideRssOpenerVerb",               string.Format("window.open('{0}','RSSXML')", this.XmlUrl));           verb1.Description = "Open RSS Feed in an external window";           verb1.Text = "Open RSS Feed";           verbs.Add(verb1);           WebPartVerb verb2 = new WebPartVerb(this.ID + "_ServerSideRssOpenerVerb",               new WebPartEventHandler(ServerSideVerbHandler));           verb2.Description = "Load the RSS Source Feed.";           verb2.Text = "View RSS Source Feed";           verbs.Add(verb2);         }         WebPartVerbCollection allverbs =             new WebPartVerbCollection(base.Verbs, verbs);         return allverbs;       }     }     public void ServerSideVerbHandler(object sender, WebPartEventArgs e) {       if (!string.IsNullOrEmpty(this.XmlUrl))         Context.Response.Redirect(this.XmlUrl);     }     // (previous code omitted for clarity)     }   } 
image from book

image from book
Figure 4-8: Web Parts support adding custom Verbs to provide extra menu items with either client-side or server-side event handlers.

Web Part Connections

Web Part connections are another Web Part technology that enables reuse among diverse applications. Connections are frequently used for master/detail records and are used in Web Parts for late-bound connections. As long as a Web Part provides data that your Web Part can consume, it can be connected using the Web Part framework.

Tip 

Web Part connections were first introduced with WSS 2.0. The implementation has changed considerably for version 3.0, which provides a much simpler interface using standard ASP.NET interfaces. As with the Microsoft.SharePoint.WebPartPages.WebPart class, the Microsoft.SharePoint.WebPartPages.Communication namespace and interfaces are only provided for backward compatibility and should be avoided for new development. Instead, use the attributes and interfaces defined in System.Web.UI.WebControls.WebParts.

Connections are enabled through the ConnectionProvider attribute. The simplest way to add a connection to your Web Part is by implementing a custom interface such as the ICustomerProvider interface in Listing 4-10 and specifying the data object with the ConnectionProvider attribute. The interface in this example simply defines the CustomerID property that will be provided to the connected Web Part. To connect to the provider Web Part, the consumer Web Part simply marks a connection method with the ConnectionConsumer attribute. Following are the required method signatures for the connection. Note that the ConnectionProvider and ConnectionConsumer attributes have multiple overloads for advanced connection types, although the simple connections demonstrated here will meet most development needs.

Listing 4-10: Example connection provider and consumer Web Parts

image from book
  Example Connection Web Parts using System; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; namespace LitwareWebParts {   public interface ICustomerProvider {     string CustomerID { get; }   }   public class SimpleProviderExample : WebPart, ICustomerProvider {     private string customerID = "P1284";     protected override void RenderContents(HtmlTextWriter writer) {       writer.Write("Customer ID: " + this.CustomerID);     }     public string CustomerID {       get { return this.customerID; }     }     [ConnectionProvider("Customer ID", AllowsMultipleConnections = true)]     public ICustomerProvider GetCustomerProvider() {       return this;     }   }   public class SimpleConsumerExample : WebPart {     private ICustomerProvider customerProvider;     [ConnectionConsumer("Customer ID")]     public void RegisterCustomerProvider(ICustomerProvider provider) {       this.customerProvider = provider;     }     protected override void RenderContents(HtmlTextWriter writer) {       if (this.customerProvider != null)         writer.Write(this.customerProvider.CustomerID);       else         writer.Write("No connection");     }   } } 
image from book

Listing 4-10 demonstrates a connection that uses a known interface for business logic. There are also interfaces defined in the System.Web.UI.WebControls.WebParts namespace for generic connections, for cases in which you may want to pass a field of data between the Web Parts. To do so, you would use the IWebPartField interface. You also can pass a row of data by using the IWebPartRow interface, or you even can pass a table of data by using the IWebPartTable interface. These interfaces are useful for connecting very loosely coupled components and for enabling connections through third-party Web Parts. By implementing the IWebPartField interface in the RSS Web Part, we can receive connections from components we write as well as List View Web Parts for Links Lists.

Tip 

The IWebPartField interface is implemented and connectable by many WSS Web Parts, including the List View Web Parts and the Image Web Part. For example, if your Web Part is connectable to an IWebPartField, you can consume the link from a Links List Web Part.

Listing 4-11 demonstrates a Web Part that implements the IWebPartField interface and provides a connection to the RSS View. This is a modification of the previous Feed List Web Part that will connect an XML URL to the RSS View Web Part. The IWebPartField interface defines the Schema property and the GetFieldValue method, where the Schema property returns reflection information about the parameter, and the GetFieldValue method will be used by a delegate in the consuming Web Part. To complete the connection implementation, a ConnectionProviderAttribute must be applied to a method that will return the provider reference to the consumer. Note that the method that provides the connection is not an interface or base class method, but is instead specified and made available by the ConnectionProvider attribute.

Listing 4-11: Converting the Feed List Web Part to an IFieldProvider

image from book
  The Feed List Web Part as IWebPartField using System; using System.ComponentModel; using System.Web; using System.Web.UI; using System.Web.UI.WebControls.WebParts; using System.Xml.Serialization; using System.Web.UI.WebControls; namespace LitwareWebParts {     public class FeedListWebPart : WebPart, IWebPartField{         /* Previous SPGridView code omitted for clarity */         private string xmlUrl;         [WebBrowsable(true),             Category("Configuration"),             Personalizable(PersonalizationScope.User),             DefaultValue(""),             WebDisplayName("Xml Url"),             WebDescription("RSS Feed XML URL")]         public string XmlUrl {             get { return xmlUrl; }             set {                 if (!string.IsNullOrEmpty(xmlUrl)) {                     Uri xmlUri = new Uri(value);                     xmlUrl = xmlUri.AbsolutePath;                 } else                     xmlUrl = null;             }         }         // Allows the consumer webpart to consume this data         public void GetFieldValue(FieldCallback callback) {             callback(Schema.GetValue(this));         }         // Gets a PropertyDescriptor for this connection         public System.ComponentModel.PropertyDescriptor Schema {             get {                 PropertyDescriptorCollection properties =                     TypeDescriptor.GetProperties(this);                 return properties.Find("XmlUrl", false);             }         }         [ConnectionProvider("XmlUrl Provider")]         public IWebPartField GetConnectionInterface() {             return this;         }     } } 
image from book

Now you will be able to consume this connection in any Web Part by specifying the ConnectionConsumer attribute on the SetConnectionInterface method. Note that the method is not in an interface or base class, but is specified by the attribute. We’ll now add a connection point to the RSS Web Part so that it can receive the connection. The code for enabling the IWebPartField connection is provided in Listing 4-12. Note that there is additional code for handling the comma-separated field value that is provided by the Links List View Web Part, where the Links List provides the link and the description separated by a comma.

Listing 4-12: Enabling a connection to an IFieldProvider

image from book
  Consuming the IWebPartField namespace LitwareWebParts {   public class RssViewWebPart: WebPart, IWebEditable {     /* Previous code omitted for clarity */     // Get the connection     [ConnectionConsumer("Xml URL Consumer", AllowsMultipleConnections = false)]     public void SetConnectionInterface(IWebPartField provider) {       provider.GetFieldValue(new FieldCallback(GetXmlUrl));     }     // A callback method for the IWebPartField's delegate     private void GetXmlUrl(object providedUrl) {       if (providedUrl != null) {         // A workaround for the Url field type         string[] urls = ((string)providedUrl).Split(',');         if (urls.Length > 0)           this.XmlUrl = urls[0];       }     }   } } 
image from book

With the connection code in place, our Web Parts now can be connected. Note that we didn’t have to code them at the same time, and that they didn’t have to know about each other before they were connected. Now our business user can connect the Web Parts at run time, and the user could even connect our Web Part to a third-party Web Part. Note that users must place a Web Part page into Edit Mode in order to create a Web Part connection. The connection can be established using the Connections menu item that appears in the standard Web Part menu, as shown in Figure 4-9.

image from book
Figure 4-9: You can establish a connection between two connectable Web Parts while the hosting Web Part page is in Edit Mode.

Using Resources

Resources are a great way to use static content in your Web Parts if control processing, with its associated overhead, is not needed. It is also a good technique for including static or localized resources without any deployment burdens. One technique for using resources is to load strings for use in the page layout. Another is to make script includes and images available through a URL that is accessible to your Web Part. To include a resource in your Web Part assembly, select the resource you want to include and choose the compile action “Embedded Resource” in the file’s properties. You can access the resource from the assembly by using the resource’s full namespace and file name. Listing 4-13 demonstrates a utility class that can be used to access a resource stream or resource as a string out of the assembly for use in a Literal Control.

This technique is useful for HTML resources within your Web Parts, such as table layouts. For example, imagine you have a complex HTML toolbar that you want included in your control. Instead of coding it using HtmlControls in ASP.NET code, you could design the table in either an HTML file or an .ascx file that is compiled as Toolbar.ascx and then add it as a LiteralControl. When you are developing HTML resources, the .ascx extension provides a better design experience in Visual Studio, but note that you should not include any server code in a resource that is compiled in as an embedded resource. Use the following code to load the text of an embedded resource named Toolbar.ascx out of our assembly’s Resource folder by using the GetNamed Resource method.

 string controlText =     WebPartResources.GetNamedResource("Resources", "Toolbar.ascx");

Another use of resources is to serve images or JavaScript files through the Assembly Resource Handler. The Assembly Resource Handler responds to Web requests mapped to webresource.axd, which is used by the ASP.NET AJAX Toolkit for script resources. With the Assembly Resource Handler, you can include any of the external resources, such as static HTML, JavaScript, or images that are referenced by your Web Part within the Web Part assembly. This enables you to attain the design goal of making the Web Part application as autonomous as possible without linking to external script resources or embedding JavaScript in the C# code.

The Web Resource Handler is an ASP.NET HTTP handler that is particularly useful in a Web Part application in which the Web Part is an independently deployable application. To make resources available through the handler, you will need to mark them with the WebResource attribute defined in the System.Web.UI namespace. You will also need to select the Build Action Embedded Resource in Visual Studio. The WebResource attribute ensures that only resources that are intended to be Web resources are served through the handler. The WebResource attribute will also let you specify a Content Type for the resource, the same way that you can specify a Content Type on a Response stream in any HTTP handler. When using the PerformSubstitution property, the Web resource will be served with replaced resource URLs so that you could specify the URL of another compiled Web resource by using the syntax <%=WebResource(“FullyQualifiedResourceName”)%>. The following attribute specifies the content type and fully qualified resource name for inclusion of the HTML file help.html as a Web resource, which could be used as online help for Litware Web Parts.

 [assembly: WebResource(@"LitwareWebParts.Resources.help.html",      @"text/html")]

When registering scripts through the System.Web.UI.ScriptManager class, the Web browser will access the resource through WebResource.axd or ScriptResource.axd. The generated URL will include a timestamp of the assembly’s last build so that it will be cached appropriately by the browser. You also can include resources in satellite assemblies for culture specific localizations, including localizing JavaScript string resources that will enable you to localize your Web Part applications for international deployment. In the next chapter, as we look at creating ASP.NET AJAX–enabled Web Parts, we will make further use of compiled JavaScript resources.

Working with Web Parts Through the SharePoint Site Model

As with all data within SharePoint, the Web Part framework is exposed through SharePoint’s object model. Web Part instances are exposed through Web Part collections that are available through the SPFile object. From the object model code, you can access the Web Part Manager by using an SPFile object’s GetLimitedWebPartCollectionManager method. This is a different class from the Web Part Manager used by the Web page, which is a control, as seen in Figure 4-10. Instead, the SPLimitedWebPartManager is a resource manager that is used only to manage the site model’s Web Parts. Whereas the SPWebPartManager derives from the WebPartManager class, which derives from Control, the SPLimitedWebPartManager derives from System.Object and contains no control functionality. This same pattern is applied to the SPLimitedWebPartCollection that it exposes as its WebParts property, which is a lightweight class similar to the SPWebPartCollection but is read-only and has limited functionality.

image from book
Figure 4-10: A comparison of the SPLimitedWebPartManager and the SPWebPartManager. For the sake of clarity, not all methods are shown.

Using the SPFile class with a specific URL, you can manipulate the Web Parts in the page’s Web Part zones by using the SPLimitedWebPartManager. For example, on feature activation, you could edit properties of known Web Parts, add Web Parts, and remove other Web Parts.

You could also export Web Parts to their XML definitions and create file modules for use in features and site definitions with embedded Web Part descriptors from existing Web sites. The code in Listing 4-14 demonstrates adding an instance of RssViewWebPart to the default Web page. The SPLimitedWebPartManager can also be used to export Web Parts to XML with the ExportWebPart method. Listing 4-15 demonstrates how to export a Web Part to a file. This method is useful when you want to re-create a page definition in XML for inclusion in a feature definition.

Listing 4-14: Manipulating Web Parts using SharePoint’s site model

image from book
  Using the SPLimitedWebPartManager to Manipulate Web Parts SPSite siteCollection = new SPSite("http://localhost"); SPWeb web = siteCollection.RootWeb; SPFile file = web.Files["default.aspx"]; SPLimitedWebPartManager webPartManager = file.GetLimitedWebPartManager(PersonalizationScope.Shared); WebPart rssPart = new RssViewWebPart(); webPartManager.AddWebPart(rssPart, "Left", 0); 
image from book

Listing 4-15: Exporting Web Parts using SharePoint’s site model

image from book
  Using the SPLimitedWebPartManager to Export Web Parts SPSite siteCollection = new SPSite("http://localhost"); SPWeb web = siteCollection.RootWeb; SPFile file = web.Files["default.aspx"]; SPLimitedWebPartManager webPartManager = file.GetLimitedWebPartManager(PersonalizationScope.Shared); XmlWriter xwriter = new XmlTextWriter("Example.webpart", Encoding.UTF8); if (webPartManager.WebParts.Count > 0)     webPartManager.ExportWebPart(webPartManager.WebParts[0], writer); 
image from book




Inside Microsoft Windows Sharepoint Services Version 3
Inside Microsoft Windows Sharepoint Services Version 3
ISBN: 735623201
EAN: N/A
Year: 2007
Pages: 92

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net