How to: Generate ChangeXML for Statusing Updates

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.

The UpdateStatus method is a powerful way to change Statusing values in Microsoft Office Project Server 2007. The ChangeXML parameter used in the method, however, is complex. The ChangeXMLUtil test application in the Microsoft Office Project 2007 SDK download allows you to generate sample ChangeXML changes programmatically. (The content of this article and the ChangeXML utility was contributed by Baron Schaaf, Sakson & Taylor company.)

The source code for the ChangeXMLUtil application is included in the Microsoft Office Project 2007 SDK, and can be used as an example to generate your own applications. For a link to the Project 2007 SDK download, see Welcome to the Microsoft Office Project 2007 SDK.

The following figure shows the user interface (UI) for the ChangeXMLUtil application.

Figure 1. ChangeXML utility main window

ChangeXML utility main window

Before you attempt to update status by using ChangeXML, you should understand how to log on to Project Server, how to set up your Project Server solution in Microsoft Visual Studio, and how to work with the Project Server Interface (PSI). For more information, see the following topics:

Important noteImportant

The ChangeXMLutility is designed to be used only on a test installation of Project Server. Do not use the ChangeXML utility on a production server. The ChangeXML utility can easily delete or change data in the Draft and Published databases.

This article contains the following sections:

  • Using the ChangeXML Utility

    • Create a Simple Change

    • Handle Multiple Changes

    • Manually Modify ChangeXML

    • Create a Period Change

    • Create a Simple Custom Field Change

    • Create a Lookup Table Custom Field Change

  • ChangeXMLUtil Code Walkthrough

    • Validate ChangeXML Against the ChangeList Schema

    • Get a List of Valid Standard Properties for Update

    • Get a List of Assignments or Tasks

    • Get a List of Custom Fields

    • Get a List of Custom Field Lookup Table Values

    • Use the XML Schema Definition Tool to Create Canonical XML

    • Send the Update to Project Server

    • Handle Log On and Log Off

  • Compiling the Code

For more information about the ChangeXML parameter, see the ChangeList Schema Reference, and the Statusing Web service.

Using the ChangeXML Utility

You can use the ChangeXML utility to create and test ChangeXML. The following sections show how to use the ChangeXMLutility to create each kind of change defined in the ChangeList.XSD.

NoteNote

Before you can submit ChangeXML, you must log on to Project Server.

Procedure 1. Log on to Project Server

  1. In the ChangeXML utility, click Log On to Project Server.

  2. In the Log On dialog box, click HTTP:// or HTTPS:// to match your Project Server setup.

  3. In the Server Name box, type your server name.

  4. In the Project Server Name box, type the path used for your Project Web Access site.

  5. Click the link at the bottom of the Server URI pane to open your Project Web Access site.

    This allows you to verify that you have entered your server parameters correctly.

  6. Click Use Windows Security or Use Project Server Forms Security.

    If you use Forms security, you must enter the user name and password in the appropriate boxes.

  7. To save your settings, click Set Default.

  8. Click Log On.

NoteNote

Project Server logs you off automatically after a period of inactivity. If this happens while you are using the application, click Log Off Project Server, and then log on again as described earlier.

Create a Simple Change

After you log on, additional features become available. You are now able to create ChangeXML.

Note

Ensure you have active projects where the signed-on user has assignments. If the user has no assignments, you cannot create a change. You can use the code sample in the UpdateStatus method topic to create a project with the current Windows user assigned to a task.

Procedure 2. Create a simple change

  1. In the ChangeXML utility, click Clear XML. This clears any text that is in the Change XML box and creates a new ChangeXML document.

  2. Click Build Change XML to open the Generate a Change dialog box.

    Figure 2. Using the Generate a Change dialog box

    Using the Generate a Change dialog box

    When you click Assignment or Task, the Items Available for Update list is updated with valid selections from the server; the box below the list is updated with properties appropriate for either an assignment or task. For more information about the valid properties, see Supported Project Fields and Field Information for Statusing ChangeXML.

  3. Select an item from Items Available for Update. If no items are available, assign the logged-on user some tasks and publish the project.

  4. Select a property to update. Entry controls suitable for that data type appear.

    NoteNote

    Do not choose Custom Field at this time. Custom fields are addressed in detail later in this article.

  5. Enter the value you want, and then click Update XML.

    The Generate a Change dialog box closes, and the Change XML box on the main application form is updated with the information you entered.

  6. Ensure that Run Updates is selected, and then click the Evaluate XML button to send the update to the server.

