How to: Create an Enterprise Custom Field

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.

You can use Project Web Access or a custom application that uses methods in the CustomFields Web service to create enterprise custom fields in Microsoft Office Project Server 2007. Custom fields that use hierarchical text lookup tables act as outline codes. Enterprise custom fields can share lookup tables.

This article shows how to create enterprise task custom fields using the CustomFields Web service of the Project Server Interface (PSI). The code sample uses an existing hierarchical text lookup table with code masks and sets a default custom field value.

For general information about custom fields and lookup tables, see Local and Enterprise Custom Fields. For a code sample that creates a lookup table and returns the GUID for a specified default value, see Walkthrough: Creating a Hierarchical Lookup Table. For the list of custom field methods, see CustomFields.

NoteNote

To delete an enterprise custom field value in a project, use QueueUpdateProject instead of QueueDeleteFromProject. For a code example that shows how to delete a task custom field value, see QueueUpdateProject.

The procedures in this article use Microsoft Visual C# and Microsoft Visual Studio 2005. For the complete sample code of the CreateTextCustomField method, see the Example section.

Creating an Enterprise Custom Field

Procedure 1 describes a method you can add to a class such as the CustomFieldUtils class in the Walkthrough: Creating a Hierarchical Lookup Table article. The CreateTextCustomField method uses a specified hierarchical text lookup table and default value. When you call the method, you also specify the type of Project Server entity (project, resource, or task) for the custom field.

Procedure 1. To create a custom field that uses a hierarchical text lookup table:

  1. In Visual Studio, add a reference to the Microsoft.Office.Project.Server.Library.dll assembly. You can copy the assembly from the [Program Files]\Microsoft Office Servers\12.0\Bin directory on the Project Server computer to your development computer. The class in which you add the custom field method must include the following references.

    using System.Web.Services.Protocols;
    using PSLibrary = Microsoft.Office.Project.Server.Library;
    
  2. Create a method named, for example, CreateTextCustomField that returns a custom field GUID and has the following parameters.

    • customField is an instance of the CustomFields class, where WebSvcCustomFields is the arbitrary name for a Web reference to the CustomFields Web service.

    • entityUniqueId is a string that includes a GUID for a particular Project Server entity. You can use the PSLibrary.EntityCollection.Entities.[Entity].UniqueId property, where [Entity] can be ProjectEntity, ResourceEntity, or TaskEntity.

    • cfName is the name of the custom field.

    • lookupTableUid is the GUID of the lookup table to use.

    • ltRowDefaultUid is the GUID of the default value in the lookup table.

    The following code is an outline of the CreateTextCustomField method.

    public Guid CreateTextCustomField(
        WebSvcCustomFields.CustomFields customField,
        string entityUniqueID,
        string cfName,
        Guid lookupTableUid,
        Guid ltRowDefaultUid)
    {
        WebSvcCustomFields.CustomFieldDataSet customFieldDataSet =
            new WebSvcCustomFields.CustomFieldDataSet();
        WebSvcCustomFields.CustomFieldDataSet.CustomFieldsRow cfRow =
            customFieldDataSet.CustomFields.NewCustomFieldsRow();
        Guid cfUid = Guid.NewGuid();
        /* Add settings for cfRow properties and create the custom field */
        return cfUid;
    }
    
  3. Set the properties for cfRow, and then add cfRow to the customFieldDataSet. Pay particular attention to the following properties:

    • MD_ENT_TYPE_UID specifies a GUID for the type of Project Server entity. You create the GUID with the entityUniqueId string.

    • MD_PROP_IS_LEAF_NODE_ONLY specifies whether users can choose only leaf nodes in a hierarchical lookup table. If false, users can choose intermediate or leaf nodes. Leaf nodes have no children. For example, in the State-County-City lookup table series WA.King.Redmond, King county is an intermediate node and Redmond is a leaf node. MD_PROP_IS_LEAF_NODE_ONLY corresponds to the Only allow codes with no subordinate values check box on the Edit Custom Field page in Project Web Access.

    • MD_PROP_TYPE_ENUM is an enumeration for one of the following custom field types: COST, DATE, DURATION, FLAG, NUMBER, or TEXT. Only a custom field of type TEXT can have a hierarchical lookup table. A custom field of type FLAG cannot use a lookup table.

    • MD_PROP_DEFAULT_VALUE is the GUID of the default value. If there is no default value, use the SetMD_PROP_DEFAULT_VALUENull method.

        cfRow.MD_PROP_UID = cfUid;
        cfRow.MD_ENT_TYPE_UID = new Guid(entityUniqueID);
        cfRow.MD_PROP_NAME = cfName;
        cfRow.MD_PROP_IS_REQUIRED = false;
        cfRow.MD_PROP_IS_LEAF_NODE_ONLY = false;
        cfRow.MD_PROP_TYPE_ENUM = (byte)PSLibrary.CustomField.Type.TEXT;
        cfRow.MD_LOOKUP_TABLE_UID = lookupTableUid;
    
        if (ltRowDefaultUid == Guid.Empty)
            cfRow.SetMD_PROP_DEFAULT_VALUENull();
        else
            cfRow.MD_PROP_DEFAULT_VALUE = ltRowDefaultUid;
    
        customFieldDataSet.CustomFields.Rows.Add(cfRow);
    
  4. Add a try…catch block and call CreateCustomFields with the customFieldDataSet you created.

        try
        {
            bool validateOnly = false;
            bool autoCheckIn = true;
            customField.CreateCustomFields(customFieldDataSet, validateOnly, autoCheckIn);
        }
        catch (SoapException ex)
        {
            string errMess = "";
            // Pass the exception to the PSClientError constructor to 
            // get all error information.
            PSLibrary.PSClientError psiError = new PSLibrary.PSClientError(ex);
            PSLibrary.PSErrorInfo[] psiErrors = psiError.GetAllErrors();
    
            for (int j = 0; j < psiErrors.Length; j++)
            {
                errMess += psiErrors[j].ErrId.ToString() + "\n";
            }
            errMess += "\n" + ex.Message.ToString();
            // Send error string to console or message box.
    
            cfUid = Guid.Empty;
        }
    

