Chapter 10. SharePoint Web Parts

The power of SharePoint as a solution platform comes in no small measure from its support for web parts. The web parts framework built into Windows SharePoint Services (WSS) provides a consistent environment for both developer and user. Standard interfaces, attributes, and deployment models make web part construction straightforward, while standard interface elements to add, remove, and modify web parts make them easy to customize. Throughout our investigation of SharePoint, we have used web parts to integrate systems, customize functionality, and display information. Although WSS and MOSS ship with a number of useful web parts, you will inevitably want to create your own. In this chapter, I will examine the fundamental construction and deployment of web parts.

Web Part Basics

Creating a custom web part in SharePoint begins by inheriting from the WebPart class. Those who wrote web parts in the previous version of SharePoint will remember that those web parts also began by inheriting from the WebPart class. The big difference, however, is that the base class for web parts in SharePoint 2003 derives from -Microsoft.SharePoint.WebPartPages.WebPart, whereas the base class for SharePoint 2007 web parts is System.Web.UI.WebControls.WebParts.WebPart. While this version of SharePoint provides full backward compatibility for web parts built on the Microsoft.SharePoint namespace, the best practice going forward is to use the System.Web namespace. Furthermore, the Microsoft.SharePoint.WebPartPages.WebPart class has been rebased in ASP.NET 2.0 so that it actually derives from the System.Web.UI.WebControls.WebParts.WebPart class anyway.

You begin the definition of a new web part by creating a new class library project in Visual Studio. After the project is created, you must set a reference to the System.Web namespace, which contains the WebPart base class. Once the reference is set, you may then set up the class to inherit from the base class. Listing 10-1 shows the foundational code for a web part.

Example 10.1. Starting a Web Part

using System;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
namespace myNamespace
{
    public class myWebPart:WebPart
    {

    }
}

Web Part Properties

Well-designed web parts function in a variety of different pages because they are configurable by an administrator or end user directly in the site. This configuration is possible because each web part supports a series of properties that can be set in the site and read by the web part at run time. In code, these properties are created in the same manner as any property for any class with the exception that they have special attributes that determine their behavior within SharePoint. The process of creating a property begins with a standard property construct.

Most properties are designed to be configured directly in the portal. Therefore, you must decorate the property with different attributes to define its behavior when a page is designed. These property values are subsequently serialized and saved when the page is processed so that the property values can be read later when an end user accesses the page. Each of the properties you define is decorated with the WebBrowsable, Personalizable, WebDisplayName, and WebDescription attributes.

The WebBrowsable attribute is a Boolean value that determines whether the built-in SharePoint property editing pane can access the property. You may set this value to either True or False. Although most of your properties will be browsable, you may have sensitive properties that should not be accessible by general users. Additionally, when you create a custom property editor later in the section titled "Custom Editor Parts," you will set this value to False.

The Personalizable attribute is an enumeration that determines whether the property values are saved for an individual or for all users of the page on which the web part sits. This attribute may be set to PersonalizationScope.User or PersonalizationScope.Shared. When the attribute is set to PersonalizationScope.User, the property value may be set for each user of a page. The web part infrastructure serializes and saves the values separately for each user. When the attribute is set to PersonalizationScope.Shared, the web part infrastructure saves only a single value of the property that is applied to all users of the page on which the web part sits.

Once you understand the property definition scheme, you can create as many as you need to properly configure the web part. Although they are easy to change, I recommend that you spend some time designing your web part before implementing the property set. If you think through the intended use of the web part, you will save yourself a lot of wasted time writing and rewriting property structures. Listing 10-2 shows a complete property definition for a database connections string that can be set by a user and then utilized by the web part.

Example 10.2. Creating a Property

[Personalizable(PersonalizationScope.Shared),WebBrowsable(true),
WebDisplayName("Connection String"),
WebDescription("The connection string for the database")]
public string Connection
{
  get{return m_connection;}
  set{m_connection=value;}
}

Rendering Web Parts

Because the WebPart class inherits from System.Web.UI.Control, the entire user interface for a web part must be created through code. Although you can make use of ASP.NET controls in your web part, there is no drag-and-drop design capability in Visual Studio. This approach is definitely a drawback and can slow your ability to create web parts. Be that as it may, it becomes less of an issue once you have created a few web parts and learned the techniques for generating the user interface.

Note

It is popular among some developers to utilize a technique where a web part is made to load a user control at run time. Using this technique allows a developer to have a drag-and-drop experience at design time supported through the user control. I have written about this technique in my book Advanced SharePoint Services Solutions (Apress, 2004) but it is beyond the scope of this book. If you are interested in this approach, visit http://workspaces.gotdotnet.com/smartpart.

Properly rendering a web part requires that you first create any ASP.NET controls that you will need in code. The required ASP.NET controls are then added to the controls collection of the web part by overriding the CreateChildControls method of the base class. Finally, you can draw the output by overriding the RenderContents method. You may use any available ASP.NET control found in Visual Studio .NET or any ASP.NET control you have written to create the user interface for a web part. Remember though that these controls cannot be dragged onto a page. Instead, they must be declared in code. When you declare ASP.NET controls in code, be sure to set a reference to the appropriate namespace. Nearly all of the ASP.NET controls that you could want belong to the System.Web.UI.WebControls namespace.

Once the controls are declared, you can set their properties and add them to the Controls collection of the web part. You can do this by overriding the CreateChildControls method. In this method, set property values for each control and then add it to the Controls collection using the Controls.Add method. You can also utilize event handlers for the ASP.NET controls, which will be necessary to capture button clicks and the like. Listing 10-3 shows several controls being added to a web part.

Example 10.3. Adding ASP.NET Controls to a Web Part

protected TextBox txtDisplay;
protected Button btnGo;

protected override void CreateChildControls()
{
    this.btnGo.Click += new System.EventHandler(this.btnGo_Click);
    this.Controls.Add(btnGo);

    txtDisplay.Width=Unit.Percentage(100);
    this.Controls.Add(txtDisplay);

}

private void btnGo_Click(object sender, System.EventArgs e)
{
    txtDisplay.Text=Text;
}

Once the controls are all configured and added to the web part, you are ready to draw the output. When rendering the user interface of the web part, you use the HtmlTextWriter class provided by the RenderContents method. This class allows you to create any manner of HTML output for the web part.

As a general rule, you should render your user interface within an HTML <table>. The reason for this is that you can never be sure what the web part page layout will look like. As you saw in Chapter 5, layouts and web part zones can take almost any form. Therefore, you should use the relative layout offered by the <table> tag to respect the width defined by the zone where the web part appears. Listing 10-4 shows how to render a table containing ASP.NET controls. You should take particular note of the width definition within the table.

Note

Style sheet purists may prefer to use a style class to control the layout of a web part. While you can certainly reference style sheet classes from a web part, SharePoint makes extensive use of layout tables, so I do not see much of an issue with using them in your web parts.

Example 10.4. Rendering ASP.NET Controls in an HTML Table

protected override void RenderContents(HtmlTextWriter writer)
{
  writer.Write("<table border="0" width="100%">");
  writer.Write("<tr><td>");
  btnGo.RenderControl(writer);
  writer.Write("</td></tr>");
  writer.Write("<tr><td>");
txtDisplay.RenderControl(writer);
  writer.Write("</td></tr>");
  writer.Write("</table>");

}

The Web Part Life Cycle

Just like ASP.NET controls, web parts participate in a server-side request/response sequence that loads a page in the portal each time it is requested, and unloads the page once it is sent to the client. Web parts, therefore, follow the same control life cycle that ASP.NET controls follow. You can see the events of this life cycle by creating a web part that generates output in response to each key event. Listing 10-5 shows the code for a complete web part with all of the major methods overridden to display the order in which they occur.

Example 10.5. Tracking the Web Part Life Cycle

//The following are included in the project automatically
using System;
using System.Collections.Generic;
using System.Text;

//The following were added manually after setting a reference to System.Web
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;


namespace SPLifecycle
{
    public class Reporter:WebPart
    {
        //variable for reporting events
        private string m_report="";

        //variable for button and text
        private Button m_button;
        private TextBox m_text;

        protected override void OnInit(EventArgs e)
        {
            m_report += "OnInit<br/>";
            base.OnInit(e);
        }
protected override void LoadViewState(object savedState)
        {
            m_report += "LoadViewState<br/>";

            object[] viewState = null;
            if (savedState != null)
            {
                viewState = (object[])savedState;
                base.LoadViewState(viewState[0]);
                m_report += (string)viewState[1] + "<br/>";
            }

        }

        protected override void CreateChildControls()
        {
            m_report+="CreateChildControls<br/>";

            m_button = new Button();
            m_button.Text = "Push Me!";
            m_button.Click += new EventHandler(m_button_Click);
            Controls.Add(m_button);

            m_text = new TextBox();
            Controls.Add(m_text);
        }

