04.09.2014, 11:00 | #1 |
Участник
|
Did you ever need to identify the current object type and ID, programmatically, from within the object? For example, detecting the current table ID in a table trigger like this guy? Or current codeunit ID from inside the codeunit?
Why would you need something like this? If you are inside a trigger in, say, table 18, you do know that you are in the table 18, and you can refer to it as 18 or DATABASE::Customer, right? Yes, but this is hardcoding. If you move this code to a different table you’d have to change the hardcoded constant to whatever that other table is. Microsoft was well aware of the need to know the currently running object ID in some cases, because there is the OBJECTID function to the CurrPage and CurrReport built-in objects. However, for tables, codeunits, XMLports, and queries, there is nothing of the sort. Now, using .NET Interop, you can easily (well, easy is relative) get this info. Let me start with theory. .NET provides a lot of functionality that gives insight into the internals of the .NET framework or current execution context. Namespaces such as System.Reflection, System.Threading, or System.Diagnostics are examples of it. In C# you can easily get access to the current method, or the method that called the current method. Also, you can detect the type of the currently executing class. When you compile an NAV object, the C/AL compiler translates it into C#. The resulting C# file contains one class that represents the object. This means that any .NET Interop code you write within C/AL can gain access to the metadata of the currently running class. So how do we gain access to the metadata of the currently running code? By using the stack trace. You can find this in the System.Diagnostics.StackTrace class. A stack trace in .NET consists of frames, each frame representing a single execution context within the call stack. The first frame in the trace (index is 0) is the currently executing method context, the next frame is the context of the method that called it, and so on. Let’s get practical now. What I want to do is to have a codeunit – let me call it Current Object Management – and I want it to have these public functions: Then, from within anywhere – for example, from the Customer table – I want the CurrentObject.Type to return ‘Record’ and CurrentObject.ID to return 18. Let’s imagine that both the Type and ID functions call a local function called Detect that does the actual detection of the current object type through the stack trace. This would mean that the call stack would be the following: However, if you create an instance of the StackTrace class with C/AL, and then iterate through the frames, you’ll quickly notice that the call stack is much more complex than what you’d expect it all to be. There are many helper classes and wrapper classes that are beefing up the call stack so it contains a lot more information than you need. This means that from within the CurrentObject.Detect method, the class to which this method belongs is not necessarily the codeunit itself. There is lot of wrapping and reflection in C# representation of C/AL code, and when you call the Detect method, it’s not called directly, but through the Invoke method of the MethodHandle class. The actual NAV context, in this case this Current Object Manager codeunit, is much deeper in the call stack. This makes things complicated. To make it simpler, let’s take a look at what C# code for NAV object looks like. For example, when you create codeunit with the ID 50001, you’ll get a class named Codeunit50001 that inherits from the Microsoft.Dynamics.Nav.Runtime.NavCodeunit base class. This means that when looking through the call stack, we would need to look for the first frame where the declaring type inherits from the NavCodeunit class. This would represent the Current Object Management codeunit, and to find the caller object (which in fact is the current object, or in our example the Customer table) we need to dig further. However, the Customer table, which is Table18 in C#, does not inherit from NavCodeunit, but from the NavRecord type, and each different object has a different base class. Thankfully, all NAV objects inherit from the Microsoft.Dynamics.Nav.Runtime.NavApplicationObjectBase class, and whenever digging through the stack trace, you only need to take those frames into account where the type descends from the NavApplicationObjectBase class. So, the algorithm should follow this logic:
Here we get the System.Type instance for the NavApplicationObjectBase type, and then we iterate through the call stack to fetch the first type that descends from NavApplicationObjectBase. The LastFrame is an integer that represents the last frame ID through which we iterated. The FindNextNavObject function looks like this: It detects the current frame, then takes it’s method information, and then the information about the type to which the method belongs. Then it increases the frame counter. Then it makes sure to stop if the frame counter exceeds available frames, and also if the detected type is an indirect descendant of the NavApplicationObjectBase type (remember that each NAV object inherits from the object base type, and then from this type, for example Record18 : NavRecord : NavApplicationObjectBase). Finally, if we have iterated through all frames in the call stack, we clear the ThisType information. Finally, to complete the detection of the calling class, write this: It detects the next NAV object, and then continues is we are still within the Current Object Management codeunit. Finally, if we have found a type (which is always except when this codeunit is run directly) we parse the type using regular expressions. This is how to do it. The regular expression has two groups, one that matches one or more non-digit characters, and another that matches one or more digit characters. Since all NAV object types follow the same pattern, there is no error handling or unnecessary checks within this function – it simply assumes that the first group in the match represents the object type, and that the second group represents the object ID. The remainder of the codeunit looks like this: Finally, to test this, create a codeunit that calls the Current Object Management: When you run it, it shows this: There are many use cases for this functionality and the better structured code you write, the more you’ll need this information. For example, can you already see how this simple codeunit can help you simplify creating a framework for configuring parameters in a façade pattern based application? To download the objects, follow this link: Currrent_Object_Management.zip Read this post at its original location at http://vjeko.com/blog/detecting-curr...ky-net-interop, or visit the original blog at http://vjeko.com. 5e33c5f6cb90c441bd1f23d5b9eeca34</img> </img> Читать дальше
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору. |
|