After you add the CreateTextCustomField method to a class, test the method and then check the results.

Procedure 2. To call the CreateTextCustomField method:

  1. Add the following references to the calling application:

    using System.IO;
    using System.Collections;
    using PSLibrary = Microsoft.Office.Project.Server.Library;
    
  2. Set class constants and variables for the Web service and the results of the CreateTextCustomField method, as follows.

    private const string CUSTOMFIELDSWEBSERVICE = "_vti_bin/PSI/CustomFields.asmx";
    private string baseUrl; // Example: http://ServerName/ProjectServerName/
    private Guid customFieldUid;
    private Guid ltRowDefaultUid;
    
  3. Create a Web reference to the CustomFields Web service, and then instantiate the CustomFields class. For example, name the Web service namespace WebSvcCustomFields.

    public static WebSvcCustomFields.CustomFields customField =
        new WebSvcCustomFields.CustomFields();
    private static CustomFieldUtils customFieldUtils = 
        new CustomFieldUtils();
    
  4. Add the Url property, and then add the Windows credentials or the Project Server forms logon cookie to the lookupTable object. The following code shows lines added to the AddContextInfo method in the LogonProjectServer.cs sample, in How to: Log on to Project Server Programmatically.

    customField.Url = baseUrl + CUSTOMFIELDWEBSERVICE;
    
    if (winLogon)   // Add Windows credentials
    {
        customField.Credentials = CredentialCache.DefaultCredentials;
    }
    else            // Add Project Server logon cookie for Forms logon
    {
        customField.CookieContainer = loginUtils.Cookies;
    }
    
  5. Get the GUID of the lookup table and the default value to use, set the custom field name, and then call the CreateTextCustomField method. The PSLibrary.EntityCollection.Entities.TaskEntity.UniqueId value sets the type to a Task custom field.

    /* Get the lookup table GUID and the default value GUID.
     * If there is no default, set leRowDefaultUid = Guid.Empty
     */
    if (lookupTableUid != Guid.Empty)
    {
        string cfName = "Test Task Custom Field";
    
        customFieldUid = customFieldUtils.CreateTextCustomField(
            customFields,
            PSLibrary.EntityCollection.Entities.TaskEntity.UniqueId,
            cfName,
            lookupTableUid, 
            ltRowDefaultUid
        );
    }
    

