none
WCF LOB, fill out my operation result RRS feed

  • Question

  • Hello there, I have some troubles understandig some details about the WCF LOB Adapter, which I don't think the tutorials explain very well.

     

    I have made an operation which uses an ComplexQualifiedType named "Types/User" as operation result. This is defined in the MetadataResolverHandler as:

     

    StructuredTypeMetadata tmUser = new StructuredTypeMetadata(typeId, "User");
    tmUser.TypeNamespace = typeNamespaceForUser;
    tmUser.Members.Add(new TypeMember("UserId", QualifiedType.IntType, false));
    tmUser.Members.Add(new TypeMember("Name", QualifiedType.StringType, false));
    tmUser.Members.Add(new TypeMember("Login", QualifiedType.StringType, false));

     

    Then when I implement the execute method for the specific operation in the OutBoundHandler-file, I starts to build my WCF response message. To resolve the OperationResult type, I uses this code (from SDK tutorial)

     

    ComplexQualifiedType cqtResult = om.OperationResult.QualifiedType as ComplexQualifiedType;

    StructuredTypeMetadata tmResult = MetadataLookup.GetTypeDefinition(cqtResult.TypeId, timeout) as StructuredTypeMetadata;

     

    My question is now, how do I fill in the values for the User-type: UserID, Name and Login? What is the next step, before returning the WCF message from the execute method?

     

    Thanks!

    Tuesday, June 24, 2008 12:58 PM

Answers

  • You need to construct the response message your self.

     

    For the simplest example, what you can do:

     

    (a) create a string parameter, which holds the body of the response xml.

    (b) create a StringReader to wrap this string

    (c) create a XmlReader to wrap the StringReader

    (d) create a WCF Message using the overload Message.CreateMessage(MessageVersion, string action, XmlReader)

    (e) return the message.

    NOTE - you could also use the overload of Message.CreateMessage which takes a BodyWriter as the 3rd parameter.

     

    To understand the format of the response xml, (i.e, how the response xml looks like for a particular operation), you'd need to understand how the DataContractSerializer serializes/deserializes wcf messages. One way to figure this out hands on:

     

    (a) create a simple WCF Service (without the ASDK). (There are tons of Calculator examples out there on the net with regards to WCF tutorials). Expose a particular operation on the contract, the xml/soap format of which you want to dig into.

    (b) create a client proxy which talks to this WCF service.

    (c) On the service side - enable WCF tracing (you should be able to find this info on the net, on how to enable tracing).

    (d) Use your client to talk to the service. Use the svctraceviewer.exe (part of the SDK) to have a look at the traces (the trace file is in xml format - so you could also just open it up in VS and have a look, though svctraceviewer.exe would make it simpler).

     

     

     

    Tuesday, June 24, 2008 3:57 PM
  • assuming that your operation metadata has just the single input parameter and operation result, and no out parameters, your xml should be:

     

    <GetUserResponse xmlns="systemx://companyx.adapters.systemx">
       <GetUserResult xmlns="systemx://companyx.adapters.systemx">
           <UserId xmlns="systemx://companyx.adapters.systemx/Types">1</UserId>
           <Name xmlns="systemx://companyx.adapters.systemx/Types">Anders</Name>
           <Login xmlns="systemx://companyx.adapters.systemx/Types">ash</Login>
        </GetUserResult>
    </GetUserResponse>

     

    Thursday, June 26, 2008 1:08 PM

