Tag Archives: DFS

Documentum Foundation Services

Addressing MaxReceivedMessageSize issues

If you’re a .NET-based consumer of Enterprise Content Services (e.g. those offered via Documentum Foundation Services) and you experience a Windows Communication Foundation CommunicationException having to do with MaxReceivedMessageSize, you may be interested in the details of this post. This post applies both to direct-to-WSDL consumers and also to consumers that leverage the DFS productivity layer for .NET. Guidance herein has more to do with WCF in general; however, it will be offered in a ECS/DFS context.

Depending on the size of incoming messages from services to your application, you may discover the need to increase the maximum received message size. For example, your application experiences the following exception raised by WCF:

System.ServiceModel.CommunicationException : The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.

An example of exceeding quota could be application requests that result in data package-based responses with a large number of data objects and/or a set of data objects with significant metadata and/or content (e.g. ObjectService.get).

If you implement a direct-to-WSDL consumer of this service using Visual Studio and WCF’s Add Service Reference designer, you will by default introduce a per service binding application configuration file into the overall solution. Therefore, to declaratively increase the maximum received message size, you will edit app.config by focusing on increasing the value of the MaxReceivedMessageSize attribute on the appropriate (named) binding element from the default value in configuration as follows:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="ObjectServicePortBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
        . . .
      </basicHttpBinding>
    </bindings>
    . . .
  </system.serviceModel>
  . . .
</configuration>

As in the case of a direct-to-WSDL consumer, a productivity layer-based consumer of the DFS Object service may also need to declaratively increase the value of MaxReceivedMessageSize more compatible with actual runtime requirements.

In the etc\config directory path of your local DFS SDK you should find an example App.config file. Please note that this app.config file is oriented toward productivity layer consumers, not direct-to-WSDL consumers via WCF. That being said, the same binding attributes apply to a solution here, too. The difference is how the bindings are declared in app.config.

The productivity layer oriented declaration names a single binding, DfsDefaultService, to act as the binding for all DFS services, except for DFS runtime services, which have separate, named bindings declared. So, Object service gets its (WCF- based) binding configuration from the “DfsDefaultService” binding…and so does, for example, Query service.

To declaratively increase the maximum received message size in productivity layer oriented app.config, you will most likely edit the MaxReceivedMessageSize attribute on the “DfsDefaultService” binding element from the default value in configuration as follows:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  . . .
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        . . .
        <binding name="DfsDefaultService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="1000000" maxBufferPoolSize="10000000" maxReceivedMessageSize="1000000" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

You may notice that the DFS SDK-based app.config binding element attribute values differ from direct-from-WCF defaults (i.e. maxBufferSize–1000000 versus 65536, maxBufferPoolSize–1000000 versus 524288, and maxReceivedMessageSize–1000000 versus 65536). This is simply a change to lessen the likelihood of encountering WCF CommunicationExceptions having to do with MaxReceivedMessageSize values.

One technique you can employ to determine what a reasonable MaxReceivedMessageSize value should be for your application is to set the value of your binding attribute/property to the absolute maximum in order to profile actual runtime message size using a web debugging proxy like Charles or Fiddler. That is, temporarily set MaxReceivedMessageSize to 2147483647 (i.e. Int32.MaxValue), pass your SOAP messages through, for example, Charles via port forwarding, review response message content length values, and reset your default runtime MaxReceivedMessageSize value accordingly.

If you prefer to take a declarative approach to WCF binding configuration for your application but you’re concerned about a user setting the value too low, you can always interrogate values at runtime in order to ensure that they’re sufficient.

For example, a productivity layer-based client could do as follows:

System.Reflection.FieldInfo appConfigInfo = typeof(ContextFactory).GetField("appConfig", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
System.Reflection.FieldInfo agentServiceBindingInfo = typeof(AppConfig).GetField("m_agentServiceBinding", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
System.Reflection.FieldInfo contextRegistryServiceBindingInfo = typeof(AppConfig).GetField("m_contextRegistryServiceBinding", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
System.Reflection.FieldInfo defaultServiceBindingInfo = typeof(AppConfig).GetField("m_defaultServiceBinding", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
BasicHttpBinding binding = new BasicHttpBinding();
binding.MaxReceivedMessageSize = 0x7fffffffL;
binding.MaxBufferSize = 0x7fffffffL;
agentServiceBindingInfo.SetValue(appConfigInfo.GetValue(contextFactory), binding);
contextRegistryServiceBindingInfo.SetValue(appConfigInfo.GetValue(contextFactory), binding);
defaultServiceBindingInfo.SetValue(appConfigInfo.GetValue(contextFactory), binding);

Of course, in a production app, I’d ensure that there is a log (auditable event) of such programmatic override activity. I might also consider presenting the user with a suggestion, requesting that the software be given the opportunity to auto-correct the value (e.g. updating the effective application configuration file).

Dynamic value assistance using DFS

Continuing my collaboration with John Sweeney (or his collaboration with me), today we’ll examine the DFS Schema service and do so in .NET as a direct-to-WSDL consumer. This tutorial builds upon a previous tutorial; so, if you’re not already familiar with it, please read this post and play with this sample. You’ll also need and be familiar with using Documentum Administrator (DA) and Documentum Composer 6.5 SP1.

Thanks to John for doing the legwork in this tutorial and for allowing me to share it all with you here. (Although, I’d really like to see John blog about this directly; he has a lot to share.)

Not only can the DFS Schema service return value-assist values, it also supports dynamic value assistance, including the handling of dependencies (e.g. one attribute value-assist dynamically built based on the value of another attribute).

This tutorial demonstrates how to use the DFS Schema service to perform dynamic value-assist tasks. Knowledge of Documentum Administrator, Documentum Composer 6.5 SP1, DFS 6.5 SP1, Visual Studio 2008 and WSDL-based consumption of DFS with .NET is assumed.

Before we begin, we’ll need to create users, groups and an object type to use as test data.

Step 1 – Create users and groups (to be performed with Documentum Administrator)

1. Create three groups named writers, reviewers and publishers in your repository.

2. Create six users. Name them appropriately, but for this tutorial, they will be referred to as User1–6. Put the users in the new groups, as specified in the following chart:

  Group   Members
  writers   User1, User2
  reviewers   User3, User4
  publishers   User5, User6

Step 2 – Create a new object type (to be performed with Documentum Composer 6.5 SP1)

1. Open Documentum Composer and create a new project.

2. Add a new type called kb_document with a supertype of dm_document.

3. Add a new attribute to the type named document_state (as shown in the following image).

4. Expand the document_state attribute and select Value mapping.

5. In the Conditional Assistance section, click New…

6. Add Draft,Review,Publish as values of Fixed List (as shown in the following image) and click OK.

Your Conditional Assistance section should now look like this:

7. Add another new attribute to the type named assigned_to as shown in the following image.

8. Expand the assigned_to attribute and select Value mapping.

9. In the Conditional Assistance section, click New…

10. Add the following query:

select users_names from dm_group where group_name = 'writers'

Set the other fields (as shown in the following image).

The value-assist list will be populated with users who are members of the writers group by default, when document_state does not have a value of Review or Publish.

11. In the Conditional Assistance section, click New…

12. Add the following query:

select users_names from dm_group where group_name = 'reviewers'

Set the other fields (as shown in the following image).

The value in the Expression field will cause the value-assist list to be populated with users who are members of the reviewers group, but only when document_state has a value of "Review."

13. In the Conditional Assistance section, click New…

14. Add the following query:

select users_names from dm_group where group_name = 'publishers'

Set the other fields (as shown in the following image).

The value in the Expression field will cause the value-assist list to be populated with users who are members of the publishers group, but only when document_state has a value of "Publish."

Your Conditional Assistance section should now look like the following:

15. Install the Documentum project to your repository.

Step 3 – Retrieve the value-assist list (using C# and the .NET Framework 3.0 or higher)

Note: the ServiceContextHeader class used in this tutorial comes from this prior sample, as noted above.

The following code snippet will return group members in the ValueAssist Values list. A PropertySet is created for document_state. The value of document_state will alter the dynamic-assist values for the assigned_to property.

// Get a reference to the DFS Schema service
SchemaServicePortClient schemaService =  
    new SchemaServicePortClient("SchemaServicePort"); 

StringProperty sp = new StringProperty();
sp.name = "document_state"; 
// assigned_to will have different dynamic-assist values, 
// depending upon the value set here
sp.Value = "Publish"; // or Review or Draft

// Create a PropertySet for document_state, which assigned_to depends upon
PropertySet ps = new PropertySet();
ps.Properties = new Property[1];
ps.Properties[0] = sp; 

using (OperationContextScope scope = 
           new OperationContextScope(schemaService.InnerChannel))
{
    // Add the ServiceContextHeader info to the outgoing request
    OperationContext.Current.OutgoingMessageHeaders.Add(
        new ServiceContextHeader(repositoryName, userName, password)); 

    // Get the values for the assigned_to property
    ValueAssist valueAssist = schemaService.getDynamicAssistValues(
        repositoryName, "DEFAULT", "kb_document", "assigned_to", ps, null); 

    // Display the values
    if (valueAssist != null || valueAssist.Values != null)
    {
        foreach (string s in valueAssist.Values)
            MessageBox.Show(s, "Value Assist Value");
    }
}

The following table shows the expected results:

  document_state value   assigned_to values
  Draft   User1, User2
  Review   User3, User4
  Publish   User5, User6

It is worth noting that if you execute SchemaService.getTypeInfo for kb_document, and examine the assigned_to property, you can determine that the property has dependencies. The returned TypeInfo.PropertyInfos[n].Dependencies will contain an entry for document_state. You can check this beforehand as a way of determining if setting one property (document_state) should trigger a refresh of the value list (calling getDynamicAssistValues again). This is very useful for a picklist property page where the values of one list depend on the value of another.

Here endeth the lesson… :-)

Update 2/6/2009: In order for you to spend more time understanding dynamic value assistance with DFS, I’ve uploaded a Visual Studio project to EDN here. Enjoy.

Consuming DFS WSDL in Visual Studio

Earlier today I posted to EDN a new sample project that demonstrates consuming DFS web services directly from their WSDL in a Visual Studio 2008 environment. Right up front, I want to credit my colleague John Sweeney, who really should start blogging, with providing the original work contained in this sample. My subsequent contribution was mostly UI/UX-related. Thanks, John!

After downloading and extracting the sample to your local development machine, open the solution in Visual Studio 2008. The default code assumes that you’re running the DFS web services locally (i.e. IP 127.0.0.1 and port 8080). You should, of course, modify the IP address and port to match your particular development environment.

Main sample UI

The main intent of this sample is to demonstrate various ways to authenticate via DFS. It’s worth noting that this sample doesn’t cover single sign-on (SSO) support in DFS, nor does it cover WS-Security header-based authentication. Good demos to be certain…

As the user interface (UI) above tries to clearly state upfront, content repository (aka Docbase) name cannot be null or empty, regardless of authentication approach. Given authentication via ServiceContext header, user name and password cannot be null or empty. Given authentication via ContextRegistry runtime service, when token is null or empty, user name and password cannot be null or empty, and when user name and password are both null or empty, token cannot be null or empty.

One thing that is still a bit obscure in the current UI is that authentication via DFS can be a two-step or one-step process, depending on the situation. If you choose “Via ContextRegistry…” and a Context Registry token isn’t provided to DFS, two runtime service invocations will be made. In all other cases, there is one service invocation.

Assuming that your connection details are valid given your authentication approach, this sample will (a) retrieve a repository list, (b) retrieve cabinets, and (c) retrieve content (i.e. transfer MenuItemTemplate.ini to your desktop).

A previous direct-to-WSDL DFS consumer sample I posted, targeted .NET 3.0 and WCF "v1." This sample targets .NET 3.5 and WCF "v2" instead, along with support for Silverlight 2. It accomplishes this change via LINQ to XML (e.g. leverages System.Xml.Linq.XDocument instead of System.Xml.XmlDocument).

If you need to target this sample at .NET 3.0, in addition to adjusting the project build properties, removing the project reference to the System.Xml.Linq.dll assembly, and removing the System.Xml.Linq namespace declaration in ServiceContextHeader.cs, you’ll need to change the relevant code within OnWriteHeaderContents. For example:

  XDocument doc = null;
  using (StringWriter sw = new StringWriter(CultureInfo.CurrentCulture))
  {
      XmlSerializer xs = new XmlSerializer(typeof(ServiceContext));
      xs.Serialize(sw, serviceContext);
      doc = XDocument.Load(new StringReader(sw.ToString()));
  }

would become:

  XmlDocument doc = null;
  using (StringWriter sw = new StringWriter(CultureInfo.CurrentCulture))
  {
      XmlSerializer xs = new XmlSerializer(typeof(ServiceContext));
      xs.Serialize(sw, serviceContext);
      doc = XmlDocument.LoadXml(new StringReader(sw.ToString()));
  }

etc.

Although, it’s not currently employed in the sample, there is also code to add a ContentTransferProfile to the ServiceContext header.

As always, consult the DFS Developer Guide for complete details on everything that may be passed in the ServiceContext header.

I do expect that this sample will be incorporated into the DFS SDK and DFS technical publications content; however, I wanted to get this into your hands sooner rather than later. Cheers…

DFS Object service consumer #2b

In our previous conversation, we left with a missing header to resolve. This post will resolve this issue and wrap-up the sample DFS Object service consumer based on direct proxy via Visual Studio/WCF.

First, we need to introduce a new internal class, ServiceContextHeader, as follows:

internal class ServiceContextHeader : MessageHeader
{
    private string repositoryName = null;
    private string userName = null;
    private string password = null;

    public ServiceContextHeader(string repositoryName, string userName, string password)
    {
        this.repositoryName = repositoryName;
        this.userName = userName;
        this.password = password;
    }

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        // Please note that there can be more to a ServiceContext instance than
        // what this intentionally simple example specifies. This message header
        // implementation doesn't cover all aspects of ServiceContext.

        // In order to proxy ServiceContext, you must first establish a service
        // reference to the DFS runtime Context Registry service.
        ServiceContext serviceContext = new ServiceContext();
        RepositoryIdentity repositoryIdentity = new RepositoryIdentity();
        repositoryIdentity.repositoryName = repositoryName;
        repositoryIdentity.userName = userName;
        repositoryIdentity.password = password;
        serviceContext.Identities = new Identity[1];
        serviceContext.Identities[0] = repositoryIdentity;

        // We haven't communicated with the DFS runtime Context Registry service;
        // so we don't have a token to "write," nor are we specifiying locale in 
        // our ServiceContext instance.

        StringWriter sw = new StringWriter();
        XmlSerializer xs = new XmlSerializer(typeof(ServiceContext));
        xs.Serialize(sw, serviceContext);
        String xml = sw.ToString();
        sw.Close();

        XmlDocument document = new XmlDocument();
        document.LoadXml(xml);

        foreach (XmlNode node in document.LastChild.ChildNodes)
        {
            XmlElement element = (XmlElement)node;
            XmlReader reader = new XmlNodeReader(element);
            writer.WriteNode(reader, true);
        }
    }

    public override string Name
    {
        get { return "ServiceContext"; }
    }

    public override string Namespace
    {
        get { return "http://context.core.datamodel.fs.documentum.emc.com/"; }
    }
}

Next, we need to “inject” this class into our application’s request message processing. So, we need wrap our original call to ObjectService.get() as follows:

DataPackage dataPackage = null;
using (OperationContextScope scope = new OperationContextScope(objectService.InnerChannel))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(new ServiceContextHeader(repositoryName, userName, password));

    dataPackage = objectService.get(objectIdentitySet, operationOptions);
}

In a less contrived context (i.e. your production application), there are other approaches to be considered, and I recommend that you at least read Nick Allen’s post on the subject.

Now, let’s re-run the modified example and examine the SOAP request message:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
        <ServiceContext xmlns="http://context.core.datamodel.fs.documentum.emc.com/">
            <Identities xsi:type="RepositoryIdentity" userName="_USER_" password="_PWD_" repositoryName="_DOCBASE_" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
        </ServiceContext>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <get xmlns="http://core.services.fs.documentum.emc.com/">
            <forObjects isInternal="false" xmlns="">
                <Identities repositoryName="_DOCBASE_" valueType="OBJECT_ID" xmlns="http://core.datamodel.fs.documentum.emc.com/">
                    <ObjectId id="09003023800037c6"/>
                </Identities>
            </forObjects>
            <options xmlns="">
                <Properties isInternal="false" xmlns="http://core.datamodel.fs.documentum.emc.com/"/>
                <Profiles xsi:type="q1:PropertyProfile" isProcessIncludedUnknown="false" filterMode="SPECIFIED_BY_INCLUDE" xmlns="http://core.datamodel.fs.documentum.emc.com/" xmlns:q1="http://profiles.core.datamodel.fs.documentum.emc.com/">
                    <q1:IncludeProperties>object_name</q1:IncludeProperties>
                </Profiles>
            </options>
        </get>
    </s:Body>
</s:Envelope>

Much better! And, as important, the dialog reveals the same object name as the same example based on the DFS SDK .NET assemblies.

You can download the Visual Studio 2008-based solution for today’s (fully resolved) sample here.

I hope that this series of posts on how the EMC Documentum platform, and especially DFS, supports Microsoft-oriented developers has been helpful.

Cheers… :-)

Update 8/9/2008: There is actually one additional change that is required to properly retrieve the object’s name for presentation on the form. In the previous post, after calling ObjectService.get() and finding the “object_name” property, our form label was set as follows:

    lblObjName.Text = property.ToString();

However, this needs to change in order to avoid prefacing the string value with ToString() content:

    StringProperty s = property as StringProperty;
    lblObjName.Text = s.Value;

This sample has been updated to include this change. Kudos to John Sweeney for the assist.

OK, back to the Olympics… :-)