The ChangeXML utility sends the XML to update the status. It then submits the changes to the manager. The next time the project manager views updates made to the project, the changes made in the ChangeXML are presented for approval.

You can take an alternative approach in your application. Instead of submitting the changes to the manager for approval, add a change to the XML by setting s_apid_update_needed to true. This allows the affected team member to submit the change from their My Tasks page in Project Web Access.

Handle Multiple Changes

If you have valid XML in the Change XML box and click Build Change XML, the ChangeXML is retained and the new change is appended to it.

The ChangeXML utility creates changes that have one change per project definition (Proj). Your application is not required to follow this convention. Each Proj can have multiple child Task definitions and Assn definitions, and each Task or Assn can contain multiple changes.

Manually Modify ChangeXML

You can type XML directly into the Change XML box, which is useful if you have XML that is failing to validate against the ChangeList Schema (ChangeList.XSD). Copy the failing XML into the Change XML box, and then validate the XML against the schema, or send the XML to the server. Any errors from the server are reported in the Results box.

Procedure 3. Manually modify ChangeXML

  1. In the ChangeXML utility, click Clear XML, and then create a simple change as described in Procedure 2.

  2. Modify the XML so that you remove a parameter.

  3. Clear Run Updates.

    Notice that the text in the green button changes from Evaluate XML and Send Updates to Server to Evaluate XML only. This allows you to validate the XML without submitting it to the server.

  4. Click Evaluate Change XML Against XSD.

    The validation message appears in the Results box.

    NoteNote

    If the ChangeXML fails validation against the ChangeList Schema, the ChangeXML utility will not submit the ChangeXML to the server.

Create a Period Change

A period change represents a change that describes a specific subsection of the time period addressed by the assignment. Period changes are valid only for work fields in assignments.

Procedure 4. Create a period change

  1. In the ChangeXML utility, click Clear XML to clear any text that is in the Change XML box and create a new ChangeXML document.

  2. Click Build Change XML.

  3. In the Generate a Change dialog box, ensure that Assignment is selected.

  4. Select any item from Items Available for Update.

  5. Select a field to update that has the word "Work" in it. This is a work field.

  6. Select the Period Change box to set it, and then set the date and time in the From and To time controls that appear.

    Figure 3. Generating an assignment period change

    Generating an assignment period change

  7. In the list boxes to the left of the From and To controls, select a number amount and the kind of time period: Minutes, Hours, or Days.

  8. Click Update XML.

    The period ChangeXML appears in the ChangeXML box.

Create a Simple Custom Field Change

A custom field change allows your application to update any enterprise custom fields defined for tasks or resources. The updates apply only at the assignment level, however. Custom field updates are valid only for assignments. Some custom fields have lookup tables associated with them, and some are simple values.

Procedure 5. Create a custom field change

  1. In the ChangeXML utility, click Clear XML to clear any text that is in the Change XML box and to create a new ChangeXML document.

  2. Click Build Change XML.

  3. In the Generate a Change dialog box, ensure that Assignment is selected.

  4. Choose any item from Items Available for Update.

  5. From the field list, select Custom Field.

    Figure 4. Generating an assignment custom field change

    Generating an assignment custom field change

  6. In the list of all defined enterprise custom fields that appears for assignments and resources, choose a Team Name. The Team Name custom field is provided by default with Project Server.

  7. In the text box that appears, type a team name.

  8. Click Update XML.

    The simple custom field ChangeXML appears in the Change XML box.

Create a Lookup Table Custom Field Change

