Walkthrough: Creating a Custom Project Server Web Part

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Microsoft Office Project Server 2007 uses Web Parts that are derived from the Windows SharePoint Services 3.0WebPart class, rather than from the Microsoft ASP.NET 2.0 WebPart class. This article shows how to develop, debug, install, and use custom Web Parts and associated tool panes that use the Project Server Interface (PSI) and integrate with Project Web Access. (The code samples in this article were contributed by Chris Boyd and Ken Stanfield, Microsoft Corporation.)

The Project 2007 Sample Code download includes two subdirectories in the Custom Web Parts directory named PWA Reference and No PWA Reference. The Microsoft Visual Studio 2005 solutions in each subdirectory are similar. Each solution creates a custom Web Part named Upcoming Tasks that uses the PSI to find the number of remaining tasks in a project. If you have modification permission for a page, you can install the Upcoming Tasks Web Part on a page in Project Web Access or in another site in the same Windows SharePoint Services farm.

Following are the differences between the sample projects:

  • PWA Reference   This solution requires a reference to the Microsoft.Office.Project.Server.PWA.dll assembly, which is not documented in the Project 2007 SDK. The tool pane for the Upcoming Tasks Web Part also requires that you configure the Web Part to select an existing Project Center Web Part on the same page. The Upcoming Tasks Web Part then dynamically reads a project name that you select in the ActiveX grid in Project Center.

  • No PWA Reference   This solution does not use a reference to the Microsoft.Office.Project.Server.PWA.dll assembly. The Upcoming Tasks Web Part is not connected to a Project Center Web Part on the page. It uses the PSI to get a list of available projects and allows you to select a project in the tool pane when you configure the Web Part.

This article contains the following sections:

  • Creating the Custom Web Part Assembly includes subsections for creating the WebPart class, creating the ToolPart class, and other requirements such as strong-naming the assembly and setting the assembly version number.

  • Installing the Custom Web Part Assembly

  • Using the Custom Web Part Assembly

  • Debugging the Custom Web Part Assembly

NoteNote

Most of the code examples in the following sections are from the PWARefWebParts sample. The procedures show where the code in the PWARefWebParts and NoPWARefWebParts solutions differs. The Project 2007 Sample Code download includes the complete source files for both solutions. The solutions require Visual Studio 2005 with the Microsoft .NET Framework 2.0.

Related video: For a video that shows, in 11 minutes, an overview of developing, installing, and using the NoPWARefWebParts sample application, see Creating Custom Web Parts for Project Server 2007. The video shows the development in Visual Studio using Visual Basic code, and the article that accompanies the video includes both the Visual Basic and Visual C# code.

Creating the Custom Web Part Assembly

The Upcoming Tasks Web Part is defined in the Microsoft Visual C# UpcomingTasksWP.cs class file. The tool part associated with the Upcoming Tasks Web Part is defined in the UTToolPart.cs file. You can modify the class files, compile your own version of the custom assembly, and then follow the procedure described in Installing the Custom Web Part Assembly to deploy the Web Part on a computer.

Creating the Web Part Class

The following procedures and code explain how to create the UpcomingTasksWP class that displays the Web Part content. The Web Part uses the WebPartPages namespace in the Microsoft.SharePoint.dll assembly.