DFS Object service consumer #2a

Rather than leverage the DFS SDK-based .NET assemblies to consume DFS services, some may prefer to leverage Visual Studio’s ability to proxy WSDL/XSD directly and choose instead to involve the Windows Communication Foundation (WCF) designer via Add Service Reference… In this post, I will discuss just such a sample application.

As in the previous example, we’ll build the same Windows Forms application to accomplish the same task (i.e. object name retrieval given object id); however we’ll ignore the .NET productivity layer and proxy the Object service contract directly using Visual Studio and WCF.

After launching Visual Studio, choosing File | New | Project… and creating a new Windows Forms Application project called ObjectServiceConsumer, go to the Solution Explorer, select the project node, right-click and choose Add Service Reference…

Enter the address for the DFS Object service instance to proxy (e.g. the same instance used in the previous example’s browser-based accessibility test). Click Go, and you should see something similar to the dialog captured above. Set the namespace for this reference and choose OK.

Return back to Visual Studio, double-click Form1.cs, after building the same dialog as in the previous example, double-click on the Retrieve button. This will tell Visual Studio to transition from (Windows Forms) designer mode to code editing mode.

Implement your button click handler as follows:

string repositoryName = txtbxRepoName.Text;
string userName = txtbxUserName.Text;
string password = txtbxPwd.Text;
string objId = txtbxObjId.Text; // e.g. a document id of 09123456789abcde