A lookup table custom field change is similar to a simple custom field change, with the difference that the value comes from a table and is identified by the GUID associated with that item in the table.

You can define a lookup table so that multiple values can be selected. The ChangeXML utility creates changes with only a single selected value. Your application can create changes with multiple selected values.

Procedure 6. Create a lookup table custom field change

  1. In the ChangeXML utility, click Clear XML to clear out any text that is in the ChangeXML box and create a new ChangeXML document.

  2. Click Build ChangeXML.

  3. In the Generate a Change dialog box, ensure that Assignment is selected.

  4. In Items Available for Update, choose any item, and then select Custom Field from the field list.

    A drop-down list of all defined enterprise custom fields for assignments and resources appears.

    Figure 5. Generating a lookup table custom field change

    Generating a lookup table custom field change

  5. From the list of custom fields, select Health. The Health custom field is provided by default with Project Server.

  6. In the box that appears, select a value, and then click Update XML.

    The lookup table custom field ChangeXML appears in the ChangeXML box.

ChangeXMLUtil Code Walkthrough

The ChangeXMLUtil solution performs many functions that you might also need to perform in your application. The following sections highlight areas of code that might prove useful in constructing your application.

Validate ChangeXML Against the ChangeList Schema

We recommend that you validate the XML before you submit it to the server. Incorrect XML could have unpredictable results. The server might catch an error, but it is better to catch and correct the error before the incorrect XML is sent to the server.

NoteNote

Statusing ChangeList Schema is included the Project Server reference documentation and in the SDK download.

The ChangeList Schema is included in the ChangeXMLUtil project, and the build action is set to Embedded Resource.

To make the XML and IO functions accessible, using statements are added to the code.

using System.Xml;
using System.Xml.Schema;
using System.IO;

A class-level variable is defined to hold the XSD.

private static XmlSchema UpdateChangeListSchema = null;

The XSD is read in and kept handy for use in validation.

System.IO.Stream xsdStream = this.GetType().Assembly.GetManifestResourceStream(this.GetType().Namespace.ToString() 
    + ".ChangeList.xsd");
UpdateChangeListSchema = 
    XmlSchema.Read(new XmlTextReader(xsdStream), null);

The XML text is loaded into an XML document object, where the XSD is set as a schema and is evaluated.

try
{
    XmlDocument changeDoc = new XmlDocument();
    changeDoc.Schemas.Add(UpdateChangeListSchema);
    changeDoc.LoadXml(changeXml);
    changeDoc.Validate(null);
    txtResult.Text="Success!";
    return true;
}
catch (XmlSchemaValidationException ex)
{
    txtResult.Text = "ChangeList XML does not validate against the schema:\r\n" + ex.LineNumber + ":" + ex.LinePosition + " " + ex.Message;
    return false;
}
catch (System.Xml.XmlException ex)
{
    txtResult.Text = "ChangeList XML is not well-formed:\r\n" + ex.LineNumber + ":" + ex.LinePosition + " " + ex.Message;
    return false;
}
catch (Exception ex)
{
    txtResult.Text = "An error occured during XML validation:\r\n" + ex.Message;
    return false;
}

Get a List of Valid Standard Properties for Update

The fields supported for update are a subset of all task and assignment properties defined in Project Server, as well as custom fields.

The standard fields are identified by an unsigned integer constant known as a Property ID (PID).

All assignment properties are defined in the AssnConstID class and begin with the s_apid_ prefix. The values all have the mask s_assn_mask(0x0F000000).

All task properties are defined in the TaskConstID class and begin with the prefix s_tpid_, and have the s_task_mask (0x0B000000) mask.

Not all of these fields are identified as valid for update through Statusing, however. Tables of identified fields can be found in Supported Project Fields and Field Information for Statusing ChangeXML.

In ChangeXMLUtil, the valid fields are listed as literals to load in the list boxes.

