Creating Web Parts that Call Web Services for Microsoft SharePoint Products and Technologies

 

Andy Baron
MCW Technologies, LLC

October 2003

Applies to:
    Microsoft® Windows® SharePoint™ Services
    Microsoft® Visual Studio® .NET
    Microsoft® Office FrontPage® 2003

Summary: This article accompanies a sample Visual Studio .NET solution that contains custom Web Parts written in Microsoft Visual C#. These sample Web Parts, which access Amazon.com Web services, demonstrate how to use asynchronous processing and data caching. In addition, the article shows how to consume Web services without writing any code, by using Microsoft Office FrontPage 2003 to configure a Data View Web Part. (32 printed pages)

Note   The information in this article also applies to Microsoft Office SharePoint Portal Server 2003, which is built on the Windows SharePoint Services platform. The code samples can be used on sites created with SharePoint Portal Server.

Download SharePoint_AmazonWebPartsSample.EXE.

Contents

Prerequisites
Installing the Sample
Configuring Security
Running the Sample Web Parts
Connecting to Web Services Asynchronously
Web Part Data Caching
Handling Shared and Personal Data
A No-Code Alternative: The Data View Web Part
Conclusion

Prerequisites

This article assumes that you have read A Developer's Introduction to Web Parts, or that you already understand the fundamentals of creating, deploying, connecting, securing, and debugging Web Parts.

To create Web Parts that consume Amazon.com Web services, you must first obtain a free Amazon.com developer's token. All messages used to access Amazon.com Web services must contain this token value.

Important   When deploying a production application, you should use your own token, not the one used in the samples accompanying this article.

To obtain your Amazon.com developer's token and to download the free Amazon Web Services Program developer's kit, see the following:

http://www.amazon.com/webservices

To earn money from your Web Part, you can also join the Amazon Associates program. This program pays referral fees based on purchases made during visits to the Amazon site based on the information displayed by your Web Part. To learn more about the Amazon Associates program, see the following:

http://www.amazon.com/associates

The sample solution includes a file named keypair.snk. This file contains a pair of private and public keys, which enable the sample code to be compiled into a strong-named assembly. The file is referenced in the AssemblyKeyFile attribute of the Assemblyinfo.cs file. For production code, you should use your own key pair file or one supplied by your company, not the one supplied with the sample. You can use the Sn.exe command line utility to create a key pair file.

Web Parts depend on Microsoft Windows® SharePoint™ Services, a feature of Microsoft Windows Server™ 2003. You must have access to a computer running Windows Server 2003 to test the sample Web Parts that accompany this article.

Installing the Sample

Download and install the sample files to create a directory named AmazonWebParts. This directory contains a Microsoft Visual Studio® .NET 2002 solution file named AmazonWebParts.sln. Double-click this file to open the solution. If you are using Visual Studio .NET 2003, Visual Studio prompts you to upgrade the solution and projects.

The sample solution contains two projects, named AmazonWebParts and AmazonCab. The AmazonCab project builds a .CAB file named AmazonWebParts.Cab, which you can use to install the sample Web Parts.

To install the sample Web Parts, run the Stsadm.exe command-line tool, which is located in the following directory on your server:

local_drive:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\60\bin

Log on with an account that has administrative rights on the server, open a command prompt, and then run the following command:

stsadm -o addwppack -filename path_to_AmazonWebParts.Cab

There is a debug version of AmazonWebParts.Cab in the Debug subdirectory of the AmazonCab folder, and a release version in the Release subdirectory. The AmazonCab project includes a Manifest.xml document that guides the installation.

To re-install the Web Parts after making changes, add the "-force" flag to the command line, to overwrite the previous copy:

stsadm -o addwppack –force -filename path_to_AmazonWebParts.Cab

You can also use the Stsadm.exe tool to delete the Web Parts by specifying the deletewppack operation and the name of the package to remove:

stsadm -o deletewppack -name AmazonWebParts.cab

Note   If you install the Web Parts without using stsadm.exe and the .Cab file, you must change the output path of the AmazonWebParts project to match your environment:

  1. In the Solution Explorer, select the AmazonWebParts project.
  2. On the Project menu, click AmazonWebParts Properties.
  3. Expand the Configuration Properties folder and then click Build.
  4. In the Output section, in the Output Path box, edit the path as necessary to point to appropriate bin the directory for the output .dll file.

Configuring Security

If the trust level for the virtual server is not set to Full, then you must add permissions for the sample to run successfully. If you are using a beta version with a security level of Full, be aware that the default trust level for the release version of Windows SharePoint Services is set to WSS_Minimal.

To check or adjust the trust level, open the Web.config file for the SharePoint site. The default location for this configuration file is:

local_drive:\Inetpub\wwwroot\Web.config

The security level is determined by the value of the level attribute of the trust element, which is found in the system.web section of the configuration file:

<trust level="WSS_Minimal" originUrl="" />

An expedient way to provide the required permissions is to set this level to Full. However, this is not recommended. It is much safer to supply only the specific permissions that you need. You can do so by modifying the policy configuration file corresponding to the WSS_Minimal trust level (or WSS_Medium, if that's what you're using).

These policy configuration files are located in the following directory:

local_drive:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\60\CONFIG

For more information about modifying the trust level for a SharePoint site, see Microsoft Windows SharePoint Services and Code Access Security.