If the data is valid and CreateTextCustomField completes without errors, you can see the new custom field using Project Web Access. Browse to the Custom Fields page in Project Web access (http://ServerName/ProjectServerName/_layouts/PWA/Admin/CustomizeFields.aspx), and click the name of the custom field you created. For an example custom field in Project Web Access that is similar to the field the code sample creates, see Figure 4 in Local and Enterprise Custom Fields.

Example

Following is the code for the CreateTextCustomField method in a class named CustomFieldUtils.

using System;
using System.Data;
using System.Net;
using System.Xml;
using System.IO;
using System.Collections;
using System.Web.Services.Protocols;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace SomeNamespace
{
    class CustomFieldUtils
    {
        public CustomFieldUtils()
        {
        } 

        /*
         * Other class methods
         */

        /// <summary>
        /// Create an enterprise text custom field, for a specified entity type, 
        /// using a specified lookup table.
        /// </summary>
        /// <param name="customField">WebSvcCustomField.CustomField object</param>
        /// <param name="entityUniqueID">Type of Project Server entity (project, resource, or task)</param>
        /// <param name="cfName">Name of the custom field</param>
        /// <param name="lookupTableUid">GUID of the lookup table to use</param>
        /// <param name="ltRowDefaultUid">GUID of the default lookup table node</param>
        /// <returns>GUID of the custom field</returns>
        public Guid CreateTextCustomField(
            WebSvcCustomFields.CustomFields customField,
            string entityUniqueID,
            string cfName,
            Guid lookupTableUid,
            Guid ltRowDefaultUid)
        {
            WebSvcCustomFields.CustomFieldDataSet customFieldDataSet =
                new WebSvcCustomFields.CustomFieldDataSet();
            WebSvcCustomFields.CustomFieldDataSet.CustomFieldsRow cfRow =
                customFieldDataSet.CustomFields.NewCustomFieldsRow();
            Guid cfUid = Guid.NewGuid();

            cfRow.MD_PROP_UID = cfUid;
            cfRow.MD_ENT_TYPE_UID = new Guid(entityUniqueID);
            cfRow.MD_PROP_NAME = cfName;
            cfRow.MD_PROP_IS_REQUIRED = false;
            cfRow.MD_PROP_IS_LEAF_NODE_ONLY = false;
            cfRow.MD_PROP_TYPE_ENUM = (byte)PSLibrary.CustomField.Type.TEXT;
            cfRow.MD_LOOKUP_TABLE_UID = lookupTableUid;

            if (ltRowDefaultUid == Guid.Empty)
                cfRow.SetMD_PROP_DEFAULT_VALUENull();
            else
                cfRow.MD_PROP_DEFAULT_VALUE = ltRowDefaultUid;

            customFieldDataSet.CustomFields.Rows.Add(cfRow);

            try
            {
                bool validateOnly = false;
                bool autoCheckIn = true;
                customField.CreateCustomFields(customFieldDataSet, validateOnly, autoCheckIn);
            }
            catch (SoapException ex)
            {
                string errMess = "";
                // Pass the exception to the PSClientError constructor to 
                // get all error information.
                PSLibrary.PSClientError psiError = new PSLibrary.PSClientError(ex);
                PSLibrary.PSErrorInfo[] psiErrors = psiError.GetAllErrors();

                for (int j = 0; j < psiErrors.Length; j++)
                {
                    errMess += psiErrors[j].ErrId.ToString() + "\n";
                }
                errMess += "\n" + ex.Message.ToString();
                // Send error string to console or message box.

                cfUid = Guid.Empty;
            }
            return cfUid;
        }
    }
}

To use the error message in the catch statement, add code to send the errMess string to a message box, the console, or an error log.

Robust Programming

Add catch statements for other specific errors, or for a non-specific Exception, as shown in the following example.

    catch (Exception ex)
    {
        // Add statements to handle ex
        cfUid = Guid.Empty;
    }

Security

To run the CreateCustomFields method, the user needs ManageEnterpriseCustomFields permission for Project Server.

See Also

Tasks

Walkthrough: Creating a Hierarchical Lookup Table

How to: Log on to Project Server Programmatically

Reference

CustomFields

Concepts

Local and Enterprise Custom Fields

Namespaces in the PSI