cboTaskPids.Items.Add(new PidDisplayItem(184549384,"Actual Work", ProjDataFormat.Work));
cboTaskPids.Items.Add(new PidDisplayItem(184549394,"Deadline", ProjDataFormat.Date));
cboTaskPids.Items.Add(new PidDisplayItem(184549407,"Remaining Duration", ProjDataFormat.Duration));
cboTaskPids.Items.Add(new PidDisplayItem(184549405,"Duration", ProjDataFormat.Duration));
cboTaskPids.Items.Add(new PidDisplayItem(184549387,"Finish", ProjDataFormat.Date));
cboTaskPids.Items.Add(new PidDisplayItem(184549403,"Task Name", ProjDataFormat.Text));
cboTaskPids.Items.Add(new PidDisplayItem(184549381,"Overtime Work", ProjDataFormat.Work));
cboTaskPids.Items.Add(new PidDisplayItem(184549410,"% complete", ProjDataFormat.Percentage));
cboTaskPids.Items.Add(new PidDisplayItem(184549411,"% work complete", ProjDataFormat.Percentage));
cboTaskPids.Items.Add(new PidDisplayItem(184549412,"Physical % Complete", ProjDataFormat.Percentage));
cboTaskPids.Items.Add(new PidDisplayItem(184549415,"Regular Work", ProjDataFormat.Work));
cboTaskPids.Items.Add(new PidDisplayItem(184549382,"Remaining Work", ProjDataFormat.Work));
cboTaskPids.Items.Add(new PidDisplayItem(184549383,"Remaining Overtime Work", ProjDataFormat.Work));
cboTaskPids.Items.Add(new PidDisplayItem(184549389,"Resume", ProjDataFormat.Date));
cboTaskPids.Items.Add(new PidDisplayItem(184549380,"Scheduled Work", ProjDataFormat.Work));
cboTaskPids.Items.Add(new PidDisplayItem(184549386,"Start", ProjDataFormat.Date));

cboAssnPids.Items.Add(new PidDisplayItem(251658257, "Actual Finish", ProjDataFormat.Date));
cboAssnPids.Items.Add(new PidDisplayItem(251658256, "Actual Start", ProjDataFormat.Date));
cboAssnPids.Items.Add(new PidDisplayItem(251658250, "Actual Work", ProjDataFormat.Work));
cboAssnPids.Items.Add(new PidDisplayItem(251658251, "Actual Overtime Work", ProjDataFormat.Work));
cboAssnPids.Items.Add(new PidDisplayItem(251658275, "Assignment Units (Work Resource)", ProjDataFormat.Percentage));
cboAssnPids.Items.Add(new PidDisplayItem(251658275, "Assignment Units (Material Resource)", ProjDataFormat.Count)); 
cboAssnPids.Items.Add(new PidDisplayItem(251658253, "Finish", ProjDataFormat.Date));
cboAssnPids.Items.Add(new PidDisplayItem(251658295, "Confirmed", ProjDataFormat.YesNo));
cboAssnPids.Items.Add(new PidDisplayItem(251658247, "Overtime Work", ProjDataFormat.Work));
cboAssnPids.Items.Add(new PidDisplayItem(251658274, "% Work Complete", ProjDataFormat.Percentage));
cboAssnPids.Items.Add(new PidDisplayItem(251658282, "Regular Work", ProjDataFormat.Work));
cboAssnPids.Items.Add(new PidDisplayItem(251658248, "Remaining Work ", ProjDataFormat.Work));
cboAssnPids.Items.Add(new PidDisplayItem(251658249, "Remaining Overtime Work", ProjDataFormat.Work));
cboAssnPids.Items.Add(new PidDisplayItem(251658246, "Work", ProjDataFormat.Work));
cboAssnPids.Items.Add(new PidDisplayItem(251658252, "Start", ProjDataFormat.Date));
cboAssnPids.Items.Add(new PidDisplayItem(251658287, "Comments", ProjDataFormat.Text));
cboAssnPids.Items.Add(new PidDisplayItem(251658286, "Update Needed", ProjDataFormat.YesNo));
cboAssnPids.Items.Add(new PidDisplayItem(1, "Custom Field", ProjDataFormat.CustomField));
NoteNote

