Tag Archives: .NET

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 CMIS WSDL in Visual Studio

As indicated previously, I’ve uploaded a CMIS v0.5 sample to EDN. This sample works with EMC Documentum CMIS EA2.

This CMIS sample is intentionally similar to a sample produced previously for DFS 6.5 SP1. The intent is to help you compare and contrast one set of service contracts from the other. In doing so, please keep in mind that CMIS is focused on basic library services for content management–common features across supporting repositories–while DFS is focused on the broader richness of the EMC Documentum ECM Platform.

It’s worth noting that in the case of its CMIS Repository service interaction, this sample EXE was also used by IBM during this week’s TC meeting against their P8-based WSDL endpoint–requiring only a binding configuration change (i.e. zero code changes).

I mentioned that the CMIS AtomPub service (introspection) document for EA2 is accessible as follows: <code>http://host:port/resources/cmis</code>. Let’s say your EA2 installation is running at localhost on 8080, then a request for this document will return the following type of response:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:service xmlns="http://www.w3.org/2005/Atom" xmlns:ns2="http://www.cmis.org/2008/05" xmlns:ns3="http://www.w3.org/2007/app">
  <ns3:workspace ns2:id="dfs">
    <title type="text">dfs</title>
    <ns2:repositoryInfo>
      <ns2:repositoryId>dfs</ns2:repositoryId>
      <ns2:repositoryName>dfs</ns2:repositoryName>
      <ns2:repositoryDescription>dfs</ns2:repositoryDescription>
      <ns2:vendorName>EMC</ns2:vendorName>
      <ns2:productName>Documentum</ns2:productName>
      <ns2:productVersion>6.5.0.033</ns2:productVersion>
      <ns2:rootFolderId>0c00302180000105</ns2:rootFolderId>
      <ns2:capabilities>
        <ns2:capabilityMultifiling>true</ns2:capabilityMultifiling>
        <ns2:capabilityUnfiling>false</ns2:capabilityUnfiling>
        <ns2:capabilityVersionSpecificFiling>true</ns2:capabilityVersionSpecificFiling>
        <ns2:capabilityPWCUpdateable>false</ns2:capabilityPWCUpdateable>
        <ns2:capabilityPWCSearchable>false</ns2:capabilityPWCSearchable>
        <ns2:capabilityAllVersionsSearchable>true</ns2:capabilityAllVersionsSearchable>
        <ns2:capabilityQuery>both</ns2:capabilityQuery>
        <ns2:capabilityJoin>inneronly</ns2:capabilityJoin>
        <ns2:capabilityFullText>fulltextandstructured</ns2:capabilityFullText>
      </ns2:capabilities>
      <ns2:cmisVersionsSupported>0.5</ns2:cmisVersionsSupported>
    </ns2:repositoryInfo>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/objects/0c00302180000105/children" ns2:collectionType="root-children">
      <title type="text">root-children</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/objects/0c00302180000105/descendants" ns2:collectionType="root-descendants">
      <title type="text">root-descendants</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/types" ns2:collectionType="types-children">
      <title type="text">types-children</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/types" ns2:collectionType="types-descendants">
      <title type="text">types-descendants</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/checkedout" ns2:collectionType="checkedout">
      <title type="text">checkedout</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/queries" ns2:collectionType="query">
      <title type="text">query</title>
    </ns3:collection>
  </ns3:workspace>
</ns3:service>

Your repository name and object identifiers will likely differ from the example references above, but hopefully you get the gist of the response payload.

Comparing the RESTful AtomPub binding to the SOAP binding in CMIS, one has to make two service requests to yield the same repository information (i.e. the block of data represented by <code>repositoryInfo</code> above) as follows:

repositories = repositoryService.getRepositories();
foreach (cmisRepositoryEntryType repository in repositories)
{
  cmisAnyXml repositorySpecificInformation;
  string repositoryId = repository.repositoryID,
         repositoryRelationship,
         repositoryDescription,
         vendorName,
         productName,
         productVersion,
         rootFolderId,
         cmisVersionsSupported;
  cmisRepositoryCapabilitiesType capabilities;
  XmlAttribute[] AnyAttr;
  XmlElement[] Any;
  repositoryService.getRepositoryInfo(ref repositoryId, out repositoryRelationship,
         out repositoryDescription, out vendorName, out productName,
         out productVersion, out rootFolderId, out capabilities,
         out cmisVersionsSupported, out repositorySpecificInformation,
         out Any, out AnyAttr);
  . . .
}

Comparing CMIS Repository service WSDL consumption with DFS Search service WSDL consumption, the same DFS-based consumer code is as follows:

Repository[] repositories = searchService.getRepositoryList(null);
foreach (Repository repository in repositories)
{
  . . .
}

To be clear, these examples are not provided for me to argue that one approach is better than another but rather to show how approaches differ based on domain model, use cases, etc.

I’ll leave it as an exercise for the reader to perform similar comparisons where query support and object support is concerned between DFS and CMIS. :-)

Perhaps it’s also useful to comment on how WS-Security header information is passed to CMIS WSDL endpoints, since CMIS WSDL doesn’t currently declare headers explicitly in the service contract.

This sample injects WS-Security headers via app.config-based declaration (versus programmatically):

<extensions>
  <behaviorExtensions>
    <add name="usernameToken" type="SecurityMessageInspector.UsernameTokenBehaviorExtensionElement, SecurityMessageInspector, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </behaviorExtensions>
</extensions>
<behaviors>
  <endpointBehaviors>
    <behavior name="UsernameTokenBehavior">
      <usernameToken username="Administrator" password="emc" passwordType="PasswordText"/>
    </behavior>
  </endpointBehaviors>
</behaviors>

Right away, understand that this is a sample–you will likely want to take a more secure approach to managing user credentials. Certainly, if you employed this approach, you should employ a secure transport (i.e. SSL). Please see the sample solution’s README file for more details if a more programmatic approach is desired (with or without SSL).

There are other ways to "wire" WS-Security header creation and passing into applications. This particular sample takes a more subtle (less in-your-face) approach to accomplish this concern. Regardless, implicit SOAP headers in a contract require extra coding on the part of a consumer.

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…