try
{
    ObjectId objectId = new ObjectId();
    objectId.id = objId;
    ObjectIdentity objectIdentity = new ObjectIdentity();
    objectIdentity.Item = objectId;
    objectIdentity.repositoryName = repositoryName;
    ObjectIdentitySet objectIdentitySet = new ObjectIdentitySet();
    objectIdentitySet.Identities = new ObjectIdentity[1];
    objectIdentitySet.Identities[0] = objectIdentity;

    PropertyProfile propertyProfile = new PropertyProfile();
    propertyProfile.filterMode = PropertyFilterMode.SPECIFIED_BY_INCLUDE;
    string[] includeProperties = {"object_name"};
    propertyProfile.IncludeProperties = includeProperties;
    OperationOptions operationOptions = new OperationOptions();
    operationOptions.Profiles = new Profile[1];
    operationOptions.Profiles[0] = propertyProfile;

    DataPackage dataPackage = objectService.get(objectIdentitySet, operationOptions);

    Property[] properties = dataPackage.DataObjects[0].Properties.Properties;
    foreach (Property property in properties)
    {
        if (property.name.Equals("object_name"))
        {
            lblObjName.Text = property.ToString();
        }
    }

    Console.WriteLine("Successfully retrieved object name for object id" + "'" + objId + "': " + lblObjName.Text);
}
catch (Exception ex)
{
    lblObjName.Text = "<error>";
    Console.WriteLine(ex.StackTrace);
    Console.WriteLine("Failed to retrieve object name with exception " + ex.Message);
}
finally
{
    Console.WriteLine("Your cleanup logic goes here.");
}