Custom Field is defined as a "magic" constant to trigger custom field handling in the list of assignment PIDs.

Get a List of Assignments or Tasks

You obtain the list of items available for update through the ReadStatus method. As noted in Statusing, statusing works only in the context of the current logged-on user. So, when StatusingUtils calls ReadStatus, it obtains items valid for only that user.

StatusingWebSvc.StatusingDataSet statusingDs = statusing.ReadStatus(Guid.Empty, DateTime.MinValue, DateTime.MaxValue);

The available items are returned in both the Assignments table and the Tasks table.

Get a List of Custom Fields

CustomFieldsUtils.cs wraps the WebSvcCustomFields namespace. Only assignment custom fields can be updated through statusing. Any task custom field or resource custom field is available at the assignment level. Any change to the custom field at the assignment level does not update the value at the parent task or resource level.

To get a list of the available resource or task custom fields, we must first define a filter. The filter details the columns to return and the search criteria.

NoteNote

Use the Secondary Custom Field ID as the Custom Field ID in the XML. This indicates the assignment-level custom field rather than the parent-level resource or task custom field.

// Instantiate the dataset to retrieve the list of literals.
// Done primarily to ensure an absence of typographical errors.
CustomFieldsWebSvc.CustomFieldDataSet ds = new CustomFieldsWebSvc.CustomFieldDataSet();


PSLibrary.Filter filter = new PSLibrary.Filter();

filter.FilterTableName = ds.CustomFields.TableName;

// List the fields.
// No sort order is specified as it is done in the 
// list box.
filter.Fields.Add(new PSLibrary.Filter.Field(filter.FilterTableName, 
            ds.CustomFields.MD_ENT_TYPE_UIDColumn.ColumnName, 
            PSLibrary.Filter.SortOrderTypeEnum.None));
// ! Important !
// The custom field ID to use is the secondary ID, as this 
// implies the assignment level, rather than the parent level.
filter.Fields.Add(new PSLibrary.Filter.Field(filter.FilterTableName, 
            ds.CustomFields.MD_PROP_UID_SECONDARYColumn.ColumnName, 
            PSLibrary.Filter.SortOrderTypeEnum.None));
filter.Fields.Add(new PSLibrary.Filter.Field(filter.FilterTableName, 
            ds.CustomFields.MD_PROP_NAMEColumn.ColumnName,
            PSLibrary.Filter.SortOrderTypeEnum.None));
filter.Fields.Add(new PSLibrary.Filter.Field(filter.FilterTableName, 
            ds.CustomFields.MD_PROP_TYPE_ENUMColumn.ColumnName, 
            PSLibrary.Filter.SortOrderTypeEnum.None));

filter.Fields.Add(new PSLibrary.Filter.Field(filter.FilterTableName, 
            ds.CustomFields.MD_LOOKUP_TABLE_UIDColumn.ColumnName, 
            PSLibrary.Filter.SortOrderTypeEnum.None));
filter.Fields.Add(new PSLibrary.Filter.Field(filter.FilterTableName, 
            ds.CustomFields.MD_PROP_DEFAULT_VALUEColumn.ColumnName, 
            PSLibrary.Filter.SortOrderTypeEnum.None));
filter.Fields.Add(new PSLibrary.Filter.Field(filter.FilterTableName, 
            ds.CustomFields.MD_PROP_MAX_VALUESColumn.ColumnName, 
            PSLibrary.Filter.SortOrderTypeEnum.None));

// Set the filter. We want only resource and task custom fields.
PSLibrary.Filter.IOperator[] fos = new PSLibrary.Filter.IOperator[2];

fos[0] = new PSLibrary.Filter.FieldOperator(PSLibrary.Filter.FieldOperationType.Equal, 
            ds.CustomFields.MD_ENT_TYPE_UIDColumn.ColumnName, 
            PSLibrary.EntityCollection.Entities.TaskEntity.UniqueId);