        protected override void OnLoad(EventArgs e)
        {
            m_report += "OnLoad<br/>";
            base.OnLoad(e);
        }

        void m_button_Click(object sender, EventArgs e)
        {
            m_report+="Button Click<br/>";
        }

        protected override void OnPreRender(EventArgs e)
        {
            m_report += "OnPreRender<br/>";
            base.OnPreRender(e);
        }

        protected override object SaveViewState()
        {
            m_report += "SaveViewState<br/>";
object[] viewState = new object[2];
            viewState[0] = base.SaveViewState();
            viewState[1] = "myData";

            return viewState;
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            m_report += "RenderContents<br/>";
            writer.Write(m_report);
            m_text.RenderControl(writer);
            m_button.RenderControl(writer);
        }

        public override void Dispose()
        {
            base.Dispose();
        }

        protected override void OnUnload(EventArgs e)
        {
            base.OnUnload(e);
        }


    }
}

When a page from a WSS site that contains web parts is requested for the first timeor when it is submitted to the serverthe web part life cycle begins. The first phase in this life cycle is marked by a call to the OnInit method of the WebPart class. During initialization, configuration values that were marked as WebBrowsable and set through the web part task pane are loaded into the web part.

After the web part is initialized, the ViewState of the web part is populated. ViewState is a property inherited from System.Web.UI.Control. The ViewState is filled from the state information that was previously serialized. Once the ViewState property is populated, the control returns to the same state it was in when it was last processed on the server. The ViewState is populated through a call to the LoadViewState method.

Although it is normally not necessary to override the LoadViewState method, it is useful if you would like to persist your own data within a web part. Overriding the LoadViewState method along with the corresponding SaveViewState method lets you store custom data within the web part's view state. In Listing 10-5, I use this technique to show how these methods fit into the overall web part life cycle.

When the viewstate becomes available, the web part's user interface may be created. As I showed earlier, this happens with a call to the CreateChildControls method. In this method, all of the constituent controls are created and added to the Controls collection. It is important to note that the CreateChildControls method does not always get called at the same point in the life cycle. When web parts are first rendered on the page, the method generally occurs after the OnLoad method. After a postback, however, the method is called before the OnLoad method. You can even force it to be called using the EnsureChildControls method. All this means that you must not make any hard assumptions about when this method will execute.

Once the web part interface has been created and the properties set from the viewstate, the server can make changes to the properties of the web part based on values that are posted by the client browser. Any new values that are posted during the requestsuch as text field valuesare applied to the corresponding property of the web part. At this point, the web part has reached the state it was in just before the postback occurred.

After all of the new property values are applied to the web part, the page may begin using the information to process the end-user request. This begins through a call to the OnLoad method of the WebPart class. The OnLoad method fires for every web part regardless of how many properties have changed. Web part developers use the OnLoad method as the basis for the functionality embodied in the web part. During this event, web parts may obtain a connection to a database or other system to retrieve information for display. The key thing to remember about this method is that it always fires after the posted data has been applied to the web part.

Once the OnLoad method completes, any events triggered by the client interaction with the web part are fired. This includes all user-generated events such as the Click event associated with a button. It is critical for the web part developer to understand that the user-generated events happen after the OnLoad event. This means that you must be careful not to rely on the results of user-generated events when you write code for the OnLoad event.

Once the web part has finished handling the user-generated events, it is ready to create the output of the control. The web part begins creating this output with a call to the OnPreRender method of the WebPart class. The OnPreRender method gives the web part developer the opportunity to change any of the web part properties before the control output is drawn. This is the perfect place to run a database query that relies on several user-supplied values, because all of the values will be available at this point in the life cycle.

After the OnPreRender event is complete, the web part output may be drawn. Drawing begins through a call to the RenderContents method, which I discussed in the "Rendering Web Parts" section. In this method, the web part must programmatically generate its HTML output. This output will be rendered in the appropriate zone on the page in the portal.

The next step is to save the state of any controls on the page in the ViewState. The ViewState of the web part is serialized and saved to a hidden field in the web page. The ViewState is saved through a call to the SaveViewState event, which is inherited from the System.Web.UI.Control class.

Once the ViewState is saved, the control web part can be removed from the memory of the server. Web parts receive notification that they are about to be removed from memory through the Dispose event. This method allows the web part developer to release critical resources such as database connections before the web part is destroyed.

The web part life cycle ends when it is finally removed from memory. The last event to fire is the OnUnload event. This event notifies the web part that it is being removed from memory. Generally web part developers do not need access to this event because all cleanup should have been accomplished in the Dispose event.

Understanding the complete life cycle helps significantly when developing web parts. In particular, understanding when certain values are available to the web part will ensure that you create components with consistent behavior. Figure 10-1 shows the output of the life cycle web part.

The web part life cycle

Figure 10.1. The web part life cycle

Deploying Web Parts

After you have finished coding the web part, you are ready to begin the process of deploying it for use in WSS. To deploy a web part, you must make several decisions regarding its location and trust level within WSS. After it is deployed, you will then need to make it available to WSS sites through the Web Parts Gallery.

Understanding Deployment Options

Because SharePoint is a web-based application with potential ties to sensitive organizational information, web part security is a significant concern. These security concerns encompass not only access to information, but also potential malicious behavior by web parts. Imagine that a power user downloads and installs a free web part from the Internet. The web part has some nice site navigation that everyone likes, but in the background it is also crawling the network.

SharePoint web parts can be deployed with or without a strong name. Assemblies with a strong name may be deployed to the Global Assembly Cache (GAC) or the /bin directory of the targeted SharePoint site collection. Assemblies without a strong name may only be deployed to the /bin directory. The best practice, however, is to digitally sign web parts with a trusted certificate so that you can guarantee all of the web parts in your system are trustworthy. In my experience, however, most web parts are simply deployed using a strong name created by the developer at design time.

Before you can give your web part a strong name, you must generate a public/private key pair to use when signing the web part. You can create a key pair using the Strong Name tool (sn.exe) or have Visual Studio do it for you through the properties dialog. Figure 10-2 shows the Signing tab of the properties dialog in Visual Studio.

Signing the web part

Figure 10.2. Signing the web part

If an organization already has a digital certificate, it may not be made generally available to developers who need to sign code. In this case, the developer may choose to delay signing the web part. When you delay signing, the web part space is reserved for the final signature, but you can still use the web part during development.

In order to delay signing the web part, you must set the AssemblyDelaySign attribute to True. You must then get the public key portion of the certificate and reference it using the AssemblyKeyFile attribute. Finally, you must instruct the .NET Framework to skip the strong name verification test for the web part by using the Strong Name tool with the following syntax:

sn -Vr [assembly.dll]

Warning

Skipping the strong name verification opens a security hole. Any web part that uses the same assembly name can spoof the genuine web part. Reserve this technique solely for development in organizations where the digital certificate is not provided to developers. Otherwise, always reference a valid key pair.

Regardless of how you choose to sign the web part, you should make sure that the version number specified in the AssemblyInfo file is absolute. Visual Studio .NET has the ability to autoincrement your project version using wildcards; however, this is not supported by strong naming. Therefore, you must specify an exact version for the web part. The following code fragment shows an example:

[assembly: AssemblyVersion("1.0.0.0")]

As I mentioned earlier, you can deploy a web part in either the GAC or the in directory of the associated web application. If you choose to deploy the web part in the in directory, you will also need to add the AllowPartiallyTrustedCallers attribute to the AssemblyInfo file. This is required because assemblies in the in directory will not operate with full trust. The AllowPartiallyTrustedCallers attribute is part of the System.Security namespace, so you must reference the namespace as shown in the following code fragment:

using System.Security;
[assembly: AllowPartiallyTrustedCallers]

Building the Web Part

After the assembly is signed, you can build the web part. Once the web part is built, you may either install it in the GAC or copy it to the in directory. Installing the web part in the GAC allows the web part to operate with full trust, whereas copying it to the in directory subjects it to the security policy defined in the associated web.config file. Generally, the best practice is to copy the web part to the in directory and adjust the security policy as required. I'll cover more on security policies in the section titled "Code Access Security."

If you are going to copy your web part to the in directory, you must locate the correct web application on the file system and create a in directory, because it does not exist by default. As I discussed earlier in the book, SharePoint creates folders for all of the web applications in the path InetpubwwwrootwssVirtualDirectories. In this folder, you will find a folder for each web application that is generally named by port number. It is underneath this folder where you will create a in directory and copy the assembly. Figure 10-3 shows an example bin directory in the File Explorer.

Creating the in directory

Figure 10.3. Creating the in directory

Warning

Do not deploy your web parts in the _app_bin directory. This is reserved for SharePoint.

Code Access Security

