Moving ViewState to the bottom of the page
02 June 08 06:51 AM | nraychev | 0 Comments   

We noticed that the ViewState in silverlightshow.net was enormous so we decided to make some improvements. The first step was to move it to the bottom of the page just for the search engine optimization. I have met many articles in internet which describe how to do this. One of them which I have used is:

http://www.hanselman.com/blog/MovingViewStateToTheBottomOfThePage.aspx

I’ll describe the solution which worked for us:

I created a class named SLSPage which extends the System.Web.UI.Page class and made all ASP.NET pages inherit this class instead of the Page class. Here our class is:

using System.Text.RegularExpressions;
using System.IO;
using System.Web.UI;
using System;
using System.Configuration;

namespace SilverlightShow
{
    public class SLSPage : System.Web.UI.Page
    {
        #region Constants
        private static readonly Regex viewStateRegex = new Regex( @"<input\s+type=""hidden""\s+name=""__VIEWSTATE""\s+id=""__VIEWSTATE""\s+value=""[^""]+""\s*/>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
        private static readonly Regex endFormRegex = new Regex( @"</form>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
        #endregion

        #region Handlers
        protected override void Render( System.Web.UI.HtmlTextWriter writer )
        {
            string originalHtml = string.Empty;
            string newHtml = string.Empty;

            using ( StringWriter stringWriter = new StringWriter() )
            {
                using ( HtmlTextWriter htmlWriter = new HtmlTextWriter( stringWriter ) )
                {
                    base.Render( htmlWriter );
                    originalHtml = stringWriter.ToString();
                }
            }

            bool success = false;
            Match viewStateMatch = viewStateRegex.Match( originalHtml );
            if ( viewStateMatch.Success )
            {
                string viewStateString = viewStateMatch.Value;
                newHtml = originalHtml.Remove( viewStateMatch.Index, viewStateMatch.Length );
                Match endFormMatch = endFormRegex.Match( newHtml, viewStateMatch.Index );
                if ( endFormMatch.Success )
                {
                    newHtml = newHtml.Insert( endFormMatch.Index, viewStateString );
                    success = true;
                }
            }
            if ( success )
            {
                writer.Write( newHtml );
            }
            else
            {
                writer.Write( originalHtml );
            }
        }
        #endregion
    }
}

We have two constant, precompiled Regex objects. The first matches the hidden field named __VIEWSTATE and the second – the closing tag of the form. Note that our solution will work only if we have only one form – the server form, but it is common scenario in ASP.NET.

#region Constants
private static readonly Regex viewStateRegex = new Regex( @"<input\s+type=""hidden""\s+name=""__VIEWSTATE""\s+id=""__VIEWSTATE""\s+value=""[^""]+""\s*/>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
private static readonly Regex endFormRegex = new Regex( @"</form>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled );
#endregion

Then I override the Render method where we have full control on the rendered html.

In this method I declare two string variables – one for the original rendered html and one for the html in which the ViewState will be at the bottom.

string originalHtml = string.Empty;
string newHtml = string.Empty;

I use the Render method of the Page class in order to render the html into a HtmlTextWriter , named htmlWriter, which wraps a StringWriter named stringWriter. Then I use the ToString method of the StringWriter in order to load the rendered html in the originalHtml variable.

using ( StringWriter stringWriter = new StringWriter() )
{
     using ( HtmlTextWriter htmlWriter = new HtmlTextWriter( stringWriter ) )
     {
           base.Render( htmlWriter );
           originalHtml = stringWriter.ToString();
     }
}

Then I declare a Boolean variable called success, which by default is false:

bool success = false;

This variable will be set to true only if all operations for moving the ViewState succeed. The following code moves the ViewState:

Match viewStateMatch = viewStateRegex.Match( originalHtml );
if ( viewStateMatch.Success )
{
    string viewStateString = viewStateMatch.Value;
    newHtml = originalHtml.Remove( viewStateMatch.Index, viewStateMatch.Length );

    Match endFormMatch = endFormRegex.Match( newHtml, viewStateMatch.Index );
    if ( endFormMatch.Success )
    {
         newHtml = newHtml.Insert( endFormMatch.Index, viewStateString );
         success = true;
    }
}

If the first match succeeds I remove the ViewState, save it in a temp variable named viewStateString, save the changed html in the newHtml variable and continue with the second match. If the second match succeeds I insert the viewstate tag just before the closing form tag and set the success variable to true. So only if all operations succeed we can use the modified html, otherwise we will use the original. I make a check:

if ( success )
{
    writer.Write( newHtml );
}
else
{
    writer.Write( originalHtml );
}

I use the HtmlTextWriter named writer, which we accept as a parameter of the Render method. This method writes the html to the output.

There are several occasions when some of the matches will not succeed and the original html must be rendered. For example when we use Ajax controls where we have partial rendering. In this situation only a part of the page html is rendered and there is no ViewState.

The second step was to optimize the ViewState so now its size is reduced up to 95%. In fact we do not need any more to move it to the bottom because only a small part of it remained. So this solution was just temporary. But if you don’t have enough time to make optimizations you can use this solution.

Using the HyperlinkButton control in Silverlight 2 Beta 1
30 May 08 07:13 AM | nraychev | 0 Comments   

I published an article about the HyperlinkButton, which you can see here:

HyperlinkButton in Silverlight 2 beta 1

Articles about Button controls in general and the Button control in Silverlight 2 Beta 1
12 May 08 01:15 AM | nraychev | 0 Comments   

I published two new articles about Silverlight controls today:

The first covers the key features of the ButtonBase class, inherited in all button controls, you can see it here: Button Controls

The second one is dedicated to the Button control. Visit it here: Button Control

Using the Border control in Silverlight 2 Beta 1
08 May 08 05:04 AM | nraychev | 0 Comments   

I completely forgot to mention my article about the Border control in Silverlight which I published a few weeks ago.

Here it is:

Using the Border control in Silverlight 2 Beta 1

SiteMap and URL Rewriting in ASP.NET 2.0
23 April 08 02:04 AM | nraychev | 0 Comments   

Introduction

A common scenario in web development is when one page shows different content depending on some query parameters. OK, but these query parameters do not look friendly to the user and are neither friendly for the search engines. Here the URL Rewriting comes. We have URL rewriting in SilverlightShow.net sitemap page. Look for example here:

http://www.silverlightshow.net/Sitemap-Articles.aspx

You see that the articles are organized in pages and when clicking on “Older Articles” or “Newer Articles” the page is changed and the URL looks slightly different. Only the number at the end of the page name is different, for example “Sitemap-Articles-1.aspx” or “Sitemap-Articles-2.aspx”.

The problem

It’s needless to say that all these different URLs are processed by a single ASP.NET page. The problem came when we decided to put an ASP.NET SiteMapPath control. By default it automatically binds using the information provided in the Web.sitemap file. OK, we have the following SiteMap structure:

<?xmlversion="1.0"encoding="utf-8" ?>
<siteMapxmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNodeurl="~/Default.aspx"title="Home" description="Home">
            <siteMapNodeurl="~/Sitemap.aspx"title="Sitemap"description="Sitemap">
                  <siteMapNodeurl="~/Sitemap-News.aspx"title="News"description="News"></siteMapNode>
                  <siteMapNodeurl="~/Sitemap-Articles.aspx"title="Articles" description="Articles">
                        <siteMapNodeurl="~/Sitemap-Announcements.aspx"title="Announcements"description="Announcements"></siteMapNode>
                        <siteMapNodeurl="~/Sitemap-Products.aspx"title="Products"description="Products">
                              <siteMapNodeurl="~/Sitemap-Controls.aspx"title="Controls"description="Controls"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-MediaPlayers.aspx"title="Media Players"description="Media Players"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-Games.aspx"title="Games"description="Games"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-Charts.aspx"title="Charts"description="Charts"></siteMapNode>
                        </siteMapNode>
                        <siteMapNodeurl="~/Sitemap-Tools.aspx"title="Tools"description="Tools"></siteMapNode>
                        <siteMapNodeurl="~/Sitemap-Moonlight.aspx"title="Moonlight"description="Moonlight"></siteMapNode>
                        <siteMapNodeurl="~/Sitemap-Demos.aspx"title="Demos"description="Demos"></siteMapNode>
                        <siteMapNodeurl="~/Sitemap-Learn.aspx"title="Learn"description="Learn">
                              <siteMapNodeurl="~/Sitemap-Videos.aspx"title="Videos"description="Videos"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-Presentations.aspx"title="Presentations"description="Presentations"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-Tutorials.aspx"title="Tutorials"description="Tutorials"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-Resources.aspx"title="Resources"description="Resources"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-Samples.aspx"title="Samples"description="Samples"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-QuickStarts.aspx"title="QuickStarts"description="QuickStarts"></siteMapNode>
                              <siteMapNodeurl="~/Sitemap-TipsandTricks.aspx"title="Tips and Tricks"description="Tips and Tricks"></siteMapNode>
                        </siteMapNode>
                        <siteMapNodeurl="~/Sitemap-Issues.aspx"title="Issues"description="Issues"></siteMapNode>
                        <siteMapNodeurl="~/Sitemap-Misc.aspx"title="Misc"description="Misc"></siteMapNode>
                  </siteMapNode>
                  <siteMapNodeurl="~/Sitemap-Shows.aspx"title="Shows"description="Shows"></siteMapNode>
            </siteMapNode>
    </siteMapNode>

</siteMap>

We have the root website node, marked with title “Home” and the sitemap node marked with title “Sitemap”. All child nodes of the “Sitemap” node except the last child (Shows) are processed by a single ASP.NET page. Since the URL is different no matter that all these nodes are in fact one ASP.NET page the SiteMapPath works the way it has to work. For example if we are on the following URL:

http://www.silverlightshow.net/Sitemap-Articles.aspx

Our SiteMapPath is rendered the following way:

sitemap_1

If you look at our Web.sitemap file you will see that it works fine. Till now everything is OK, but imagine that we want to open the last page in the Articles page, which is on the following URL:

http://www.silverlightshow.net/Sitemap-Articles-1.aspx

It works on SilverlightShow.net because we have made it work but in common scenario it wouldn’t work. No SiteMapPath would be shown because in the Web.sitemap we don’t have a node with url="~/Sitemap-Articles-1.aspx". You can always say that we can add this node too but in our case lots of articles are added to the site every day and the pages become more and more. I don’t like the option of checking for new pages every day and updating the Web.sitemap file.

The solution

The solution is to attach to the SiteMap.SiteMapResolve in the OnLoad handler of the page that processes the request where paging is needed.

We have the following code in the code behind of our page:

protected override void OnLoad( EventArgs e )
{
     base.OnLoad( e );

     SiteMap.SiteMapResolve += new SiteMapResolveEventHandler( this.ConstructSiteMap );

Note: You can do the same in the Page_Load method if your ASP.NET page is marked with: AutoEventWireup="true". I’m overriding the OnLoad event handler because it is faster :)

Note: SiteMap class is located in the System.Web namespace and is an in-memory representation of the navigation structure of the website. It is used by the SiteMapPath control.

Here is the handler for the SiteMapResolve event.

private SiteMapNode ConstructSiteMap( Object sender, SiteMapResolveEventArgs e )
{
     SiteMapNode currentNode;
     if ( SiteMap.CurrentNode == null )
     {
         currentNode = e.Provider.FindSiteMapNode( "~/Sitemap-Articles.aspx " );
     }
     else
     {
          currentNode = SiteMap.CurrentNode.Clone( true );
     }
     return currentNode;

}

In our case if the URL is with paging it is not found in the Web.sitemap file and that’s why the SiteMap.CurrentNode property will be null. This causes the SiteMapPath control to disappear. OK, now we check if this property of the SiteMap is null and if it is we just create a SiteMapNode using the current provider’s SiteMapProvider.FindSiteMapNode method giving the URL without paging. Note that we surely will know the URL that must be given to the FindSiteMapNode method because we are in the page which processes the request. If this property is not null we just clone it. The return value of our handler will be the SiteMapNode which will be used to render the SiteMapPath control.

This is a better solution than updating the Web.sitemap file every day.

Additional Issues and solutions

I found out that the SiteMap class has a strange way of caching. For example if we go to: http://www.silverlightshow.net/Sitemap-Controls.aspx our SiteMapPath will look like this:

sitemap_2

After that we are going to http://www.silverlightshow.net/Sitemap-Articles.aspx and our SiteMapPath is normal:

sitemap_1

But the problem comes when we go through the pages. If we hadn’t find a solution our SiteMapPath path would look this way, no matter that we are on the Articles page and not on the Controls page:

sitemap_2

This behavior is as strange as our solution. It seems that the SiteMap remembered that when last time the CurrentNode was null we made it “~/Sitemap-Controls.aspx” and when we are on Articles page and this node is again null it uses the previous value from Controls. As I told you our solution is also strange but works :) We are putting our page path in variable instead of literal:

currentNode = e.Provider.FindSiteMapNode( this.currentPagePath );

Our variable of type string currentPagePath contains for example the following value “~/Sitemap-Games.aspx” for the following page: http://www.silverlightshow.net/Sitemap-Games.aspx. The behavior is exactly the same. But if we make this variable null just after our page is unloaded no caching takes place:

protected override void OnUnload( EventArgs e )
{
    base.OnUnload( e );
    this.currentPagePath = null;

}

Note: In our case our Controls and Articles pages are processed by a single ASP.NET page. But the caching behavior of the SiteMap takes place even these two URLs are processed by different ASP.NET pages. Our solution works for our Shows page which is different ASP.NET page. You just must make the variable which contains the current node path null on both places.

Using the GridSplitter control in Silverlight 2 Beta 1
15 April 08 07:30 AM | nraychev | 0 Comments   

I wrote a little article about using the GridSplitter control.

 You can see it here:

GridSplitter Article

I also wrote another article in response of a guy who asked me about how the Canvas control deals with the Measure/Arrange process when it is laid out by a parent Panel.

Here it is:

Canvas and Measure/Arrange

A few articles about layout controls in Silverlight 2 Beta 1.
09 April 08 01:21 AM | nraychev | 0 Comments   

Hi to all :)

As a first post in this blog I'd like to present the articles about Silverlight which we published this week. They describe how to use the layout controls in Silverlight 2 Beta 1 and can be used as tutotials:

 Yolu can see them here:

Layout controls in Silverlight 2 Beta 1

Using the Canvas control in Silverlight 2 Beta 1

Using the StackPanel control in Silverlight 2 Beta 1

Using the Grid control in Silverlight 2 Beta 1