Set the ROClient ID in C#

Hi,

I’m completely new to C# and trying to implement a client for an RO DataAbstract SOAP service. How do I set the ROClientID? Here’s my code so far:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

    private void button1_Click(object sender, EventArgs e)
    {
        // Create a new instance of the login service
        MyLibrary.LoginServiceClient LoginClient = new MyLibrary.LoginServiceClient();

        // Call the Login method on the server
        MyLibrary.TLoginInfo LoginInfo = LoginClient.Login("Admin", "master");

        // Set the Session ID
        var SessionID = LoginInfo.SessionId;

        // Create a new instance of the Utils service
        MyLibrary.UtilsClient UtilsClient = new MyLibrary.UtilsClient();          

        // Set the ROClient ID???????
        // How do i set the ROClientID for the next call ????????????????

        // Get the Add Result
        var IPVersion = UtilsClient.AddValues(1, 1);                        
    }
}

}

Hello.

Client ID information is stored in “Header” node of SOAP message. The next information should be put manually into client SOAP request:


    
      {72F2025B-8E86-42D3-BE18-E4C75E1DEF2F}   //ClientID
    
  

Unfortunately it isn’t simple to add custom header into request using C# client. I have found solution http://www.codeproject.com/Articles/19889/SOAP-Header-Extensions-How-To that allows to insert custom XML into header and then process SOAP message. Please consider to use it.

Thanks.

Thanks Andrey.

Is this the only way to do it? It seems very tricky for something that needs to be done with any RO SOAP server that uses sessions.

Hi Andrey

I’ve tried your suggestion. It looks like it will do the job, but due to my lack of C# knowledge I can’t get it to work (The SoapExtention methods are never called). Can you please take a look and tell me how I ‘attach’ the handlers to the SOAP calls?

using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using System.Web.Services.Protocols;

namespace MySOAPDemo
{
    public partial class MainForm : Form
    {
        string FSessionID;

        public MainForm()
        {
            InitializeComponent();
        }

        private void btnLogin_Click(object sender, EventArgs e)
        {
            if (btnLogin.Text.Equals("Login"))
                Login();
            else
                Logout();
        }

