Mai 2006 - Posts
Yeah, the project is live.
BetAndWin.com has a new Mobile site now. we're working on that since the beginning of the year. and its cool. http://Wap.BetAndWin.com give it a try.
Just searched a little bit over the Sphere engine (wow, you get really good results in there), and found that entry from Mads Kristensen about Getting the length of the Viewstate out of your Page and thought about, there is a smarter solution.
First of all, what does i not like at that solution?
- it Buffers all data (so no direct output to the client)
- it writes everything into a string and writes it again
- it adds it directly to the output (not really important, but i like using my own TextWriters and that would get me into troubles)
- it does a string search
- so it slows down the page
Where and when can i get the viewstate from?
Yep, its hard to find, where the viewstate can be accessed in an Page. It should also be an solution that is easy to manage and does not need big changes in your code.
The only point, where you can access that value is at an PageAdapter. this is also a very nice solution, because you can turn it on and off just in an .browser file. so no need to change any existant code file.
In there is an Property Named ClientState. that is all your State that is serialized in the Viewstate Field (in there is the Viewstate and the new Controlstate).
So, lets create an short example. we want to have it in the Debug output written, since its just for debugging purposes. (btw: on demos i call the most things with the My prefix. since its very easy for understanding. but i don't recommand to use this in code)
public class MyPageAdapter :PageAdapter
{
public MyPageAdapter() {}
protected override void OnUnload(EventArgs e) {
base.OnUnload(e);
if(ClientState != null)
System.Diagnostics.Debug.WriteLine("Viewstate size:" + ClientState.Length);
}
}
Create an short Adapter, derrive it from the PageAdapter and lets override the Unload event (since its the last one, and we need something after the SaveState is called). in there, first call the base, so we dont let any code not execute, that executed bevore. then see, if the ClientState is filled (because if you get an error page, your event is processed, but you have no content), and then write our Length to the debug window.
Now, just activate the PageAdapter in an myBrowser.browser file. and we're finished.
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page"
adapterType="MyPageAdapter" />
</controlAdapters>
</browser>
</browsers>
i've written more information about the browser file in my post for minimizing output.
Extended: But, we could do even cooler stuff. now, we have the information. but do i allways look on it? i think not. so lets make it a little bit more visible, if it exeeds a specific threshold. and also all of your managers will tell you "Don't exeed that size of KB." so lets use also kilobytes.
To do that, we're rjust replacing the line where we wrote the Viewstate to the Debug Output.
System.Diagnostics.Debug.Assert(ASCIIEncoding.ASCII.GetByteCount(ClientState.Length) < 15000);
Thats all. first of all, we determine the kilobytes that this string will result in. then compare to our limit (here 15kb) and we'll assert that. if the Assertion fails, it will stop right here and display you a nice Window, that tells you that in debug Mode. so when you are programming, and come thru that window, you know you should tune now.
I think, we are now on a really good solution.
The IdSeparator is the char, that splits 2 id's in NamingContainers. per default the char is the '$' (dollar) and you'll see it in every id, name and so on in the source code of your Page. like ctl00$HolderContent$Login1$pnlForm. so it should be something, that you don't use in your control Id's.
So, why do you ever want to change these?
Either you want a nicer char for that, or you really want to hide, that you are using .net, or, as we find out, the $ char is not allowed in wml. and we made a rendering also for wml with all that cool new stuff like Themes, Master Pages and so on (yep, mobile pages cannot use them. but beside of that, we dont want to be limited in one control in the width).
But mobile Pages Support wml, how did they made it there?
they completly changed the id handling there. made it short and a list of them (so not deriving from your id). i did'nt like that. thats possible, if you have an easy layout. but you can get into problems with an big one (changing the order of your controls).
So what was our solution?
public override char IdSeparator {
get {
return '_';
}
}
The Page has an overrideable Property, that is called idSeperator. this is the char, that it will use to join the Parent id's. just change it to '_' (wml only supports _, a-z, 0-9 in form names. and yes, you are not allowed to use that '_' any more in your id's) and it will use it everywhere -- that was my thought.
The Warnings...
So, lets read something, what we'll do here. the Documentation say "This property supports the .NET Framework infrastructure and is not intended to be used directly from your code. ". since i should'nt use it, it's maybe not intended to override that at all. also there is NONE information about that in the internet. not even a link to the MSDN.
The Problems started...
Made the first Postback with the new values, nothing worked.
In the code on the client, the source looks good. but the post actions are not executed.
after quick debugging and looking up some functions, i saw the problem. If there are Postback values, it looks, if there is an Control with that id with the FindControl Method of the Page (thats, where controls are looked up). And there are HARDCODED, that you only can use Dollar or Column as an seperator (new char[] { '$', ':' };) . c'mon guys. thats not funny. why in the heck do you did that? i know, that the Mobile Pages have the : configured. since its better for chtml. but hardcoded?. not really extensible.
Not nice, but not so a problem. the solution is done quickly.
override the DeterminePostBackMode() method. This method gets the Right collection, where your PostBack Variables are (btw, if this returns null, the IsPostback is false. otherwise true). then, go thru all post values, and replace the underscores with '$' again. so the FindControl fetch the right control again. seems not so hard.
public override System.Collections.Specialized.NameValueCollection DeterminePostBackMode() {
if (this.Page.Request == null) {
return null;
}
NameValueCollection postValues = null;
// if i have a post, lets add the viewstate to the collection, since it is needed.
if (this.Page.Request.HttpMethod == "POST") {
// Get our transformed post values
postValues = ProcessPostValues(this.Page.Request.Form);
} else {
if (string.IsNullOrEmpty(this.Page.Request.QueryString["__viewstate"]))
return null;
postValues = this.Page.Request.QueryString;
}
return postValues;
}
private NameValueCollection ProcessPostValues(NameValueCollection col) {
NameValueCollection ret = new NameValueCollection();
foreach (string name in col.AllKeys) {
if (name.StartsWith("__"))
ret.Add(name, col[name]);
else {
ret.Add(name.Replace("_", "$"), col[name]);
}
}
return ret;
}
Compiled, Tried, and it worked!
For the most Pages. but there are somthing, that are not like all others. Checkboxes and Radio Buttons. Here, The Postback is the Unique Name of the Group. they Register itself in the Page, that they handle PostBack values by thereown. First of all, they Compare their UniqueId if it is in the postValues. so we need to have a match there (their id is with '_', but we changed the postValues to have '$'). that solution is also easy done. just add a second value to the collection with '_', the original value.
The second Problem was, that here, it Stores the UniqueId's of the Controls, that want to handle the postback by theirown, and then look for them again. so it uses the FindControl again. this time, with the UniqueId's with the '_'. that again dont work.
here we overriden the FindControl in the Page, and Modified the base call with an Replace.
public override Control FindControl(string id) {
return this.FindControl(id.Replace('_', '$'), 0);
}
so, and now we have a working Programm again. would be nice from ms, to make it a little bit easier. Maybe i'll make a followup on how to debug postback issues in one of my next posts. since i've done a mass of them now.
Why didn't i just replaced them at the output?
Also tried that... but...
So, we have an own HtmlTextWriter (i post about that how in my last post) and theres the really cool method WriteAttribute where, as a normal person would think, you can run some code whenever an attribute is written. but No, the id's are written directly with the write method. isn't that funny?..
Tags:
Asp.net, Mobile, idSeperator
so,
this is my first HowTo article.
if i digg into things, that were hard to find, or i find cool, i'll post it here.
this post is for Asp.Net 2.0
Expecially when you want to make your Pages for an Mobile Browsers ( nearly all can handle xhtml now. so its easy to support it. if you want futer details just ask, i'll make an post) the size of the code that is transferd should be as less as possible. the data rates on mobiles are not that good. and also you should think about, that the most clients pay for the data you send.
so, what can we do?
1) save the ViewState serverside in the Session
2) get rid of all breaks and tabs
3) remove the id attributes (if we dont need javascript. i dont recommand to use javascript on mobiles)
1) the ViewState and how it is stored is managed in PageStatePersiter. these are used to load and safe viewStates of the current Page. and here we are Lucky, because microsoft allready made an second persister (nebst HiddenFieldPageStatePersister, the default one).
its called SessionPageStatePersister and stores the viewstate in an Queue in the Session and only an pretty short reference to it in the hidden field.
we have just to tell the Page, that it should use these class instead. and here we have 2 possibilities. first is, to override the PageStatePersister method in the Page. but if you have a bunch of aspx files, this is not nice (yea, you can derrive all your Pages from an class, but then you have to think about that every time and so on...).
the second possibility is, to make an Page Adapter. with these, you can easily override many properties of the page without having to make your own.
Make a new Class in your App_Code folder, and derrive it from the System.Web.UI.Adapters.PageAdapter Class. For everything, you want to handle by yourself you just have to override the Base Method. In our Case it is the GetStatePersister() Method.
public override PageStatePersister GetStatePersister() {
return new SessionPageStatePersister(Page);
}
to make that possible, we need to add our code at specific positions of the executions.
this is handled in browser files. these files define the limitations of each handset and what is the best for it. they are stored in your web directory unter App_Browsers with the extension .browser. just create one with right-click on the directory, add New item and select the Browser File.
What you get now, is an XML file, where you see some of the possibilities you have.
The browser Node identifies an browser. you can either define a new one (id="NewBrowser") or override properties of an existing one (refID="Mozilla"). we want to override ALL. so we reference to the Default (refID="Default"). all we change here, is valid for all browsers, that are not overriding the value by itself. and under the browser Node, we define the controlAdapters Node. in there we can set Adapters for any type of Control. like the Page.
<browsers>
<browser refID="Default">
<controlAdapters markupTextWriterType="code4ward.testTextWriter">
<adapter controlType="System.Web.Ui.Page"
adapterType="code4ward.testAdapter" />
</controlAdapters>
</browser>
</browsers>
The adapter Node defines the 2 attributes. controlType defines the Control Type, where the Adapter will be binded to, and adapterType what adapter to bind.
markupTextWriterType is for our next Point.
The XhtmlTextWriter is the class, that in the end Build from the Object orientated Control Structure the plain XHTML. this class has masses of write() Methods to write each type of content. So the next Point is, make our own that derive from that class and implement our Tweaks.
public class testTextWriter : XhtmlTextWriter {
public testTextWriter(TextWriter writer)
: base(writer, string.Empty) {
this.CoreNewLine = new char[] { };
this.NewLine = string.Empty;
CommonAttributes.Remove("id");
}
public override void WriteLine() {
// no lines to write
}
public override void WriteLine(string s) {
// no lines to write
Write(s);
}
public override void Write(string s) {
base.Write(s.Replace("\r\n",string.Empty));
}
}
This is really short.
The Page will Instantiate your Class with the Parameter of the TextWriter where you can write too.
Your Base provide you with an extended Constructor, where you also can define what string it should use when it should write an tab to the output. so we give him an string.Empty. we don't want any.
Next, we dont want Line breaks in the output. so we set the CoreNewLine and NewLine also to empties.
Normally you dont have JavaScript on an mobile Page. so we dont need the id's. the XhtmlTextWriter have an architecture, to remove any attribute we dont like. so say it, that we don't like id's.
Then, there are 3 common Methods of the masses of Write() methods, that we should override to get the Line breaks out.
The first 2 are easy, and self explaining.
For the third, there is an simple source, where you directly get the Line Breaks into the write method. UserControls. Everything the .net engine writes to an output has to be in an Control. but UserControls have some floating HTML, that is not in an runat="server" Tag. these Content gets into an LiteralControl class as Plain Text output. to catch this, we have to search for it, and remove it. (this also catches, if you write content that has Linebreaks in it)
So, this simply is it. just really short code, and you got rid of half of the page (if not more).
If you have questions, just write me an mail or post an comment. i would be glad to help you, or post an follow up, if its very interresting.
Tags: Asp.Net, XhtmlTextWriter, optimizing, mobile
yea cool. just waited for the info.
now the Google Code Jam is also opened in europe.
you can register until the 23th of March.
at least, i want to have such an T-Shirt.
The Code Jam is a Cometition for Programmers.
you get 3 tasks and need to solve them fast.
then you can try to bring the code of your opponents to fall.
really cool and funny.
if you want to find me inside, my callsign is cmn.
will have some practice this evening...
yust mail me, if you want to join practising
huh, silently, not mentioned by anyone, the a9.com search engine, with one of the coolest Browser Toolbars i know, are now searching with the Live.com engine. and not with google.
would be nice to know, why they switched.
Tags:
a9.com
google
Search Engine
live.com