So You Want to Learn VB.NET - pt.3
In part 1 and part 2 of this series, we have seen how to handle VB.Net variables, call existing functions, and manipulate basic data-structures. This is enough to write simple scripts or member formulas; but if we do that, we'll likely find ourselves copypasting a lot of code in multiple places. This is wasteful and error-prone: if we later find a bug in our script, we'll have to remember to correct every single place we copied it into. What if we could write our code once, in a single place, and then reference it from other scripts...? Of course we can! Let's see how.
Namespace
VB.Net Namespaces are basic containers for our code. When you go to the Business Rule Page and click "Create New Business Rule", what you end up with is a Namespace. This allows us to organize our code neatly and logically, rather than having one humongous file with everything in it. Imagine storing all code for the entire OneStream in a single file!
We declare a Namespace at the top of our code, just after the Imports section (more on that later). Namespace declarations in OneStream will all start with "OneStream.BusinessRule", followed by the type of rule (".Finance"), followed by the name of this particular namespace (".MyRule").
Namespace OneStream.BusinessRule.Finance.MyRule
[ ... your code here ... ]
End Namespace
Note that OneStream will type all that for you automatically, so you don't need to worry; just don't remove those lines.
Class
We have actually already encountered classes in our previous posts; we just didn't know about them!
Every time you're working with a VB object, like a String or an Integer, you're working with an instance of a Class. A class defines what objects of that class can (or cannot) do, and which properties they have; basically, it defines a type of object. The system will use these instructions next time that it needs to create an object of that type (an instance), or interact with it.
For example, let's say that we want to code up a ToDo list. Each user will have a different set of ToDos, so we can create a class to represent such lists and then create a different instance for each user. Each object will have a property called "todos", which will contain the actual items.
Public Class TodoList
Public Property todos As New List(Of String)
End Class
' ... then, somewhere else ...
Dim todoForAlice as TodoList = New TodoList()
todoForUserA.todos.Add("Pay Jack")
Dim todoForBob as TodoList = New TodoList()
todoForUserB.todos.Add("Buy milk")
Sub
As we've seen in part 1, objects don't just have properties (i.e. personal variables) associated to them; they also have methods, sets of instructions that can be invoked from the object with one line. In VB.Net, these are called subroutines, or Sub for short.
When defining a Sub, we can lay out a number of operations that should be executed when the sub is called. This allows us to write our processes once, and invoke them hundreds or thousands of times from other scripts.
Let's say, for example, that we want our TodoList to send all our todos to an email address. That sort of operation will require fairly complex code, which we don't want to write more than once. We can then stuff it in a Sub, to be easily called up when needed.
Public Class TodoList
Public Property todos As New List(Of String)
Public Sub SendViaEmail(ByRef emailAddress as String)
' ... lots of geeky network code will go here ...
End Sub
End Class
' ... then, in some other rule...
Dim todoForAlice as new TodoList()
todoForAlice.todos.add("Pay Jack")
todoForAlice.SendViaEmail("bigboss@bigcompany.corp")
As you can see, Subs can accept parameters, so that we can pass values and objects to the code inside the routine. In this case, we say that calling code should pass a String object, which we call emailAddress to clarify what it should contain. (Note: you don't have to use parameters, a Sub that doesn't accept any parameter will still work fine.)
Function
Subs are cool, but sometimes we want something more than just executing some code; we want the result to be handed back to the caller, for further use. That means writing a Function, which is just a Sub that will actually return a result.
For example, we might want to know how many of those todos contain a particular word. Let's write a Function for that!
Public Class TodoList
Public Property todos As New List(Of String)
Public Function HowManyWith(ByRef someWord as String) as Integer
' set up a variable that will hold our value
Dim result as Integer = 0
' loop through todos to test them
For Each todoItem As String In Me.todos
' if we find the word, we increase our counter
If todoItem.Contains(someWord) Then
result += 1
End If
Next
' return the value
Return result
End Function
End Class
' ... then, in some other rule...
Dim todoForAlice as new TodoList()
todoForAlice.todos.add("Pay Jack")
todoForAlice.todos.add("Sell Enron")
Dim howManyToPay as Integer = todoForAlice.HowManyWith("Pay")
' howManyToPay will now contain 1
Notice how the "Public Function" declaration ends with As Integer, because a Function must declare what type of result will return. This allows the compiler (and Intellisense) to tell you upfront whether that result will fit in a variable you created, which we've seen in part 1.
Me
In the previous example, we had to loop through todos held in a property of the object itself. In regular code, we look up a property of an object using the variable name, but inside a Function or Sub we don't have any such thing. That's why we use the special name Me. "Me" refers to the object itself, and you can use it whenever you need to use a Property, Sub, or Function, belonging to the object itself. In our previous example, that's how we got hold of our "todos" property, with Me.todos.
In OneStream
All of this is nice, but you might wonder how to apply it to OneStream proper. As we mentioned earlier, OneStream Business Rules are just VB.Net Namespaces that follow certain conventions, specifically:
- The Namespace name must start with OneStream.BusinessRule.<known type of BR>
- It has to contain a Public Class called MainClass.
- MainClass must contain a Function called Main, which must accept 4 parameters (specific to the BR type) and return an object.
Beyond that, they're just VB! So you can, for example, add custom Functions to MainClass, to isolate complex or verbose chunks of code that would otherwise clutter the main flow
Public Class MainClass
Public Function Main(ByVal si As SessionInfo, ByVal globals As BRGlobals, ByVal api As FinanceRulesApi, ByVal args As FinanceRulesArgs) As Object
' this is our Main flow, we want it nice and tidy
Select Case api.FunctionType
Case Is = FinanceFunctionType.ConditionalInput
' here we invoke the messy code found further down
If Me.IsDriverIntersection(api) Then
Return ConditionalInputResultType.NoInput
Else If Me.IsDetailIntersection(api) Then
Return ConditionalInputResultType.NoInputAllowCellDetail
End If
Return ConditionalInputResultType.Default
End Select
Return Nothing
End Function
' we isolate the long, verbose checks to their own functions
Public Function IsDriverIntersection(ByVal api As FinanceRulesApi) As Boolean
If api.Pov.Account.Name.XFEqualsIgnoreCase("SomeAcc") Or _
api.Pov.Account.Name.XFEqualsIgnoreCase("SomeOther") Or _
blablabla '.... you get the gist
Return True
Else
Return False
End If
End Function
Public Function IsDetailIntersection(ByVal api As FinanceRulesApi) As Boolean
If api.Pov.Account.Name.XFEqualsIgnoreCase("DetailAcc") Or _
api.Pov.Account.Name.XFEqualsIgnoreCase("OtherDeet") Or _
blablabla '.... you get the gist
Return True
Else
Return False
End If
End Function
End Class
Conclusion
We've seen how we can create custom Classes, Subs, and Functions, and how we can apply this knowledge to OneStream Business Rules in a basic way. Stay tuned for more advanced uses in a future post!
Blogs from Experts on OneStream