Stephen Smith's Blog

Musings on Machine Learning…

Posts Tagged ‘error reporting

Error Reporting in Sage 300 ERP

with 18 comments

Introduction

A very important aspect of all applications is how they handle errors and how they inform the end user about these errors. When everything is entered properly and people take what is called the “happy path” through the program then there is no issue. But end users will stray from the “happy path” and other circumstances can conspire to cause the need for informing the user of unusual circumstances.

In our previous blog postings on using the .Net API we have been deferring our discussion of error reporting, but now we are going to tackle it and add error reporting to the ASP.Net MVC sample program we started last week. In doing so we will introduce some new concepts including starting to use JQuery and Microsoft’s Unobtrusive Ajax.

Errors

Generally when we refer to errors in this articles we will also mean warnings and informational messages. Often you hit save on a form and if something is wrong then you get a message box telling you what was wrong (and maybe even what to do about it). However this is bit of an oversimplification. Sage 300 ERP is a three tier client server application. Many of the errors originate in the business logic or the database server. These may be running as Windows services and have no way of doing any user interaction. They can’t simply popup a message box. The error message must be transmitted to where ever the UI is running (say in a web browser somewhere on the Internet) and displayed there in a nice form that fits in with the general design of the form.

Further there may be more than one error. It is certainly annoying to have error messages popup one at a time to be answered and it is also very annoying to Save have one error message appear and correct the thing wrong, hit save again and then have a further thing wrong and so on.

To solve these problems we collect errors, warnings and messages up into a collection which is maintained during processing and forwarded up to the higher levels when processing is complete. We see this in the .Net API with the Errors collection on the Session object. As processing proceeds any business logic component can add messages to this collection. This collection can then be processed by the UI and cleared after it has reported the errors.

All the actual error messages referenced from the business logic are stored in Windows resource files and one of these is provided for each language. So the API used by the Business Logic will access the correct language resource file for the language of the Sage 300 user associated with the session object.

Inside the collection, each error has a priority such as Severe Error, Error, Security, Warning or Message. This way we can decide further how to display the error. Perhaps have the error dialog title bar red for errors, blue for warnings and green for messages.

Exceptions

Inside our .Net API we will return a return code for simple things that should be handled by the program. For instance if we go to read a record, we return false if it doesn’t exist, true if it does. But for more abnormal things we will throw an exception. You should always catch these exceptions, since even if you are debugging a program, these messages can be very helpful.

Just because an exception is thrown, doesn’t mean there is an error on the error stack. The exception might be because of some other exception, say from the .Net runtime. Because of this you will see in our exception handler, that if there is no error from Sage 300 then we display the error that is part of the exception (perhaps from .Net).

Sample Program

To add error reporting to our ASP.Net MVC sample programming we are going to start introducing how to program richer functionality in the browser. As we left off you just hit submit, the processing was done and the page was refreshed completely. This is rather old school web programming and a bit better user interaction is expected these days. Now I’ve updated the sample ARInvEntry sample (located here) to do an Ajax request rather than a page submit request. As a result the page isn’t completely redrawn and we can do a bit more processing. In this case we will use JQuery to put up a dialog box with any messages, warnings or errors that were encountered. Normally when you save an Invoice there is no success message displayed, but here we will put up a success message as well.

First off now that we are programming in JavaScript in the Browser, this is an interpreted environment which means there is no compiler to catch dumb programming mistakes and typos. One thing is to watch the syntax highlighting in the editor and use intelli-sense to avoid some typos. The other things is that we are passing data structures from C# on the server to JavaScript on the client. Which means there is no validation that C# and JavaScript have the same understanding of the object. Further JavaScript tends to silently ignore errors and keep on going, so you can be rather mystified when things don’t work. Some good tools to look for errors are Firebug and Fiddler. Trying different browsers can help also.

First I changed the page submit call to an Ajax call using Microsoft’s unobtrusive JavaScript library.

        @using (Ajax.BeginForm("CreateInvoice", "Home", null,
             new AjaxOptions { OnSuccess = "showResult" }))

Now the request will be made with Ajax and any returned data will be passed to the showResult JavaScript function. Pretty easy change, but there is one major missing piece. We must include the JavaScript library for this. The JavaScript file for any library needs to be added to the Scripts section of the project and then an entry needs to be added to BundleConfig.cs. Here we added jquery.unobtrsive-ajax.js to the jquery bundle.

     bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
         "~/Scripts/jquery-{version}.js",
         "~/Scripts/jquery-ui-{version}.js",
         "~/Scripts/jquery.unobtrusive-ajax.js"));

We also added the jquery-ui-{version}.js. The framework will figure out which version to fill in from the js files in the scripts folder. This way we can update these without having to change any C# code. If we didn’t add the unobtrusive library then nothing would happen and we wouldn’t get any errors because nothing would be hooked up to fail. When having trouble with JavaScript always check you have the correct libraries added. Similarly we have added the css and images for JQuery to the project. You could put these in script tags in your HTML, but the bundles help with eventual deployment as we’ll see in a later article. These bundles are referenced in the cshtml files to actually include them in the generated HTML and one gotcha is that sometimes the order of including them matters due to namespace conflicts, for instance the bootstrap bundle must be placed before the jQuery bundle or something in JQuery won’t work due to some naming conflicts.

