Print
Parent Category: Datorer
Category: Programmering
Hits: 1090

This article about SOAP(client) is actually platform-specific. But programming in C# is very popular and is a big part of .NET I feel it's important to learn/get a hang of C# and its way of handling the SOAP-service. It's been a long time since the last time I used Visual Studio, but I remember it as being a very nice experience. I used to do some programming in Visual C++ and the DirectX interface. I think it was DirectX 8..or 9..using Direct3D/DirectSound and so on...but this is a digression...The point of this article is to connect to a server using the SOAP-protocol via a WSDL. I think using the WSDL is the more simple way, and it's easy to use. Must admit that this is the first time I appoach C#. I went through a "basics"-tutorial and found out that the syntax and language was quite like C, but also a little bit like Java(even though I do not have a lot of experience programming Java).

Like the other articles I've written I will do two operations:

  1. Import WSDL-data services
  2. Get OrderInformation from a server
  3. Create an Order from an xml-file


1. Importing the WSDL services to your project

But first of all we have to import the available services/methods to our project. My project is a simple console application.

1. I create the project/Solution through "Visual C#" -> Console App (.NET Framework).

2. Right-Click on "References" in the "Solution Explorer" pane on the right and click on the "Add Service Reference". See image below:

3. In the Dialog Box that appears, enter the wsdl adresse that you want top connect to. After that click "Go". The VS will then connect to that adresse and collect/get all available services/methods. When everything is downloaded/scanned create a neamespace name for this which you will use in your project and click "OK". See pic below:

4. After clicking "OK", you will be back at your source-file, but on the right-pane you will see a new connected service that is available. See pic below:

5. Double-click the "myNamespaces"-line and a new tab will open up in your main window showing the classes and its methods and member variables. See pic below:

When this is "a-ok" we're ready to start making an "app"/connecting to the server.

2. Getting Order-Information/Using the WSDL-Interface.

It was really interesting doing this in C# since I've never programmed in C#. But it was not a problem since the syntax and the source layout is quite like C/C++ or PHP. What was new for me was the usage of namespaces. But not a problem.

1. The first thing we have to do is to tell the program that we're using the new service that's been downloaded/created. See line 6:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using soapGetOrder.OnGoing;

namespace soapGetOrder
{
    class TestO
    {
        private const string password = "password";
        private const string username = "UserName";
        private const string id = "goods_owner_id";
        private const string oid = "goodsowner_order_number";

        
        public int Get_order_by_id()