There are three sets of changes you must make to the policy configuration file: add a reference to the additional required permission classes, define a new permission set that includes these permissions, and define a new code group that provides the permissions when an appropriate membership condition is met.

To reference the additional required permission classes, open the wss_minimaltrust.config (or wss_mediumtrust.config) file, and add the following lines to the <SecurityClasses> element block:

IMPORTANT   For publishing purposes, the following XML examples include line breaks within attribute values. If copying this XML and pasting into a configuration file, you must eliminate these line breaks. Each attribute value (each string surrounded by quote marks) should appear on a single line.

<SecurityClass Name="SharePointPermission" 
 Description="Microsoft.SharePoint.Security.SharePointPermission, 
 Microsoft.SharePoint.Security, Version=11.0.0.0, Culture=neutral, 
 PublicKeyToken=71e9bce111e9429c"/>
<SecurityClass Name="WebPermission" Description="System.Net.WebPermission, 
 System, Version=1.0.5000.0, Culture=neutral, 
 PublicKeyToken=b77a5c561934e089"/>

Add the following lines to the <NamedPermissionSets> element block to define the new permission set:

<PermissionSet
 class="NamedPermissionSet" version="1" Name="AmazonWP">
 <IPermission class="AspNetHostingPermission" 
  version="1" Level="Medium"/>
 <IPermission class="SecurityPermission"
  version="1" Flags="Execution"/>
 <IPermission class="WebPartPermission"
  version="1" Connections="True"/>
 <IPermission class="WebPermission"
  version="1">
  <ConnectAccess>
   <URI uri="http://soap\.amazon\.com/.*"/>
  </ConnectAccess>
 </IPermission>
 <IPermission class="SharePointPermission"
  version="1" ObjectModel="True" UnsafeSaveOnGet="True"/>
</PermissionSet>

The SharePointPermission entry at the end of this set enables use of the Microsoft.SharePoint object model and allows data to be saved based on HTTP-Get requests. Without this, a security exception is thrown when the Web Part attempts to set its SaveProperties property to true.

Next, locate the outermost CodeGroup element that has a class attribute value of FirstMatchCodeGroup. Its PermissionSetName attribute is set to Nothing because it is a container for other code groups that reference named permission sets. Nest the following lines inside that outer FirstMatchCodeGroup code group:

<CodeGroup class="UnionCodeGroup" version="1" 
 PermissionSetName="AmazonWP">
 <IMembershipCondition class="StrongNameMembershipCondition"
  version="1" PublicKeyBlob= 
  "002400000480000094000000060200000024000052534131000400000100010003
   F3219572A8EAAC5034ABEBEB6ED7FE46E7D49F7CD87EAE73EE634FB508CC464904
   A50A94F07584DFD134C6AC4172D017CB2DD2B5A9EA45AA480273E191EB3A3A2A34
   48A8E62FB503F8A39A3D84B4F5E1B67F5D1D0BDA78C8F0071FF5A9FB5980D6493B
   EE9B8C09CEE321A25A3F9DE07B02E8FC510E077214A436808CBBBAD0"
  Name="AmazonWebParts"/>
</CodeGroup>

The AmazonWebParts sample project includes a file, named SAMPLE_wss_minimaltrust.config, which contains commented blocks showing where the new configuration settings should be inserted. These settings expand the default WSS_Minimal trust level to include added permissions that apply only to the AmazonWebParts assembly.

Note   The PublicKeyBlob value listed here and in the sample config file only works if you use the keypair.snk file that is included with the sample. If you use your own key pair file (as you should), then you must specify the PublicKeyBlob corresponding to your public key. You can extract the PublicKeyBlob value from your compiled assembly by running the Secutil.exe tool from a command line, as follows:

secutil.exe –hex –s path_to_AmazonWebParts.dll

You also must modify the value of the PublicKeyToken attribute in the .dwp file for each Web Part. To extract the PublicKeyToken value from the assembly, run the Sn.exe tool from a command line, as follows:

sn.exe –T path_to_AmazonWebParts.dll

An additional necessary security setting is already configured in the AmazonWebParts sample project. AssemblyInfo.cs contains the following attribute:

[assembly: AllowPartiallyTrustedCallers()]

This attribute allows an assembly to be called by another assembly that is assigned a lower level of trust. This is necessary here because a temporary assembly is automatically generated by the .NET XmlSerializer during a call to the Amazon Web service. Without the AllowPartiallyTrustedCallers attribute, the temporary assembly demands full trust and throws an exception unless the AmazonWebParts assembly runs with full trust.

By default, Web Parts installed using stsadm.exe are not given full trust and are installed to the bin directory for a specified virtual server. You can execute stsadm.exe with the -globalinstall switch to install strongly named Web Parts to the Global Assembly Cache (GAC), thereby giving them full trust, but it is safer to provide only the permissions that are needed.

Lastly, after modifying the security settings to allow this sample to run, reset Internet Information Services (IIS) by running iisreset at a command prompt, to ensure your changes take affect.

For more information about code access security for Microsoft SharePoint Products and Technologies, see Microsoft Windows SharePoint Services and Code Access Security.

For more information on the AllowPartiallyTrustedCallers attribute, see Version 1 Security Changes for the Microsoft .NET Framework.

Running the Sample Web Parts

