Monday, January 7, 2008

Handling Viewstate in Dot net Apllication using OpenSTA

A quick background on VIEWSTATE and how to handle it. This is fairly well explained in most .Net scalability books.
What is Viewstate?

Supposing you have a form which has an input field called "f", whose default value is "x", like so:

[x ]

Along with this visible input field, .Net will create a hidden field called VIEWSTATE.

This VIEWSTATE contains an encoded version of the information, "f=x".

Now, the user decides to change the value "x" to "y", and presses SUBMIT (or OK, or something that causes a postback to the server). The POST request gets sent as follows:

POST ...
... VIEWSTATE={encoded version of f=x}&f=y

The .NET engine receives this post; it decodes the VIEWSTATE; it recreates the control for "f"; it initializes it to a value of "x" (as per the viewstate); it then applies the change for "f=y", and in the control, the "?OnValueChanged" event fires.
What does this mean for me, the OpenSTA script writer?

It means: when you FIRST visit the page, record the viewstate. When you do a POST back to the page, send the recorded viewstate along with.

This is how .Net figures out what has changed since the page was first presented.
How can I get VIEWSTATE?

Two ways work for me. One is to do a straight ~LOCATE(). The other is to locate the address of the viewstate via the interactive HTML browser in the script modeller, and pull it out via "LOAD RESPONSE_INFO WITH ....". Here's an example from a script that we use:

LOAD RESPONSE_INFO BODY ON 2 &
INTO VIEWSTATE &
,WITH
"HTML(0)/BODY(1)/CENTER(0)/TABLE(0)/TBODY(0)/TR(4)/TD(2)/DIV(1)/FORM(0)/INPUT(0):ATTRIBUTE:" &
"value(2)"

Hint: it took me a long time to find the HTML tree thing. Here's how: if you recoreded your script on that computer, then you click on the yellow "=>" arrow on the toolbar, and that takes you to a select-a-HTTP-action screen, and then it initializes those mysterious HTML_Tree things on the right hand part of the Script Modeller. Browse through the HTML tree till you find VIEWSTATE (or search for it), right click on the value, select "COPY ADDRESS TO CLIPBOARD", go over to the code and paste it, and you'll have the above string (HTML(0)/BODY(1)....).

Hint #2: Its not documented, but there's apparently an XML-based parser in there somewhere too. My coworker found it, its way cool. Anybody have more info on that?
But! That's not enough!

Viewstate contains funky characters. When POST'ing, these characters need to be encoded.

To deal with this generically, we wrote a subroutine which we call URLENCODE, which we include at the end of whatever scripts that need it. We call it as such:

CALL URLENCODE[VIEWSTATE]

It returns its information in a THREAD variable named SB_URL which we declare in GLOBALS.INC.

''PS: URLENCODE would be a GREAT add-on function to add to OpenSTA, 'cause we have to do it !#$ ALL the time.''

We then pass the encoded viewstate back in the POST as such:

PRIMARY POST URI
"http://"+S_HOST+"/_layouts/tcclogin/login.aspx?ReturnURL=http%3a%2f%2f"
+S_HOST+"%2fca" &
...
,BODY
"__VIEWSTATE="+SB_URL+"&txtUsername="+S_DOMAIN+"%5C"+S_USER+"&txtPassword="+S_PASSWORD+"&Logon=Login&hd" &
"nChangingPassword=False"

So what's with this URLENCODE business anyway?

Glad to share it. Maybe somebody else can find a better way, but this is what we had to do:

In Global_Variables.Inc, we set aside a section for subroutine return values:

! ---- Input/output parameters for standard code subroutines
CHARACTER*512 SB_URL, THREAD ! -- General purpose Subroutine URL variable
INTEGER SB_INDEX, THREAD ! -- General puprose Subroutine Index variable
INTEGER SB_INDEX2, THREAD ! --

Note: for us, "SB_" means "related to a subroutine" -- it's a prefix we standardized on.

Thank you, BASICA for teaching me how to do this in 1984. Long live the TRS-80! Except here, I don't have to memorize a line number to GOSUB to, so that's an improvement. The poor new folks who've only ever known mature languages like Java and C... Have a hard time when dealing with something like SCL.

We created a file in the Include\ directory, called "standardcode.inc". This contains our subroutine definitions. We have several; most of them are specific to what we do; one of them is shareable. Note that this encodes more than is needed for viewstate -- I think viewstate only uses the "+" symbol.. But like I said, we've had to call this from many places.

! Input: SB_URL = url to be encoded
! Output: SB_URL contains encoded URL
! Uses: SB_INDEX, SB_INDEX2

SUBROUTINE URLENCODE [ SB_URL ]
CONTINUE1:

Set SB_INDEX = ~LOCATE("{", SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%7B" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE1
ENDIF

CONTINUE2:
Set SB_INDEX = ~LOCATE("}",SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%7D" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE2
ENDIF

CONTINUE3:
Set SB_INDEX = ~LOCATE("|",SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%7C" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE2
ENDIF

CONTINUE4:
Set SB_INDEX = ~LOCATE(":",SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%3A" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE4
ENDIF

CONTINUE5:
Set SB_INDEX = ~LOCATE("/",SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%2F" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE5
ENDIF

CONTINUE6:
Set SB_INDEX = ~LOCATE("+",SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%2B" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE6
ENDIF

CONTINUE7:
Set SB_INDEX = ~LOCATE("=",SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%3D" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE7
ENDIF

!-- Added 'white space' to URL ENCODE --Antony Marcano (remove this comment)
CONTINUE8:
Set SB_INDEX = ~LOCATE(" ",SB_URL)
IF (SB_INDEX<>-1) THEN
Set SB_INDEX2 = SB_INDEX + 1
Set SB_URL = ~EXTRACT(0,SB_INDEX,SB_URL) + "%20" &
+ ~EXTRACT(SB_INDEX2,65500,SB_URL)
goto CONTINUE8
ENDIF

END SUBROUTINE

Finally, in the script which needs to call URLENCODE, at the end of the file, we include the code:

include "StandardCode.inc"

1 comment:

DaveBlog said...

This is very helpful,
I am trying to run OpenSTA to login submit bids (which increment) logout login as a different user then bid at a different amount and logout again.

All in .net

I would be very interested in seeing a working example of the scripting you describe here for manipulating the viewstate