SharePoint is based on ASP.NET technology. As such, it is bound by the same security limitations that apply to any ASP.NET application. Practically speaking, this means that web parts are often restricted from accessing enterprise resources such as databases and web services unless you specifically configure SharePoint to allow such access. Managing how code can access enterprise resources is known as code access security.

Understanding Configuration Files

Code access security is implemented by a series of configuration files. The first configuration file of concern is web.config located in C:WindowsMicrosoft.NET Frameworkv2.0.50727CONFIG. This file specifies master settings that will be inherited by all WSS sites that run on the server. This particular file is densely packed with information, and a complete discussion of the contents is beyond the scope of this book. However, one section<securityPolicy>is of immediate importance.

The <securityPolicy> section defines five levels of trust for ASP.NET applications: Full, High, Medium, Low, and Minimal. The trust level definitions allow you to assign partial permissions to an ASP.NET application that determine what resources the application can access. For example, applications with High levels of trust can read and write to files within their directory structure, whereas an application with a Low trust level can only read files. The permissions allotted by each level of trust are defined within a separate policy file designated by the <trustLevel> element. The following code shows the <securityPolicy> section for the machine.config file associated with an installation of WSS:

<securityPolicy>
    <trustLevel name="Full" policyFile="internal"/>
    <trustLevel name="High" policyFile="web_hightrust.config"/>
    <trustLevel name="Medium" policyFile="web_mediumtrust.config"/>
    <trustLevel name="Low" policyFile="web_lowtrust.config"/>
    <trustLevel name="Minimal" policyFile="web_minimaltrust.config"/>
</securityPolicy>

The security policy files referenced by the <trustLevel> element are also XML files. These files contain a separate section for each policy that the file defines. Examining each of the files referenced in the web.config file results in the complete picture of the trust levels and permissions.

The web.config file represents the highest level of configuration for ASP.NET applications; however, each application may have a supplemental configuration file also named web.config. This file is typically found in the root directory of an application, and for SharePoint it is located in inetpubwwwrootwssVirtualDirectories[WebApplication]. Opening this file will reveal that it also has a <securityPolicy> section that defines two additional levels of trust known as WSS_Medium and WSS_Minimal. The following code shows the <securityPolicy> section from the file:

<securityPolicy>
  <trustLevel name="WSS_Medium"
  policyFile="C:Program FilesCommon FilesMicrosoft Shared
Web Server Extensions12configwss_mediumtrust.config" />
  <trustLevel name="WSS_Minimal"
  policyFile="C:Program FilesCommon FilesMicrosoft Shared
Web Server Extensions12configwss_minimaltrust.config" />
</securityPolicy>

The default installation of WSS defines a trust level of WSS_Minimal for all sites. Because web parts are deployed to the in directory, they are affected by the trust level set in the web.config file. This means that web parts associated with a SharePoint site have significant limitations. Most importantly, web parts running under WSS_Minimal cannot access any databases nor can they access the objects contained in the SharePoint object model. The Common Language Runtime (CLR) will throw an error if a web part attempts to access an unauthorized resource. Therefore, you must always implement appropriate error handling in a web part during attempts to access resources. Exception classes for these errors can be found in the Microsoft.SharePoint.Security namespace.

Customizing Policy Files

Because one of the major reasons to write a web part is to access data in other systems, you will undoubtedly want to raise the trust level under which certain web parts will run. You have three options for raising the trust level for assemblies in the in directory. All three have strengths and weaknesses you need to consider depending upon whether you are in a development, testing, or production environment.

The first option is simply to raise the trust level for all SharePoint Services sites by modifying the web.config file directly in a text editor. The trust level for SharePoint Services is set in the <system.web> section of the web.config file. To raise the level of trust, modify the <trust> tag to use any one of the seven defined levels. The following code shows an example with the trust level set to WSS_Medium:

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

Although making a global change to the trust level is simple, it should only be done in development environments. Generally, you should strive to limit access to resources to only essential web parts in a production environment. The default WSS_Minimal level is recommended for production.

The second option is to deploy all of your web parts into the GAC. The GAC grants the Full level of trust to web parts installed there without requiring a change to the web.config file. Once again, this is a fairly simple way to solve the problem, but it does make the web part available to all applications and servers. This is a potential problem, because a highly trusted component is now more widely accessible. Web parts can be added to the GAC using the command-line tool gacutil.exe with the following syntax:

gacutil -i [assembly.dll]

The final option for raising the trust level associated with a web part is to create your own custom policy file. Although this approach requires the most effort, it is easily the most secure. This approach should be considered the recommended best practice for production environments.

To create a custom policy file, follow these steps:

Note

If you are strictly following this text, you may not have developed your first web part yet. If this is the case, complete this series of steps after you finish the exercise at the end of the chapter.

  1. Open the Windows File Explorer and navigate to Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12config.

  2. Copy wss_minimaltrust.config and paste it back to create a copy of the file.

  3. Rename the copied file wss_sqltrust.config.

  4. Open wss_sqltrust.config in Visual Studio for editing.

  5. In the <SecurityClasses> section, add a reference to the SqlClientPermission class so web parts can access SQL databases:

    <SecurityClass Name="SqlClientPermission"
    Description="System.Data.SqlClient.SqlClientPermission, System.Data,
    Version=1.0.53383.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089"/>
  6. In the <NamedPermissionSets> section, add a new permission set that grants all of the rights you want to define for your new policy, including access to SQL databases.

  7. Extract the public key for the assembly from a web part you have developed by using the Security Utility tool with the following syntax:

    secutil.exe -hex -s [assembly.dll]
  8. Create a new <CodeGroup> section to apply the policy to the web part. This <CodeGroup> must precede the existing <CodeGroup> section defined for ASP.NET, because once a policy is assigned, processing stops. The following code shows an example:

    <CodeGroup
        class="UnionCodeGroup"
        version="1"
        PermissionSetName="wss_sqltrust">
        <IMembershipCondition
            class="StrongNameMembershipCondition"
            version="1"
    PublicKeyBlob="0x002433830048338300943383338
    3060233833383243383052534131338343383001338310
    0936E3CD84B98E97825E63A7DBD7C15C10893315D16B5D9
    8E7B7F38814BF0861D0BB5279A710EFFA
    CA29A01BB745136FA2DDCAF8F5105C5F429DFF904A0B94
    F0A4A8D27D3F8329CA4E7B44962D8764B8
    D8A38D9F16859A035C23AC69D39D2969D03680C791C4D7
    5B38BBE4D12C30467B6FE8F41131FC859E
    D3B9B6F0D432478DC"
            Name="SPPivotalContacts"
    />
  9. Save and close the file.

  10. Open the web.config file in Visual Studio.

  11. Edit the <securityPolicy> section to add a reference to the new policy as shown here:

    <securityPolicy>
      <trustLevel name="WSS_Medium" policyFile="C:Program Files
    Common FilesMicrosoft SharedWeb Server
    Extensions12configwss_mediumtrust.config" />
      <trustLevel name="WSS_Minimal" policyFile="C:Program Files
    Common FilesMicrosoft SharedWeb Server
    Extensions12configwss_minimaltrust.config" />
      <trustLevel name="WSS_SQL" policyFile="C:Program Files
    Common FilesMicrosoft SharedWeb Server
    Extensions12configwss_sqltrust.config" />
    </securityPolicy>
  12. In the <system.web> section, modify the <trust> element to use the new policy as shown here:

    <trust level="WSS_SQL" originUrl="" />
  13. Save and close the file.

  14. Restart IIS and the new policy will be in effect.

Listing 10-6 shows the final XML.

Example 10.6. Defining a New Policy

<PermissionSet
    class="NamedPermissionSet"
    version="1"
    Name="wss_sqltrust">
        <IPermission
            class="AspNetHostingPermission"
            version="1"
            Level="Minimal"
        />
        <IPermission
            class="SecurityPermission"
            version="1"
            Flags="Execution"
        />
<IPermission class="WebPartPermission"
            version="1"
            Connections="True"
        />
        <IPermission
            class="SqlClientPermission"
            version="1"
            Unrestricted="true"
        />
</PermissionSet>

The predefined security policies available to SharePoint lack templates for defining access to several key resources. These resources include the SharePoint object model and web services. Therefore, I will review the necessary modifications you must make to policy files in order to access these resources.

If you want your web part to be able to access the classes in the SharePoint namespace, you must define a new <IPermission> element in the policy file similar to what was done here for SQL access. The following code shows how to define the element:

<IPermission
    class="SharePointPermission"
    version="1"
    ObjectModel="true"
/>

Similarly, if you want your web part to be able to call a web service, you must also define a new <IPermission> element. In this element, you specify the Uniform Resource Identifier (URI) of the web service to access. This URI may be in the form of a regular expression, which means you can set it up to match more than one available web service. The following code shows how to define the element:

<IPermission class="WebPermission" version="1">
<ConnectAccess>
<URI uri="http://localhost/services/callreport.asmx?WSDL"/>
</ConnectAccess>
</IPermission>

