Microsofts DotNet architecture provides a feature that seems very easy to use for web development, but prooves to be tricky for testing web applications: The ViewState.
Please note, that handling the ViewState with OpenSTA requires some insight and experience with OpenSTA and especially SCL. If you just started using OpenSTA and are in desperate need to test DotNet applications that make use of the ViewState, you might want to consider CommercialSupport or OstaConsultant
Article by Antony Marcano
in May 2003, Antony Marcano has posted a reference to an article on stickyminds.com here on this site. This article provides some insight into the ViewState problems you might face.
This was the first reported public information about handling the ViewState from OpenSTA.
After a Mailing list discussion in May/June 2004, Sunny Gulati posted the following background information (slightly edited for Wiki formatting), following further down this page
Mailing List post by Sunny Gulati
please note, that due to Wiki TextFormattingRules, the contained code might have been rendered with errors - refer to your experience with SCL to correct the errors and feel free to edit this page in order to make this example code work
I work with OpenSTA and .Net stuff as well. I totally understand
Suresh's confusion -- there are a LOT of pieces of information that one
needs to have before one can see the overall picture.
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.
<nostalgia type="silly">
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.
</nostalgia>
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"
This is really annoying. Can I bypass VIEWSTATE altogether?
In many instances, its safe to bypass Viewstate. In this case, in your
POST body, just DELETE the reference to VIEWSTATE=xxxx. The .Net
engine, seeing no viewstate, does NOT assign default values to the
controls before applying the posted values -- which is okay, in most
cases. It depends on the code inside the controls on that page.
How did you figure out all this stuff?
Oh, about 30,40,50 hours of painstaking "this is not working",
frustration, etc.
That was several months ago, I can talk about it now without an elevated
heartbeat.
Updated - White Space added as CONTINUE8:
Well done to Sunny Gulati for this much more graceful solution. In the case that resulted in my article, I didn't have 40-50 hours unfortunately... ;-) -once again - good work. -- Antony Marcano
WRT ViewState and by-passing it...
You increase the risk of errors occuring by not sending ViewState since that is how the application is tracking the state of that session. The server-side processing is affected by the content of ViewState and you may change the load profile if you ignore it alltogether. If you don't post-back with ViewState in your scripts, make sure you understand the implications and associated risks as well as any time-related benefits in terms of developing your scripts.
Discourage developers from using ViewState... If your developers are using ViewState - discuss with them why... it is not a good idea if trying to build a performant application supporting a lot of users. There are numerous articles that warn against it on http://msdn.microsoft.com including this one see "Tip 10" - http://msdn.microsoft.com/msdnmag/issues/05/01/ASPNETPerformance/default.aspx
--Antony Marcano