To create a Web Part class:

  1. Create a class library project and solution in Visual Studio, for example, PWARefWebParts.

  2. Rename the Class1.cs file to match the name of the Web Part class you will create, for example, UpcomingTasksWP.cs.

  3. Add references to the following assemblies: System.Drawing.dll, System.Web.dll, and System.Web.Services.dll. Add a reference to the Microsoft.SharePoint.dll assembly in the following directory, or copy the assembly to your development computer.

    [Program Files]\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI

  4. If you plan to integrate with other Project Server Web Parts on the same page, add a reference to the Microsoft.Office.Project.Server.PWA.dll assembly.

    1. To find the assembly in Windows Explorer and copy it to your development computer, run the following in a Command Prompt window.

      regsvr32 /u [Windows]\Microsoft.NET\Framework\v2.0.50727\shfusion.dll

    2. Copy the assembly from the following directory.

      [Windows]\assembly\GAC_MSIL\Microsoft.Office.Project.Server.PWA\12.0.0.0__71e9bce111e9429c

    3. Restore the default assembly view by re-registering shfusion.dll.

      regsvr32 [Windows]\Microsoft.NET\Framework\v2.0.50727/shfusion.dll

  5. Add a Web references for each PSI Web service that the Web Part needs.

    NoteNote

    If you are compiling the sample applications in the SDK download, delete the Web references and then add them again with the same namespace names. This ensures that wsdl.exe generates proxy code from the installed version of Project Server.

    1. In Solution Explorer, right-click the References node, and then click Add Web Reference.

    2. Type the URL in the Add Web Reference dialog box, for example:

      http://ServerName/ProjectServerName/_vti_bin/psi/project.asmx

    3. Rename the Web reference, for example, ProjectWS, and then click Add.

  6. Edit UpcomingTasksWP.cs to create the UpcomingTasksWP class. In the following code, gridID, numTasks, and projWS are private fields for class properties. The gridID field is not needed in the NoPWARefWebParts solution.

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Xml.Serialization;
    using System.Net;
    using System.Data;
    using Microsoft.SharePoint.WebPartPages;
    
    namespace PWARefWebParts
    {
    //[DefaultProperty("Text")]
       //[ToolboxData("<{0}:UpcomingTasksWP runat=server></{0}:UpcomingTasksWP>")]
        [XmlRoot(Namespace = "PWARefWebParts")]
    
    public class UpcomingTasksWP : WebPart
    {
            private const string PROJECT_SERVER_URI = "http://localhost/pwa"; // Change to your value
            private const string PROJECT_WEBSERVICE = "/_vti_bin/psi/project.asmx";
            private string gridID;                    // ID of the ActiveX grid in the selected 
                                                      //     Project Center Web Part
            private int numTasks;                     // Number of upcoming tasks to display
            private DataGrid displayGrid;             // The data grid to display in the Web Part
            private ProjectWS.Project projWS = null;  // Project object in the Web service        
    
            public UpcomingTasksWP()
            {    
            }
        }
    

    If you use the Visual Studio Web Part template to create the project, it adds three class attributes for Windows SharePoint Services 2.0. In the Visual Studio design environment, when you drag an .aspx or .ascx file onto a page or a user control, the DefaultProperty and ToolboxData attributes govern behavior. DefaultProperty specifies the property that is selected by default in the Visual Studio Property pane. The tag prefix that is registered for the control in the Page directive (or the Control directive for a user control) replaces the {0} token in the ToolboxData attribute.

    To import a Web Part, you must define an XML namespace for all of the Web Part properties. You can use the XmlRoot attribute in the class definition or an XmlElement attribute for each property in the class. The XML namespace can be any value you want; it is generally given the namespace of the custom Web Part.

  7. Add public class properties for access by other classes in the Web Part, such as a class for the tool part. The NoPWARefWebParts solution does not need the GridID property, and adds a ProjectName property.

    #region Properties
    // ProjectWS handles the connection to the Project Web Service. 
    // It is a public property so that other classes can use the same connection.
    public ProjectWS.Project ProjectWS
    {
        get {
                // If we are not connected to a project server, make the connection.
                if (projWS == null)
                {
                    projWS = new ProjectWS.Project();
                    projWS.Url = "http://localhost/projectserver/" + PROJECTWEBSERVICE;
                    projWS.CookieContainer = new CookieContainer();
                    projWS.Credentials = CredentialCache.DefaultCredentials;
                }
                return projWS; 
            }
        }
    
    // These properties are exposed so the tool part can update
    // the selected Project Center grid and the number of tasks to show.
    
    [   Browsable(false),
        Category("Miscellaneous"),
        WebPartStorage(Storage.Personal)
    ]
    public string GridID
    {
        get { return gridID; }
        set { gridID = value; }
    }
    
    [   Browsable(false),
        Category("Miscellaneous"),
        WebPartStorage(Storage.Personal)
    ]
    public int NumDisplayTasks
    {
        get { return numTasks; }
        set { numTasks = value; }
    }
    /* Add the following only in the NoPWARefWebParts solution.
    [   Browsable(false),
        Category("Miscellaneous"),
        WebPartStorage(Storage.Personal)
    ]
    public string ProjectName
    {
        get { return projectName; }
        set { projectName = value; }
    } 
    */
    #endregion
    
  8. Override the WebPart.GetToolParts method to specify the order of tool parts in the tool part pane. For more information, see the GetToolParts method in the Windows SharePoint Services 3.0 SDK.

    // To use our own tool part, we must override the GetToolParts method.
    // The CustomPropertyToolPart is a container for the dynamic drop-down list of 
    // Project Center Web Parts.
    public override ToolPart[] GetToolParts()
    {
        ToolPart[] toolParts = new ToolPart[3];
    
        toolParts[0] = new CustomPropertyToolPart();
        toolParts[1] = new WebPartToolPart();
        toolParts[2] = new UTToolPart();
    
        return toolParts;
    }
    

The remainder of the UpcomingTasksWP class adds controls to the Web Part. You can add or change controls in the custom Web Part by modifying the OnPreRender method.

To change or add controls to the Upcoming Tasks Web Part:

  1. Modify the OnPreRender method in the WebPart class. For example, the following override method adds script to the Web page that gets information from the Project Center Web Part. The script is constructed by the GetClientScript method, which is described later in this article. The method then adds custom controls for a title and a grid to the Web Part content. The GetWebPartTitle method defines the custom title control and also returns the selected project name.

    The LiteralControl additions insert raw HTML around the title control. Instead of using LiteralControl objects, you could include the HTML in the Label text of the GetWebPartTitle method.

    protected override void OnPreRender(EventArgs e)
    {
        string projName;
    
        this.Page.ClientScript.RegisterStartupScript(this.GetType(), 
            this.ID + "KEY_UTWPSCRIPT", GetClientScript(), false);
    
        Controls.Add(new LiteralControl("<b>"));
        Controls.Add(GetWebPartTitle(out projName));
        Controls.Add(new LiteralControl("</b><br/><br/>"));
        Controls.Add(GetWebPartContent(projName));
        base.OnPreRender(e);
    }
    
  2. To add the standard Web Part header and menus, call the OnPreRender method of the base class after you add custom controls.

The GetWebPartTitle method creates a custom control that uses a System.Web.UI.WebControls.Label control. When the project name is defined, the title includes the project name; otherwise, the title is for the Web Part configuration directions, which are defined in the GetWebPartContent method.

In the PWARefWebParts solution, the GetWebPartTitle method gets the project name from an HTTP request for the page when the user clicks a project row in the Project Center Web Part.

In the NoPWARefWebParts solution, the GetWebPartTitle method gets the project name from the ProjectName property of the UpcomingTasksWP class. The UIToolPart class for the tool pane sets the ProjectName property when the user configures the Upcoming Tasks Web Part. GetWebPartTitle returns a Control object as well as the projName string for use by the GetWebPartContent method.

[C#]

private Control GetWebPartTitle(out string projName)
{
    Control titleControl;
    Label titleControlLabel = new Label();
    titleControlLabel.ForeColor = Color.Blue;

    projName = Page.Request["UTWPProjName"];
    // The following line is for the NoPWARefWebParts solution.
    // projName = ProjectName;

    if (projName != null && projName != string.Empty)
    {
        titleControlLabel.Text = "Upcoming Tasks for Project: " + projName;
    }
    else
    {
        projName = string.Empty;
        titleControlLabel.Text = "Upcoming Tasks: Web Part Configuration Directions";
    }
    titleControl = titleControlLabel;
    return titleControl;
}

If there is no project name, Project Web Access has just shown the Project Center page, so the GetWebPartContent method creates a Label with the configuration instructions.

If the project name is defined, the user has clicked a project row (in the PWARefWebWebParts solution) or has selected a project in the tool part pane of the NoPWARefWebParts solution. GetWebPartContent creates a DataGrid object with rows for the uncompleted tasks.

The following code shows the basic outline of the GetWebPartContent method and the section that creates the webControlLabel object with configuration instructions.

When there is no project name, the method assigns webControlLabel to the webControl.

[C#]

private Control GetWebPartContent(string projName)
{
    int iTask = 0;
    Guid projGuid;
    bool isTasksUncompleted = false;
    ProjectWS.ProjectDataSet projectDS;

    DataRow taskDataRow;
    DataRow displayTaskDataRow;
    DataRow[] taskRows;
    DataTable displayTaskDT = new DataTable();

    Control webControl;
    Label webControlLabel = new Label();

    if (projName != string.Empty)
    {
        /* Add code to create the grid and add rows for uncompleted tasks.
         */
    }
    else // No project name is selected, so show instructions to configure the Web Part.
    {
        string labelText = "Click a project row in the associated ";
        labelText += "Project Center Web Part<br/>to see the upcoming tasks for a project.<br/>";
        labelText += "<br/>Configure this Web Part to set the associated Project Center Web Part<br/>";
        labelText += "and the number of tasks to display. The current number is ";
        labelText += NumDisplayTasks.ToString() + ".<br/>";

        webControlLabel.Text = labelText;
        webControl = webControlLabel;
    }
    return webControl;
}

For the code that creates the grid in the GetWebPartContent method, both solutions call the ReadProjectList method of the Project Web service to get a ProjectDataSet object with the names of all projects to which the user has access. Except for the text the Upcoming Tasks Web Part displays when it is initialized, the GetWebPartContent code is the same for both solutions.

The code performs the following steps:

  • Gets the GUID of the selected project.

  • Reads all of the project information into a ProjectDataSet variable named projectDS.

  • Creates a DataTable object named displayTaskDT and adds the columns to display in the grid.

  • Filters the uncompleted tasks into the taskRows variable.

  • Copies the specified number of uncompleted tasks to displayTaskDT.

  • Binds the displayTaskDT to the displayGridDataGrid object.

  • Assigns displayGrid to the webControl variable to show the tasks in the Web Part.

The following code creates the grid within the if (projName != string.Empty){ . . . } section of the GetWebPartContent method, in the PWARefWebParts solution.

[C#]

    displayGrid = new DataGrid();
    displayGrid.CellPadding = 3;

    // Get a DataSet of all the projects that the user has access to.
    ProjectWS.ProjectDataSet projectListDS = ProjectWS.ReadProjectList();

    DataRow[] projectRows = projectListDS.Project.Select("PROJ_NAME = '" + 
        projName + "'");

    projGuid = (Guid)projectRows[0]["PROJ_UID"];

    // Get a DataSet of all the tasks in a project.
    projectDS = ProjectWS.ReadProject(projGuid, 
        PWARefWebParts.ProjectWS.DataStoreEnum.PublishedStore);

    // Ignore the project summary task.
    if (projectDS.Task.Count > 1) 
    {
        // Create a table with just the three columns we want to display in the Web Part.
        displayTaskDT.Columns.Add(new DataColumn("Task Name", 
            projectDS.Task.Columns[projectDS.Task.TASK_NAMEColumn.ColumnName].DataType));
        displayTaskDT.Columns.Add(new DataColumn("Start", 
            projectDS.Task.Columns[projectDS.Task.TASK_START_DATEColumn.ColumnName].DataType));
        displayTaskDT.Columns.Add(new DataColumn("Finish", 
            projectDS.Task.Columns[projectDS.Task.TASK_FINISH_DATEColumn.ColumnName].DataType));

        // Filter out tasks that are already completed.
        taskRows = projectDS.Task.Select(projectDS.Task.TASK_PCT_COMPColumn.ColumnName + 
            " <> 100 AND " + 
            projectDS.Task.TASK_IS_SUMMARYColumn.ColumnName + " = 0", 
            projectDS.Task.TASK_FINISH_DATEColumn.ColumnName + " ASC");

        if (taskRows.Length > 0)
        {
            // Copy the number of tasks defined by the user into the DataTable object.
            while (iTask < taskRows.Length && iTask < NumDisplayTasks)
            {
                taskDataRow = taskRows[iTask];
                displayTaskDataRow = displayTaskDT.NewRow();

                displayTaskDataRow["Task Name"] =
                    taskDataRow[projectDS.Task.TASK_NAMEColumn.ColumnName];
                displayTaskDataRow["Start"] =
                    taskDataRow[projectDS.Task.TASK_START_DATEColumn.ColumnName];
                displayTaskDataRow["Finish"] =
                    taskDataRow[projectDS.Task.TASK_FINISH_DATEColumn.ColumnName];

                displayTaskDT.Rows.Add(displayTaskDataRow);

                iTask++;
            }
            isTasksUncompleted = true;
        }
    }

    if (isTasksUncompleted)
    {
        // Bind the DataTable object to the DataGrid object we display in the Web Part.
        displayGrid.DataSource = displayTaskDT;
        displayGrid.DataBind();

        displayGrid.HeaderStyle.BackColor = System.Drawing.Color.Beige;
        displayGrid.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
        displayGrid.BorderStyle = BorderStyle.None;

        webControl = displayGrid;
    }
    else
    {
        webControlLabel.Text = "<br/>This project has no upcoming tasks to display.<br/>";
        webControl = webControlLabel;
    } 

In the PWARefWebParts solution, the OnPreRender override method inserts the string from the GetClientScript method into the page. The string contains a script that gets the project name from the ActiveX grid in the Project Center Web Part. The NoPWARefWebParts solution does not use the GetClientScript method, because it does not communicate with the Project Center Web Part.

[C#]

        private string GetClientScript()
        {
            string sRetVal = String.Empty;
            
            sRetVal = String.Format(@"
<SCRIPT for={0} event='OnNewRow(y,PaneNum)' language='jscript'>
   location='{1}?UTWPProjName=' + {0}.GetCellValue(y, 'Project Name') + '&UTWPRow=' + y;
</SCRIPT>
",
               // Replaceable parameters
               GridID,    // {0}
               GetUrl()); // {1}

            return sRetVal;
        }

In the GetClientScript code, GridID is a property of the UpcomingTasksWP class. The tool part (UTToolPart) finds the value of GridID and sets the property when the user first configures the Upcoming Tasks Web Part. For example, if the GridID value is ctl00_m_g_cfef6446_3753_4c1d_a861_668b85258ab5_MSPJGrid and the value from GetUrl is /pwa/projects.aspx, then the OnPreRender method inserts the following script into the page before the browser renders it.

<SCRIPT for=ctl00_m_g_cfef6446_3753_4c1d_a861_668b85258ab5_MSPJGrid event='OnNewRow(y,PaneNum)' language='jscript'>
   location='/pwa/projects.aspx?UTWPProjName=' + ctl00_m_g_cfef6446_3753_4c1d_a861_668b85258ab5_MSPJGrid.GetCellValue(y, 'Project Name') + '&UTWPRow=' + y;
</SCRIPT>

The GetUrl method finds the '?' URL option in the PathAndQuery string, and then returns just the part of the URL that corresponds to the Project Web Access page. For example, if the complete URL of the page request is http://servername/pwa/projects.aspx?UTWPProjName=TestProj2&UTWPRow=3, the PathAndQuery string is /pwa/projects.aspx?UTWPProjName=TestProj2&UTWPRow=3, and the returned value is /pwa/projects.aspx.

private string GetUrl()
{
    string url;

    int questionIndex = Page.Request.Url.PathAndQuery.IndexOf('?');

    if (questionIndex > 0)
    {
        url = Page.Request.Url.PathAndQuery.Remove(questionIndex); 
    }
    else
    {
        url = Page.Request.Url.PathAndQuery;
    }
    return url;
}

Creating the Tool Part Class

The following procedures and code explain how to create the UTToolPart class for the tool part that configures the Upcoming Tasks Web Part. Both the PWARefWebParts and NoPWARefWebParts solutions use the WebPartPages namespace in the Microsoft.SharePoint.dll assembly. The procedures show the PWARefWebParts solution, which also requires the Microsoft.Office.Project.PWA.WebParts namespace in the Microsoft.Office.Project.Server.PWA.dll assembly.

NoteNote

For more information about the ApplyChanges and RenderToolPart methods, see the ToolPart class in the Windows SharePoint Services 3.0 SDK.

To create a ToolPart class:

  1. Add a class to the PWARefWebParts project in Solution Explorer. Rename the class file for the tool part, for example, UTToolPart.cs.

  2. Add the statements for using the SharePoint.WebPartPages and PWA.WebParts namespaces, and set the UTToolPart class to inherit from the ToolPart class.

  3. In the class constructor, set the tool part title and add an event handler for the ToolPartInit event. The UTToolPart_Init event handler creates unique names for the custom controls in the tool part. The fieldGuid name for the ActiveX grid in the Project Center Web Part is not a GUID, but it is unique.

    using System;
    using System.Web.UI;
    using System.Data;
    using Microsoft.SharePoint.WebPartPages;
    using Microsoft.Office.Project.PWA.WebParts;
    
    namespace PWARefWebParts
    {
        // This tool part provides a way to select which Project Center 
        // Web Part to use, if there is more than one Project Center on a page. 
        // The user can also select the number of upcoming tasks to display.
        public class UTToolPart : ToolPart
        {
            private string fieldGuid;               // ID of the ActiveX Grid
            private string fieldNumDisplayTasks;    // Number of tasks to display
    
            // Provide unique names for the controls. 
    private void UTToolPart_Init(object sender, System.EventArgs e )
    {
                fieldGuid = this.UniqueID + "ProjectName";
                fieldNumDisplayTasks = this.UniqueID + "NumDisplayTasks";
    }
    
    public UTToolPart()
    {
    this.Title = "Display Details";
    this.Init += new EventHandler(UTToolPart_Init);
    }
            /* Override the ApplyChanges and RenderToolPart methods . . . */
        }
    }
    
    NoteNote

    The NoPWARefWebParts solution uses a fieldProjectName variable for the selected project instead of a fieldGuidName variable for the selected ActiveX grid.

  4. Override the ApplyChanges method to save the custom configuration settings to the UpcomingTasksWP properties when the user clicks Apply in the tool pane.

    // Save the Project Center Web Part name and number of tasks to display 
    // in the UpcomingTasksWP class properties. 
    public override void ApplyChanges()
    {
        UpcomingTasksWP upcomingTasksWebPart = 
            (UpcomingTasksWP)this.ParentToolPane.SelectedWebPart;
    
        upcomingTasksWebPart.GridID = 
            Page.Request.Form[fieldGuid];
        upcomingTasksWebPart.NumDisplayTasks = 
            Convert.ToInt32(Page.Request.Form[fieldNumDisplayTasks]);
    
        base.ApplyChanges();
    }
    
  5. Override the RenderToolPart method to add the custom controls to the tool pane. When the page is refreshed, the Windows SharePoint Services WebPartPages infrastructure calls RenderToolPart on an HTTP post-back.

    The PWARefWebParts solution gets all Web Parts on the page and populates a drop-down list that shows all instances of the Project Center Web Part, and then saves the ID of the selected ActiveX grid in the UpcomingTasksWP.GridID property.

    The NoPWARefWebParts solution (not shown in the following code) uses a ProjectDataSet object to populate the list with the names of all projects to which the user has access. Both solutions create a text box in which the user can type the number of tasks to show.

    // Create the drop-down list for the Project Center Web Part names and 
    // the text box for the number of tasks to display.
    protected override void RenderToolPart(HtmlTextWriter textOutput)
    {
        string sGridID;
    
        UpcomingTasksWP upcomingTasksWebPart = 
            (UpcomingTasksWP)this.ParentToolPane.SelectedWebPart;
    
        textOutput.Write("<br/>");
        textOutput.Write("Project Center Web Part: ");
        textOutput.Write("<br/>");
        textOutput.Write("<select NAME = '" + fieldGuid + "'>");
    
        foreach (WebPart oWebPart in WebPartManager.WebParts)
        {
            if (oWebPart is ProjectCenterPart)
            {
                sGridID = ((ProjectCenterPart)oWebPart).ClientID + "_MSPJGrid";
    
                if (sGridID == upcomingTasksWebPart.GridID)
                {
                    textOutput.Write("<option selected VALUE='" + sGridID +
                        "'>" + oWebPart.Title);
                }
                else
                {
                    textOutput.Write("<option VALUE='" + sGridID + "'>" + 
                        oWebPart.Title);
                }
    
            }
        }
        textOutput.Write("</select>");
        textOutput.Write("<br/>");
        textOutput.Write("<br/>");
        textOutput.Write("Number of tasks to display:");
        textOutput.Write("<br/>");
        textOutput.Write("<input type='text' name='" + fieldNumDisplayTasks + 
            "' value='" + upcomingTasksWebPart.NumDisplayTasks + "'>");
    }
    

Other Requirements for the Web Part Assembly

Web Parts are designed to be distributed over the Internet or an intranet. For security reasons when creating a custom Web Part, you must strong name it to ensure that users can trust the Web Part.

A strong name consists of the assembly's identity, a public key, and a digital signature. You can use the Strong Name tool (sn.exe) that is installed with the .NET Framework SDK to manage keys, generate signatures, and verify signatures.

To strong name the assembly:

  1. Open a Visual Studio 2005 Command Prompt window and type the following.

    cd <path to PWARefWebParts project>

    sn -k StrongName.snk

  2. Open your PWARefWebParts project in Visual Studio, and on the Project menu, click PWARefWebParts Properties.

  3. Click the Signing tab on the side of the properties page, and then select Sign the assembly.

  4. In the drop-down list, browse to the StrongName.snk file you created in Step 1.

By default, the AssemblyVersion property of your project is set to increment each time you recompile your Web Part. A Web Part Page identifies a Web Part with the version number specified in the web.config file. With the AssemblyVersion property set to increment, if you recompile your Web Part after importing it into a Web Part Page, the Web Part infrastructure looks for the old version number you specified in the web.config file.

If the assembly version numbers in the Web Part and the web.config file do not match, an error occurs. To prevent the version number of your Web Part from incrementing each time you recompile, you need to set the full version number in AssemblyInfo.cs.

To set the assembly version number:

  1. In Solution Explorer, expand the Properties folder and double-click AssemblyInfo.cs.

  2. Change the line [assembly: AssemblyVersion("1.0.*")]

    to the following:

    [assembly: AssemblyVersion("1.0.0.0")]

Installing the Custom Web Part Assembly

The following procedures show how to install the PWARefWebParts assembly on a target computer that hosts Project Web Access. Following is a summary of the steps:

  1. Copy the assembly to the target computer.

  2. Register the assembly in the global assembly cache.

  3. Register the assembly and mark it safe in the web.config file.

  4. Create a Web Part description (.dwp) file for the assembly.

  5. Import the .dwp file to a Web Part gallery.

  6. Use the gallery to add the Web Part to a Web Part Page. For the PWARefWebParts assembly to work, the page must also include at least one Project Center Web Part.

  7. Configure the Web Part by using settings in the tool pane.

To install the custom Web Parts .NET assembly to a target computer:

  1. Copy the compiled assembly (PWARefWebParts.dll) to any convenient directory on the target computer.

  2. Open the global assembly cache on the Project Web Access server. On the Start menu, click Run, and then type assembly.

  3. Drag the PWARefWebParts.dll from the temp directory to the global assembly cache window.

  4. Right-click the assembly in the global assembly cache window, click Properties, double-click the value of the Public Key Token, and then copy it to the Clipboard.

  5. Register the assembly as safe in the web.config file that is associated with the Web site. There are typically several Web sites on a server. To find the correct web.config file, do the following:

    1. In the Microsoft Internet Information Services (IIS) Manager, expand the Web Sites node, right-click the Web site you will use, and click Properties. In many cases, Project Web Access is in the Default Web Site.

    2. In the Web Site Properties dialog box, click the Home Directory tab. You need to modify the web.config file in the directory specified in the Local path box.

      For example, if Local path is C:\Inetpub\Sample, use Notepad or any text editor to open the file C:\Inetpub\Sample\web.config.

      NoteNote

      If you edit the wrong web.config file, IIS does not recognize the assembly as safe for the Web site you want. If you import and then try to use an unregistered Web Part, you get the following error message: "A Web Part or Web Form Control on this Web Part Page cannot be displayed or imported because it is not registered on this site as safe."

  6. Create the following child element on one line within the SafeControls element.

    <SafeControl Assembly="PWARefWebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=[token]" 
    Namespace="PWARefWebParts" TypeName="*" Safe="True" />
    
  7. Replace [token] with the public key token value. The SafeControl element and attributes must all be on one line. If there are any newline characters or other variations in the line, you get the error message previously described when you try to import the Web Part.

  8. Restart IIS. In a Command Prompt window, type iisreset.

  9. Create a Web Part description file for the Upcoming Tasks Web Part. To quickly make the file, open Notepad or another text editor, and copy and paste the following text. Add the PublicKeyToken and TypeName values, and then save the file as PWARefWebParts.dwp.

    <?xml version="1.0"?>
    <WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">
       <Assembly>PWARefWebParts, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=</Assembly>
       <TypeName>namespace.classname</TypeName>
       <Title>Upcoming Tasks</Title>
       <Description>Shows upcoming tasks for a project.</Description>
    </WebPart>
    

    The TypeName element is the class name of the Web Part assembly in the format namespace.classname. For the sample code, the TypeName value is PWARefWebParts.UpcomingTasksWP.

    If you open the UpcomingTasksWP.cs file in the Visual Studio PWARefWebParts.sln solution file, you can see the namespace is PWARefWebParts. The following line shows the class name is UpcomingTasksWP, and the class inherits from the WebPart class.

    [C#]

    public class UpcomingTasksWP : WebPart

    NoteNote

    The version number must match the value for the PWARefWebParts.dwp on your server. The Assembly element must all be on one line, and the values must exactly match the values from the global assembly cache that you entered in the web.config file.

Using the Custom Web Part Assembly

You can use the Web Part assembly specified in PWARefWebParts.dwp just as you use any other Web Part. One of the advantages of the custom assembly is that the computer running Windows SharePoint Services or Office SharePoint Server 2007 does not have to be provisioned by Project Server. You can import a Web Part directly into a Web Part Page as long as the Web Part is within the same server farm as Project Server.

If you add a Web Part to a Web Part gallery, the Web Part is available when you browse the gallery from any site within the Windows SharePoint Services server farm.

  1. Open Project Web Access, and on the Site Actions menu, click Site Settings.

  2. On the Site Settings page, in the Galleries section, click Web Parts.

  3. In the Web Part Gallery page, click Upload.

  4. In the Upload Web Part: Web Part Gallery page, browse to the PWARefWebParts.dwp file you created in the previous procedure. Select Overwrite existing file(s), and then click OK.

  5. The PWARefWebParts.dwp file provisions the Web Part Gallery: Upcoming Taskspage (Figure 1). In the Group section, select Default Web Parts in the drop-down list.

    Figure 1. Setting properties for the Web Part gallery

    Setting properties for the Web Part Gallery

  6. To include the Upcoming Tasks Web Part in the Quick Add Groups list, select Default. To leave the custom Web Part out of the list, clear both check boxes, and then click OK.

When the Web Part is in a gallery, you can add it to any page you choose where you have permission to modify the page. The following procedures show how to add the Upcoming Tasks Web Part to the Project Center page in Project Web Access, and how to configure the Web Part.

To add the Upcoming Tasks Web Part to a page:

  1. In Project Web Access, click Project Center in the Quick Launch.

  2. On the Project Center page, in the Site Actions menu, click Edit Page.

  3. In the Main zone, click Add a Web Part. In the Add Web Parts to Main dialog box, click Advanced Web Part gallery and options.

  4. Click the Project Web Access gallery, and then click Next until you see the Upcoming Tasks Web Part.

  5. Drag the Upcoming Tasks Web Part to the bottom of the Project Center Web Part.

  6. Click Exit Edit Mode on the top right part of the page.

NoteNote

If you make changes in the Web Part code and recompile, you must re-register the assembly in the global assembly cache, and then reset IIS. You do not need to re-import the .dwp file into the Web Part Gallery.

To configure the Upcoming Tasks Web Part:

  1. Click Modify Shared Web Part in the Web Part Menu to display the tool pane.

  2. In the Appearance, Layout, and Advanced sections of the Upcoming Tasks tool pane, make the changes you want.

  3. Expand the Display Details section of the tool pane to see the custom properties, and make the necessary changes.

    Figure 2 shows the standard Web Part property groups in addition to the custom Display Details section in the tool pane. The Project Center Web Part list contains the titles of all of the Project Center Web Parts on the same page. The user can select which Project Center Web Part to use and the number of tasks to display.

    Figure 2. Configuring the Upcoming Tasks Web Part

    Configuring the Upcoming Tasks Web Part

  4. Click Apply to see the changes, or click OK to save the changes and exit design mode.

When the Upcoming Tasks Web Part is linked to a Project Center Web Part, the user can click a project row to see the specified number of uncompleted tasks in that project. The Upcoming Tasks Web Part gets the name and GUID of the project from the Project Center Web Part and the task data from Project Server, and then displays the data (Figure 3).

Figure 3. Using the Upcoming Tasks Web Part

Using the Upcoming Tasks Web Part

Debugging the Custom Web Part Assembly

To debug a Web Part assembly, use Visual Studio 2005 and attach the debugger to all of the w3wp.exe processes. The following process shows how to find the value of the script that the GetClientScript method inserts in the PWARefWebParts solution.

NoteNote

Every time you recompile the Web Part assembly, you must reregister the assembly in the global assembly cache, reset IIS, and then refresh the Web Page to create the new w3wp.exe processes. You do not need to re-import the .dwp file into the Web Part gallery.

To debug a custom Web Part assembly:

  1. Register and test the Web Part assembly as described in the previous procedures.

  2. If you are debugging from a remote computer, install and run the Microsoft Visual Studio Remote Debugging Monitor on the Project Web Access computer. If the development computer is running Windows XP SP2 or Windows Vista, you must unblock TCP Port 135 and UDP 4500 / UDP 500. For more information, see Configure Firewall for Remote Debugging and How to: Set Up Remote Debugging.

  3. In Visual Studio, open the Web Part solution that you compiled and registered, and on the Debug menu, click Attach to Process.

  4. In the Attach to Process dialog box, select the Default transport and browse to the Project Web Access computer name for the Qualifier. Click Select, and then in the Attach to options, select only the Managed box.

  5. Select Show processes from all users and Show Processes in all sessions.

  6. To put Visual Studio into debug mode, in the Available Processes list, CTRL+click all of the w3wp.exe processes and then click Attach.

  7. On the Tools menu, click Options. Expand the Debugging node in the options list, and then click Symbols. Click the folder icon and paste the PWARefWebParts debug build symbol path in the new symbol file location, as shown in the following example.

    [Project 2007 SDK path]\CustomWebParts\PWA Reference\UpcomingTasks\bin\Debug\PWARefWebParts.pdb
    
  8. Select Load symbols using the updated settings when this dialog is closed to load the PWARefWebParts.pdb symbol file, and then click OK.

  9. In the code window UpcomingTasksWP.cs, put a breakpoint near the end of the GetClientScript method at the return sRetVal statement.

    NoteNote

    If the breakpoint shows only a red outline with a yellow warning symbol, either the PWARefWebParts.pdb symbol file is not loaded or the browser has not loaded the page with the Web Part. Common causes are that the assembly was recompiled but not reregistered in the global assembly cache, IIS was not reset, or the page was not refreshed. You do not need to reregister the Web Part in the Web Part gallery or add the Web Part to the page again. After you attach to the w3wp.exe processes, you can check whether the symbol file is loaded, or you can manually load it. Click Windows on the Debug menu, and then click Modules. Right-click in the Modules tab pane, and then click Show Modules for All Processes. Scroll to the PWARefWebParts.dll module and select the Symbol Status and Symbol File columns.

  10. Trigger the HTTP post-back event by clicking the left side of a project row in the Project Center grid, and the Visual Studio debugger breaks in the GetClientScript method.

  11. Pause the mouse pointer over the sRetVal variable and then click the magnifying glass icon to open the Text Visualizer dialog box. For example, the value of sRetVal is the following (all on one line):

    <SCRIPT for=ctl00_m_g_cfef6446_3753_4c1d_a861_668b85258ab5_MSPJGrid 
       event='OnNewRow(y,PaneNum)' language='jscript'>
       location='/pwa/projects.aspx?UTWPProjName=' + 
       ctl00_m_g_cfef6446_3753_4c1d_a861_668b85258ab5_MSPJGrid.GetCellValue(y, 'Project Name') + 
       '&UTWPRow=' + y;
    </SCRIPT>
    
  12. Right-click the Web page in the browser and then click View Source. To see the inserted script in the page HTML, search for <SCRIPT for=.

See Also

Concepts

Web Part Concepts

Developing Custom Web Parts

Other Resources

Creating Custom Web Parts for Project Server 2007

ToolPart Class

Configure Firewall for Remote Debugging

How to: Set Up Remote Debugging