Remember that in any case where a strongly named web part is in use, all of the other components must also be strongly named. This can cause problems when you are accessing web services or other libraries. In these cases, you must either install your web part to the GAC or implement a custom security policy.

Marking Web Parts As Safe

Adding a new web part to the in directory or the GAC handles the code access security issues for the part, but it is not sufficient to allow the part to be imported into SharePoint. In addition to permission to access resources, web parts also need permission to be imported into SharePoint. This permission is granted by marking the web part as Safe in the web.config file.

The web.config file contains not only the code access security policy but also the list of all assemblies allowed to run in a web part page. This information is kept in the <SafeControls> section of the file. Before a web part can be imported into SharePoint, it must be listed in the section. Listing 10-7 shows a truncated example of a <SafeControls> section.

Example 10.7. Controls Marked As Safe

<SafeControls>
<SafeControl Assembly="SPMaskTool, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=eb3e58846fb2ac2b" Namespace="SPMaskTool" TypeName="*" />
<SafeControl Assembly="SPPageView, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=eb3e58846fb2ac2b" Namespace="SPPageView" TypeName="*" />
<SafeControl Assembly="SPDataList, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=eb3e58846fb2ac2b" Namespace="SPDataList" TypeName="*" />
<SafeControl Assembly="SPDataSet, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=eb3e58846fb2ac2b" Namespace="SPDataSet" TypeName="*" />
<SafeControl Assembly="SPPivotalContacts, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=eb3e58846fb2ac2b"
Namespace="SPPivotalContacts" TypeName="*" />
<SafeControl Assembly="Citrix, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=eb3e58846fb2ac2b" Namespace="Citrix" TypeName="*" />
</SafeControls>

In the <SafeControls> section, you must add a <SafeControl> element for each web part that you want to use in SharePoint. In the <SafeControl> element, you must specify several attributes:

  • The Assembly attribute contains the fully qualified assembly name along with the -Version, Culture, and PublicKeyToken attributes.

  • The Version attribute contains the assembly version as it appears in the manifest file.

  • The Culture attribute contains the culture designation, or "neutral" if none is provided.

  • The PublicKeyToken attribute contains the token generated from the Strong Name tool.

  • The Namespace attribute contains the namespace as defined in the web part project.

  • The TypeName attribute contains the fully qualified name of the type or an asterisk to denote every web part in the assembly.

  • Safe is an optional attribute that is normally set to True, but can be set to False to deny permission to a specific web part.

Creating Solution Packages

Setting the output path for the web part project to in or copying the assembly are simple ways to ensure that the final web part assembly is deployed to the right location; however, this technique is only useful in a development environment. For production environments, you will want to build a solution package that can be deployed independent of Visual Studio.

Creating solution packages is done inside Visual Studio .NET as a new cabinet project in the same solution where the web part project is located. When you create the cabinet, you need to create a solution manifest file and include the assembly file to successfully deploy the web part. SharePoint recognizes cabinet files with a WSP extension as solution packages. Solution packages can be used to deploy all manner of components.

Before you can create the solution package, you must create a manifest file to describe the solution. This file must be named manifest.xml and will include a reference to the assembly as well as an appropriate entry for the SafeControls section of the web.config file. The simplest way to make this file is to include it directly in the web part project. Listing 10-8 shows an example manifest.xml file that deploys a web part to the in directory.

Example 10.8. A Typical manifest.xml File

<Solution xmlns=http://schemas.microsoft.com/sharepoint/
  SolutionId="997310BA-AB2E-4d97-87B4-4DABAB0EB796" >
  <Assemblies>
    <Assembly DeploymentTarget="WebApplication" Location="SPScript.dll">
      <SafeControls>
        <SafeControl
        Assembly="SPScript, Version=1.0.0.0, Culture=neutral,
        PublicKeyToken=689f1d0ba493bcce"
        Namespace="SPScript" Safe="True" TypeName="*"/>
      </SafeControls>
    </Assembly>
  </Assemblies>
</Solution>

The Solution element contains a SolutionId attribute that uses a GUID to uniquely identify the solution. The Assembly element contains a DeploymentTarget attribute that can be set to WebApplication or GlobalAssemblyCache to specify whether the assembly should be deployed to the in directory or the GAC, respectively. The SafeControl element is used to specify an entry in the web.config file for the assembly. Once you have the manifest.xml file created, you can create a deployment package for the web part.

Here are the steps to follow to create a deployment package:

Note

If you are strictly following this text, you may not have developed your first web part yet. If so, complete this series of steps after you finish the exercise at the end of the chapter.

  1. Start Visual Studio.

  2. Open a solution containing a web part project.

  3. From the Visual Studio main menu, select File

    A Typical manifest.xml File
  4. Click the Setup and Deployment Projects folder.

  5. Select to create a new CAB project.

  6. Name the project and click OK.

  7. In the Solution Explorer, right-click the CAB project and select Add

    A Typical manifest.xml File
  8. In the Add Project Output Group dialog box, select the web part project you want to deploy from the Project drop-down list.

  9. In the configuration drop-down list, select Release Any CPU.

  10. In the Project list box, select Primary Output to include the assembly in the cabinet file.

  11. Click OK.

  12. In the Solution Explorer, right-click the CAB project again and select Add

    A Typical manifest.xml File
  13. In the Add Project Output Group dialog box, select the web part project you want to deploy.

  14. In the configuration drop-down list, select Release Any CPU.

  15. In the Project list box, select Content Files to include the manifest.xml file.

  16. Click OK.

  17. Build the cabinet project.

Once the cabinet file is created, you should rename it to have a WSP extension. Then you may add it to the solution store, which is a central location for deploying solutions to web applications. In order to add your solution to the store, you must use the administration tool StsAdm.exe. The tool is located in the directory Program FilesCommon FilesMicrosoft Sharedweb server extensions12in. Use the following command to add a solution to the solution store:

Stsadm.exe -o addsolution -filename mypart.wsp

Once your solution is added to the store, you can deploy it to any web application. Open the Central Administration web site and click the Solution Management link on the Operations tab. The Solution Management page displays all of the solutions in the store. Figure 10-4 shows the Solution Management page in the Central Administration web site.

The solution store

Figure 10.4. The solution store

From the Solution Management page, you can click any solution and then choose to deploy it. Deploying a solution from the solution store is much easier than copying the same files repeatedly to different web applications. Additionally, the Solution Management page can be used to remove a solution from a web application.

Using Custom Web Parts

Regardless of whether you edit the web.config file and copy the assembly by hand or use a solution package, a custom web part is not available for use until it is added to the Web Parts Gallery. You can access the Web Parts Gallery from the Site Settings page associated with the top-level site in a collection. This gallery shows all of the web parts that are available for use in the site collection.

You can add your custom web part to the gallery by clicking the New button, which will open the New Web Parts page. Any web part deployed manually or using a solution will appear on this page. You simply check the web parts that you want to add to the gallery, and then click the Populate Gallery button. After this, the selected web parts will be available for use on a web page. Figure 10-5 shows the New Web Parts page.

Adding web parts to the gallery

Figure 10.5. Adding web parts to the gallery

Using Client-Side Script

Utilizing client-side script in a web part opens new development possibilities that include interacting with a rendered SharePoint page, incorporating Asynchronous JavaScript and XML (AJAX) solutions, and improving the user experience through dynamic HTML (DHTML). While an exhaustive examination of each of these techniques is beyond the scope of this book, it is pretty easy to incorporate some basic scripts.

Adding scripts to a page is accomplished using the ClientScriptManager object. Using this object, you can add either a block of script to the page or include a separate script file. The ClientScriptManager object includes the methods RegisterClientScriptBlock and RegisterClientScriptInclude for adding blocks and files, respectively.

You can use the ClientScriptManager object by getting a reference from the ClientScript property of the Page object. Typically, you will then call the IsClientScriptBlockRegistered method or the IsClientScriptIncludeRegistered method using a unique key for the script. If a script with that key has not already been registered, you proceed to register your own script.

When you register a script block, the code is inserted directly into the page. When you register a script file, on the other hand, a JavaScript include directive is created in the page. Listing 10-9 shows the code for two web parts; the first web part uses a script block, and the second web part uses an included file.

Example 10.9. Using Scripts

//This web part generates a script block that looks like this
/*
<script type="text/javascript">
 <!--
  code
 // -->
</script>
 */

namespace SPScriptBlock
{
    public class Block:WebPart
    {
        string m_scriptBlock = "";
        string m_scriptKey = "scriptKey";

        //Key Property
        [Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
        WebDisplayName("Script Key"),
        WebDescription("A unique key for the script.")]
        public string ScriptKey
        {
            get { return m_scriptKey; }
            set { m_scriptKey = value; }
        }