 - SoapGetOrder points to this programs' namespace.
 - OnGoing points to the name of the service that we created earlier on. See the Solution Explorer under "Connected Services"

In my class TestO I create a public function that will connect to the service and get order-information. See code below:

public int Get_order_by_id()
{
        Order OrderResponse;
        ServiceSoapClient testClient = new ServiceSoapClient();
        OrderResponse = testClient.GetOrderByOrderNumber(id, username, password, oid);
        
        if(!OrderResponse.Success)
        {
            Console.WriteLine("OrderResponse.Success returned false. Could not get order");
            return 1;
        }
        else
        {

 - Line 3: Create a variable that will hold the Order.
 - Line 4: Craete an instance of the ServiceSoapClient. This variable can actually be made implicitly if you want. It can also be overloaded. Some of the params that can be added is endpoint-configuration-name and remote adresse if it's needed. Here we do not need them.

 - Line 5: Get the order by using the member-function GetOrderByOrderNumber and assign result to the OrderResponse variable.
And that's it! If the operation was successfull OrderResponse will contain all the orderinformation that's available. I tested for some information that i wrote into the console. See code below:

            else
            {
                Console.WriteLine("Getting some information from the OrderInfo-object:");
                Console.WriteLine("Getting the order was a success. Testing som variables.");
                Console.WriteLine("OrderInfo->OrderId: {0}", OrderResponse.OrderInfo.OrderId);
                Console.WriteLine("OrderInfo->Status: {0}", OrderResponse.OrderInfo.OrderStatusText);
                Console.WriteLine("OrderInfo->WayOfDelivery: {0}", OrderResponse.OrderInfo.WayOfDeliveryTypeName);
                Console.WriteLine("OrderInfo->OrderRemark: {0}\n\n", OrderResponse.OrderInfo.OrderRemark);
                Console.WriteLine("Total number of Items on order\"{0}\"", OrderResponse.OrderInfo.OrderedNumberOfItems);
                Console.WriteLine("Total number of Picked Items \"{0}\"", OrderResponse.OrderInfo.PickedNumberOfItems);
                TransporterClass tc = OrderResponse.Transporter;
                Console.WriteLine("\nTransporter info: \nName: {0}\nCode: {1}\nServiceCode: {2}\nTransporterName: {3}", tc.Name, tc.Code, tc.ServiceCode, tc.TransporterName);

                //total number of order-lines
                int NumberOfOrderLines = OrderResponse.PickedOrderLines.Length;
                //Get ordlines in order
                if (NumberOfOrderLines > 0)
                {
                    Console.WriteLine("Items in Order: ");
                    for (int i = 0; i < NumberOfOrderLines; i++)
                    {
                        Console.WriteLine("Number of items: {0} of {1}", OrderResponse.PickedOrderLines[i].OrderedNumberOfItems, OrderResponse.PickedOrderLines[i].Article.ArticleNumber);
                    }
                }
                else
                    Console.WriteLine("No orderlines in order");

                //Total number of picked orderlines
                int NumberOfPickedArticleItems = OrderResponse.PickedArticleItems.Length;
                if (NumberOfPickedArticleItems > 0)
                {
                    Console.WriteLine("Picked items: ");
                    for (int i = 0; i < NumberOfPickedArticleItems; i++)
                    {
                        Console.WriteLine("Picked {2} of Article: {1}. Article name: {0}", OrderResponse.PickedArticleItems[i].Article.Name, OrderResponse.PickedArticleItems[i].Article.ArticleNumber, OrderResponse.PickedArticleItems[i].NumberOfItems);
                    }
                }
                else
                    Console.WriteLine("Nothing is picked yet");
            }

Result of this is the following:

Getting some information from the OrderInfo-object:
Getting the order was a success. Testing som variables.
OrderInfo->OrderId: 21696
OrderInfo->Status: Skrevet ut
OrderInfo->WayOfDelivery:
OrderInfo->OrderRemark: mobil: 97501600-Test xml->obj->send request using gSOAP----FM


Total number of Items on order"6,000"
Total number of Picked Items "3,000"

Transporter info:
Name: PostNord NO MyPack Collect
Code: DTPG
ServiceCode: P19NO
TransporterName: PostNord Logistics AS (Norge)
Items in Order:
Number of items: 3,000 of 101213
Number of items: 1,000 of 101363
Number of items: 2,000 of 101502
Picked items:
Picked 2,000 of Article: 101213. Article name: ProSmart varmefolie 60w/m2-  1,2x3,15m
Picked 1,000 of Article: 101502. Article name: Tilkoblingsledning 3m

In the OrderInfo->OrderRemark it mentions gSOAP. This order was created with the gSOAP API. See Talking SOAP with C/C++

3. Creating an Order (from a xml-file)

Now I'm going to create an order from a datafile in xml-format. This file has not been given any namespace. Here's an excerpt:

<?xml version="1.0" encoding="utf-8" ?>
<CustomerOrder>
 <OrderInfo>
    <OrderIdentification>GoodsOwnerOrderNumber</OrderIdentification>
    <OrderOperation>CreateOrUpdate</OrderOperation>
    <GoodsOwnerOrderNumber>201904162010-test</GoodsOwnerOrderNumber>
    <ReferenceNumber>10077072</ReferenceNumber>

To fill our CustomerOrder-object with data we're goint to have to deserialize data in the file to. We use the  Class XmlSerializer. This class has a function that can deserialize a stream.

But I had a problem, which I did not foresee. Visual Studio sets an order-variable that locks all data in a certain order/sequence. In other words: the data in the xml-file has to be in a certain order. This is not a problem if another process serializes the data in the correct sequence/order, but it would be nice NOT to be coerced to this specific order.

What we do to solve this, is that we create our own class to hold the data from the file. To do this we do the following:

1. Select all data in the file.
2. Press 'Ctrl-C' (copy the data)
3. Press Edit->'Paste Special'->Paste XML as Classes in Visual Studio. See pic below:

Visual Studio will now create a Class based on the xml-data that can serialize. Rename This class to something other than what Visual Studio created earlier from the WSDL. I called mine for 'CustomerOrderFromFile'. Now we can deserialize the xml-data. See code below:

string path = @"path_to_\create_order.xml";
var ots = new CustomerOrderFromFile.CustomerOrder();
XmlSerializer serializer = new XmlSerializer(typeof(CustomerOrderFromFile.CustomerOrder));
StreamReader reader = new StreamReader(path);
           
ots = (CustomerOrderFromFile.CustomerOrder)serializer.Deserialize(reader);
reader.Close();

The next thing to do is to set the data in the original CustomerOrder class. This is alot of work but it's worth it so that we are not locked to the specific reading order of the data. I made a function that does that. Here's an excerpt:

private int coffToServiceCo(CustomerOrderFromFile.CustomerOrder coff)
{
            var co = new CustomerOrder(); //Original CustomerOrder class
            
            //map/insert OrderInfo
            co.OrderInfo = new OrderInfoClass();
            var cooi = co.OrderInfo;
            cooi.OrderIdentification = (OrderIdentificationType)Enum.Parse(typeof(OrderIdentificationType), coff.OrderInfo.OrderIdentification);
            cooi.OrderOperation = (OrderOperation)Enum.Parse(typeof(OrderOperation), coff.OrderInfo.OrderOperation);
            cooi.GoodsOwnerOrderNumber = coff.OrderInfo.GoodsOwnerOrderNumber;
            cooi.ReferenceNumber = coff.OrderInfo.ReferenceNumber.ToString();
            cooi.OrderRemark = coff.OrderInfo.OrderRemark;
            cooi.SalesCode = coff.OrderInfo.SalesCode.ToString();
            cooi.DeliveryDate = coff.OrderInfo.DeliveryDate;
            cooi.OrderStatusCreated = coff.OrderInfo.OrderStatusCreated;
            cooi.OrderStatusUpdated = coff.OrderInfo.OrderStatusUpdated;
            
            //map/insert the goodsowner-class variables
            co.GoodsOwner = new GoodsOwner();
            var cogo = co.GoodsOwner;
            cogo.GoodsOwnerIdentification = (GoodsOwnerIdentificationType)Enum.Parse(typeof(GoodsOwnerIdentificationType), 
            coff.GoodsOwner.GoodsOwnerIdentification);
            cogo.GoodsOwnerId = coff.GoodsOwner.GoodsOwnerId;

-------------- Etc ------------------------

For the OrderLines I do this:

            //here comes the orderlines...
            var mappedLines = new List<CustomerOrderLine>();
            foreach (var fileLine in coff.CustomerOrderLines)
            {
                var mappedLine = new CustomerOrderLine();
                mappedLine.OrderLineIdentification = (OrderLineIdentificationType)Enum.Parse(typeof(OrderLineIdentificationType), fileLine.OrderLineIdentification);
                mappedLine.ArticleIdentification = (ArticleIdentificationType)Enum.Parse(typeof(ArticleIdentificationType), fileLine.ArticleIdentification);
                mappedLine.ArticleNumber = fileLine.ArticleNumber.ToString();
                mappedLine.NumberOfItems = fileLine.NumberOfItems;

                mappedLines.Add(mappedLine);
            }
            co.CustomerOrderLines = mappedLines.ToArray();

            this.custO = co; //Assign the data to our-classes member-variable of type CustomerOrder

The next thing to do is to send the order. I made a function called sendOnGoingOrder():

        public int sendOnGoingOrder()
        {
            ServiceSoapClient Oh = new ServiceSoapClient();
            FileResultClass frc = new FileResultClass();
            frc = Oh.ProcessOrder(coid, username, password, this.custO);
            if (frc.Success)
            {
                Console.WriteLine("Inserting the order was a success");
                Console.WriteLine("Message: ", frc.Message);
                Console.WriteLine("OrderId = {0}", frc.OrderId);
                Console.WriteLine("GoodsOwnerORderNumber = {0}", frc.GoodsOwnerOrderNumber);
            }
            else
            {
                Console.WriteLine("ErrorMessage: {0}", frc.ErrorMessage);
                return 1; //program should quit with an error-number...1
            }
            return 0;
        }

 

The output of this is:

OrderInfo->OrderRemark SOAP/WSDL test using Visual Studio Community 2017 using C# - CreateOrder----FM
GoodsOowner->GoodsOwnerId 67
TransporterContract-> TransporterCode DTPG
CustomerOrderlines[0]->ArticleNumber 101213
Inserting the order was a success
Message:
OrderId = 21777
GoodsOwnerORderNumber = 201904181612-test