fos[1] = new PSLibrary.Filter.FieldOperator(PSLibrary.Filter.FieldOperationType.Equal, 
            ds.CustomFields.MD_ENT_TYPE_UIDColumn.ColumnName, 
            PSLibrary.EntityCollection.Entities.ResourceEntity.UniqueId);

// Set the logical operator
PSLibrary.Filter.LogicalOperator lo = new 
            PSLibrary.Filter.LogicalOperator(PSLibrary.Filter.LogicalOperationType.Or, fos);

filter.Criteria = lo;

Finally, we use that filter to retrieve the custom fields.

customFieldDs = customFields.ReadCustomFields(filter.GetXml(), false);

Get a List of Custom Field Lookup Table Values

Some custom fields obtain their values from a lookup table. You can get these lookup tables by using the WebSvcLookupTable namespace.

If a custom field references a lookup table, the unique ID of the lookup table can be found in the MD_LOOKUP_TABLE_UID property.

The table unique ID is then passed into the ReadLookupTablesByUids method to obtain the lookup table values.

LookupTableUtils contains the call to obtain the lookup table values.

ds = myLookupTableSvc.ReadLookupTablesByUids(new Guid[] { tableUid }, false, -1);

Note

Do not use the ReadLookupTables method to obtain lookup table values, as custom field management permissions are required.

Use the XML Schema Definition Tool to Create Canonical XML

The ChangeXML utility uses the XML Schema Definition Tool (Xsd.exe) capability to generate classes based on ChangeList.XSD. These classes generate ChangeXML when serialized.

The following procedure shows how to create those classes and generate XML from them.

Prerequisities

  • Create a Visual Studio solution.

  • Copy the ChangeList.XSD to the solution directory.

Procedure 7. Generate serializable classes based on ChangeList XSD

  1. Open a Visual Studio Command Prompt window, and then change directories to the solution directory. Following is an example.

    cd %USERPROFILE%\My Documents\Visual Studio 2005\Projects\SampleApp
    
  2. Execute the XSD command to generate classes. Following is an example.

    xsd ChangeList.xsd /classes /language:cs
    
  3. Add the generated classes to your application.

    1. Ensure that your solution is open, and it is not running.

    2. On the Project menu, click Add Existing Item.

    3. Select ChangeList.cs, and then click Add.

NoteNote

The ChangeList.cs class file is positioned under the ChangeList.XSD in the Visual Studio Solution Explorer.

ChangeListWrapper.cs uses the classes to instantiate the objects. It is idiosyncratic to this application. We recommend that you create your own wrapper. You might want to modify the generated classes to make them easier to use. One common modification is to change an Array class to a more flexible collection class, such as an ArrayList. This has a disadvantage, however, in that the changes are lost if you regenerate the classes to support a new version of the XSD.

ChangeListWraper.cs defines a singleton changes variable to hold the class hierarchy.

static private Changes changes = null;

Then, it sets it when it needs to be initialized.

changes = new Changes();

For every change, the program adds a new Proj element. Your application can combine multiple changes in one Proj node. The following section creates a project and adds a change to it.

ChangesProj proj = new ChangesProj();

proj.ID = projectId.ToString();

if (changes.Proj == null)
{
    changes.Proj = new ChangesProj[] { proj };
}
else
{
    ChangesProj[] projs = changes.Proj;
    Array.Resize<ChangesProj>(ref projs, ((int)changes.Proj.Length + 1));
    changes.Proj = projs;
    changes.Proj.SetValue(proj, changes.Proj.Length - 1);
}

It then goes down the hierarchy to add the child nodes, depending on type.

if(isTask)
{
   AddTaskChange(changes.Proj[changes.Proj.Length-1],changeItem, itemId, value);
}
else
{
   AddAssnChange(changes.Proj[changes.Proj.Length - 1], changeItem, itemId, value, 
         cfDisplayItem, luDisplayItem, isPeriodChange, periodStart, periodEnd);
}

The task change is relatively straightforward, as shown in the following code example.