Now we add the showResult function and what it calls.

function showResult(data)
{
    $.fn.showMessageDialog(data.Warnings, data.Errors, data.Messages);
}
(function ($) {
    $.fn.showMessageDialog = function (warnings, errors, messages) {
        $("#infoWarningsBlock").hide();
        $("#infoSuccess").hide();

        if ((warnings != null && warnings.length > 0) ||
            (errors != null && errors.length > 0) ||
            (messages != null && messages.length > 0)) {

            var text = "";
            var i;
            if (messages != null && messages.length > 0) {
                $("#infoSuccess").show();
                for (i = 0; i < messages.length; i++) {
                    text = text + "<li>" + messages[i] + "</li>";
                }
            }
            $("#infoSuccess").html(text);
            text = "";
            if (errors != null && errors.length > 0) {
                $("#infoWarningsBlock").show();
                for (i = 0; i < errors.length; i++) {
                    text = text + "<li>" + errors[i] + "</li>";
                }
            }
            if (warnings != null && warnings.length > 0) {
                $("#infoWarningsBlock").show();
                for (i = 0; i < warnings.length; i++) {
                    text = text + "<li>" + warnings[i] + "</li>";
                }
            }
            $("#infoWarnings").html(text);

            $("#informationDialog").dialog({
                title: "Information",
                modal: true,
                buttons: {
                    Ok: function () {
                        $(this).dialog("close");
                        $(this).dialog("destroy");
                    }
                }
            });
        }
    }

}(jQuery));

Besides setting up the HTML to display, this code mostly relies on the JQuery dialog function to display a nice message/error/warning dialog type box in the Browser using an iFrame.

This routine requires a bit of supporting HTML which we put in _Layout.cshtml file so it can be shared.

    <div id="informationDialog" style="display: none;">
        <ul id="infoSuccess" style="display: none;"></ul>
        <div id="infoWarningsBlock" style="display: none;">
            <div id="infoWarningsHeader">
                <h4>Warnings/Errors</h4>
            </div>
            <ul id="infoWarnings"></ul>
        </div>
    </div>

Notice it has display set to none so it is hidden on the main page.

From the C# code on the server here is the error handler:

        //
        // Simple generic error handler.
        //
        private void MyErrorHandler(Exception e)
        {
            if (session.Errors == null)
            {
                Errors.Add(e.Message);
                Console.WriteLine(e.Message);
            }
            else
            {
                if (session.Errors.Count == 0)
                {
                    Errors.Add(e.Message);
                    Console.WriteLine(e.Message);
                }
                else
                {
                    copyErrors();
                }
            }
        }

        private void copyErrors()
        {
            int iIndex;

            for (iIndex = 0; iIndex < session.Errors.Count; iIndex++)
            {
                switch (session.Errors[iIndex].Priority)
                {
                    case ErrorPriority.Error:
                    case ErrorPriority.SevereError:
                    case ErrorPriority.Security:
                    default:
                        Errors.Add(session.Errors[iIndex].Message);
                        break;
                    case ErrorPriority.Warning:
                        Warnings.Add(session.Errors[iIndex].Message);
                        break;
                    case ErrorPriority.Message:
                        Messages.Add(session.Errors[iIndex].Message);
                        break;

                }
                Console.WriteLine(session.Errors[iIndex].Message);
            }
            session.Errors.Clear();
        }

It separates the errors, warnings and messages. Also the copyErrors method was separated out so it can be called in the case of success, in case there are any warnings or other messages that should be communicated.

The controller now just passes the variables from the model back to the web browser.

        public JsonResult CreateInvoice(Models.CreateInvoice crInvObj)
        {
            ResultInfo results = new ResultInfo();

            results.Messages = crInvObj.Messages;
            results.Errors = crInvObj.Errors;
            results.Warnings = crInvObj.Warnings;

            crInvObj.DoCreateInvoice();

            return Json(results);      
        }

All the work here is done by the Json method which translates all the C# objects into Json objects. It even translates the C# list collections into JavaScript arrays of strings. So for the most part this handles the communication back to the Browser and the JavaScript world.

error2

Summary

We discussed the Sage 300 ERP error reporting architecture and saw how to integrate that into our ASP.Net MVC sample program. Since this was our first use of Ajax and JQuery, we had a bit of extra work to do to set all that up to operate. Still we didn’t have to write much JavaScript and the framework handled all the ugly details of Ajax communications and translating data between what the server understands and what the browser understands.

Update 2015/01/29: Sometimes View calls will return a non-zero return code for special informational purposes. These will cause the API to throw an exception. To handle these you need to catch the exception, check the View’s LastReturnCode property to see what it is and then continue processing. Perhaps a bit of a pain to do this, but it is the exception rather than the rule.