After you've deployed the Amazon Web Parts to your server, you are ready to add them to a Web Part Page. To create a page, click Create at the top of the home page for a SharePoint site. On the Create Page page, in the Web Pages section, click Web Part Page. On the New Web Part Page page, type a Name for your page, such as AmazonSearch, and then select a template from the Choose a Layout Template list, such as Full Page, Vertical. In the Document Library list, select a document library, such as Shared Documents, as the save location for your new page. Click Create at the bottom of the page, as shown in Figure 1.

Figure 1. Creating a Web Part Page (Click picture to see larger image)

When the Web Part Page is created, it automatically displays in Shared view, so any changes are seen by all users of the page. Also, the Add Web Parts task pane is open in Browse mode, allowing you to select Web Parts from any of the four default Web Part Galleries. Click Virtual Server Gallery to see the sample Amazon Web Parts you installed, named Amazon Details Web Part and Amazon Search Web Part. Drag an instance of each Amazon Web Part onto the page, beginning with the Amazon Search Web Part, as shown in Figure 2.

Figure 2. Drag and drop instances of the Web Parts from the task pane (Click picture to see larger image)

The Amazon Search Web Part allows users to select a search category and sort order, and to type one or more keywords. The Web Part then calls an Amazon Web service to retrieve matching Amazon.com items and display the items. The Amazon Details Web Parts displays additional information, configurable by the user, for a particular Amazon.com item.

You can connect these sample Web Parts to each other or to other Web Parts. The AmazonSearchWebPart class implements the ICellConsumer interface, which allows it to get keywords for searches from other Web Parts, and it also implements the IRowProvider interface to make its search results available to other Web Parts.

The AmazonDetailsWebPart class implements the ICellConsumer interface, enabling it to display the details of an Amazon.com item that is selected using another Web Part.

To connect the two Web Parts after adding them to a page, close the Add Web Parts task pane, if it is still open, and click Modify Shared Page and then click Design this Page. Click the arrow in the title bar of the Amazon Details Web Part to display its Web Part Menu and then click Connections, Consume ASIN/ISBN From. This brings up a submenu that shows all the Web Parts on the page that implement interfaces compatible with the ICellConsumer interface, as shown in Figure 3. Click Amazon Search Web Part.

Figure 3. Connect the Amazon Details Web Part to the Amazon Search Web Part (Click picture to see larger image)

A dialog box opens to allow you to select which column the Details Web Part should use from the row provided by the Search Web Part, as shown in Figure 4. Select ASIN/ISBN (Amazon Standard Item Number/International Standard Book Number).

Figure 4. Select the column from the Amazon Search Web Part to display by the Amazon Details Web Part

Click Modify Shared Page and then deselect Design this Page to return from design view. Experiment with typing search categories, sorts, and keywords in the Amazon Search Web Part and click Go. The Web Part expands to display the search results, as shown in Figure 5.

Figure 5. The Amazon Search Web Part displays results based on search parameter values

CAUTION   If you get an error message indicating that one of the Web Parts has timed out, you may need to make an adjustment in the Web.config file for the site, located by default at local_drive:\Inetpub\wwwroot\Web.config. Increase the value of the Timeout attribute for WebPartWorkItem from the default 5000 milliseconds to a higher value—try 15000. This setting is explained later in this article.

When you select a particular item from the results in the Amazon Search Web Part, the Amazon Details Web Part displays additional information about the selected item, as shown in Figure 6.

Figure 6. The Details Web Part shows information based on the ASIN value supplied by the Search Web Part (Click picture to see larger image)

Now click the Web Part Menu of the Amazon Details Web Part and select Modify Shared Web Part. The task pane allows you to select which details to display and the size of the image, as shown in Figure 7. There is also a property for setting the Associate ID that is sent to Amazon.com if the user clicks hyperlinks in the display. This enables you to collect a referral fee for purchases made by a user who clicks on links in your Web Part. The user interface for these custom properties is defined in the AmazonDetailsToolPart class in the sample AmazonWebParts project.

Figure 7. Set custom properties to modify the contents displayed in the Amazon Details Web Part and to specify an Associate ID (Click picture to see larger image)

The AmazonWebParts sample project includes over a thousand lines of code, and this article does not attempt to cover them all. Most of the techniques used in the sample are explained in A Developer's Introduction to Web Parts. The following sections focus on two techniques that are especially useful when calling Web services—making asynchronous data requests and caching Web Part data.

Connecting to Web Services Asynchronously

Visual Studio .NET makes it very easy to connect to a Web service by adding a Web Reference to your project. The AmazonWebParts project includes a Web Reference based on this URL:

http://soap.amazon.com/schemas3/AmazonWebServices.wsdl

When you add a Web reference using that URL, Visual Studio automatically creates wrapper classes for the message types exposed by the Amazon.com Web service. To make the file containing these classes visible in Visual Studio, in Solution Explorer click Show All Files. You can then inspect the generated wrapper classes in Visual Studio by reviewing the Reference.cs file, shown in the Solution Explorer in Figure 8.

Figure 8. Visual Studio .NET generates wrapper classes for the Amazon.com Web services in Reference.cs

Whenever you write code that makes calls over HTTP, it is important to consider the use of asynchronous processing. Network latency can degrade the performance of any ASP.NET code that blocks while it waits for a response from a Web service. For this reason, the classes generated for Web references automatically include support for asynchronous method calls.