private static void AddTaskChange(ChangesProj proj, PidDisplayItem changeItem, Guid itemId, string value)
{
   // To be robust, do not assume task is empty.
   if (null == proj.Task)
   {
      proj.Task = new ChangesProjTask[1];

   }
   else
   {
      ChangesProjTask[] tasks = proj.Task;
      Array.Resize<ChangesProjTask>(ref tasks, ((int)tasks.Length + 1));
   }
   // Create the task and add it to the Proj element.
   ChangesProjTask projTask = new ChangesProjTask();
   proj.Task[proj.Task.Length - 1] = projTask;
   
   // Fill in the properties.
   projTask.ID = itemId.ToString();

   // Create the change.
   // Add it to the task.
   // We'll just have the one child.
   projTask.Change =new ChangesProjTaskChange[1];
   ChangesProjTaskChange projTaskChange = new ChangesProjTaskChange();
   projTask.Change[0] = projTaskChange;

   // Fill in the properties.
   projTaskChange.PID = changeItem.ValueMember;
   projTaskChange.Value = value;
}

Because there are so many types, the assignment change requires more handling. If your application is not updating custom fields, or is not creating period changes, this can be simplified. The following code example shows the generic assignment change handling.

private static void AddAssnChange(ChangesProj proj, PidDisplayItem changeItem, 
         Guid itemId, string value, CFDisplayItem cfDisplayItem, 
         LookupTableDisplayItem ltDisplayItem, bool isPeriodChange, 
         DateTime periodStart, DateTime periodEnd){

   // To be robust, do not assume assns is empty.
   if (null == proj.Assn)
   {
      proj.Assn = new ChangesProjAssn[1];

   }
   else
   {
      ChangesProjAssn[] assns = proj.Assn;
      Array.Resize<ChangesProjAssn>(ref assns, ((int)assns.Length + 1));
   }

   // Create the assignment.
   ChangesProjAssn projAssn = new ChangesProjAssn();
   
   // Add it to the project. 
   proj.Assn[proj.Assn.Length - 1] = projAssn;
   
   // Fill in the properties.
   projAssn.ID = itemId.ToString();
   
   // We can assume that Items is empty, because
   // we are doing everything else right here at once.
   // Items is another word for change
   projAssn.Items= new Object[1];
   
   // Create the boxed typed change.
   Object change  = CreateTypedAssnChange(changeItem, value, cfDisplayItem, ltDisplayItem, isPeriodChange, ref periodStart, ref periodEnd);
   projAssn.Items[0] = change;
}

Drilling down by change type necessitates the more detailed method to create the different assignment change types, and return it boxed.