All replies

  • You need to construct the response message your self.

     

    For the simplest example, what you can do:

     

    (a) create a string parameter, which holds the body of the response xml.

    (b) create a StringReader to wrap this string

    (c) create a XmlReader to wrap the StringReader

    (d) create a WCF Message using the overload Message.CreateMessage(MessageVersion, string action, XmlReader)

    (e) return the message.

    NOTE - you could also use the overload of Message.CreateMessage which takes a BodyWriter as the 3rd parameter.

     

    To understand the format of the response xml, (i.e, how the response xml looks like for a particular operation), you'd need to understand how the DataContractSerializer serializes/deserializes wcf messages. One way to figure this out hands on:

     

    (a) create a simple WCF Service (without the ASDK). (There are tons of Calculator examples out there on the net with regards to WCF tutorials). Expose a particular operation on the contract, the xml/soap format of which you want to dig into.

    (b) create a client proxy which talks to this WCF service.

    (c) On the service side - enable WCF tracing (you should be able to find this info on the net, on how to enable tracing).

    (d) Use your client to talk to the service. Use the svctraceviewer.exe (part of the SDK) to have a look at the traces (the trace file is in xml format - so you could also just open it up in VS and have a look, though svctraceviewer.exe would make it simpler).

     

     

     

    Tuesday, June 24, 2008 3:57 PM
  • Thank you....I will try this out!

     

    But isen't a strange way, that you have to build the WCF responses yourself when you have uses time and effort to define the types in the metadata resolver. It like you have to do some detective work to figure out how the responses should look like which maximizes the chances to make mismatches between the response and the contract based on the metadata types.

    Wednesday, June 25, 2008 9:00 AM
  • Hello again :-)

     

    Based on the prevoius post, I have developed the code below in the Execute metode in the LOB adapter. I have been doing some tracing before the method returns the message, that shows the xml-result to be included in the message-body looks good according to my metadata-type (See original post):

     

    <GetUserResponse xmlns="systemx://companyX.adapters.SystemX">
       <User xmlns="systemx://CompanyX.adapters.SystemX/Types">
          <UserId>1</UserId>
          <Name>Anders Spur Hansen</Name>
          <Login>ash</Login>
       </User>
    </GetUserResponse>

     

    But when I call the adapter through my client, all I get back is a null-object instead of a valid user object. In svctraceviewer, I just get the message that "An unrecognized element was encountered in the XML during deserialization which was ignored."...but it don't shows me the xml, so I can figure out why this fails.

     

    Any ideas....?

     

    // Generate WCF response message   
    StringBuilder outputString = new StringBuilder();
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.OmitXmlDeclaration = true;

    XmlWriter replywriter = XmlWriter.Create(outputString, settings);

    replywriter.WriteStartElement(om.DisplayName + "Response", SystemX.SERVICENAMESPACE);

     

    ComplexQualifiedType cqtResult = om.OperationResult.QualifiedType as ComplexQualifiedType;
    StructuredTypeMetadata tmResult = MetadataLookup.GetTypeDefinition(cqtResult.TypeId, timeout) as StructuredTypeMetadata;
    replywriter.WriteStartElement(tmResult.TypeName, tmResult.TypeNamespace);

     

        replywriter.WriteStartElement("UserId");
        replywriter.WriteValue(1);
        replywriter.WriteEndElement();

     

        replywriter.WriteStartElement("Name");
        replywriter.WriteValue("Anders Spur Hansen");
        replywriter.WriteEndElement();

     

        replywriter.WriteStartElement("Login");
        replywriter.WriteValue("ash");
        replywriter.WriteEndElement();

     

    replywriter.WriteEndElement();
    replywriter.WriteEndElement();
    replywriter.Close();

     

    XmlReader replyReader = XmlReader.Create(new StringReader(outputString.ToString()));

    Message wcfReturnMessage= Message.CreateMessage(message.Version, om.OutputMessageAction, replyReader);            

    return wcfReturnMessage;

    Wednesday, June 25, 2008 1:46 PM
  • Hi

     

    As for the detective work part, its really easy when you get the hang of it Smile

     

    Anyways, I see a problem with your code above. Here's what you should do when you face a situation where the proxy does not seem to be serializing your response correctly - place some "temporary" code in your adapter; now, after your replyReader was constructed, use code like:

     

    XmlDocument d = new XmlDocument();

    d.Load(replyReader);

    d.Save(filename);

     

    and then look at the data in the filename.

     

    (Note, the above 3 lines can't always be present in the adapter, since an xmlReader can be read only once, and since u read it to write to a temporary file, the WCF proxy won't be able to read it). (There are a couple of ways to get around this, but later).

     

    Anyways, have a look at your temporary file (which got created by the XmlDocument). Do you see the issue? Since in your WriteStartElement() calls you haven't specified a namespace, the namespace is assumed to be "" (String.Empty).

     

    Wednesday, June 25, 2008 2:06 PM
  • I have now implemented a method which always dumps my response-xml, before putting it into the WCF message.

     

    According to the namespaces I have tried a lot diffrent approches today, but without luck. Right know my code looks like this. (Only changed the areas where I fill out the elements values)

     

    replywriter.WriteStartElement(tmResult.TypeName, tmResult.TypeNamespace);

     

        replywriter.WriteStartElement("UserId", tmResult.TypeNamespace);
        replywriter.WriteValue(1);
        replywriter.WriteEndElement();

     

        replywriter.WriteStartElement("Name", tmResult.TypeNamespace);
        replywriter.WriteValue("Anders Spur Hansen");
        replywriter.WriteEndElement();

     

        replywriter.WriteStartElement("Login", tmResult.TypeNamespace);
        replywriter.WriteValue("ash");
        replywriter.WriteEndElement();

     

    replywriter.WriteEndElement();

     

    My hope was then, that the elements would get a prefix for the namespace. But the xml is the same as before, when I look at my dumps. The error is still that an element can't be deserialize, why my response is empty when it reaches the client.

     

     

    Thursday, June 26, 2008 12:31 PM
  • Can you attach your proxy (the one that you're using with the adapter), and the xml&n******sp;(the one which your adapter gives out)?

    Thursday, June 26, 2008 12:39 PM
  • First the xml which is returned from the adapter (dumped), next the proxy-class that was created in my client application. As you can see, I have another function which works fine (CountActiveUsers). The only diffenrence is, that the result is a simple Int and not an user-object

     

    <GetUserResponse xmlns="systemx://companyx.adapters.systemx">
       <User xmlns="systemx://companyx.adapters.systemx/Types">
           <UserId>1</UserId>
           <Name>Anders Spur Hansen</Name>
           <Login>ash</Login>
        </User>
    </GetUserResponse>

    Then the proxy class:


    [assembly: System.Runtime.Serialization.ContractNamespaceAttribute("systemx://companyx.adapters.systemx/Types", ClrNamespace="companyx.adapters.systemx.Types")]

    namespace companyx.adapters.systemx.Types {
        using System.Runtime.Serialization;
       
       
        [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
        [System.Runtime.Serialization.DataContractAttribute()]
        public partial class User : object, System.Runtime.Serialization.IExtensibleDataObject {
           
            private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
           
            private int UserIdField;
           
            private string NameField;
           
            private string LoginField;
           
            public System.Runtime.Serialization.ExtensionDataObject ExtensionData {
                get {
                    return this.extensionDataField;
                }
                set {
                    this.extensionDataField = value;
                }
            }
           
            [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
            public int UserId {
                get {
                    return this.UserIdField;
                }
                set {
                    this.UserIdField = value;
                }
            }
           
            [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, Order=1)]
            public string Name {
                get {
                    return this.NameField;
                }
                set {
                    this.NameField = value;
                }
            }
           
            [System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, Order=2)]
            public string Login {
                get {
                    return this.LoginField;
                }
                set {
                    this.LoginField = value;
                }
            }
        }
    }


    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(Namespace="systemx://companyx.adapters.systemx", ConfigurationName="UserOutboundContract")]
    public interface UserOutboundContract {
       
        [System.ServiceModel.OperationContractAttribute(Action="Users/CountActiveUsers", ReplyAction="Users/CountActiveUsers/response")]
        int CountActiveUsers();
       
        [System.ServiceModel.OperationContractAttribute(Action="Users/GetUser", ReplyAction="Users/GetUser/response")]
        companyx.adapters.systemx.Types.User GetUser(int UserID);
    }

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    public partial class UserOutboundContractClient : System.ServiceModel.ClientBase<UserOutboundContract>, UserOutboundContract {
       
        public UserOutboundContractClient() {
        }
       
        public UserOutboundContractClient(string endpointConfigurationName) :
                base(endpointConfigurationName) {
        }
       
        public UserOutboundContractClient(string endpointConfigurationName, string remoteAddress) :
                base(endpointConfigurationName, remoteAddress) {
        }
       
        public UserOutboundContractClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
                base(endpointConfigurationName, remoteAddress) {
        }
       
        public UserOutboundContractClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
                base(binding, remoteAddress) {
        }
       
        /// <summary>Returns the present number of active users in systemx</summary>
        /// <returns></returns>
        public int CountActiveUsers() {
            return base.Channel.CountActiveUsers();
        }
       
        /// <summary>Returns a specific user based on the user ID</summary>
        /// <param name="UserID">Specific id for the wanted systemx user</param>
        /// <returns></returns>
        public companyx.adapters.systemx.Types.User GetUser(int UserID) {
            return base.Channel.GetUser(UserID);
        }
    }

    Thursday, June 26, 2008 1:02 PM
  • assuming that your operation metadata has just the single input parameter and operation result, and no out parameters, your xml should be:

     

    <GetUserResponse xmlns="systemx://companyx.adapters.systemx">
       <GetUserResult xmlns="systemx://companyx.adapters.systemx">
           <UserId xmlns="systemx://companyx.adapters.systemx/Types">1</UserId>
           <Name xmlns="systemx://companyx.adapters.systemx/Types">Anders</Name>
           <Login xmlns="systemx://companyx.adapters.systemx/Types">ash</Login>
        </GetUserResult>
    </GetUserResponse>

     

    Thursday, June 26, 2008 1:08 PM
  • It works!!! Great

     

    The xml you wrote was the key. I removed the line showed below which created the <User> node and the node is not nessesary at all, what I believed it to be.

     

    replywriter.WriteStartElement(tmResult.TypeName, tmResult.TypeNamespace)

     

    Thank you very much for the help...it was needed! Now I will move on to implement my inbound handler class.

     

    /Anders

    Thursday, June 26, 2008 1:21 PM