Cross-Call State Sharing in Web Services

  • Post category:Development
  • Post comments:11 Comments
  • Reading time:12 mins read

imageWeb services in NAV have an interesting feature: they are stateless. For a system which is pretty stateful otherwise, this feature can be outright annoying. You must get used to it, and then make sure you never ever write code as if there was any state preserved on the other end.

The reason for this is simple – there is no actual protocol that you use to communicate with NAV through SOAP. Calls are ad-hoc, essentially atomic, each one can accomplish a great deal of things in a single go, and it makes programming a whole lot simpler. The price you pay is the state. Once you close the connection, the session ends and the transaction commits (or rolls back). Next call starts from scratch.

If you need to preserve any state between the calls, whatever that state might be, you are toast. NAV simply doesn’t support it out of the box. A common misconception is that single-instance codeunits help. They don’t. The single instance is always single per session, and since each call is an isolated session, it means that each single instance codeunit dies at the end of the call.

Pretty annoying, isn’t it?

Well, it is, and it isn’t. I won’t argue about validity of situations where you need to preserve state across multiple web services calls – I am going to show you how to do it when you need it.

And what I’m going to show you works in both NAV 2009 R2 and 2013.

There are a hundred ways to skin a cat (no animals were harmed during writing of this post) and there are possibly as many ways to store state between calls. They mostly depend on non-volatile approaches, such as database, file system or similar. If you ask me, state should be volatile, so how do we accomplish this?

The concept I like to apply here is the one I explained and demonstrated in my Web Services Black Belt session at NAV TechDays 2012 in Antwerp: it’s the static classes in .NET. Static classes, or static members of dynamic classes in .NET are isolated for an application domain and they remain in memory as long as the application is running. Pretty volatile if you ask me. Since NST is a .NET application, keeping a static class in its domain achieves my goal.

Step 1: The Class

I’ll first create a new class in .NET. It must be classic and must have some methods to store and retrieve objects from it. In my example, I’ll use a Dictionary object to store key-value pairs of strings and objects. This allows you to store any simple data type into the State. Here’s the code:

namespace Demo
{
    public static class StateManager
    {
        private static Dictionary<String, Object> _state =
         
new Dictionary<String, Object>();

        public static void Set(string key, object value)
        {
            Clear(key);
            _state.Add(key, value);
        }

        public static Object Get(string key)
        {
            if (_state.ContainsKey(key))
            {
                return _state[key];
            }
            return null;
        }

        public static void Clear(string key)
        {
            if (_state.ContainsKey(key))
            {
                _state.Remove(key);
            }
        }

        public static void Clear()
        {
            _state.Clear();
        }

        public static bool Exists(string key)
        {
            return _state.ContainsKey(key);
        }

    }
}

 

Should be pretty self explanatory. If it isn’t, there is a static private dictionary member that is instantiated on the first access. There are Set and Get methods to store and retrieve anything to and from the state. There is a Clear method which can either remove a single object from the state, or clear all objects. Finally, there is a method to check if an object exists in the state.

Now compile this and deploy it to either Add-ins or GAC. Your choice entirely.

Yeah, yeah, I know, all you .NET ninjas out there – this is not thread safe. Scroll down, it’s our last step. I don’t want to confuse all non-DotNet folks here with unnecessary stuff. First things first.

Step 2: Consuming the Class

Now that we have the class, let’s consume it. I’ll create a new codeunit that provides access to this state. It is nothing but a C/AL wrapper around the C# class above. Here it goes:

Documentation()

OnRun()

SetString(Key : Text;Value : Text)
State.Set(Key,Value);

GetString(Key : Text) : Text
EXIT(State.Get(Key));

SetDate(Key : Text;Value : Date)
State.Set(Key,Value);

GetDate(Key : Text) : Date
EXIT(DT2DATE(State.Get(Key)));

Exists(Key : Text) : Boolean
EXIT(State.Exists(Key));

Clear(Key : Text)
State.Clear(Key);

ClearAll()
State.Clear;

I’m pretty sure that I don’t need to explain what the State variable in the above piece of code is. I’ll say one thing: it’s global (even though it should be pretty obvious, too). I also hope that I don’t need to explain why I have GetString, SetString, GetDate, SetDate functions here. C/AL doesn’t understand the concept of boxing and that’s why you’ll need to create as many Get/Set function pairs as there are data types you want to store. You cannot use variants because they make the codeunit unavailable to web services, and you can’t return a variant from a function.

A funky thing here is that when you box a C/AL Date type into Object, it gets automatically translated into DateTime, but when you unbox it, you cannot directly unbox it into the C/AL Date type. Probably a bug in .NET interoperability, but the workaround is extremely simple, as you can see above.

Save this codeunit, publish it as a web service, and off you go.

Step 3: Implement the State

Let’s take a real-life scenario: work date. In Web services, work date always defaults to current system date. Sometimes, especially in page web services, in relation to, for example, number series, this can be pretty annoying. Let’s use our state manager to make web services use a different work date for page calls.

I’ll first add the SetWorkDate and ApplyStateWorkDate functions to the state manager codeunit:

SetWorkDate(Date : Date)
SetDate(Text_STATE_WORKDATE,Date);

ApplyStateWorkDate()
IF Exists(Text_STATE_WORKDATE) THEN
  WORKDATE := GetDate(Text_STATE_WORKDATE);

Here, you only need to declare a text constant with a unique value that you use as a key to store the date in the state manager.