private static Object CreateTypedAssnChange(PidDisplayItem changeItem, 
         string value, CFDisplayItem cfDisplayItem, 
         LookupTableDisplayItem ltDisplayItem, bool isPeriodChange, 
         ref DateTime periodStart, ref DateTime periodEnd)
{
   Object change;
   //Create the change based on type.
   if (isPeriodChange)
   {
      if (changeItem.DataFormat == ProjDataFormat.Work)
      {
         ChangesProjAssnPeriodChange periodChange = new ChangesProjAssnPeriodChange();
         periodChange.PID = changeItem.ValueMember;
         periodChange.Start = Convert.ToDateTime(periodStart.ToString("yyyy-MM-ddTHH:mm:ss"));
         periodChange.End = Convert.ToDateTime(periodEnd.ToString("yyyy-MM-ddTHH:mm:ss"));
         periodChange.Value = value;
         change = (object)periodChange;
      }
      else
      {
         change = null;
         throw new Exception("Period changes are valid for Work data only.");
      }
   }
   else
   {
      if ((ProjDataFormat)changeItem.DataFormat == ProjDataFormat.CustomField)
      {
         if (cfDisplayItem.HasLookup)
         {

            if (ltDisplayItem != null)
            {
               ChangesProjAssnLookupTableCustomFieldChange cfLuChange = new ChangesProjAssnLookupTableCustomFieldChange();
               ChangesProjAssnLookupTableCustomFieldChangeLookupTableValue cfLuChangeValue = new ChangesProjAssnLookupTableCustomFieldChangeLookupTableValue();
               cfLuChange.CustomFieldGuid = cfDisplayItem.ValueMember.ToString();
               cfLuChange.CustomFieldName = cfDisplayItem.DisplayMember;
               cfLuChange.CustomFieldType = getCustomFieldChageType(cfDisplayItem.DataType);
               cfLuChange.IsMultiValued = cfDisplayItem.IsMultiValued;
               cfLuChange.LookupTableValue = new ChangesProjAssnLookupTableCustomFieldChangeLookupTableValue[1];
               cfLuChangeValue.Guid = ltDisplayItem.ValueMember.ToString();
               cfLuChangeValue.Value = ltDisplayItem.BoxedValue.ToString();
               cfLuChange.LookupTableValue[0] = cfLuChangeValue;
               change = cfLuChange;
            }
            else
            {
               throw new MissingFieldException("No lookup value was specified. Please choose a lookup table item.", "Lookup Table Value");
            }
         }
         else
         {
            ChangesProjAssnSimpleCustomFieldChange cfChange = new ChangesProjAssnSimpleCustomFieldChange();
            cfChange.CustomFieldGuid = cfDisplayItem.ValueMember.ToString();
            cfChange.CustomFieldName = cfDisplayItem.DisplayMember;
            cfChange.CustomFieldType = getCustomFieldChageType(cfDisplayItem.DataType);
            cfChange.Value = value;
            change = cfChange;
         }
      }
      else
      {
         ChangesProjAssnChange stdChange = new ChangesProjAssnChange();
         stdChange.PID = changeItem.ValueMember;
         stdChange.Value = value;

         change = (Object)stdChange;
      }
   }
   return change;
}

After the change is created, the application requests the XML in string form to update the ChangeXML box. ChangeListWrapper.cs serializes out the classes to a string for the main application.

Note

The UpdateStatus method fails without error if you include the XML definition tag. This procedure strips out the root XML tag.

static public string GetCurrentXmlAsString(bool OmitXmlTag)
{
   XmlSerializer serializer = new XmlSerializer(typeof(Changes));
   StringWriter writer = new StringWriter();

   serializer.Serialize(writer, changes);

   string xml = writer.ToString();
   writer.Close();
   if(OmitXmlTag)
   {
      xml = xml.Substring(xml.IndexOf("<Changes"));
   }
   return xml;
}

Send the Update to Project Server

After you have the XML string, the call to the UpdateStatus method is simple. ChangeXMLUtil has a wrapper class for the Statusing object.

statusing.UpdateStatus(changeXml);

Then, you must submit the status updates.

statusing.SubmitStatus(null, statusMsg);

As mentioned previously, you can take an alternative approach in your application. Instead of submitting the changes to the manager for approval with SubmitStatus, add a change to the XML by setting s_apid_update_needed to true. This allows the affected team member to submit the change from their My Tasks page in Project Web Access.

Handle Log On and Log Off

Log on and log off of the Web service wrappers are handled through the ProjSecureWebSvc base class. Any module that ends in Utils is a Project Server Web service wrapper.

LoginSource implements two interfaces, ILoginSvcConsumer and ILoginSvcController.

ILoginSvcController is used by the Log On form to control logging off and logging on the Project server.

ILoginSvcConsumer is used by the base class ProjSecureWebSvc and the main form to set state based on the log-on status.

Compiling the Code

As mentioned previously, you should review Prerequisites for Reference Code Samples. You must have a local copy of Microsoft.Office.Project.Server.Library, and a test Project Server installation.

The ChangeXMLUtil project should compile after you have set your reference to the local copy of Microsoft.Office.Project.Server.Library.

See Also

Reference

UpdateStatus

Statusing

ReadStatus

ReadLookupTablesByUids

Concepts

Prerequisites for Reference Code Samples

Statusing ChangeList Schema

Supported Project Fields and Field Information for Statusing ChangeXML

Welcome to the Microsoft Office Project 2007 SDK

Other Resources

ChangeList Schema Reference

XML Schema Definition Tool (Xsd.exe)