Each method created for a Web Reference wrapper class is supplemented by two variations on the method that can be used to make asynchronous calls. For example, the KeywordSearchRequest method of the AmazonSearchService class is supplemented by a BeginKeywordSearchRequest method and an EndKeywordSearchRequest method. The KeywordSearchRequest method blocks execution until the Web service returns a response. Your code can call BeginKeywordSearchRequest instead, which returns without waiting for a response. A separate thread is used to invoke the Web method, and when a response is received, your code is notified to call EndKeywordSearchRequest to get the response data.

This asynchronous pattern works well in many situations, but it is not suitable for use with Web Parts. The Web Part infrastructure must orchestrate potentially complex interactions between the various Web Parts on a Web Part Page, and it needs to maintain control over the threads being executed. To support asynchronous processing, the WebPart class, from which all Web Parts derive, provides a protected method named RegisterWorkItemCallback.

Your code can call RegisterWorkItemCallback to request a thread from the Web Part infrastructure's thread pool for safe asynchronous execution that is compatible with the Web Part event model.

The pattern to use when calling RegisterWorkItemCallback is very similar to the pattern you use when calling ThreadPool.QueueUserWorkItem. You must pass in two parameters—a System.Threading.WaitCallback delegate, which refers to a procedure that gets called when the work item is executed, and a parameter of type System.Object that you can use for providing any data that you want passed to the callback procedure when invoking the delegate. This is especially useful if you are invoking a single callback procedure from multiple asynchronous calls—use the second parameter to provide the particulars for each call.

The sample Amazon Web Part classes use RegisterWorkItemCallback whenever they call an Amazon.com Web service. To make this call, the classes override the GetData method of the WebPart base class. This is the recommended practice because it gives the Web Part infrastructure control over the timing of data requests.

The infrastructure first calls GetRequiresData, which must return a Boolean value. If GetRequiresData returns true, then the infrastructure calls the Web Part's GetData method. The sample Amazon Web Parts use a private Boolean field, calledWebService, to track whether the Web service was already called. GetRequiresData returns true if calledWebService is false. The Amazon Search Web Part, which implements IRowProvider, also uses a private Boolean field to track whether its RowReady event fired in PartCommunicationMain. GetRequiresData returns true if the Web service is not called yet or if the RowReady event has not fired:

    public override bool GetRequiresData()
    {
      return (!calledWebService) || 
       (rowProviderConnected && !sentRowReady);
    }

If GetRequiresData returns true, then the infrastructure calls GetData, which calls the Web service asynchronously. The following line of code from the GetData method calls RegisterWorkItemCallback, passing in a delegate pointing to the procedure to call asynchronously.

RegisterWorkItemCallback(new WaitCallback(GetDataFromWebService), null);

The private procedure, GetDataFromWebService, has the required signature—a single object parameter:

private void GetDataFromWebService(Object state)

The parameter is required to conform to the WaitCallback delegate, even though it is not used in this sample. Similarly, the second parameter of RegisterWorkItemCallback is always null in the sample code. This is because RegisterWorkItemCallback is only called once by each Web Part, and no distinguishing data is required.

The call to the Web service in GetDataFromWebService is very straightforward. First, the code sets a private Boolean field, named calledWebService, to true. This variable is referenced in the GetRequiresData method. Next, the code checks that the data isn't already cached—this code is covered later, in the discussion on data caching.

When the code determines that the data is not already cached, it gathers parameter values into the properties of an Amazon object that is passed to the Web service method. For the AmazonSearchWebPart class, this Amazon object is of type KeywordRequest:

{
    KeywordRequest keywordReq = new KeywordRequest();
    keywordReq.tag = AssociateID;
    keywordReq.devtag = "DJYVGQ3AW0XX8"; //replace with your tag
    keywordReq.mode = Mode; 
    keywordReq.type = "heavy";
    keywordReq.sort = Sort;
    keywordReq.page = ResultPage.ToString();
    keywordReq.keyword = Keyword; 

The call to the Web Service is wrapped in a Try. . .Catch block. If the necessary permissions are not supplied to the assembly, this call generates a security exception. If successful, the method returns data in an Amazon object of type ProductInfo, which is stored in the private productInfo variable:

    try
    {
      productInfo = amazonSearch.KeywordSearchRequest(keywordReq);
    }
    catch (Exception ex)
    {
      productInfo = null;

      if (ex is SecurityException)
      {
        errorText = ex.Message;
      }
    }

This code in GetDataFromWebService calls the KeywordSearchRequest method synchronously—it doesn't use BeginKeywordSearchRequest. However, the call is actually made asynchronously in relation to the main thread, because it is invoked using the delegate passed to RegisterWorkItemCallback.

There are a couple of hidden traps that you need to watch out for when you call RegisterWorkItemCallback. First, you must make the call prior to the execution of the RenderWebPart method. At the beginning of the render cycle, the WebPart base class waits for any pending work items to complete. The safest place to call RegisterWorkItemCallback is inside the GetData method, which is where the sample classes make the call. GetData is automatically called by the Web Part infrastructure at the appropriate time.

In addition, you should be aware of how work item timeouts are handled. By default, work items time out after 5000 milliseconds (5 seconds). Administrators can adjust this timeout period by editing the Timeout attribute of the WebPartWorkItem element in the Web.config file. The following entry creates a timeout period of 15 seconds:

<SharePoint>
   <WebPartWorkItem Timeout="15000" />
</SharePoint>

The WebPart class has a RenderWorkItemTimeout method, which you can override to render HTML when the timeout period expires. If you do not override this method, then a default system error is rendered. Also, you can check the value of the Boolean WorkItemTimeout property of the WebPart class—if it is true then at least one pending work item has timed out.

You can use the technique demonstrated in the sample for making asynchronous calls to a Web service when executing any operation that may impede performance. By relying on the Web Part infrastructure to perform the operation using a thread from its thread pool, you enable the processing of other Web Part events on the page to continue in parallel.

Web Part Data Caching

In addition to asynchronous processing, the Amazon Web Parts use another technique designed to improve performance—data caching. In addition to aiding the performance of the page, data caching also relieves the Amazon.com Web service from the need to handle redundant requests from your site for slow-changing data.

The Amazon Web Services SDK recommends that you use caching to improve performance and robustness. However, the Amazon.com license also imposes limits on how long you may cache Amazon data. You can store data retrieved from the Web services for no more than 24 hours, and you must refresh any pricing data displayed on your site at least once per hour (these terms are, of course, subject to change, so be sure to read your license).

ASP.NET provides rich support for caching. However, many ASP.NET caching techniques are not suitable for use on a Web Part Page. Page output caching is not appropriate because the contents of the page are so unpredictable. Unlike standard ASP.NET pages, the contents of Web Part Pages are expected to change as users add, close, move and modify Web Parts. Some modifications apply to all users and some apply only to the user who makes them. Standard ASP.NET pages are dynamic, but they usually vary in response to changes in predictable parameters that you can track more easily.

Although page output caching is not suitable for Web Part Pages, ASP.NET data caching can be useful. The ASP.NET Page.Cache property provides a convenient way to store data in the memory of a Web server, and it supports flexible control over the expiration of cached objects. One shortcoming of this form of caching is that cached items can't be shared among multiple servers—unlike session state, there is no way to designate an external state server or a database to use for storing cached ASP.NET data.

To address these issues, the Web Part infrastructure provides caching support that builds on what is available in ASP.NET. The WebPart class has a set of methods for reading, writing, and invalidating cached data.

Two important features distinguish the Web Part data caching methods from ASP.NET caching: developers can choose whether to cache data as Personal or Shared, and administrators can choose whether the data is stored in memory, using the ASP.NET cache, or in the content database for the SharePoint site.

Objects stored in the Web Part cache must be serializable, to support caching in the content database rather than in the memory of the Web server. The AmazonSearchWebPart.cs file includes the following definition of a class used for caching Amazon objects:

 [Serializable()]
public class KeywordResultCache
{
  public KeywordRequest req = null;
  public ProductInfo result = null;

  public KeywordResultCache(KeywordRequest req, ProductInfo result)
  {
    this.req = req;
    this.result = result;
  }
}

In the GetDataFromWebService method, before calling the Web service, the code attempts to retrieve cached data:

KeywordResultCache cached = 
(KeywordResultCache) PartCacheRead(Storage.None, cacheKeywordResult);

Note that the PartCacheRead method takes two parameters. The first parameter is of type Storage, which is an enumeration that allows you to specify whether to look for cached data stored as Shared or Personal. The use of Storage.None is explained in the next section of this article, which discusses handling of shared and personal data. The second parameter is a string—in this case cacheKeywordResult is a private string constant—that specifies the key to use when searching the cache for data. The cache stores key/value pairs.

CAUTION   In Beta versions prior to build 5308, which includes Beta 1 and the original Beta 2 (but not the Beta 2 Technical Refresh), there is a bug that requires a workaround when calling PartCacheRead from an asynchronous thread. You must first call PartCacheRead from the main thread or an exception is thrown from Microsoft.SharePoint.Library.SPRequest.ReleaseResources. If you are using a version that requires this workaround, add a call to PartCacheRead—exactly like the line above—in the GetData method, before calling GetDataFromWebService asynchronously.

The code in GetDataFromWebService checks for cached data and, if found, checks whether the cached data was based on the same keyword, search mode, sort, and results page currently selected in the Web Part. If those conditions are met, then the productInfo variable is set to the cached data. If not, then the code calls the Web service to set productInfo:

 if (cached != null && cached.result != null
  && cached.req.keyword.Equals(Keyword)
  && cached.req.mode.Equals(Mode)
  && cached.req.sort.Equals(Sort)
  && cached.req.page.Equals(ResultPage.ToString()))
  {
    productInfo = cached.result;
  }
  else
  {
    // ... code to call the Web service

After the call to the Web service returns, the code checks whether data is retrieved and, if so, caches the data, specifying the type of storage, the key, the object to cache, and the duration until the cached item is invalidated:

if (productInfo != null)
      {
        // Cache the value for 30 minutes
        PartCacheWrite(Storage.None, cacheKeywordResult, 
         new KeywordResultCache(keywordReq, productInfo),
         new TimeSpan(0,30,0));
      }

Administrators can configure whether cached data is stored in memory or in the content database, by editing the value of the Storage attribute of the WebPartCache element in the Web.config file. The valid values are Database or CacheObject:

  <SharePoint>
    <WebPartCache Storage="Database" />
  </SharePoint>

CAUTION   To support caching data in a database, you must ensure that all cached objects are created from classes that are marked as

[Serializable()]

. This includes any classes defined in a Reference.cs file generated from a Web service's WSDL file. In some cases, you may need to manually add the

[Serializable()]

attribute to one or more of these automatically generated classes—any that may need to be cached. Otherwise, your Web Part throws an exception if it attempts to cache a non-serializable object while the Storage attribute of the WebPartCache element in Web.config is set to Database. In the sample, you can see that many of the classes in Reference.cs are serializable. This is not done automatically when the code is generated—the

[Serializable()]

attributes were added manually and you must add them if you regenerate the code.

Handling Shared and Personal Data

One of the fundamental features of the Web Part infrastructure is its ability to store and manage both shared property settings that apply to all users and personal settings that apply only to the current user. As a Web Part developer, you sometimes need to detect whether your Web Part is viewed on a Web Part Page in shared or in personal mode. In addition, when you cache data, you must ensure that the data is cached appropriately in shared or personal storage, so that the right data can be retrieved in the future.

You may have noticed that the Amazon Search Web Part displays a warning message when the page is in shared mode. The message, which is shown in Figure 3 above, warns that "You are now in shared mode—this query is viewable by other users." If you use the Modify Shared Page menu to switch to Personal View, then the message disappears.

The code that conditionally displays this warning is in the RenderWebPart method. It uses a property of the base WebPart class, EffectiveStorage, to determine the current mode:

      if (EffectiveStorage == Storage.Shared)
      {
        output.Write("<b>You are now in shared mode&#151;this query will 
         be viewable by other users.</b>");
      }

It may seem paradoxical that this warning is necessary even though the Keyword property, which stores the search phrase, is marked with an attribute indicating that its data should be stored in personal storage:

    [WebPartStorage(Storage.Personal)]

Even though the property is marked as personal, the value you set in shared mode is seen not only by other users who view the page in shared mode, but even by users who view the page in personal mode if they did not yet store a personal value for the property. Personal property settings created in shared mode become the default values for those properties in personal mode.

You may need to detect when a user changes a page from one mode to another. The Amazon Search Web Part code does this in the event handler for the Load event:

    private void OnLoadHandler(object sender, EventArgs e)
    {
      object LastEffectiveStorage = ViewState["LastEffectiveStorage"];
      if (LastEffectiveStorage != null 
          && (int)EffectiveStorage != ((int)LastEffectiveStorage))
      {
        ResultPage = 1;
        SelectedRow = -1;
      }
      ViewState["LastEffectiveStorage"] = (int)EffectiveStorage;
    }

This code uses the ASP.NET ViewState object to persist the page storage mode across postback requests. ViewState values are encoded in a hidden input control on the page and are automatically read back from the page during a postback request. If the code detects that the user changed between personal and shared mode, then the search results automatically reset to display the first page of results and no result is selected.

There is one other section of code where this sample Web Part takes account of the fact that a user might switch between shared and personal mode. In the CreateChildControls method, the code creates the controls for the Web Part but it does not initialize their values. Instead, the values are set in the event handler for the PreRender event. This is done to avoid having the change events of the controls fire when the user simply switches between shared and personal modes, which causes a postback. During those postback requests, no change event is required—the correct data for the new mode is automatically retrieved and set during PreRender. The intended behavior is for change events to fire only during a postback caused by the user actually changing a value. This happens if the values are set in PreRender, but not if they are set in CreateChildControls.

Handling of shared and personal storage can be especially tricky when it comes to writing and reading cached values. The Web Part infrastructure maintains separate caches for shared and personal data, and it doesn't prevent you from reading or writing data for a mode that is not the same as the one that the page is currently in. Furthermore, the storage option that automatically selects the correct mode to use is not an obvious choice—Storage.None.

Both the PartCacheRead and PartCacheWrite methods have a required parameter that takes a member of the enumeration, Microsoft.SharePoint.WebPartPages.Storage. This enumeration is also used for specifying how property values should be stored. In that context, Storage.None indicates that the value of the property should not be stored in the content database. However, the meaning of Storage.None is completely different when applied to reading or writing cached data. You should use Storage.None when calling PartCacheRead and PartCacheWrite if you want the Web Part infrastructure to pick the appropriate form of storage based on the current state of the page and of the Web Part. You can use Storage.Personal or Storage.Shared to force a particular type of storage, but that is rarely a good idea.

Here is the code that the Amazon Search Web Part uses to attempt to retrieve data from the cache before calling the Web service:

  KeywordResultCache cached = 
   (KeywordResultCache) PartCacheRead(Storage.None, cacheKeywordResult);

This is the code used to write to the cache (and to specify a 30-minute timeout period):

  PartCacheWrite(Storage.None, cacheKeywordResult,
   new KeywordResultCache(keywordReq, productInfo), new TimeSpan(0,30,0));

You might be tempted to use the EffectiveStorage property of the Web Part to detect the current state—shared or personal—and to use that value when specifying the storage mode for PartCacheRead or PartCacheWrite. But this won't always work the way you expect. The problem is that when a Web Part is not yet personalized, you must read and write cached data using shared cache storage, even if the current mode is personal. Web Parts use shared storage, even for personal properties, until a user actually saves a personalized setting. When you use Storage.None, the Web Part infrastructure automatically takes care of detecting this state and doing the right thing. The only time to use Storage.Personal or Storage.Shared for caching, rather than Storage.None, is if you know that you want one or the other form of storage, regardless of whether the Web Part is personalized yet for the current user. This is rarely the case.

A No-Code Alternative: The Data View Web Part

Creating a hand-coded custom Web Part is not the only way to consume an XML Web service from a Web Part page. One easy alternative is to make use of the Data View Web Part, which you can configure in Microsoft Office FrontPage 2003 without writing any code. This Web Part provides a generic shell for retrieving and displaying data. You can also use the generic Form Web Part to supply parameter data to a Data View Web Part.

Creating a SharePoint Site with FrontPage

FrontPage 2003 provides rich new support for designing SharePoint sites and Web Part Pages. You can follow the steps outlined below to create a SharePoint site and a page that performs a keyword search for books by calling the Amazon.com Web service.

  1. Open FrontPage 2003 and on the File menu, click New. In the New task pane, in the New Web site section, click SharePoint Team site.

  2. In the Web Site Templates dialog box, type the location for the new site and select a template. For a simple test site, you can select the One Page Web Site template.

  3. In the Web Site view in FrontPage, right-click the default.htm page in your new site, and rename it to default.aspx so it can contain Web Parts. Click Yes in the dialog that asks if you're sure you want to do this. Double-click the page to open it in Design view.

  4. In the Data menu, select Insert Web Part Zone, and then select Insert Web Part. . .. From the Web Parts task pane, with Team Web Site Gallery selected, drag a Form Web Part into the Web Part zone. The Form Web Part contains a text box and a button.

    Note   You are not required to use Web Part Zones when inserting Web Parts in FrontPage. You can add Web Parts directly to pages, if you prefer.

  5. Right-click on the Form Web Part and select Web Part Properties. . .. In the properties dialog box, expand the Appearance node and edit the Title to read Enter search keywords. Click OK in the dialog box.

  6. Now click on the text box in the Web Part. The <input> tag corresponding to the text box is highlighted at the top of the design window. Click the arrowhead next to the highlighted <input> tag and select Tag Properties. . . from the drop-down menu, as shown in Figure 9.

    Figure 9. Configuring a Form Web Part in Microsoft Office FrontPage 2003 (Click picture to see larger image)

  7. In the Text Box Properties dialog box, edit the Name from T1 to keyword. For Initial value, enter cryptography (or any other keyword that interests you). For Width in characters, enter 20 (or a larger number to make the text box wider) and click OK.

Creating and Configuring a Data Source

To add a Data View Web Part to your page, you must first create a Data Source, which retrieves data from a SharePoint list, a document library, an XML file, a Web service, or by running server-side script.

On the Data menu, click Insert Data View. . . to display the Data Source Catalog task pane. Click the XML Web Services node in the Data Source Catalog and click Add to Catalog. . ., as shown in Figure 10.

Figure 10. Adding a Data Source to retrieve data from a Web service

In the Data Source Properties dialog box, type the following URL and click Connect Now:

http://soap.amazon.com/schemas3/AmazonWebServices.wsdl

FrontPage retrieves the specified Web Services Description Language (WSDL) file from Amazon and displays the service's parameters, as shown in Figure 11.

Figure 11. Use the Data Source Properties dialog box to configure your connection (Click picture to see larger image)

The KeywordRequest parameter displayed in the dialog box is a complex type composed of several data elements. Click Details to display the Parameter Details dialog box, which allows you to access the individual elements.

Double-click an item, or select it and click Modify, to edit the item in the Parameter dialog box, shown in Figure 12. Type the default values shown in the following table and designate keyword as a parameter that can have its value set at runtime (for a production application, be sure to replace these tag and devtag values with your Associate ID and Developer Token).

Table 1. Default parameter values

Name Value Runtime Parameter
keyword cryptography yes
page 1 no
mode books no
tag ase_mcwtechnologies no
type lite no
devtag DJYVGQ3AW0XX8 no

Figure 12. Use the Parameter Details dialog box to view and modify parameter settings

The Parameter dialog box optionally allows you to specify a token to use to dynamically set values at runtime, as shown in Figure 13. For example, you can configure a parameter to get its value from the URL query string or from the name of the logged in user. In this example, all the parameter values are hard coded, except for keyword, which gets its value by connecting to another Web Part on the Web Part Page.

Figure 13. Parameters can optionally get certain values automatically at runtime

After configuring the parameters, click OK in all the dialog boxes to return to the main window in FrontPage. Under the Web Services node in the Data Source Catalog task pane, FrontPage adds a listing for AmazonSearchService on soap.amazon.com. Now that you created a Data Source, you can add a Data View to the page.

Note   If you call a Web service from behind a proxy server, you must add an entry to the Web.config file found in the following path:

Local_drive:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\60\Config\

Use the following syntax to specify the address and port of your proxy server:

  <system.net>
      <defaultProxy>
        <proxy proxyaddress="http://proxy_address:proxy_port_number"/>
      </defaultProxy>
  </system.net>

Inserting and Configuring a Data View Web Part

The Data View Web Part provides a graphical user interface for creating XSLT to customize the display of XML data. You use dialog boxes and property settings to control which data is displayed and how it is formatted on the page.

In the Data Source Catalog task pane, click the AmazonSearchService item to view a list of available actions, and select Show Data. The display changes to show the Data View Details task pane, which includes a tree view of the XML data returned by the Web service, as shown in Figure 14.

Figure 14. The Data View Details task pane displays a tree view of the returned XML data

CAUTION   If you see an error message rather than a tree view in the Data View Details task pane after clicking Show Data, this probably indicates that the Amazon Web service timed out. Wait a bit and try again.

In the tree view of the XML returned by the Web service, press the CTRL key and click to select Url, ProductName, and ImageUrlSmall. In the Design pane, click Zone 1 to select that Web Part Zone, and in the Data View Details pane, click Insert Data View to insert a Data View Web Part that displays the data you selected.

To format the URL data in the Data View, right-click a data cell under the URL heading in the table that you added to the Data View, click Format Item as, and then click Hyperlink. In the ImageURLSmall section, right-click a data cell, click Format Item as, and then click Picture, as shown in Figure 15.

Figure 15. By default, data appears as text, but you can specify that it should be displayed as a hyperlink or as a picture (Click picture to see larger image)

To add a bit more pizzazz to the display, click any cell in the Data View table and on the Table menu click Table AutoFormat. . .. In the dialog box click Column 1, or whichever format you prefer, and then click OK. You can also select text in the table and use the Format toolbar to alter the font, style, or alignment, by clicking buttons similar to those used in Microsoft Word.

Behind the scenes, FrontPage implements your formatting selections as XSLT code, which you can view by selecting the Code tab at the bottom of the window. Have you ever wished for a WYSIWYG (what you see is what you get) XSLT editor? Here it is! The Data menu also enables you to define sorting, grouping, filtering, and conditional formatting options, which are all implemented for you as XSLT. To view or edit the generated XSLT, right-click on the Data View Web Part and select Web Part Properties. . .. At the top of the property window is a simple text-based XSLT editor.

Displaying the URL for each product as a separate column is somewhat ugly, so here are steps you can follow to turn each product name into a hyperlink that links to the product's URL.

  1. Select a ProductName value in the Data View.
  2. From the Insert menu, select Hyperlink. . ..
  3. Click the Parameters button on the Insert Hyperlink dialog box.
  4. In the Hyperlink Parameters dialog box, click Insert Field Value.
  5. Click XSL: URL.
  6. Click OK twice to accept your entries and dismiss the dialogs. The product name is now formatted as a hyperlink.

You now no longer need the URL to be displayed as a separate column. To delete it from the Data View, follow these steps.

  1. Click the URL column heading.
  2. From the Table menu, click Select and then click Column.
  3. From the Table menu, now click Delete Columns.

One final requirement is to connect the two Web Parts on the page.

  1. Right-click the Data View and then click Web Part Connections. . ..

  2. In the Web Part Connections Wizard, select Modify View Using Parameters From in the source action drop-down list, and then click Next.

  3. Verify the Connect to a Web Part on this page radio button is selected and then click Next..

  4. Verify the Provide Form Values to target action is selected and then click Next.

  5. Map the input text box in the Form Web Part to the keyword input parameter in the Data View, as shown in Figure 16.

    Figure 16. Use the Wizard to map the keyword in the Form Web Part to the parameter in the Data View Web Part (Click picture to see larger image)

  6. On the File menu, click Save to save the page design.

  7. Open a browser window and go to the new page, using the address you specified when you created the site.

  8. Type one or more keywords and then click Go.

The Data View Web Part retrieves Amazon data for that keyword entry and formats the data, as shown in Figure 17.

Figure 17. The Data View Web Part uses XSLT to format XML data for display in a browser (Click picture to see larger image)

CAUTION   You may find that you cannot open a page containing the example Amazon Web Parts in FrontPage. A known bug affects Web Parts that cache objects defined within the Web Part. This problem occurs if the Web Part you configure in FrontPage is installed in the BIN directory and if Web Part cache storage is set to Database.

Conclusion

As more data is exposed through Web services, it becomes increasingly common for developers and users of SharePoint Products and Technologies to want to work with Web Parts that retrieve data by making SOAP calls over HTTP. The Amazon.com Web services provide a good example of how useful Web services can be. These Web services enable you to create sites that integrate seamlessly with Amazon, potentially providing revenue based on your users' Amazon purchases.

The Web Part samples that accompany this article demonstrate how to call Web services asynchronously from Web Parts, so that multiple Web Parts on a Web Part Page can initiate Web requests in parallel. The examples also show how to use data caching in Web Parts to reduce traffic when working with data that is not continuously changing.

The Data View Web Part enables non-programmers working with Microsoft Office FrontPage 2003 to configure Web Parts that display data retrieved from a variety of data sources, including Web services. Data Views also make it very easy to create customized formatting, filtering, grouping, and sorting. Even experienced programmers gain the opportunity to create complex XSLT by using a WYSIWYG editor and graphical dialogs. FrontPage also provides dialog boxes for configuring the exchange of data between Data Views and other Web Parts.

About the Author

Andy Baron is a senior consultant and project manager at MCW Technologies, a Microsoft Certified Partner. Andy also creates training materials for AppDev, and he has received Microsoft's MVP designation since 1995.