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…
http://craigrandall.net/
@craigsmusings
Pingback: Consuming DFS WSDL in Visual Studio | Craig's Musings
Pingback: Addressing MaxReceivedMessageSize issues | Craig's Musings
I have found it necessary to instantiate the ServiceContextHeader class, and assign it to a variable, so that the Close() method can be called after execution. In addition, I utilized a using statement for the class.
When I didn’t use the Close() statement, I received authentication errors under a heavy load. Probably because I ran out of connections somewhere.
Thanks for all your help!
KM
Previous comment was not helpful. I still get authentication errors under load. I’ll be looking into where the maximum connection count is configured.
Thanks for sharing. I’m interested in what kind of load you need to support as well as the specific errors you’re seeing.
The Content Server Administration Guide talks about how to enable connection pooling (i.e. set the dfc.session.pool.enable key key in the dfc.properties file to TRUE) and how to configure concurrent sessions (i.e. the concurrent_sessions key in the server.ini file).
I’m assuming that since you’re passing ServiceContext headers in your SOAP requests, that you’re being stateless–a good thing! So, that’s why I’m referring to DFC and Content Server configuration. Please correct me, if I’ve assumed incorrectly.
While it’s true that Visual Studio can automatically generate an app.config file for your project, app.config is an artifact recognized by .NET (i.e. it’s not unique to Visual Studio or any other .NET IDE). Other posts in my blog cover how to programmatically leverage what may be declaratively expressed in an application configuration file.