        //Script Property
        [Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
        WebDisplayName("Script"),
        WebDescription("The JavaScript to insert in the page.")]
        public string Script
        {
            get { return m_scriptBlock; }
            set { m_scriptBlock = value; }
        }
protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (m_scriptBlock != "" &&
               !Page.ClientScript.IsClientScriptBlockRegistered(m_scriptKey))
                Page.ClientScript.RegisterClientScriptBlock(
                  typeof(string), m_scriptKey, m_scriptBlock, true);

        }

    }
}

//This part creates the include directive
//<script src="{js file}" type="text/javascript"></script>

namespace SPScriptInclude
{
    public class Loader:WebPart
    {
        string m_scriptFile = "";
        string m_scriptKey = "scriptKey";

        //Key Property
        [Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
        WebDisplayName("Script Key"),
        WebDescription("A unique key for the script.")]
        public string ScriptKey
        {
            get { return m_scriptKey; }
            set { m_scriptKey = value; }
        }

        //Script Property
        [Personalizable(PersonalizationScope.Shared), WebBrowsable(true),
        WebDisplayName("Script File"),
        WebDescription("The name of the JavaScript file to insert in the page.")]
        public string ScriptFile
        {
            get { return m_scriptFile; }
            set { m_scriptFile = value; }
        }


        protected override void OnPreRender(EventArgs e)
        {
base.OnPreRender(e);

            if (m_scriptFile != "" &&
 !Page.ClientScript.IsClientScriptIncludeRegistered(m_scriptKey))
                Page.ClientScript.RegisterClientScriptInclude(m_scriptKey,
 Page.ResolveClientUrl(m_scriptFile));


        }

    }
}

Building Connectable Web Parts

The philosophy behind the use of web parts in SharePoint is that end users should be able to access information and assemble views without having to rely upon programmers to create custom web pages. One of the ways that this philosophy is put into action is through the use of web part connections. Connecting web parts in the portal allows a value from one web part to be used as an input, a sort, or a filter for the display of another web part. In Chapter 5, you saw this functionality from the end user perspective.

When you create connectable web parts, you can design them to use a custom interface of your own making or a standard set of interfaces provided by SharePoint. Using your own custom interfaces is significantly easier to implement than using the standard set of SharePoint interfaces. However, your own custom interfaces will be limited to connecting with other web parts that utilize the same interface. If you want to connect with existing SharePoint web parts, lists, and libraries, you must implement the standard set of SharePoint interfaces.

Building Custom Connection Interfaces

When you build custom connection interfaces, you start by defining an interface that returns the type of data that you want to pass from one part to another. There are no special requirements for this interface, and it looks like any you would define. The following code shows a simple interface defined to pass a string from one web part to another:

public interface IStringConnection
{
    string ProvidedString { get;}
}

The provider web part, which supplies the value to be passed, will implement the custom connection interface. Along with the implementation of the interface, the provider web part exposes a property that returns a reference to the connectable interface. The property must then be decorated with the ConnectionProvider attribute, which marks the interface as connectable within the web part infrastructure. Listing 10-10 shows how a web part class would implement an interface and expose it for connections.

Example 10.10. Implementing a Custom Interface

public class StringProvider:WebPart,IStringConnection
{

    protected string m_string = "Test Data";

    [ConnectionProvider("String Provider")]
    public IStringConnection ConnectionInterface()
    {
        return this;
    }

    //The passed value
    public string ProvidedString
    {
        get { return m_string; }
    }
}

The consumer web part, which will receive the passed value, must contain a method for receiving the connection interface. This method is also decorated with the ConnectionProvider attribute. This attribute determines which web parts on a page are compatible for connections. Listing 10-11 shows how a web part class would obtain a reference to the connectable interface and receive the passed value.

Example 10.11. Receiving a Value

public class SPStringConsumer : WebPart
{

    IStringConnection m_providerPart = null;

    [ConnectionConsumer("String Consumer")]
    public void GetConnectionInterface(IStringConnection providerPart)
    {
        m_providerPart = providerPart;
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
      try
      {
         writer.Write(m_providerPart.ProvidedString);
      }
catch
      {
         writer.Write("No connection.");
      }
    }
}

Once you have created and deployed compatible web parts, you can add them to a page. After they are on the page, they can be connected using the web part menu associated with either the provider or the consumer. When compatible web parts are on the same page, a menu item titled Connections will be available for selecting a compatible web part. This menu item functions identically to the ones you have used previously in the book to connect out-of-the-box web parts.

Using Standard Connection Interfaces

While creating custom interfaces is a quick way to connect web parts, they cannot be used to connect to libraries, lists, or out-of-the box web parts. SharePoint provides a separate set of interfaces that constitute a standard mechanism for connecting web parts. These interfaces expose methods and events that allow the connection infrastructure to query your web parts for appropriate connection information and provide notification when another web part wants to connect. The available interfaces support passing a single piece of data, a row of data, or an entire list of data. Table 10-1 lists the available interfaces and their purposes.

Table 10.1. Connection Interfaces

Interface

Purpose

IWebPartField

Allows a web part to provide or consume a cell of data

IWebPartRow

Allows a web part to provide or consume a row of data

IWebPartTable

Allows a web part to provide or consume a recordset of data

IWebPartParameters

Allows a web part to provide or consume multiple cells of data that can be mapped to the data cells in another web part

If a provider web part and a consumer web part are exchanging similar datasuch as a single cellthey may be connected directly using the web part menu associated with either part. Connection interfaces, however, can often allow connections that are not immediately obvious. For example, a web part that provides an entire row can be connected to a web part that only consumes a single field.

Determining which interfaces are compatible is handled by the web part infrastructure according to several rules. The first, and most obvious, rule is that web parts providing or consuming the same data structures are always compatible. For interfaces that use different data structures, extended connectionsknown as transformersare allowed where they make sense. The web part infrastructure implements a selection dialog that allows end users to map fields from compatible interfaces when necessary. Figure 10-6 shows a typical field selection dialog in SharePoint.

Connecting web parts in SharePoint

Figure 10.6. Connecting web parts in SharePoint

Building a Provider Web Part

You begin the process of creating a provider web part by deciding what kind of data it will expose. Based on this decision, your web part will implement the appropriate interface from Table 10-1. The code is very similar to creating your own custom interface except there are a couple of extra methods. Listing 10-12 shows the basic structure for stubbing out providers based on each of the available interfaces.

Example 10.12. Stubbing Out the Provider

public class FieldProviderStub:WebPart,IWebPartField
{
    [ConnectionProvider("Data")]
    public IWebPartField ConnectionInterface()
    {
        return this;
    }

    public void GetFieldValue(FieldCallback callback)
    {
    }

    public PropertyDescriptor Schema
    {
        get {}
    }

}


public class RowProviderStub:WebPart,IWebPartRow
{
    [ConnectionProvider("Data")]
    public IWebPartRow ConnectionInterface()
    {
        return this;
    }
public void GetRowData(RowCallback callback)
    {
    }

    public PropertyDescriptorCollection Schema
    {
        get {}
    }

}


public class TableProviderStub:WebPart,IWebPartTable
{
    [ConnectionProvider("Data")]
    public IWebPartTable ConnectionInterface()
    {
        return this;
    }

    public void GetTableData(TableCallback callback)
    {
    }

    public PropertyDescriptorCollection Schema
    {
        get {}
    }

}


public class ParameterProviderStub:WebPart,IWebPartParameters
{
    [ConnectionProvider("Data")]
    public IWebPartParameters ConnectionInterface()
    {
        return this;
    }

    public void GetParametersData(ParametersCallback callback)
    {

    }
public PropertyDescriptorCollection Schema
    {
        get {}
    }

    public void SetConsumerSchema(PropertyDescriptorCollection schema)
    {
    }
}

In just the same way as you did for custom interfaces, the provider must supply a reference to itself that is returned through a method decorated with the ConnectionProvider attribute. This reference is used by the web part infrastructure when connecting web parts.

Each provider must also implement a method for receiving a reference to the consumer web part. These methods are GetFieldValue, GetRowData, GetTableData, and GetParametersData, depending upon which interface you are implementing. During the connection process, the web part infrastructure will pass a callback reference in that you can use later to pass data to the consumer web part.

Each provider web part must also implement a method for exposing the schema for the data that will be returned. The method returns a System.ComponentModel.PropertyDescriptor if you are only passing a cell of data, or a System.ComponentModel.PropertyDescriptorCollection if you are passing a row, a table, or parameters. The returned schema can subsequently be used by the consumer to validate the passed data and ensure that it is meaningful.

The best way to implement these methods is to create a web part property for each piece of data that you want to make available. When you make a web part property, you can then use the System.ComponentModel.TypeDescriptor object to return information about the properties you want to expose. The following code shows how to return the schema for a LastName property.

public PropertyDescriptor Schema
{
    get { return TypeDescriptor.GetProperties(this)["LastName"];}
}