A few points concerning the above code:

  • There is a private field on the Form class, objectService, that is of type ObjectServicePortClient (i.e. from the WCF-generated proxy code) and is set to null initially.
  • In the Form class constructor, after the standard Windows Forms InitializeComponent(), objectService is set as follows: new ObjectServicePortClient(“ObjectServicePort”);
  • “ObjectServicePort” is a named binding in the WCF-generated proxy code (i.e. declared within app.config).
  • As in the previous example, I’m using a pre-release version of DFS 6.5 for this example, but I’m not using anything that isn’t available in DFS 6.0 SP1–this is a very simple example by design.
  • You should notice some cosmetic differences between the code above and the code here (e.g. more verbose, fewer conveniences, etc.).

You should now be able to build your application and run it. When you enter a valid object id for the specified repository connection, you should see the object name replace “<tbd>” on the dialog.

But, you don’t. Why?

Well, first of all, if you are running your application server with a console window for output message capture, you likely saw the following message:

. . . com.emc.documentum.fs.rt.SerializableException: Authorization failed, could not find identities in service context with token “temporary/127
.0.0.1-1216079837407–1167916486618885387″
        at com.emc.documentum.fs.services.core.ObjectServiceWebService.get(ObjectServiceWebService.java:268) . . .

Looking at the code above, it’s hopefully clear that the variables userName and password aren’t employed. (If you’re using the JetBrains ReSharper add-in for Visual Studio, the IDE (plugin) actually visually indicates this condition.)

