none
XML and .Net

    Question

  • I'm having an issue with sending XML from VFP to a .Net application.

    In VFP I create a cursor, populate some data, then use xmltocursor("mycursor","lcXml",1) to create an XML string.  I then pass this string to the .NET app.

    The problem is that .NET complains about the string.  (I also get the same error if I save the XML to a file)

    According to the intellisense, the third parameter of 1 is supposed to create xml that can be consumed by .net  This does not seem to be the case.  Here is the error I receive from .Net

    "Unexpected end of file while parsing Name has occurred. Line 1, position 6."

    Line 1:  <?xml version = "1.0" encoding="UTF-8" standalone="yes"?

    I can create the xml string manually, excluding the first line generated by cursortoxml, and it works fine with .Net.

    What am I missing here?

    Tuesday, October 10, 2006 9:41 PM

Answers

  • Oh my! You're "sending" the xml as a commandline argument (I first didn't understand what you mean by "it is using spaces as delimiters which makes sense").

    Why do you call a C# exe via ShellExecute? As I can see you both have the control of VFP and C# source codes. You could create a COM communication (VFP from C# or C# from VFP). Check west-wind.com for a nice article from Rick Strahl about this approach (Sedna uses this approach AFAIK).

    Another easy and quick way would be (if you'd follow ShellExecute approach) to save the XML to a file and pass the filename as the parameter (check my sample - there args[0] is meant to be fullpath of generated XML). In VFP code then you could directly use the second parameter as a filename (use flag 512 then).

    Yet another way, if that cursor is created with simple SQL ( I mean if doesn't need a lot of processing to obtain before passing to C# ) then you could directly get it from C# using VFPOLEDB provider with no XML in between. Hard to guess w/o knowing your actual purpose (maybe even Web service is what you're after).

    If you wouldn't mind me talking on your code a bit:

    foreach (string strInput in strInputXml)
     {
         if (string.IsNullOrEmpty(strAssemble))
         {
             strAssemble = strInput;
         }
         else
         {
             strAssemble = strAssemble + " " + strInput;
         }

    Is not an efficient way to handle assembling a string from pieces. Here each strAssembel = line causes a new string to be created on the heap (string type is immutable). It can quickly get very slow when pieces are a lot. Instead use StringBuilder class. It's optimized for operations like this. ie: Same code written with StringBuilder:

    StringBuilder sb = new StringBuilder();

    for(int i = 0;i<strInputXml.Length;i++)
     {  
        if (i != 0)
            sb.Append(" ");
        sb.Append( strInputXMLIdea );
     }
     strAssemble = sb.ToString();

     

    Wednesday, October 11, 2006 7:56 PM

All replies

  • .Net doesn't have a problem with cursortoxml() generated XML (nor VFP has a problem with .Net generated XML from a dataset).

    Here is a sample to test:

    VFP code:

    USE customer
    CURSORTOXML("customer","lcXML",1,0,0,"1") && doesn't matter if you specify only first 3 params
    STRTOFILE(m.lcxml,"c:\temp\myCustomers.xml")

    C#:
    using System;
    using System.Data;

    class CheckVFPXML
    {
      static void Main(string[] args)
      {
        DataSet ds = new DataSet();
        ds.ReadXml(args[0]);
        ds.Tables[0].TableName = "fromVFP";
        for (int i=0;i < 5 && i < ds.Tables[0].Rows.Count; i++)
        {
         for (int j=0;j < 5 && j < ds.Tables[0].Columns.Count; j++) {
           Console.WriteLine( ds.Tables[0].RowsIdea[j].ToString() ); }
        }
      }
    }

    It reads pretty well. Probably the problem is in your sending. How do you "send" the XML? Or it might be that UTF-8 I see in your sample. With which version cursortoxml(alias, var, 1) generates UTF-8 encoding? If you used flag 32 I expect you to get this error. Use 1252 default codepage IMHO.

     

    Wednesday, October 11, 2006 11:31 AM
  • Are you including a schema with the XML? Check all the parameters of CURSORTOXML() in the help file.
    Wednesday, October 11, 2006 3:07 PM
  • I mispoke when I said that I received the same error when I saved the XML to a file, and then sent the filename as the input parameter.  This actually works fine.

    Here is the problem.  The input parameter for the c# app is splitting the input string into a string array.  

    So the value for strInputXml[0] = "<?xml", strInputXml[1] = "version", etc... 

    It is using spaces as the delimiter, which actually makes sense.

    Here is how I got things to work.

    First I had to remove the line [<?xml version="1.0" encoding="Windows-1252" standalone="yes" ?>] on the VFP side. 

    I tried enclosing the whole XML string in quotes, but because there are quotes included in this line, .Net did not handle it as I wished.

    Second, in .Net, I added the code:

    foreach (string strInput in strInputXml)
    {
        if (string.IsNullOrEmpty(strAssemble))
        {
            strAssemble = strInput;
        }
        else
        {
            strAssemble = strAssemble + " " + strInput;
        }
    }

    StringReader xmlSR = new StringReader(strAssemble);
    ...

    Here is my resulting code:

    VFP Code Before:

    CURSORTOXML("mydata","lcXml",1)

    DECLARE INTEGER ShellExecute IN shell32.dll ;
     INTEGER hndWin, ;
     STRING cAction, ;
     STRING cFileName, ;
     STRING cParams, ;
     STRING cDir, ;
     INTEGER nShowWin

    lcFileName = "MyCSharpApp.exe"
    lcAction = "Open"
    lcPath = "C:\Temp"

    ShellExecute(0,lcAction,lcFileName,lcXml,lcPath,1)

    VFP Code After:

    CURSORTOXML("mydata","lcXml",1)
    lnStart = AT("<VFPData>",lcXml)
    lcInputXml = SUBSTR(lcXml,lnStart)

    DECLARE INTEGER ShellExecute IN shell32.dll ;
     INTEGER hndWin, ;
     STRING cAction, ;
     STRING cFileName, ;
     STRING cParams, ;
     STRING cDir, ;
     INTEGER nShowWin

    lcFileName = "MyCSharpApp.exe"
    lcAction = "Open"
    lcPath = "C:\Temp"

    ShellExecute(0,lcAction,lcFileName,lcInputXml,lcPath,1)


    C# code Before:

    static void Main(string[] strInputXml)
    {
     DataSet dsInput = new DataSet();
     StringReader xmlSR = new StringReader(strInputXml[0]);
     dsInput.ReadXml(xmlSR);   // This is where the error occurs
     ...
    }

    C# code After:

    static void Main(string[] strInputXml)
    {
     DataSet dsInput = new DataSet();
     string strAssemble = "";

     foreach (string strInput in strInputXml)
     {
         if (string.IsNullOrEmpty(strAssemble))
         {
             strAssemble = strInput;
         }
         else
         {
             strAssemble = strAssemble + " " + strInput;
         }
     }


     StringReader xmlSR = new StringReader(strAssemble);
     dsInput.ReadXml(xmlSR);
     ...
    }

    I am open to further suggestions on more elegant ways to handle this.

    Thanks for the responses....

    Wednesday, October 11, 2006 5:02 PM
  • Oh my! You're "sending" the xml as a commandline argument (I first didn't understand what you mean by "it is using spaces as delimiters which makes sense").

    Why do you call a C# exe via ShellExecute? As I can see you both have the control of VFP and C# source codes. You could create a COM communication (VFP from C# or C# from VFP). Check west-wind.com for a nice article from Rick Strahl about this approach (Sedna uses this approach AFAIK).

    Another easy and quick way would be (if you'd follow ShellExecute approach) to save the XML to a file and pass the filename as the parameter (check my sample - there args[0] is meant to be fullpath of generated XML). In VFP code then you could directly use the second parameter as a filename (use flag 512 then).

    Yet another way, if that cursor is created with simple SQL ( I mean if doesn't need a lot of processing to obtain before passing to C# ) then you could directly get it from C# using VFPOLEDB provider with no XML in between. Hard to guess w/o knowing your actual purpose (maybe even Web service is what you're after).

    If you wouldn't mind me talking on your code a bit:

    foreach (string strInput in strInputXml)
     {
         if (string.IsNullOrEmpty(strAssemble))
         {
             strAssemble = strInput;
         }
         else
         {
             strAssemble = strAssemble + " " + strInput;
         }

    Is not an efficient way to handle assembling a string from pieces. Here each strAssembel = line causes a new string to be created on the heap (string type is immutable). It can quickly get very slow when pieces are a lot. Instead use StringBuilder class. It's optimized for operations like this. ie: Same code written with StringBuilder:

    StringBuilder sb = new StringBuilder();

    for(int i = 0;i<strInputXml.Length;i++)
     {  
        if (i != 0)
            sb.Append(" ");
        sb.Append( strInputXMLIdea );
     }
     strAssemble = sb.ToString();

     

    Wednesday, October 11, 2006 7:56 PM
  • Yes, I agree that it would be better to create a VFP com object and have C# make calls to the VFP dll.  In fact, that is what I have done in a previous application.  The VFP com would return datasets in the form of XML, and the c# app would consume this XML.  That actually worked quite nicely, which was part of my frustration.  Why did this approach work in the previous instance, and not here? 

    The problem here is that the C# application has been designed to be called from any number of different external applications, including dos batch files.  The solution we decided on was to have the developers of these applications create an xml string and pass it to the executable.  I have since modified the C# app to accept either a file name or an XML string.  Using SQL server is also a good suggestion, but not feasable in this case. 

    I was using foxpro here, because many of the legacy applications here have been written in VFP7.  I was creating a program sample that the other VFP developers could use as a reference. 

    On a side note, I have been a VFP developer since 1989, and within the last two years have been digging into C#.  I was recently hired by my current employer to create new business applications in the .Net environment.  I have to bite my tounge here a bit because our IT manager is under the impression that VFP is a dead language.  It's funny how that misconception still exists.  Personally I have found VFP to be easy to use, yet verbose enough to accomplish quite complicated tasks.  I still prefer the VFP IDE over the Visual Stutio IDE.

    Also, thanks for the suggestion on using the stringbuilder.  I too felt the bit of code was a bit heavy handed.

    Wednesday, October 11, 2006 10:10 PM
  • I don't know if there are any limitations on commandline parameter length. If there isn't you could put quotes around lcXML:

    '"'+m.lcXML+'"'

    However I would never risk of passing it as a commandline parameter.

    PS: I didn't mean SQL Server. I meant if it's feasible for you then you could directly make Select-SQL calls from C# to VFP using VFPOLEDB driver.

    Thursday, October 12, 2006 1:16 PM
  • BTW I would ask a question to you. Don't you get "Access denied" error code when you ShellExecute to a C# exe? If you don't would you tell me what you did to pass it:) A security setting with SeDebugPrivilege or what? I included [STAThread] but it didn't help.
    Thursday, October 12, 2006 7:38 PM
  • I did not do anthing in particular.  Maybe I'm just lucky. 

    Are you referring to how I was able to debug the c# app?  What happened there was that when the C# app generated the exception, I was promped for a de-bugger to use.  I just selected the default visual studio option, and I was brought to the line of code that caused the exception.  Other than that, I did not do anything special.

    What I can tell you is that the executable is just a wrapper around a class that actually does all the work.  When I created the exe, I started by creating a new console application.  I then set the Ouput type to "Windows Application". 

    Then my code looks something like this:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Data;
    using System.IO;
    using System.Windows.Forms;

    namespace MyExe
    {
        public class MyExe
        {
            private static string strMyString;
            private static MyBaseControls.MyClass myClass;

            private static DataSet dsInput;
           
            static void Main(string[] strInputXml)
            {
                myClass.DoSomeProcess();
            }
        } 
    }

    Friday, October 13, 2006 7:54 PM
  • OK I found why it was failing:) 

    Actually I created C# code to test what you were trying to do. I guessed that commandline should have a limit but still tried to call it with a long XML (full customer table namely). When I shorted it started to work. Here is my ShellExecute call from VFP:

    ShellExecute(0,"open","c:\myPath\CSharpXMLHandler.exe", ["]+m.lcXML+[" "c:\temp\Output Here.txt"],0,1)

    If lcXML content is long it doesn't work (return code is 5-Access denied). Otherwise full xml goes as a single parameter. IOW first line was not a problem in your case. You'll hit the same wall if you call this say from a batch file. Save to a file and pass filename instead.

    Friday, October 13, 2006 11:48 PM