If you are implementing the IWebPartParameters interface, you will also have to create a SetConsumerSchema method. This method is called by the consumer web part during the connection process to pass the schema of the properties it has available for mapping. The provider can use this information to validate that the requested data is meaningful.

The data itself is transferred between the web parts on the server when the page is submitted. In the section "Understanding the Connection Life Cycle," I look in detail at the web part life cycle and how it is affected by connections. For now, examine Listing 10-13, which shows a complete provider web part that exposes a text value entered into a field.

Example 10.13. A Complete Provider Web Part

public class TextProvider : WebPart, IWebPartField
{
    //Member variables
    protected Button button = null;
    protected TextBox text = null;
    string m_data = null;

    //Text Property
    [Personalizable(PersonalizationScope.Shared), WebBrowsable(false),
    WebDisplayName("Text"),
    WebDescription("The text to send")]
    public string Text
    {
        get { return m_data; }
        set { m_data = value; }
    }

    //Child controls
    protected override void CreateChildControls()
    {
        button = new Button();
        button.Text = "Send Data";
        button.Click += new EventHandler(button_Click);
        Controls.Add(button);

        text = new TextBox();
        Controls.Add(text);
    }

    //Show UI
    protected override void RenderContents(HtmlTextWriter writer)
    {
        button.RenderControl(writer);
        text.RenderControl(writer);
    }

    //The connection description
    [ConnectionProvider("Text")]
    public IWebPartField ConnectionInterface()
    {
        return this;
    }
//Callback object
    public void GetFieldValue(FieldCallback callback)
    {
        //Send data to consumer
        callback.Invoke(text.Text);
    }

    //Publish schema
    public PropertyDescriptor Schema
    {
        get
        {return TypeDescriptor.GetProperties(this)["Text"];}
    }

    void button_Click(object sender, EventArgs e)
    {
        m_data = text.Text;
    }

}

Building a Consumer Web Part

When you create a consumer web part, you must first determine what kind of data structure it will receive. However, you do not have to implement any specific interface to use the data structure. Instead, you create a method decorated with the ConnectionProvider attribute to receive a reference to the provider part. Additionally, you must register a callback method with the provider part so the consumer can receive the data structure. This is done using delegates that are defined in the WebParts namespace. Listing 10-14 shows code stubbed out for the different data structures that consumers can receive.

Example 10.14. Stubbing Out the Consumer

public class FieldConsumerStub:WebPart
{

    [ConnectionConsumer("Field")]
    public void GetConnectionInterface(IWebPartField providerPart)
    {
        FieldCallback callback = new FieldCallback(ReceiveField);
        providerPart.GetFieldValue(callback);
     }

    public void ReceiveField(object field)
    {
    }

}
public class RowConsumerStub:WebPart
{
    [ConnectionConsumer("Row")]
    public void GetConnectionInterface(IWebPartRow providerPart)
    {
        RowCallback callback = new RowCallback(ReceiveRow);
        providerPart.GetRowData(callback);
    }

    public void ReceiveRow(object row)
    {
    }
}


public class TableConsumerStub:WebPart
{
    [ConnectionConsumer("Table")]
    public void GetConnectionInterface(IWebPartTable providerPart)
    {
        TableCallback callback = new TableCallback(ReceiveTable);
        providerPart.GetTableData(callback);
    }

    public void ReceiveTable(ICollection table)
    {
    }
}


public class ParametersConsumerStub:WebPart
{
    [ConnectionConsumer("Parameters")]
    public void GetConnectionInterface(IWebPartParameters providerPart)
    {
        //Specify what properties this part can map
        PropertyDescriptor[] property =
        {TypeDescriptor.GetProperties(this)["propertyName"]};
        PropertyDescriptorCollection schema =
        new PropertyDescriptorCollection(property);
        providerPart.SetConsumerSchema(schema);

        //Give provider reference to callback function
        ParametersCallback callback = new ParametersCallback(ReceiveParameters);
        providerPart.GetParametersData(callback);
    }
public void ReceiveParameters(IDictionary parameters)
    {
    }
}

Understanding the Connection Life Cycle

The life cycle of parts involved in a connection follows the same events as an unconnected web part, with a few additions. The key new events occur when the connection interface is queried and the callback object is passed to the provider. The exact sequence of events that occurs during a connection is critical because certain values and objects may not exist until a specific time, and calling to them before they are created will result in errors.

The connection process begins when you choose to connect two web parts using the web part menu. When you make your selection, the current page is posted back and both the provider and the consumer execute the OnInit, LoadViewState, and OnLoad methods. So far, this is identical to the life cycle of an unconnected part.

After the web parts are loaded, the connection infrastructure queries the connection interfaces to get a reference to the provider web part and pass it to the consumer web part. The consumer web part then responds by sending the callback object to the provider. If the consumer expects to receive parameters, it may also publish its schema using the SetConsumerSchema method. At this point, the provider may immediately use the callback method to send a value.

Once references and data have been exchanged, the life cycle proceeds as normal. The CreateChildControls, OnPreRender, SaveViewState, RenderContents, OnUnload, and Dispose methods are executed. The consumer web part may use the passed data to affect its output as necessary.

After the connection is made, subsequent postbacks follow a slightly different sequence. Just like an unconnected web part, the connected web parts will execute OnInit, LoadViewState, CreateChildControls, and OnLoad. Now the sequence becomes interesting because the provider web part will proceed to execute any control events such as the Click method, but this will occur before the connection infrastructure provides the reference to the consumer web part.

It is important to understand that the references between the provider and the consumer are not persistent. They are reestablished by the connection infrastructure during each postback. Therefore, you cannot send data from provider to consumer until the reference is reestablished. This means that you cannot write code in control events to send data to the consumer, which is what you naturally want to do. Instead, use your control events to store data in the viewstate or another control. Then you can send the stored data as the source for the consumer. Listing 10-13 earlier shows this exact technique for a provider web part. At the end of this chapter, you will also find a complete exercise for creating connected web parts.

Custom Editor Parts

Throughout our investigation of web parts, you have used properties to configure the parts within SharePoint. The web parts you have created have supported fundamental types such as String and Boolean. The tool pane in SharePoint automatically creates the appropriate user interface elementcalled an editor partfor these basic properties in the tool pane. For example, the tool pane uses a text box editor part for String properties and a check box editor part for Boolean properties.

There may be times, however, when you want to create more complex properties. In these cases, you may need to create your own custom editor parts to allow the end user to set the properties of your web part. These custom editor parts allow you significant control over how your web parts are configured.

Creating an Editor Part

To create a custom editor part, you need to build a new class that inherits from the EditorPart class. Because an editor part is essentially a specialized web part that runs in the tool pane of SharePoint, you will find that you use many of the same skills to build an editor part that you used previously to build web parts.

Just like a standard web part, editor parts must override the CreateChildControls method to build a user interface. You draw the user interface by overriding the RenderContents method in the same way you would for a web part. When the user interface is drawn, the child controls show up in the property pane.

What makes an editor part different from a standard web part is that it has methods that allow it to receive events from the property pane in SharePoint. These events are fired whenever a user clicks Apply, OK, or Cancel in the tool pane. The EditorPart class allows your custom editor part to receive these events through the ApplyChanges and SyncChanges methods.

The ApplyChanges method is called by the web part infrastructure whenever a user clicks Apply or OK. In this method, you retrieve the new value of the property as it was entered into the property pane by the end user. You must in turn pass the property to the web part so that it can update its own display. In order to pass a value from the property pane to the web part, you must retrieve a reference to the web part using the WebPartToEdit property.

After any changes are made in the property pane, the web part infrastructure calls the SyncChanges method. This method is used to pass changes back from the web part to the property pane. This is necessary because the web part and the property pane can be out of sync if the user cancels an action or if there is a validation error you need to report to the user. Listing 10-15 shows a complete editor part for a custom phone number field that uses a regular expression to ensure the phone number is formatted correctly.

Example 10.15. A Custom Editor Part

public class PhoneEditor:EditorPart
{
    TextBox property = null;
    Label messages = null;

    protected override void CreateChildControls()
    {
        property = new TextBox();
        Controls.Add(property);

        messages = new Label();
        Controls.Add(messages);
    }
protected override void RenderContents(HtmlTextWriter writer)
    {
        property.RenderControl(writer);
        writer.Write("<br/>");
        messages.RenderControl(writer);
    }

    public override bool ApplyChanges()
    {
        try
        {
            Regex expression = new Regex(@"(ddd)sddd-dddd");
            Match match = expression.Match(property.Text);
            if (match.Success == true)
            {
                ((PhoneLabel)WebPartToEdit).Phone = property.Text;
                messages.Text = "";
            }
            else
            {
                ((PhoneLabel)WebPartToEdit).Phone = "Invalid phone number";
            }
        }
        catch (Exception x)
        {
            messages.Text += x.Message;
        }
        return true;
    }