Then, in the page you want to make use the work date from the state, modify the OnInit trigger to include this piece of code:

StateMgt.ApplyStateWorkDate;

 

I’ll use the SalesOrder page (42).

Step 4: Verifying the State

Next step is to verify that this works. So, publish the page 42 (or whichever one you wanted to use) as a web service, and from your consumer application, call the state manager codeunit web service first to set a work date to some value, and then call the sales order page web service to create a new sales order. This is my demo code, you might need to tweak it left or right:

var state =
    new StateManagement
        {
            UseDefaultCredentials = true,
            Url =              "
http://localhost:7147/DynamicsNAV70/WS/CRONUS International Ltd./Codeunit/StateManagement"
        };
state.SetWorkDate(new DateTime(2011, 10, 10));

var salesOrderSvc =
    new SalesOrder.SalesOrder_Service
        {
            UseDefaultCredentials = true,
            Url =
"
http://localhost:7147/DynamicsNAV70/WS/CRONUS International Ltd./Page/SalesOrder"
        };

var salesOrder = new SalesOrder.SalesOrder();
salesOrderSvc.Create(ref salesOrder);

MessageBox.Show(salesOrder.Document_Date.ToString());

Now, run it and admire it. It first stores October 10, 2011 as your work date, and then creates a sales order on this date. Pretty fly for a web service call.

Step 5: Thread safety, and other ninja stuff

If you’ve made it this far, you must speak both C/AL and C# fluently and you have been keeping that “yes, but…” for quite a while. As I mentioned earlier – this stuff is not thread safe.

Thread safety is something that, before .NET Interoperability, you never had to worry about in C/AL. Now, you must worry about it whenever writing your own classes intended for consumption from C/AL. You also must worry about it when consuming thread-unsafe third-party assemblies, which is a story for another post.

In short – thread safety is a concept that applies to situations when multiple threads are accessing the shared piece of data. Since NAV Service Tier is a heavily multi-threaded application, and since static classes are shared data, you must make all of your static classes thread-safe.

There are many different approaches to achieving thread safety. The simplest is to synchronize access to shared data through locks. Simply lock the _state variable before each access, and you are good. For example, instead of the code above for Set and Get methods, write this:

public static void Set(string key, object value)
{
    lock (_state)
    {
        Clear(key);
        _state.Add(key, value);
    }
}

public static Object Get(string key)
{
    lock (_state)
    {
        if (_state.ContainsKey(key))
        {
            return _state[key];
        }
        return null;
    }
}

Of course, do this for all methods that access the _state variable.

Another possibility you have here is to use the ConcurrentDictionary class of .NET Framework 4.0 and above, but this is applicable only to NAV 2013.

Just remember – your static classes, and classic members of your dynamic classes, must be thread safe. If they are not, see for yourself what happens. Hint: it doesn’t work.

And in the end…

And now that you reached this far, let me say that yes – I am still alive, and no – I didn’t stop blogging or have any intentions of stopping blogging. Over last year I was pretty busy writing content for NAV 2013 courseware, and this year I started doing some very similar stuff, which I’ll soon also blog about. The point is – I’m here, I’m glad that you are here, too, and I’ll blog whenever I get a chance about stuff that I believe is relevant. Stay tuned, and see you around.

Vjeko

Vjeko has been writing code for living since 1995, and he has shared his knowledge and experience in presentations, articles, blogs, and elsewhere since 2002. Hopelessly curious, passionate about technology, avid language learner no matter human or computer.

This Post Has 11 Comments

  1. Jurica Bogunović

    Glad to see you are still writing, and very useful post as always!

  2. Jaap Mosselman

    Perhaps I misses something, but I think this is only going to work if there is only 1 process at a time, which is executing the code like in step 4. But think about a web application. If there are multiple users adding a salesorder at the same time and they want to use a different workdate (for whatever reason), you have still a problem.
    I think a better approach is to create a code unit with functions which are exposed as web services and that functions should have parameters to carry the needed state. In this example you would need a function: AddSalesOrder(workdate: Date).

    1. Vjekoslav Babic

      @Jaap: you are not missing anything, what you say is true. This is just an example which clearly shows how cross-call state works. If you want to make it bulletproof, there are other things you need to do. The point of my article is not to tell you how to change the workdate for web services, but to show how to achieve a simple, volatile, cross-call state management. And I hope it helps there.

      1. Jaap Mosselman

        @Vjekoslav: of course the workdate is only an example. My point is that if you have a client process like in step 4, which eventually will be executed multiple times parallel (e.g. in a web app), you will have a problem.

        1. Vjekoslav Babic

          @Jaap: we could go on like this forever, couldn’t we? 😉 Anyway – again, you are right, but if you wanted to control cross-call state, then of course you wouldn’t simply let an asynchronous front-end to keep throwing uncontrolled requests at your NST web services layer. Some synchronization proxy mechanism would be required in the middle, and here again there are a hundred ways to skin a cat.

  3. SomeRandomNAVDevelopper

    And there was light.

    Thanks a lot for this !

    1. Vjeko

      Be careful with this! It’s dangerous because you can also share state across sessions, even across tenants.

      1. SomeRandomNAVDevelopper

        Sharing informations between sessions was exactly what I was looking for, I am building a SessionTree .NET class to keep an eye on sessions launched with STARTSESSION and their relations, so for examples informations generated in a grand children session can be accessed in any other session of the family.

Leave a Reply