So, let’s do some detective work.

Using a web debugging proxy (e.g. Charles), re-run the above code (i.e. with port forwarding in place). After valid data entry and clicking the Retrieve button, you should see an entry in your web debugging proxy session. Examine the SOAP request message (i.e. “pretty printed” for readability’s sake):

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <get xmlns="http://core.services.fs.documentum.emc.com/">
            <forObjects isInternal="false" xmlns="">
                <Identities repositoryName="_DOCBASE_" xmlns="http://core.datamodel.fs.documentum.emc.com/">
                    <ObjectId id="09003023800024ec"/>
                </Identities>
            </forObjects>
            <options xmlns="">
                <Profiles xsi:type="q1:PropertyProfile" isProcessIncludedUnknown="false" xmlns="http://core.datamodel.fs.documentum.emc.com/" xmlns:q1="http://profiles.core.datamodel.fs.documentum.emc.com/">
                    <q1:IncludeProperties>object_name</q1:IncludeProperties>
                </Profiles>
            </options>
        </get>
    </s:Body>
</s:Envelope>

Next, go back to the previous example, and re-run it to capture its SOAP request message. Before you hit Run, though, comment out the following line of code:

context = contextFactory.Register(context);

(We’ll talk more about this in a future post.)

The DFS SDK .NET assemblies-based application issues the following SOAP request message:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
        <ServiceContext token="temporary/127.0.0.1-1216195928765-692634301" xmlns="http://context.core.datamodel.fs.documentum.emc.com/">
            <Identities xsi:type="RepositoryIdentity" userName="_USER_" password="_PWD_" repositoryName="_DOCBASE_" domain="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
            <RuntimeProperties/>
        </ServiceContext>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <get xmlns="http://core.services.fs.documentum.emc.com/">
            <forObjects isInternal="false" xmlns="">
                <Identities repositoryName="_DOCBASE_" valueType="OBJECT_ID" xmlns="http://core.datamodel.fs.documentum.emc.com/">
                    <ObjectId id="09003023800037c6"/>
                </Identities>
            </forObjects>
            <options xmlns="">
                <Properties xmlns="http://core.datamodel.fs.documentum.emc.com/"/>
                <Profiles xsi:type="q1:PropertyProfile" filterMode="SPECIFIED_BY_INCLUDE" xmlns="http://core.datamodel.fs.documentum.emc.com/" xmlns:q1="http://profiles.core.datamodel.fs.documentum.emc.com/">
                    <q1:IncludeProperties>object_name</q1:IncludeProperties>
                </Profiles>
            </options>
        </get>
    </s:Body>