    public override void SyncChanges()
    {
        try
        {
            property.Text = ((PhoneLabel)WebPartToEdit).Phone;
        }
        catch { }
    }

}

Using an Editor Part

Once you have created a custom editor part, you must associate it with a web part. This is accomplished by overriding the CreateEditorParts method of the web part. When the CreateEditorParts method is called, you can create instances of any editor parts you want to load into the property pane. After the editor parts are created, you must add them to a new EditorPartCollection and return the collection. Listing 10-16 shows a complete web part for displaying a phone number with a custom format.

Example 10.16. Using a Custom Editor Part

public class PhoneLabel:WebPart
{
    protected string m_phone;

    [Personalizable(PersonalizationScope.Shared), WebBrowsable(false),
    WebDisplayName("Phone"),
    WebDescription("Phone number")]
    public string Phone
    {
        get { return m_phone; }
        set { m_phone = value; }
    }

    public override EditorPartCollection CreateEditorParts()
    {
        ArrayList partsArray = new ArrayList();

        PhoneEditor phonePart = new PhoneEditor();
        phonePart.ID = this.ID + "_editorPart1";
        phonePart.Title = "Phone Number";
        phonePart.GroupingText = "(xxx) xxx-xxxx";
        partsArray.Add(phonePart);

        EditorPartCollection parts = new EditorPartCollection(partsArray);
        return parts;
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write("<p>" + m_phone + "</p>");
    }
}

Exercise 10.1. A Complete Web Part

Throughout this chapter, you have examined all the aspects of creating custom web parts. In this exercise, you'll bring together several of the concepts into a complete web part. This web part will be used to access a database and display the results in a table.

Installing the AdventureWorks Database

You'll need a sample database for creating this web part. If you followed the setup instructions in Chapter 2, you should have SQL Server 2005 available to you. Unfortunately, SQL Server 2005 does not ship with a sample database. Therefore, you'll have to install a sample database before you can get started.

Follow these steps to install the sample database:

  1. Log in to VSSQL as an administrator.

  2. Open the browser and navigate to http://go.microsoft.com/fwlink/?linkid=31046.

  3. Run the AdventureWorksDB.msi file.

  4. Select Start

    Installing the AdventureWorks Database
  5. Connect to the default instance of SQL Server running on VSSQL.

  6. In SQL Server Management Studio, right-click the Databases folder and select Attach from the context menu.

  7. In the Attach Databases dialog, click the Add button.

  8. In the Locate Database Files dialog, select the AdventureWorks_Data.mdf file and click the OK button.

  9. In the Attach Databases dialog, click the OK button.

  10. Close SQL Server Management Studio.

Creating the New Project

Web parts are created as classes in Visual Studio 2005. In order to create the web part, you'll need to create a class and set some references to namespaces you will use. After creating the project, you'll import the referenced namespaces.

Follow these steps to create the new project:

  1. Open Visual Studio .NET 2005.

  2. Select File

    Creating the New Project
  3. In the New Project dialog, select the Visual C# folder.

  4. From the project items, select Class Library.

  5. Name the new project SPDataPart.

  6. Click OK.

  7. In the Solution Explorer, right-click the file Class1.cs and select Rename from the context menu.

  8. Rename this file Adventure.cs.

  9. In the source code, have the Adventure class inherit from the WebPart class as shown in the following code:

    public class Adventure:WebPart
  10. Select Project

    Creating the New Project
  11. In the Add Reference dialog, select System.Web and System.Drawing from the component list.

  12. Click the OK button.

  13. Add the following statements to the top of the class definition to reference the required namespaces:

using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;

Coding the Web Part

Once you have the basic class stubbed out, you can proceed to code the web part. In this exercise, you'll add a property and some child controls to the web part. You'll use the properties and controls to query a database and display a result set.

Defining the Properties

The web part only requires a single property for defining the database connection string. Once deployed, you will have to set this string inside of SharePoint. Although we are using a connection string for simplicity, you should be careful about saving sensitive user and password information in a web part property. SharePoint has a single sign-on service that you'll learn about in Chapter 11, which is the appropriate place for storing credentials.

Add the following code to the Adventure class to define the Connection property:

//Member variables for properties
protected string m_connection;

//Connection Property
[Personalizable(PersonalizationScope.Shared),WebBrowsable(true),
WebDisplayName("Connection String"),
WebDescription("The connection string for the AdventureWorks database")]
public string Connection
{
    get{return m_connection;}
    set{m_connection=value;}
}

Defining the Child Controls

In order to use existing ASP.NET controls in your new web part, you must override the CreateChildControls method. In this method, you programmatically create a new instance of each child control, adjust its properties, and add it to the Controls set for the web part. Listing 10-17 shows how to create the child controls for the web part.

Example 10.17. Defining the Child Controls

DataGrid grid;
Label messages;

protected override void CreateChildControls()
{
    //Add grid
    grid = new DataGrid();
    grid.AutoGenerateColumns = false;
    grid.Width = Unit.Percentage(100);
    grid.GridLines = GridLines.Horizontal;
    grid.HeaderStyle.CssClass = "ms-vh2";
    grid.CellPadding = 2;

    //Grid columns
    BoundColumn column = new BoundColumn();
    column.DataField = "FullName";
    column.HeaderText = "Associate";
    grid.Columns.Add(column);
column = new BoundColumn();
    column.DataField = "Title";
    column.HeaderText = "Title";
    grid.Columns.Add(column);

    column = new BoundColumn();
    column.DataField = "SalesTerritory";
    column.HeaderText = "Territory";
    grid.Columns.Add(column);

    column = new BoundColumn();
    column.DataField = "2002";
    column.HeaderText = "2002";
    column.DataFormatString = "{0:C}";
    column.ItemStyle.BackColor = Color.Wheat;
    grid.Columns.Add(column);

    column = new BoundColumn();
    column.DataField = "2003";
    column.HeaderText = "2003";
    column.DataFormatString = "{0:C}";
    grid.Columns.Add(column);

    column = new BoundColumn();
    column.DataField = "2004";
    column.HeaderText = "2004";
    column.DataFormatString = "{0:C}";
    column.ItemStyle.BackColor = Color.Wheat;
    grid.Columns.Add(column);

    Controls.Add(grid);

    //Add label
    messages = new Label();
    Controls.Add(messages);

}

Rendering the Web Part

Rendering the web part is done in the RenderContents method. In this exercise, you will create a layout table in HTML and then render the web part's constituent controls in the table. Layout tables are a good way to ensure that your web part looks good regardless of where it is positioned on the page.

Add the following code to the Adventure class to render the user interface:

protected override void RenderContents(HtmlTextWriter writer)
{
    DataSet dataSet = null;
    string sql = "SELECT     FullName, Title, SalesTerritory, " +
    "[2002], [2003], [2004] FROM Sales.vSalesPersonSalesByFiscalYears";

    //Get data
    using (SqlConnection conn = new SqlConnection(Connection))
    {
        try
        {
            conn.Open();
            SqlDataAdapter adapter = new SqlDataAdapter(sql, conn);
            dataSet = new DataSet("root");
            adapter.Fill(dataSet, "sales");

        }
        catch (SqlException x)
        {
            messages.Text = x.Message;
        }
        catch (Exception x)
        {
            messages.Text += x.Message;
        }

    }
//Bind data
    try
    {
        grid.DataSource = dataSet;
        grid.DataMember = "sales";
        grid.DataBind();
    }
    catch (Exception x)
    {
        messages.Text += x.Message;
    }

    //Display data
    writer.Write("<table border="0" width="100%">");
    writer.Write("<tr><td>");
    grid.RenderControl(writer);
    writer.Write("</td></tr>");
    writer.Write("<tr><td>");
    messages.RenderControl(writer);
    writer.Write("</td></tr>");
    writer.Write("</table>");

    }
}

Deploying the Web Part

In order to deploy the web part, you must give it a strong name and allow partially trusted callers to access the web part. Additionally, you must create a solution package so that administrators can control which web applications will use the web part. In the next section you will create a strong name for the web part, but for now, open the AssemblyInfo.cs file and add the attribute [assembly: System.Security.AllowPartiallyTrustedCallers()].

Creating a Strong Name

Web parts should have a strong name when used with SharePoint. In order to give the web part a strong name, you have to create a key pair file using the Strong Name tool, sn.exe. Once the strong name is created, you must create a reference to it in the assembly file. Although you can also create a strong name in Visual Studio for your assembly, it's easier to use the same key pair file for all your testing and debugging.

Perform these steps to create a strong name:

  1. Open a command window by selecting Start

    Deploying the Web Part
  2. In the command-line window, create a key file by executing the following line:

    sn.exe -k c:keypair.snk
  3. In Visual Studio right-click the SPDataPart project and select Properties from the context menu.

  4. In the Properties dialog, click the Signing tab.

  5. Check the box labeled Sign the Assembly.

  6. Select <Browse...> from the drop-down list on the Signing tab.

  7. In the Select File dialog, navigate to c:keypair.snk and select it.

  8. Click the Open button.

