Options

Complex return datatype webservices

BennoBenno Member Posts: 20
edited 2010-09-03 in NAV Three Tier
It is possible to use a complex datatype as input for webservices in NAV2009 by using XMLport. But is it also possible to use complex datatype as return value? It isn't possible to use XMLport as return value.

Thanks in advance.

Comments

  • Options
    DenSterDenSter Member Posts: 8,304
    It wouldn't really make sense to return a NAV specific, internal object type in a webservice, unless you're thinking about creating some sort of object control.
  • Options
    BennoBenno Member Posts: 20
    Hi Denster,
    It make sense to me. If you want to create a webservice for consumers of a webservice the interface/contract for the request and response must be known. It is now possible to use an XMLport as input parameter of a codeunit function (as a complex type). This is a great start, but it isn't possible yet to define the datatype as easy (date, int, etc.) But if you create an XMLPort to export serveral data from Navision, this XMLport should also be used (in my opinion) to define the contract of the webservice response.

    Other question:
    What can I do in Navision to define the response interface/contract if the response exist of serveral values/elements?

    Kind regards
  • Options
    freddy.dkfreddy.dk Member, Microsoft Employee Posts: 360
    Maybe I haven't understood the problem correctly, but can't you just set the XML port as VAR parameter?
    I remember having done something like that earlier, this way I can retrieve multiple data in my VS app.
    Freddy Kristiansen
    Group Program Manager, Client
    Microsoft Dynamics NAV
    http://blogs.msdn.com/freddyk

    The information in this post is provided "AS IS" with no warranties, and confers no rights. This post does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion.
  • Options
    DenSterDenSter Member Posts: 8,304
    An XMLPort is an internal NAV object type that processes XML Documents. You would have to have a NAV client to be able to work with an XMLPort. The XMLPort itself does not contain any data, it's an object. I still don't get how useful this can be. What use is an XMLPort for any external source?

    Say you have a webservice that checks the spelling. You wouldn't send the spellchecker itself as a data type, but spellchecked data. Similarly in NAV, you wouldn't send the XMLPort itself, but the XML Document that it processes.
  • Options
    freddy.dkfreddy.dk Member, Microsoft Employee Posts: 360
    It isn't the type XMLPort you get out - it is the XML Document which the XML Port outputs - meaning that the XMLport is executed and you get the result as output. I will find my sample and post it.
    Freddy Kristiansen
    Group Program Manager, Client
    Microsoft Dynamics NAV
    http://blogs.msdn.com/freddyk

    The information in this post is provided "AS IS" with no warranties, and confers no rights. This post does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion.
  • Options
    DenSterDenSter Member Posts: 8,304
    I know that webservices return XML documents. The original question, howeer, was about returning an XMLPort, not an XML document.
    Benno wrote:
    It is possible to use a complex datatype as input for webservices in NAV2009 by using XMLport. But is it also possible to use complex datatype as return value? It isn't possible to use XMLport as return value.
  • Options
    BennoBenno Member Posts: 20
    Hi Denster,

    The purpose of this post is not to return an XMLPort, but i want to define the schema (XSD) definition for the WSDL for the response message of the webservice. It is now possible to define the structure for the request in an XMLPort. If the XMLPort is then used as parameter of a function then the WCF recognize the structure in de XMLPort and use this structure in de WSDL. This is very nice, but is this also possible for the response message?

    Benno
  • Options
    DenSterDenSter Member Posts: 8,304
    I'm afraid that I don't know what you mean :mrgreen:. You said you can use a complex datatype, an XMLPort, as a parameter, and then you asked if it was possible to return a complex datatype. To me that implied that you were asking to return an XMLPort in a webservice.

    I wasn't aware that an XMLPort could be sent into a webservice (I probably misunderstood that part too), and I still don't see how that could possibly be of any use to anyone, since it is an internal NAV specific object type. You are first talking about using an XMLPort as a parameter, and then you say that it only defines the structure of the request. That confuses me. I don't know whether you mean using an XMLPort as the actual parameter or the structure of the XML Document that is defined BY the XMLPort.

    I can see how you can structure XML that is accepted by a webservice by way of an XMLPort, and should also be available for the other way around. How that all ties into eachother though I have no clue. But again I apparently don't understand what you said in the first place. Sorry, can't help you on this one :mrgreen:
  • Options
    freddy.dkfreddy.dk Member, Microsoft Employee Posts: 360
    If you create the following function in AL:
    getXMLPort(VAR MyCustomers : XMLport WSTestCustomer)
    BEGIN
      cust.FINDFIRST();
      cust.SETRANGE(cust."No.", '10000', '20000');
      MyCustomers.SETTABLEVIEW(cust);
    END
    
    Where WSTestCustomer is an XML port defined as:
    Node Name   Node Type   Source Type   Data Source
    MyXMLCusts  Element     Text          <Customers>
    MyXMLCust   Element     Table         <Customer>(Customer)
    No          Element     Field         <Customer>::No.
    Name        Element     Field         <Customer>::Name
    Bookmark    Element     Text          Bookmark1
    
    Then this will result in the following function definition in the WSDL:
    <element name="GetXMLPort">
      <complexType>
        <sequence>
          <element minOccurs="1" maxOccurs="1" name="myCustomers" type="q1:MyXMLCusts" xmlns:q1="urn:microsoft-dynamics-nav/xmlports/x99207" /> 
        </sequence>
      </complexType>
    </element>
    <element name="GetXMLPort_Result">
      <complexType>
        <sequence>
          <element minOccurs="1" maxOccurs="1" name="myCustomers" type="q2:MyXMLCusts" xmlns:q2="urn:microsoft-dynamics-nav/xmlports/x99207" /> 
        </sequence>
      </complexType>
    </element>
    
    with the following schema definition in the WSDL
    <schema elementFormDefault="qualified" targetNamespace="urn:microsoft-dynamics-nav/xmlports/x99207" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:microsoft-dynamics-nav/xmlports/x99207">
      <complexType name="MyXMLCust">
        <sequence>
          <element minOccurs="1" maxOccurs="1" name="No" type="string" /> 
          <element minOccurs="1" maxOccurs="1" name="Name" type="string" /> 
          <element minOccurs="1" maxOccurs="1" name="Bookmark" type="string" /> 
        </sequence>
      </complexType>
      <complexType name="MyXMLCusts" mixed="true">
        <sequence>
          <element minOccurs="1" maxOccurs="unbounded" name="MyXMLCust" type="tns:MyXMLCust" /> 
        </sequence>
      </complexType>
      <element name="MyXMLCusts" type="tns:MyXMLCusts" /> 
    </schema>
    
    and when you look at this from Visual Studio you can do like this:
    WSTest1.MyXMLCusts myXMLcusts = new WSTest1.MyXMLCusts();
    test.GetXMLPort(ref myXMLcusts);
    foreach (WSTest1.MyXMLCust myXMLcust in myXMLcusts.MyXMLCust)
    {
      // Do Stuff
    }
    
    But you could also fill out the myXMLcusts before calling the function and send stuff into the XML Port.

    So as I tryed to explain earlier - the XML Port isn't exposed, but the schema of the XML Port is.
    and if you put VAR on the XML Port - you will get the result of the XML Port back.
    Freddy Kristiansen
    Group Program Manager, Client
    Microsoft Dynamics NAV
    http://blogs.msdn.com/freddyk

    The information in this post is provided "AS IS" with no warranties, and confers no rights. This post does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion.
  • Options
    BennoBenno Member Posts: 20
    Yes, this is exactly what I mend
  • Options
    JutJut Member Posts: 72
    I tried this example and it works great.

    However, what I was not able to accomplish is to have some control on the xmlport from visual studio. It would be for example great to dynamically pass the filter(-string) to the xmlport/codeunit instead of having it hard-coded in the codeunit. Is this possible? I tried the "normal" NAV-way and created a second function in my published Codeunit that is exposing the XMLPort whereas this function just stored the filter in a global variable. Unfortunately, the value seems to be lost when the GetXMLPort-Method is run - even after changing the codeunit to singleInstance = true.

    The C#-Code looks like:

    CustomerWS.Customers myXMLCusts = new CustomerWS.Customers();
    custWS.SetFilter("10000");
    custWS.GetXMLPort(ref myXMLCusts);
    foreach (CustomerWS.Customer myXMLcust in myXMLCusts.Customer)
    {
    label1.Text = myXMLcust.Name;
    }

    Thanks a lot in advance!

    Jut
  • Options
    JutJut Member Posts: 72
    Oh, that was easier than expected. I did not realize that the GetXMLPort-function could just get additional parameters such as my filter-string...

    Yet, I would still love to know if it is possible to store/persist values on the middle-tier in between multiple calls of a single client.

    Thanks,
    Jut
  • Options
    kinekine Member Posts: 12,562
    Jut wrote:
    Oh, that was easier than expected. I did not realize that the GetXMLPort-function could just get additional parameters such as my filter-string...

    Yet, I would still love to know if it is possible to store/persist values on the middle-tier in between multiple calls of a single client.

    Thanks,
    Jut
    You can store them in a table and the client must identify the session somehow (e.g. using same guid as a parameter when calling the functions - something like corelation ID)
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • Options
    JutJut Member Posts: 72
    Thanks! That is certainly an option - whereas the database overhead makes me wonder if other alternatives exist.
  • Options
    MrWhoMrWho Member Posts: 59
    Is it possible to render the result sent back to for example the .NET application into a Dataset? Rather than having to travers each table/node by a loop, like in the example:
    WSTest1.MyXMLCusts myXMLcusts = new WSTest1.MyXMLCusts();
    test.GetXMLPort(ref myXMLcusts);
    
    myXMLcust.ToString() //This needs to be converted to a stream so it can be read by the dataset, but it only returns WSTest1.MyXMLCusts 
    System.IO.Stream s = null;
    s.Read(*****);
    
    Dataset ds = new Dataset();
    ds.ReadXML(s)
    
    
  • Options
    kinekine Member Posts: 12,562
    Try to assign the myXMCusts as datasource to the grid...
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • Options
    MrWhoMrWho Member Posts: 59
    Unfortunately no success there.

    "Unvalid type for datasource". It must be of type "IListSource,IENumerable or IDataSource"
  • Options
    kinekine Member Posts: 12,562
    For me this was working:
                    service = new NAV2009_Customer.Customer_Service();
                    service.UseDefaultCredentials = true;
                    NAV2009_Customer.Customer_Filter[] filters = { new NAV2009_Customer.Customer_Filter(), new NAV2009_Customer.Customer_Filter() };
                    filters[0].Field = NAV2009_Customer.Customer_Fields.City;
                    filters[0].Criteria = "P*";
                    filters[1].Field = NAV2009_Customer.Customer_Fields.Name;
                    filters[1].Criteria = "P*";
                    customers = service.ReadMultiple(filters, "", 1000);
                    listBox1.DataSource = customers;
                    listBox1.DisplayMember = NAV2009_Customer.Customer_Fields.Name.ToString();
    
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • Options
    MrWhoMrWho Member Posts: 59
    I've setup my function in Nav to use BigText as a var parameter instead. Then I use the new TempBlob table to fill the BigText variable with the XMLPort content.

    Then I use memorystream and text encoding in my .NET application to be able to use the "ds.ReadXML()" . It work's lik a charm, and using the memorystream felt like the whole application became faster .. :)
  • Options
    MrWhoMrWho Member Posts: 59
    I was not doing any of the filtereing. I suppose you called your function in Nav for "ReadMultiple"? I was just calling my webservice function with the root element of the xmlport as parameter, and then setting that parameter as the datasource. Also I was trying with GridView rather then Listbox.
  • Options
    kinekine Member Posts: 12,562
    Ok, the difference is that I have used the Page as source, but you your own function with XMLPort. Than you need to do some transfer from the result into different class like some List or DataSource, which could be used as datasource for ListBox or Grid.
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • Options
    kinekine Member Posts: 12,562
    Kamil Sacek
    MVP - Dynamics NAV
    My BLOG
    NAVERTICA a.s.
  • Options
    MrWhoMrWho Member Posts: 59
    I'm happy with using BigText for now since it was read easily into a dataset. Then I could continue to use the rest of my code that was earlier developed to be used against MSMQ. :)

    If using Page wouldn't you narrow down the result to just one table?

    Here is part of my Project if anyone is interested:

    XMLPort in Dynamics Nav:


    Webservice Codeunit beeing exposed:
    OBJECT Codeunit 50007 My Invoices
    {
      OBJECT-PROPERTIES
      {
        Date=03.09.10;
        Time=09:52:47;
        Modified=Yes;
        Version List=;
      }
      PROPERTIES
      {
        OnRun=VAR
                bt@1000000000 : BigText;
              BEGIN
              END;
    
      }
      CODE
      {
        VAR
          tblCustomer@1000000000 : Record 18;
    
        PROCEDURE GetMyInvoices@1000000001(VAR xmlBT@1000000000 : BigText;PartnerID@1000000001 : Integer;AccountID@1000000006 : Text[30]);
        VAR
          xmlMyInvoicesData@1000000002 : XMLport 50000;
          tblTempBlob@1000000003 : TEMPORARY Record 99008535;
          oStream@1000000004 : OutStream;
          iStream@1000000005 : InStream;
        BEGIN
          tblTempBlob."Primay Key" := 1;
          tblTempBlob.INSERT;
          tblTempBlob.Blob.CREATEOUTSTREAM(oStream);
    
          tblCustomer.SETCURRENTKEY(" Partner ID"," Account ID");
          tblCustomer.SETRANGE(" Partner ID",PartnerID);
          tblCustomer.SETRANGE(" Account ID",AccountID);     
    
          xmlMyInvoicesData.SETTABLEVIEW(tblCustomer);
          xmlMyInvoicesData.SETDESTINATION(oStream);
          xmlMyInvoicesData.EXPORT;
    
          tblTempBlob.MODIFY;
          tblTempBlob.CALCFIELDS(Blob);
          tblTempBlob.Blob.CREATEINSTREAM(iStream);
          xmlBT.READ(iStream);
        END;
    
        BEGIN
        END.
      }
    }
    
    

    In Visual Studio the Code look like this:
            int partnerId = Int32.Parse(Request.QueryString.Get("partnerid"));
            String accountId = Request.QueryString.Get("accountid");
    
            CPSMyInvoices myInvoicesService = new CPSMyInvoices();                
            
            NetworkCredential credentials = new NetworkCredential("***", "***", "***");
            myInvoicesService.Credentials = credentials;
            myInvoicesService.PreAuthenticate = true;
    
            string xmlBT = "";
    
            myInvoicesService.GetMyInvoices(ref xmlBT,partnerId,accountId);
            byte[] byteArray = new byte[xmlBT.Length];
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            byteArray = encoding.GetBytes(xmlBT);
    
            System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(byteArray);
    
            ds.ReadXml(memoryStream);
        
            DataSet dsCustomer = ds;
    
            if (dsCustomer.Tables.IndexOf("Customer") != -1)
            {
    
           and then  you can do what you want :) 
    
    

    And the result looks like this:

Sign In or Register to comment.