</s:Envelope>

Yep, there’s definitely a missing header in today’s sample, as coded above. :-)

It’s also worth noting a few things before continuing:

  • The code above requires SSL to avoid cleartext data being sent over the wire. Of course, SSL–and replacing http://… with https://…–only provides transport-level security that is point-to-point in nature (i.e. compared with message-level security that is end-to-end in nature).
  • valueType=”OBJECT_ID” – this is missing on the <Identities> element in today’s example but is present in the previous example
  • <Properties xmlns=”http://core.datamodel.fs.documentum.emc.com/”/> – this is missing in today’s example but is present in the previous example
  • filterMode=”SPECIFIED_BY_INCLUDE” – this is missing on the <Profiles> element in today’s example but is present in the previous example
  • isProcessIncludedUnknown=”false” – this is present in today’s example but is missing in the previous example

Before we solve the case of the missing header, let’s resolve the differences just listed:

...
ObjectIdentity objectIdentity = new ObjectIdentity();
objectIdentity.valueType = ObjectIdentityType.OBJECT_ID; //FIX
objectIdentity.valueTypeSpecified = true; //FIX
...
propertyProfile.filterMode = PropertyFilterMode.SPECIFIED_BY_INCLUDE;
propertyProfile.filterModeSpecified = true; //FIX
...
OperationOptions operationOptions = new OperationOptions();
...
operationOptions.Properties = new PropertySet(); //FIX
...

As for the isProcessIncludedUnknown-based difference aforementioned, a quick read of the reference-level documentation for PropertyProfile.setProcessIncludedUnknown() is helpful: “If false, ignore any property in the includeProperties list that is not a property of the repository type. If true, throw an exception if such a property is specified in the includeProperties list. Default value is false.” That is, the WCF-generated proxy makes this default explicity in its SOAP messages; so, although the two XML messages have this difference, they are functionally equivalent.

Next: Leveraging WCF extensibility to add the missing header that the DFS Object service expects…