Creating a Manifest File

Even though the web part has compiled successfully, it cannot run in SharePoint until it is deployed to the target web application and marked as safe. While you can certainly accomplish these tasks manually, the best way to deploy a web part is with a solution file. The solution file will make the web part available for deployment through the Central Administration web site, which will allow it to be deployed to multiple web applications from a single location. In order to create a solution, you must first add a manifest file to the web part project. The manifest file contains the information necessary to deploy the web part and update the web configuration file.

Follow these steps to create the manifest file:

  1. In the Solution Explorer, right-click the project and select Add

    Deploying the Web Part
  2. In the Add New Item dialog, select XML File.

  3. Enter manifest.xml in the Name field and click the Add button.

  4. When the new file opens, add the following code and save the file.

<?xml version="1.0" encoding="utf-8" ?>
<Solution xmlns="http://schemas.microsoft.com/sharepoint/"
  SolutionId="AF597DAB-65D7-4c1a-A012-D04184CA647E">
    <Assemblies>
      <Assembly DeploymentTarget="WebApplication"
      Location="SPDataPart.dll">
       <SafeControls>
        <SafeControl
         Assembly="SPDataPart, Version=1.0.0.0, Culture=neutral,
         PublicKeyToken=8c9fc716f38d08b2"
         Namespace="SPDataPart" TypeName="*"/>
        </SafeControls>
      </Assembly>
    </Assemblies>
</Solution>

Note

Be sure to change the PublicKeyToken in the previous listing to match your assembly.

Creating a Solution Package

Once the manifest file is completed, you are ready to create a solution package. The solution package is essentially a cabinet file that contains the web part assembly and manifest file. Once created, you may add the solution to the Central Administration solution store where an administrator can determine which web applications can utilize the new web part.

Follow these steps to create the solution package:

  1. From the Visual Studio main menu, select File

    Deploying the Web Part
  2. Click the Setup and Deployment Projects folder.

  3. Select to create a new CAB Project.

  4. Name the project SPDataPartSolution and click OK.

  5. In the Solution Explorer, right-click the CAB project and select Add

    Deploying the Web Part
  6. In the Add Project Output Group dialog box, select SPDataPart from the Project drop-down list.

  7. In the configuration drop-down list, select (Active).

  8. In the project list box, select Primary Output to include the assembly in the cabinet file.

  9. Click OK.

  10. In the Solution Explorer, right-click the CAB project again and select Add

    Deploying the Web Part
  11. In the Add Project Output Group dialog box, select SPDataPart from the Project drop-down list.

  12. In the configuration drop-down list, select (Active).

  13. In the Project list box, select Content Files to include the manifest.xml file.

  14. Click OK.

  15. Build the Cabinet project, which should also build the web part project for you automatically.

  16. Once you have built the solution project, locate the SPDataPartSolution.CAB file and rename it to SPDataPartSolution.WSP.

  17. Run the following command-line operation to add the solution package to the solution store:

Stsadm.exe -o addsolution -filename SPDataPartSolution.wsp

Deploying the Web Part to a Web Application

Once the solution package has been added to the solution store, you can access it from the Central Administration web site. From the Central Administration web site, you may select to deploy the solution to a web application. Once deployed to a web application, a site collection administrator can make the web part available through the Web Parts Gallery.

Follow these steps to deploy the web part:

  1. Select Start

    Deploying the Web Part
  2. In the Central Administration web site, click the Operations tab.

  3. On the Operations page, click the Solution Management link under the Global Configuration section.

  4. On the Solution Management page, click the link for the spdatapartsolution.wsp package to open the Solution Properties page.

  5. On the Solution Properties page, click the Deploy Solution button.

  6. On the Deploy Solution page, click the OK button.

Using the Web Part

Once the web part is properly deployed to a web application, it can be used in a page. To use the web part, it must be added to the Web Parts Gallery. Once it's added, it can drag it onto a page and have its properties set.

Perform these steps to use the web part:

  1. Log in to the home page of a site collection as a site administrator.

  2. Select Site Settings

    Using the Web Part
  3. On the Site Settings page, click the Web Parts link under the Galleries section.

    Note

    If you do not see the Web Parts Gallery listed, you are not at the top-level site. Click the link titled Go to Top Level Site Settings.

  4. On the Web Part Gallery page, click the New button.

  5. On the New Web Parts page, check the box next to the SPDataPart.Adventure web part and click the Populate Gallery button.

  6. Navigate to the site where you want to use the web part.

  7. On the site, select Edit Page from the Site Actions menu.

  8. Click the Add a Web Part link in any zone.

  9. In the Add Web Parts dialog, check the Adventure web part and click the Add button.

  10. When the web part appears on the page, drop the Edit menu and select Modify Shared Web Part.

  11. Locate the Connection String property in the SPDataPart task pane and enter Data Source=VSSQL;Initial Catalog=AdventureWorks;Integrated Security=SSPI;. Figure 10-7 shows the final web part.

Note

Web parts that access databases require at least the WSS_Medium security policy in the web.config file. If you receive a security message from the web part, check the trust element in the web.config file.

The completed web part

Figure 10.7. The completed web part

Exercise 10.2. Connectable Web Parts

Connectable web parts allow you to use one web part to modify the behavior of another. A common use for connected web parts is for one web part to provide a filter value for the other. In this exercise, you will modify the web part from the previous exercise to consume a filter value. Then you will create a SharePoint contact list to filter the results by person. Because Exercise 10.1 is a prerequisite for this exercise, be sure you have it completed and running before you proceed.

Adding the Filter Property

After finishing Exercise 10.1, the Adventure web part will return an entire result set of information. In order to filter the results, you must add a property that can be used as a filter in the query. This property will filter by last name and be set by another web part. Therefore, the property will not be visible in the property pane.

Open the SPDataPart project and add the following code to the Adventure class to add the new LastName property for filtering:

//Filter value
string m_lastName = null;

//Filter Property
[Personalizable(PersonalizationScope.Shared), WebBrowsable(false),
WebDisplayName("Last Name"),
WebDescription("The last name to use as a filter")]
public string LastName
{
    get { return m_lastName; }
    set { m_lastName = value; }
}

Adding the Callback Method

Once the new property is added, you must add the code that will get a reference to the provider part and allow you to pass a reference to the callback function. Since this web part will only filter by last name, you will accept connections with providers that implement IWebPartField or an interface that can be transformed to provide a single value.

Add the following code to support these connections:

[ConnectionConsumer("Last Name")]
public void GetConnectionInterface(IWebPartField providerPart)
{
    FieldCallback callback = new FieldCallback(ReceiveField);
    providerPart.GetFieldValue(callback);
}

public void ReceiveField(object field)
{
    if(field!=null)
        LastName = field.ToString();
}

Modifying the SQL Statement

Once the web part connection is made, you will need to use the passed value to filter the SQL statement. In this exercise, you will simply change the SQL in the web part to use the passed value. Alter the SQL statement to appear as this:

string sql = "SELECT     FullName, Title, SalesTerritory, " +
"[2002], [2003], [2004] FROM Sales.vSalesPersonSalesByFiscalYears " +
"WHERE FullName LIKE '%" + LastName +"'";

Building and Deploying the Web Part

Once the web part modifications are made, build the part. If you followed the steps in the previous exercise, simply copy the new assembly into the in directory. There are no other steps required because you already gave it a strong name and modified the web configuration file.

Creating the Contact List

After the web part is deployed and functioning, you'll need to create a list to act as a filter. After you create the list, you'll connect it to the web part. Once connected, you can select items in the list and filter the data shown in the web part.

Follow these steps to create the list:

  1. On the same site as the web part, select Create from the Site Actions menu.

  2. On the Create page, click the Contacts link.

  3. On the New page, enter Sales Reps in the Name field and click the Create button.

  4. In the Quick Launch area of the site, click the Sales Reps link.

  5. On the Sales Reps page, add a contact entry for Michael Blythe, Jillian Carson, and Linda Mitchell.

  6. Return to the home page of the site and select Edit Page from the Site Actions menu.

  7. Click the link to Add a Web Part in any zone.

  8. In the Add Web Parts dialog, select the Sales Reps list and click the Add button.

  9. Using the web part menu associated with the Adventure web part, select Connections

    Creating the Contact List
  10. In the Configure Connection dialog, select Last Name from the drop-down list.

  11. Click the Finish button.

  12. Click the link titled Exit Edit Mode.

  13. You should now be able to select contacts in the list and filter the results in the data grid. Figure 10-8 shows the final connected web parts.

Connected web parts

Figure 10.8. Connected web parts

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.168.56