        private void Login()
        {
            try
            {
                // Create a new instance of the login service
                MyLibrary.LoginServiceClient LoginClient = new MyLibrary.LoginServiceClient();
                LoginClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(edtURL.Text);

                // Call the Login method on the server
                MyLibrary.TLoginInfo LoginInfo = LoginClient.Login(edtUsername.Text, edtPassword.Text, 0, 0, MyLibrary.TClientType.ctWindowsClient);

                // Set the Session ID
                FSessionID = LoginInfo.SessionId;

                btnLogin.Text = "Logout";

                lblStatus.Text = "Connected - Session ID = " + FSessionID;
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void Logout()
        {
            MyLibrary.LoginServiceClient LoginClient = new MyLibrary.LoginServiceClient();
            LoginClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(edtURL.Text);

            LoginClient.Logout();

            btnLogin.Text = "Login";

            lblStatus.Text = "Not Connected";
        }

        private void btnUtils_Click(object sender, EventArgs e)
        {
            try
            {
                MyLibrary.MyUtilsClient UtilsClient = new MyLibrary.MyUtilsClient();
                UtilsClient.Endpoint.Address = new System.ServiceModel.EndpointAddress(edtURL.Text);

                string[] NewLines = new string[2];

                NewLines[0] = UtilsClient.PadIPAddress(edtUtilsSubnet.Text);
                NewLines[1] = UtilsClient.GetSubnet(edtUtilsSubnet.Text, int.Parse(edtUtilsCIDRMask.Text));

                memUtilsResults.Lines = NewLines;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }

    public class myExtension : SoapExtension
    {
        public bool outgoing = true;
        public bool incoming = false;

        private Stream outputStream;

        public Stream oldStream;
        public Stream newStream;

        public override void Initialize(object initializer)
        {

        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return "";
        }

        public override object GetInitializer(Type WebServiceType)
        {
            return "";
        }

        public override Stream ChainStream(Stream stream)
        {
            // save a copy of the stream, create a new one for manipulating.
            this.outputStream = stream;
            oldStream = stream;
            newStream = new MemoryStream();
            return newStream;
        }

        public string getXMLFromCache()
        {
            newStream.Position = 0; // start at the beginning!
            string strSOAPresponse = ExtractFromStream(newStream);
            return strSOAPresponse;
        }

        private String ExtractFromStream(Stream target)
        {
            if (target != null)
                return (new StreamReader(target)).ReadToEnd();
            return "";
        }

        public override void ProcessMessage(SoapMessage message)
        {
            StreamReader readStr;
            StreamWriter writeStr;
            string soapMsg1;
            XmlDocument xDoc = new XmlDocument();
            // a SOAP message has 4 stages. We're interested in .AfterSerialize
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    break;
                case SoapMessageStage.AfterSerialize:
                    {
                        // Process the code here

                        break;
                    }
                case SoapMessageStage.BeforeDeserialize:
                    {
                        // Make the output available for the client to parse...
                        readStr = new StreamReader(oldStream);
                        writeStr = new StreamWriter(newStream);
                        soapMsg1 = readStr.ReadToEnd();
                        xDoc.LoadXml(soapMsg1);
                        soapMsg1 = xDoc.InnerXml;
                        writeStr.Write(soapMsg1);
                        writeStr.Flush();
                        newStream.Position = 0;
                        break;
                    }
                case SoapMessageStage.AfterDeserialize:
                    break;
                default:
                    throw new Exception("invalid stage!");
            }
        }

    }
}

Any chance somebody from RO could help me out here?

Hello.

Unfortunately, all the solution provided do not solve the problem. Please consider to use the next solution:

  1. Set RequireSession property to ‘false’ for "Main"Service
  2. Add additional string parameter for each method of MainService. It will be used as SessionId parameter.
  3. Create new method in MainService_Impl.cs that will return boolean value: true - if the session got from client exists, false - if not.
 private Boolean CheckClientSession(string SessionID)
        {
            var session = memorySessionManager.GetExistingSession(new Guid(SessionID));
            if (session == null)
                return false;
            else
                return true;
        }
  1. Call this method before executing each service method, for example:
        public virtual String GetEchoString(string EchoString,string SessionID)
        {
            if (CheckClientSession(SessionID))
                return EchoString;
            else
                return "";
        }
  1. On the client side call service methods as follows:
                ROServerService.MainService service = new MainService();
                var res = service.GetEchoString("EchoString", FSessionID);

Hope this helps.

Sorry, this will not work for us. We have a product that is used by hundreds of customers. They need to connect to the existing service, not a new one with a completely different API.

In addition, we have 100s of methods in across several services. It is not practical to add a session ID field to every call.

It is trivial to get this working in Delphi. I can’t believe it is this hard to add a new header to a SOAP call in C#.

What other options do we have?

Hello Paul.

Please use the next solution:

  1. Create two custom header classes that derived from SoapHeader as follows:

     [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://tempuri.org/")]
     [System.Xml.Serialization.XmlRootAttribute("ROClientIDHeader",  IsNullable = false)]
     public class ROClientIDHeaderHeader : SoapHeader
     {
         public IDHeader ID = new IDHeader();
     }
    

    [System.Xml.Serialization.XmlRootAttribute(“ID”, IsNullable = false)]
    public class IDHeader : SoapHeader
    {
    [System.Xml.Serialization.XmlTextAttribute()]
    public string IDValue;
    }

Corresponding objects will be used to create custom SOAP Header:



{72F2025B-8E86-42D3-BE18-E4C75E1DEF2F} //ClientID


2. Create an instance of the custom header in the generated service client.

public partial class ROServer62ServiceService : System.Web.Services.Protocols.SoapHttpClientProtocol {
{
public ROClientIDHeaderHeader creds;
. . .
}
  1. Refer to the custom header before each call that requires the custom header:

         [System.Web.Services.Protocols.SoapHeaderAttribute("creds", Direction = System.Web.Services.Protocols.SoapHeaderDirection.InOut)]  //Need to add
         [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:ROServer62Library-ROServer62Service#Sum", RequestElementName="ROServer62Service___Sum", RequestNamespace="http://tempuri.org/", ResponseElementName="ROServer62Service___SumResponse", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
         [return: System.Xml.Serialization.XmlElementAttribute("Result")]
         public int Sum(int A, int B) {
         . . .
         }  
     
  2. Call the modified service as follows:

                 ROClientIDHeaderHeader customSoapHeader = new ROClientIDHeaderHeader();
                 mySoapHeader.ID.IDValue = SessionGuid;
    
             ROServerService.ROServer62ServiceService service = new ROServer62ServiceService();
             service.creds = customSoapHeader;
             var res = service.Sum(1, 2);
    

Hope this helps.

Hi Andrey,

Many thanks for the answer. It looks more like it will work for us.

As I mentioned, I am completely new to C# and some of the code above has my head spinning. Can you clarify your answer a little?

Corresponding objects will be used to create custom SOAP Header:

Does this mean I need to add this SOAP code somewhere or are you saying this is the header that will be generated?

  1. Create an instance of the custom header in the generated service client.

I don’t understand where this header needs to go. Can you explain a little more?

  1. Refer to the custom header before each call that requires the custom header:

Where does this go? Do I need to alter the actual client code that is generated from the WSDL?

Of course, it would be easier to understand if you attached a small sample project. Is that possible?

Cheers,
Paul

Hello Paul.

Please take a look at the attachment archive. It shows what I have said above in action:

  1. “Create two custom header classes that derived from SoapHeader as follows”. Please take a look at Form1.cs file. Corresponding SoapHeaders are declared there. They could be declared everywhere in the client project.

“Corresponding objects will be used to create custom SOAP Header:” - corresponding objects are created manually and described in step 4.

                ROClientIDHeaderHeader customSoapHeader = new ROClientIDHeaderHeader();
                mySoapHeader.ID.IDValue = SessionGuid;
 
                . . .
                service.creds = customSoapHeader;
  1. “Create an instance of the custom header in the generated service client.”. Please take a look at Reference.cs file for ROServer62ServiceService.
public ROClientIDHeaderHeader creds; is defined for  public partial class ROServer62ServiceService
  1. “Refer to the custom header before each call that requires the custom header”. Please take a look at Reference.cs file for ROServer62ServiceService. For each service method SoapHeaderAttribute is defined:
        [System.Web.Services.Protocols.SoapHeaderAttribute("creds", Direction = System.Web.Services.Protocols.SoapHeaderDirection.InOut)]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:ROServer62Library-ROServer62Service#Sum", RequestElementName="ROServer62Service___Sum", RequestNamespace="http://tempuri.org/", ResponseElementName="ROServer62Service___SumResponse", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        [return: System.Xml.Serialization.XmlElementAttribute("Result")]
        public int Sum(int A, int B) {

        [System.Web.Services.Protocols.SoapHeaderAttribute("creds", Direction = System.Web.Services.Protocols.SoapHeaderDirection.InOut)]
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("urn:ROServer62Library-ROServer62Service#GetServerTime", RequestElementName="ROServer62Service___GetServerTime", RequestNamespace="http://tempuri.org/", ResponseElementName="ROServer62Service___GetServerTimeResponse", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        [return: System.Xml.Serialization.XmlElementAttribute("Result")]
        public System.DateTime GetServerTime() {

You need to alter generated client code manually.

  1. “Call the modified service as follows”. Please take a look at Form1.cs file.

Hope this helps.

Hi,

I’m having a similar problem.I try to connect with a .NET WS. I don’t find the attachament archive. Can you please upload the archive again?

Thank you.

I’m sorry. I don’t understand. What is ‘fo’ ? It’s some kind of mirc shortcut?