In August last year I posted a way to use dotnet interop and webrequest to replace the automation objects.  I saw that we had a limitation that we have gotten used to.  When using the WinHTTP automation we where able to look at the status and if the status was not the integer 200 we had an error.  We where able to show this error to the user and continue running the code.  When we use the dotnet interop we are missing this error handling method.  We use

[code]  HttpWebResponse := HttpWebRequest.GetResponse;[/code]

and if we have an error the code stoppes.  I have gotten a few request about this error handling and finally today I took a look at this and saw no easy way to solve this in C/Side.  So I created a DLL.  I created a C# class library in Visual Studio 2010 with the following code

 

and the DLL that I build I put in my add-ins folder, both on the server and on my developement client machine.

Next I add this DLL and the HttpWebException to my Codeunit

 

and instead of using GetResponse as I showed before I use my new class library

 

and I now have the possibility to log errors and continue my batch.

The DLL needed is attached.

NAVWebRequest

22 thoughts on “Dotnet WebRequest error handling

  1. Erik says:

    This looks resolved in this way (this is good) but requires an extra install of the dll.

    Are there other options? We use a cloud based NAV and can’t install addins.

    1. Sure

      For example

      PROCEDURE DownloadTextFile@1200050000(URL@1200050000 : Text[1024]);
      VAR
      TempFileName@1000000001 : Text[1024];
      TempFolderName@1000000000 : Text[1024];
      BEGIN
      IF URL = ” THEN EXIT;

      IF NOT CONFIRM(Text016,TRUE,URL) THEN EXIT;

      DialogMgt.WindowOpen(‘#1#############################################\\’ + Text017 + ‘\’ + Text018);
      DialogMgt.WindowUpdateText(1,STRSUBSTNO(Text012,URL));

      ServerHTTPRequest := ServerHTTPRequest.XMLHTTPRequestClass;
      ServerHTTPRequest.open(‘GET’,URL,FALSE,”,”);
      ServerHTTPRequest.send(”);
      IF ServerHTTPRequest.status <> 200 THEN
      ERROR(Text023,ServerHTTPRequest.status,ServerHTTPRequest.statusText);

      TempFileName := FileMgt.ServerTempFileName(‘txt’);
      ServerFile.WriteAllText(TempFileName,ServerHTTPRequest.responseText);

      IF GUIALLOWED THEN
      DialogMgt.WindowUpdateText(1,STRSUBSTNO(Text019,URL));

      ReadServerFile(TempFileName,’utf-8′);
      ServerFile.Delete(TempFileName);

      DialogMgt.WindowClose;
      END;

      Where

      ServerHTTPRequest@1200050031 : DotNet “‘Microsoft.MSXML, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.MSXML.XMLHTTPRequestClass”;

      1. Or

        PROCEDURE DownloadZipFile@1200050001(URL@1200050001 : Text[1024]);
        VAR
        Encoding@1000000000 : DotNet “‘mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.Text.Encoding” RUNONCLIENT;
        TempFileName@1200050008 : Text[1024];
        TempFolderName@1200050003 : Text[1024];
        FileName@1200050002 : Text[1024];
        Index@1200050004 : Integer;
        BEGIN
        IF URL = ” THEN EXIT;

        IF NOT CONFIRM(Text016,TRUE,URL) THEN EXIT;

        DialogMgt.WindowOpen(‘#1#############################################\\’ + Text017 + ‘\’ + Text018);
        DialogMgt.WindowUpdateText(1,STRSUBSTNO(Text012,URL));

        ServerHTTPRequest := ServerHTTPRequest.XMLHTTPRequestClass;
        ServerHTTPRequest.open(‘GET’,URL,FALSE,”,”);
        ServerHTTPRequest.send(”);
        IF ServerHTTPRequest.status <> 200 THEN
        ERROR(Text023,ServerHTTPRequest.status,ServerHTTPRequest.statusText);

        TempFileName := FileMgt.ServerTempFileName(‘zip’);
        TempFolderName := FileMgt.GetDirectoryName(TempFileName) + ‘NAV_NR’;
        IF ServerDirectory.Exists(TempFolderName) THEN BEGIN
        ServerDirectory.Delete(TempFolderName,TRUE);
        SLEEP(100);
        END;

        ServerFile.WriteAllBytes(TempFileName,ServerHTTPRequest.responseBody);
        ServerZipFile.ExtractToDirectory(TempFileName,TempFolderName);
        ServerExtractedFiles := ServerDirectory.GetFiles(TempFolderName);
        IF ServerExtractedFiles.Length > 0 THEN
        FOR Index := 1 TO (ServerExtractedFiles.Length) DO BEGIN
        FileName := ServerExtractedFiles.GetValue(Index – 1);
        IF GUIALLOWED THEN
        DialogMgt.WindowUpdateText(1,STRSUBSTNO(Text019,FileName));
        ReadServerFile(FileName,’iso-8859-1′);
        ServerFile.Delete(FileName);
        END;

        IF ServerFile.Exists(TempFileName) THEN
        ServerFile.Delete(TempFileName);
        IF ServerDirectory.Exists(TempFolderName) THEN
        ServerDirectory.Delete(TempFolderName,TRUE);

        DialogMgt.WindowClose;
        END;

        Where
        ServerHTTPRequest@1200050031 : DotNet “‘Microsoft.MSXML, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’.MSXML.XMLHTTPRequestClass”;
        ServerDirectory@1000000004 : DotNet “‘mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.IO.Directory”;
        ServerPath@1000000006 : DotNet “‘mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.IO.Path”;
        ServerFile@1200050027 : DotNet “‘mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.IO.File”;
        ServerZipFile@1000000005 : DotNet “‘System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.IO.Compression.ZipFile”;
        ServerExtractedFiles@1000000007 : DotNet “‘mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.System.Array”;

  2. erik says:

    Thx, for your examples. You obviously have converted classic code to rtc code a lot 🙂

  3. Michael says:

    Hello Gunnar,

    I happened upon this in trying to solve my own problem… of doing the exception-handling with dotnet System Variables alone (no MSXML), on the basis of system.httpwebrequest. The Problem is – I cannot send the request, then evaluate the status code – I have to use HTTPWebRequest.GetResponse() – which immediately throws an exception. Using the “Equals”-Methods of the dotnet Variable to compare GetResponse with an instance of WebException doesn’t work either – as soon as GetResponse is evaluated, the exception is thrown. A further codeunit called with ‘RUN’ to suppress errors might be an option, but I would have to be able to pass a lot of paramters to the function, which I’m not sure how to do with a simple RUN, where AFAIK I can only pass a Record of a Table.

    Installing another DLL is… well… exactly what I wanted to avoid, so I was wondering if you had any ideas on how to handle HTTPWebRequest-Exceptions in NAV.

    I recently read the adage “anything you can do with throw/catch, you can do with if/else”… seems that isn’t quite true for NAV with DotNet.

    Thank you for sharing your expertise.

    1. Hi Michael

      If you create a codeunit for the error handling you can add a bunch of functions to that codeunit to set parameters and then use the the RUN trigger to call the HTTPWebRequest.GetResponse.

      For example, a codeunit 50000 will be a Global variable MyHTTPWebRequest

      MyHTTPWebRequest.SetParameters(Parameter1,Parameter2,Parameter3…)
      MyHTTPWebRequest.SetURL(Url);
      Success := MyHTTPWebRequest.RUN;
      IF NOT Success THEN
      ERROR(GETLASTERRORTEXT);

      1. Michael says:

        Thanks for the response!

        I just finished this today – and in general, it’s working fine (for sending and requesting data). I created helper functions to try the requests a specified number of times if they fails, waiting a set number of milliseconds between attempts.

        But I didn’t consider that using CodeUnit.RUN with a return value is not possible during write transmissions. When I go through the records in a table, updating those that have changed since the last update, I cannot write the new update-datetime back to the record or make any changes unless I make a commit after every modify/insert – otherwise the codeunit won’t be executable for the next record I have to update.

        The only way around this I could see would be to write all the update-information to a temporary table – then back to the physical table after the table-update is finished, then do a commit before moving on to the next table.

        This has the advantage that the probability of other users/processes encountering blocks because of SQL lock escalations now is extremely small… but it also means that for large tables, any error during the process means no record will have been set as “updated”, and the whole thing will have to be done over again.

        It’s an option – but the try-catch DLL really seems to be the easiest way to do this. It can also most easily be made backwards-compatible with pre-2013 versions where we use XML Requests for Web-interfacing.

  4. Prajeesh says:

    Hello Gunnar,

    I was trying the web service differently. But its not working for me. Please have a look on my Code-unit and Guide me. I am getting an error while getting the Response in XML to the DotNet Variable.

    //setup the temporary table so that we can handle the XML without saving it to disk first
    //create a couple of streams to transfer the data in and out of the BLOB field

    CLEAR(TempTable);
    TempTable.Blob.CREATEINSTREAM(InStr);
    TempTable.Blob.CREATEOUTSTREAM(OutStr);

    //Create a XML request
    XMLDotNet := XMLDotNet.XmlDocument();
    CreateRequestGetItem(XMLDotNet,XMLDotNet,tssArticleNumber,originOfData);

    //create the HTTP connector
    ServerHTTPRequest := ServerHTTPRequest.XMLHTTPRequestClass();

    //tell it where the web service is located
    //set some values in the request header depending on what the service requires

    GenaralLedgerSetup.GET;
    ServerHTTPRequest.open(‘POST’,GenaralLedgerSetup.”Get Item Http Conn. Address”,FALSE,”,”);
    ServerHTTPRequest.setRequestHeader(‘Host’,GenaralLedgerSetup.”Get Item Http Conn. Host”);
    ServerHTTPRequest.setRequestHeader(‘SOAPAction’,GenaralLedgerSetup.”Get Item Http Soap Action”);
    ServerHTTPRequest.setRequestHeader(‘Content-Type’,’text/xml’);

    //actually send the message
    ServerHTTPRequest.send(XMLDotNet);

    //get the response
    XMLDotNet.Load(ServerHTTPRequest.responseXML); //Error-
    {
    —————————
    Microsoft Dynamics NAV Development Environment
    —————————
    The function call was ambiguous. No matching method was found.
    —————————
    OK
    —————————
    }

    //tell us if we got an error (it is 200 because the response definition said “200 OK”)
    IF ServerHTTPRequest.status 200 THEN BEGIN
    MESSAGE(‘Status %1 %2’,ServerHTTPRequest.status,ServerHTTPRequest.statusText);
    EXIT;
    END;

    //this is for diagnostics only, so you can see what you got back
    XMLDotNet.Save(‘C:\Temp\Get Item\XMLResponse1.txt’);

    //take away the namespaces
    RemoveGetItemNamespace(XMLDotNet,XMLDotNet);

    //this is for diagnostics only, so you can see what it looks like after the namespaces have gone
    XMLDotNet.Save(‘C:\Temp\Get Item\XMLResponse2.xml’);

    XMLDotNet.Save(OutStr);
    //fill the BLOB with the response XML

    //the response XMLport reads the data from the BLOB and processes it

    {
    CLEAR(XP2);
    XP2.SETSOURCE(InStr);
    XP2.IMPORT;
    }
    //UpdateTSSGPSPriceTable(“DocumentNo.”,”LineNo.”,currencyCode,countryCode,marketSegmentCode,customerClassCode,tssArticleNumber,quantity);

    CreateRequestGetItem(Source : DotNet “System.Xml.XmlDocument”;VAR Destination : DotNet “System.Xml.XmlDocument”;tssArticleNumber : Code[20];originOfData : ‘ ,SME,SMA’)
    TempTable.Blob.CREATEOUTSTREAM(StyleOutStr);
    TempTable.Blob.CREATEINSTREAM(StyleInStr);
    MESSAGE(‘%1-%2’,FORMAT(tssArticleNumber),FORMAT(originOfData));

    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”+FORMAT(ArticleNumber)+”);
    StyleOutStr.WRITETEXT(”+FORMAT(originOfData)+”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);
    StyleOutStr.WRITETEXT(”);

    Destination := Destination.XmlDocument();
    Destination.Load(StyleInStr);
    Destination.Save(‘C:\Temp\Get Item\XMLRequest.txt’);

    RemoveGetItemNamespace(Source : DotNet “System.Xml.XmlDocument”;VAR Destination : DotNet “System.Xml.XmlDocument”)
    //this has been taken from a Microsoft knowledgebase aricle and strips out the
    //namespaces from an XML message using a style sheet
    XslTransform := XslTransform.XslTransform;
    XMLStyleSheet := XMLStyleSheet.XmlDocument;
    XMLStyleSheet.InnerXml(
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +
    ” +

    );

    XslTransform.Load(XMLStyleSheet);
    writer := writer.StringWriter();
    XslTransform.Transform(Source, nullXsltArgumentList, writer);
    Destination := Destination.XmlDocument;
    Destination.InnerXml(writer.ToString());

    1. Have you tried

      XMLDotNet.Load(ServerHTTPRequest.responseBody);

      instead of

      XMLDotNet.Load(ServerHTTPRequest.responseXML);

      ?

      1. Prajeesh says:

        Thanks Gunnar, I did the other way as you showed in another Post

        GeneralLedgerSetup.GET;
        HttpWebRequest := HttpWebRequest.Create(GeneralLedgerSetup.”Get Item Http Conn. Address”);
        HttpWebRequest.Timeout := 30000;
        //User Name and Password Session

        HttpWebRequest.Method := ‘POST’;
        HttpWebRequest.ContentType := ‘text/xml; charset=utf-8’;
        HttpWebRequest.Accept := ‘text/xml’;
        HttpWebRequest.Headers.Add(‘SOAPAction’,GeneralLedgerSetup.”Get Item Http Soap Action”);
        MemoryStream := HttpWebRequest.GetRequestStream;
        XMLDotNet.Save(MemoryStream);
        MemoryStream.Flush;
        MemoryStream.Close;

        //get the response
        HttpWebResponse := HttpWebRequest.GetResponse;
        MemoryStream := HttpWebResponse.GetResponseStream;
        XMLDotNet := XMLDotNet.XmlDocument;
        XMLDotNet.Load(MemoryStream);
        MemoryStream.Flush;
        MemoryStream.Close;

        XMLDotNet.Save(‘C:\Temp\Get Item\XMLResponse1.xml’);

  5. Navision says:

    Hi Gunaar,

    I have read you blog and found it very helpful. Thanks a lot.

    I have followed it and performed modification to use functionality via Web Services. I do not what is missing on my server and I am getting error when try to test functionality. Please see below Event Log on Server where Navision Application Server is installed..

    Server instance: DynamicsNAV71B37221-02
    Tenant ID: default
    Session type: WebServiceClient
    Session ID: 879
    User: [Domain Name]\websitelogin
    Type: Microsoft.Dynamics.Nav.Types.Exceptions.NavNCLDotNetCreateException
    SuppressMessage: False
    SuppressExceptionCreatedEvent: False
    FatalityScope: Call
    Message: Cannot create an instance of the following .NET Framework object: assembly Microsoft.MSXML, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, type MSXML.XMLHTTPRequestClass.
    StackTrace:
    at Microsoft.Dynamics.Nav.Types.NavAutomationHelper.GetTypeFromAssembly(String assemblyFullName, String typeName)
    at Microsoft.Dynamics.Nav.Types.NavAutomationHelper.CreateDotNetObject(String assemblyFullName, String typeName, Object[] arguments)
    at Microsoft.Dynamics.Nav.Runtime.NavDotNet.CreateDotNet(Object[] arguments)
    Source: Microsoft.Dynamics.Nav.Types
    HResult: -2146233088
    ———————————-
    Type: System.IO.FileNotFoundException
    Message: Could not load file or assembly ‘Microsoft.MSXML, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its dependencies. The system cannot find the file specified.
    FileName: Microsoft.MSXML, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
    FusionLog:
    WRN: Assembly binding logging is turned OFF.
    To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
    Note: There is some performance penalty associated with assembly bind failure logging.
    To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

    StackTrace:
    at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
    at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
    at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean forIntrospection)
    at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
    at System.Reflection.Assembly.Load(String assemblyString)
    at Microsoft.Dynamics.Nav.Types.NavAutomationHelper.GetTypeFromAssembly(String assemblyFullName, String typeName)
    Source: mscorlib
    HResult: -2147024894

    I have already server for “microsoft.msxml.dll” field in Visual Studio steups for Version 2010,2012 and on Server assebly but I did not find it anywhere on server.

    Could you please help me to resolve this issue ? Any help is appreciated

    1. My first idea is that the dll you downloaded is blocked on the server. Is that possible ?

      1. henrikohm says:

        We have the same problem. How to check if it is blocked?

  6. phenno says:

    Has anyone found a solution for this error (Cannot load Microsoft.MSXML)? I’m trying to make a httprequest to an external web service but having problem on a production site for a missing Microsoft.MSXML). On a development site I can find file at: c:\Program Files (x86)\Microsoft.NET\Primary Interop Assemblies\microsoft.msxml.dll but it’s missing on production site. Not sure where to get it from. The only suggestion I’ve found is to install MIcrosoft Document Explorer 2005 copied from some other computer but couldn’t find that either (it’s rather old suggestion).

    1. Can’t you use webclient or httprequest from dot net?

      1. phenno says:

        Wasn’t this httprequest from dotnet? Which other can be used to male a web service call?

        Well, I did find solution by copy to nav service tier addon folder.

  7. phenno says:

    Meanwhile, I’ve found shortcut soultion, to copy microsoft.msxml.dll from my computer to server AddIns folder of NAV service.

    I did not find where does this installation comes from. I suppose it comes with office.

  8. Lars Westman says:

    Hi.

    Thank’2 for providing this!

    I tried this but I get “Internal Server Error 500” in HttpWebException.Message, so it makes no difference for me at the moment.

    When I use MSXML I can catch the error message thrown in the other NAV company I’m communicating with but I would like to use .Net instead.

    Any ideas why this is?

    /Lars

  9. Lars Westman says:

    Since I’m on 2016 I’ll go this path: https://community.dynamics.com/nav/w/designpatterns/244.tryfunction-net-exception-handling-in-cal

    Haven’t tried Try functions yet. Will be fun (I hope…)

    1. Lars, Take a look at how the OCR service communication is done.

      1. Lars Westman says:

        Finally I realized that the best way is to avoid Internal Server Error….

        By using Try-functions in the code run by the web service I could put GETLASTERRORTEXT in a variable and return that to the code calling the web service function and trap the error there with ERROR(TheReturnedErrorMessage)

Leave a Reply

%d bloggers like this: