:::: MENU ::::

Friday, February 20, 2009

We at times come across a requirement where a feature in a web application is supposed to behave similar to that of its windows counterpart. One such requirement is to either maintain the focus on the control that caused a postback or to shift focus to the next control after a postback.

Ryan Farley has two cool articles on Determining the Control that Caused a PostBack  and Set Focus to an ASP.NET Control. We will make use of his code to determine the control that caused a postback. We will then build on that code and use LINQ to loop through the controls, find the TabIndex of the control that caused postback and then shift focus to the control having the next TabIndex. I got this idea of using LINQ while reading a forum post and thought that this solution would be worth sharing with others.

In an application where a lot of text fields are involved, we generally see that when a user tabs out of a textbox, some calculation is performed and the calculated value is then displayed back in the textbox. On a page not powered with ASP.NET AJAX, the desired behavior is to display the calculated value in the textbox and shift the focus to the next one.

In an ASP.NET page, the __doPostBack is used by server controls to cause a postback. If you observe the html markup after a postback, ASP.NET automatically adds two hidden fields (“__EVENTTARGET” and “__EVENTARGUMENT”) and a client-side script method (“__doPostBack”) to the page. The EVENTTARGET is the ID of the control that caused the postback and the EVENTARGUMENT contains any arguments passed that can be accessed on the server. The __doPostBack method sets the values of the hidden fields and causes the form to be submitted to the server. Ryan makes use of the __EVENTTARGET to find the control that caused postback.

Note: Remember that Button and the ImageButton do not use the __doPostBack unless the UseSubmitBehaviour property is set explicitly.

In this sample of ours, I am using a couple of controls on the form to test out our logic. There are a few TextBoxes with AutoPostBack = true which will cause a postback whenever the control looses focus. I also have a couple of Buttons and ImageButton on the page. We will set the TabIndex of each of these controls as shown below:

The markup looks similar to the following:

   <form id="form1" runat="server">

    <div>

        <asp:TextBox ID="TextBox1" runat="server" AutoPostBack="True" TabIndex="1">

        </asp:TextBox>

        <br />

        <asp:TextBox ID="TextBox2" runat="server" AutoPostBack="True" TabIndex="2">

        </asp:TextBox>

        <br />

        <asp:Button ID="Button1" runat="server" Text="Button" TabIndex="3" />

        <br />

        <asp:Button ID="Button2" runat="server" Text="Button" TabIndex="4" />       

        <br />

    </div>

    </form>

The code to set focus to the next control after a postback is given below:

C#

    protected void Page_Load(object sender, EventArgs e)

    {

        if (Page.IsPostBack)

        {

            WebControl wcICausedPostBack = (WebControl)GetControlThatCausedPostBack(sender as Page); 

            int indx = wcICausedPostBack.TabIndex;                      

            var ctrl = from control in wcICausedPostBack.Parent.Controls.OfType<WebControl>()

                       where control.TabIndex > indx

                       select control;

            ctrl.DefaultIfEmpty(wcICausedPostBack).First().Focus();

        }

    }

 

    protected Control GetControlThatCausedPostBack(Page page)

    {

        Control control = null;

 

        string ctrlname = page.Request.Params.Get("__EVENTTARGET");

        if (ctrlname != null && ctrlname != string.Empty)

        {

            control = page.FindControl(ctrlname);

        }

        else

        {

            foreach (string ctl in page.Request.Form)

            {

                Control c = page.FindControl(ctl);

                if (c is System.Web.UI.WebControls.Button || c is System.Web.UI.WebControls.ImageButton)

                {

                    control = c;

                    break;

                }

            }

        }

        return control;

 

    }  


VB.NET

   Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        If Page.IsPostBack Then

            Dim wcICausedPostBack As WebControl = CType(GetControlThatCausedPostBack(TryCast(sender, Page)), WebControl)

            Dim indx As Integer = wcICausedPostBack.TabIndex

            Dim ctrl = _

             From control In wcICausedPostBack.Parent.Controls.OfType(Of WebControl)() _

             Where control.TabIndex > indx _

             Select control

            ctrl.DefaultIfEmpty(wcICausedPostBack).First().Focus()

        End If

    End Sub

 

    Protected Function GetControlThatCausedPostBack(ByVal page As Page) As Control

        Dim control As Control = Nothing

 

        Dim ctrlname As String = page.Request.Params.Get("__EVENTTARGET")

        If ctrlname IsNot Nothing AndAlso ctrlname <> String.Empty Then

            control = page.FindControl(ctrlname)

        Else

            For Each ctl As String In page.Request.Form

                Dim c As Control = page.FindControl(ctl)

                If TypeOf c Is System.Web.UI.WebControls.Button OrElse TypeOf c Is System.Web.UI.WebControls.ImageButton Then

                    control = c

                    Exit For

                End If

            Next ctl

        End If

        Return control

 

    End Function

The code makes use of the GetControlThatCausedPostBack function written by Ryan to find the control that caused the postback. We then determine the TabIndex of the control and use LINQ to select the control with the next tabindex and set focus to it .I have used LINQ as I find it very useful when I loop over collections. It gives me all the control I need over the code, keeping it tight and without much effort.

Just run the application and test out the functionality. When you tab out of a TextBox or click on the Button control, the focus shifts to the next control after a postback. That’s it for now. I hope you liked the article and I thank you for viewing it.

 

Wednesday, February 18, 2009

Introduction

This article describes a way to use ASP.NET Routing to avoid 404 Not Found errors when changing folder structure or folder names in a website.

What to do with obsolete links to your website?

Having a website means spending some time and effort promoting the site on the Internet, making sure search engines index all the pages, and trying to get exposure through blogs or discussion boards.

And, then you get new ideas and really need to restructure your site – change some folder names, move some pages, etc. What will happen with all those third-party links to your site you were so proud of? Do you want to lose them?

Route old URLs to new site structure

With the arrival of the .NET Framework 3.5 SP1, we have got an elegant way of solving this problem – ASP.NET Routing. Initially, it was a part of the ASP.NET MVC Preview 2, and now it is a part of the framework.

The idea is to add special "Routes" to the site, having a single goal of processing requests to pages which are no longer present on the site. In its simplistic form, the processing can happen in a single ASPX page responsible for proper handling of requests. Here is an example:

The attached project contains all the parts you'll need: WebFormRouteHandler created by Chris Cavanagh representing an IRouteHandler implementation, a Global.asax file registering your Routes, a web.config file where you register the WebFormRouteHandler, and a Default.aspx page responsible for actual request processing.

Let's take a look at the Global.asax:

http://www.codeproject.com/images/minus.gifCollapse

void Application_Start(object sender, EventArgs e)
{
    // runs on application startup
    RegisterMyRoutes(System.Web.Routing.RouteTable.Routes);
}
 
private void RegisterMyRoutes(System.Web.Routing.RouteCollection routes)
{
    // reference IRouteHandler implementation
    // (example created by Chris Cavanagh)
    // see http://chriscavanagh.wordpress.com/
    //            2008/03/11/aspnet-routing-goodbye-url-rewriting/
    var startPageRouteHandler = new WebFormRouteHandler("~/default.aspx");
 
    // exclude .axd to handle web services and AJAX without checking all routs
    // see http://msdn.microsoft.com/en-us/library/
    //            system.web.routing.stoproutinghandler.aspx
    routes.Add(new System.Web.Routing.Route("{resource}.axd/{*pathInfo}", 
               new System.Web.Routing.StopRoutingHandler()));
    routes.Add(new System.Web.Routing.Route("{service}.asmx/{*path}", 
               new System.Web.Routing.StopRoutingHandler()));
 
    // mapping:
    // extracts folder name and page name as items in HttpContext.Items
    routes.Add(new System.Web.Routing.Route("{folderName}/", 
               startPageRouteHandler));
    routes.Add(new System.Web.Routing.Route("{folderName}/{pageName}", 
               startPageRouteHandler));
}

Here, we defined a single route handler - default.aspx, as well as routing rules.

Rule #1:

http://www.codeproject.com/images/minus.gifCollapse

routes.Add(new System.Web.Routing.Route("{folderName}/", startPageRouteHandler));

states that all requests to a URL with the structure "http://mysite.com/something" will be processed by the default.aspx page if there is no actual "something" found on the site. For example, there is a RealPage.aspx page present on the site, so requests to http://mysite.com/RealPage.aspx will be processed by that page.

But, if the client requests RealPage2.aspx, that request will be processed by the default.aspx page according to rule #1. Note that the client will not be redirected to default.aspx, it will be just the web server running code in default.aspx in response to the request. For the client, the response will come from RealPage2.aspx.

You can add as many routes as you want, for example, rule #2:

http://www.codeproject.com/images/minus.gifCollapse

routes.Add(new System.Web.Routing.Route("{folderName}/{pageName}", startPageRouteHandler));

stating that all requests to a URL with the structure "http://mysite.com/somefolder/somethingelse" will be processed by the default.aspx page if there is no actual "somefolder/somethingelse" found on the site.

The code behind default.aspx shows how to extract those parts of the request. As you can see, they will be placed in the HttpContext.Items collection.

http://www.codeproject.com/images/minus.gifCollapse

lblFolder.Text = Context.Items["folderName"] as string;
lblPage.Text = Context.Items["pageName"] as string;

How it works in real life

Here is a real life website actually using this technique - Digitsy Global Store. Besides handling obsolete URLs, the ASP.NET Routing is being used to handle multiple languages on the site, switching CultureInfo on the fly:

http://www.codeproject.com/images/minus.gifCollapse

protected void Page_PreInit(object sender, EventArgs e)
{
    CultureInfo lang = new CultureInfo(getCurrentLanguage());
    Thread.CurrentThread.CurrentCulture = lang;
    Thread.CurrentThread.CurrentUICulture = lang;
}
private static string getCurrentLanguage()
{
    string lang = HttpContext.Current.Items["language"] as string;
    switch (lang)
    {
        case "france":
            return "fr-FR";
        case "canada":
            return "en-CA";
        case "germany":
            return "de-DE";
        case "japan":
            return "ja-JP";
        case "uk":
            return "en-GB";
        case "russia":
            return "ru-RU";
        default:
            return "en-US";
    }
}

As you can see, the default language is English, United States: "en-US". In internal links, the site uses the structure http://{sitename}/{language}/…other things…

So, if you try http://digitsy.com/us/, you'll get the US version, trying http://digitsy.com/japan/ will bring you the Japanese one, and if you try http://digitsy.com/whatever – you'll not get a 404 error, you'll get the US version again.

ASP.NET Routing makes restructuring of the site really easy. The folder structure "{language}/{index}/category/{categoryID}" was recently replaced by "{language}/{index}/shopping/{categoryID}". There is supposed to be no "category" folder in the site structure anymore. But because both routes are pointing to the same handling page, both the folders "category" and "shopping" return valid responses.

Trying http://digitsy.com/us/Electronics/shopping/541966 will use the rule:

http://www.codeproject.com/images/minus.gifCollapse

routes.Add(new System.Web.Routing.Route("{language}/{index}/shopping/{categoryID}", 
           categoryRouteHandler));

while trying http://digitsy.com/us/Electronics/category/541966 will use:

http://www.codeproject.com/images/minus.gifCollapse

routes.Add(new System.Web.Routing.Route("{language}/{index}/category/{categoryID}", 
          categoryRouteHandler));

and both will resolve to the same route handling page.

Things to remember

This is really simple if you know what you are doing. I mean, you should be aware of some implications. Check out Phil Haack's post discussing "one subtle potential security issue to be aware of when using routing with URL Authorization."

You should also verify if your hosting provider supports SP1 for .NET Framework 3.5. Many hosting providers still don't have SP1 installed on their servers because of incompatibility with some old software.

More

This past week, I had a bug assigned to me about a user canceling a long running process in their browser, but it kept running on the server. It was a process to generate PDF files, and the component we use is kind of memory intensive. As a result, having it run for no reason is troublesome. And of course, there's always the possibility that the user goes back and kicks off the process again, doubling the number of resources used.

There were a solution that immediately popped into my mind: make it asynchronous, and have a way to indicate to the user that it's done, and give them away to pick up the files on demand. This frees them up to move on to do other things, and solves the original problem. It's a good long term fix, and one we'll eventually move to.

But the question was more about being able to find a way to cancel the processing if the user left the page. Could we do that, deploy a quick fix that alleviated the resource hog, and give us some breathing room to implement the long-term fix?

It looks like the answer is yes. I hadn't run across it's usage very often before, but you can use Response.IsClientConnected to see if the browser is still waiting for the request to complete. You can check it periodically, and if the client has moved on, then you can cancel processing. In our case, we were generating a number of PDFs in a loop, so checking every time around worked fine, and the code is rather simple:

   1: public void GeneratePdfs()

   2: {

   3:     foreach(var pdf in PdfList)

   4:     {

   5:         // Get PDF Data and write it out

   6:        

   7:         if (!Response.IsClientConnected)

   8:         {

   9:             Response.End();

  10:         }

  11:     }

  12:    

  13:     // Zip PDFs and send to browser.

  14: }

I wrote a quick test page to verify how it works, and fired up the debugger to watch the output window. Here was my Page_Load method:

   1: protected void Page_Load(object sender, EventArgs e)

   2: {

   3:     while (true)

   4:     {

   5:         if (Response.IsClientConnected)

   6:         {

   7:             Debug.WriteLine("Connected");

   8:         }

   9:         else

  10:         {

  11:             Debug.WriteLine("Disconnected");

  12:             break;

  13:         }

  14:     }

  15: }

Firing up the browser (I tested in IE and Firefox) and navigating to the page showed a steady stream of "Connected" in the output window. Closing the browser immediately wrote out "Disconnected", as did clicking back or navigating to another page. The only case I could find that didn't react as I'd expect was clicking stop or hitting escape on the page. It still showed as connected. Only after navigating to another page or closing the browser did it get the disconnected message. Still, in most cases, it's better than letting the process run it's course for no reason.

I know in this case, doing it asynchronously is the long term solution, but I'm sure in some cases, this will come in handy. And of course, I want to make sure it's the best way to handle it, so if you've done something similar, or better yet, better, what was it?

 

Friday, February 13, 2009

Attaching to a process is something that developers use very often to debug, but what happens when the process (A) you want to debug is launched from another process (B) and you want to debug is the initialization of process A,  Supposing that process A can only run within the context of process B in that case the only way to start process A is to do so from process so classic F5 won't help us to debug the initialization of A.

I had this scenario at work so at first I "trained" myself to do a very fast Alt+Ctrl+P select the process and press Attach button, of course this training didn't help much, after googling a little I found this macro

 Sub AttachAspNet()
        Dim process As EnvDTE.Process

        If Not (DTE.Debugger.DebuggedProcesses Is Nothing) Then
            For Each process In DTE.Debugger.DebuggedProcesses
                If (process.Name.IndexOf("aspnet_wp.exe") <> -1) Then
                    Exit Sub
                End If
            Next
        End If

        For Each process In DTE.Debugger.LocalProcesses
            If (process.Name.IndexOf("myprocess.exe") <> -1) Then
                process.Attach()
                Exit Sub
            End If
        Next
    End Sub 
but still it would have to wait for the process to start and then attach to it, so finally I created this Visual Studio plug-in, which have this option.

 

The main idea is to wait for the process to start and then attach to it:

 

        private AttachResult PessimisticAttach(AttachType attachType)

        {

            AttachResult res = Attach(attachType);

 

            DateTime timeout = DateTime.Now.AddSeconds(WaitTimeout);

 

            while(res == AttachResult.NotRunning && timeout > DateTime.Now)

            {

                res = Attach(attachType);

                System.Threading.Thread.Sleep(100);

            }

            return res;

        }

        private AttachResult Attach(AttachType attachType)

        {

            string engine =attachTypesMap[attachType];

 

            if(IsBeingDebugged())

            {

                return AttachResult.BeingDebugged;

            }

 

            Debugger2 dbg = dte.Debugger as Debugger2;

            Transport trans = dbg.Transports.Item("Default");

            Engine eng;

 

            eng = trans.Engines.Item(engine);

 

            EnvDTE80.Process2 proc = null;

 

            try

            {

                proc = dbg.GetProcesses(trans, "").Item(processName) as EnvDTE80.Process2;

            }

            catch(Exception ex)

            {

                if(ex.Message.Contains("Invalid index."))

                {

                    return AttachResult.NotRunning;

                }

            }

 

            proc.Attach2(eng);

 

            return AttachResult.Attached;

 

        }

 

 

I wrote the plug-in using Declarative Visual Studio addin buttons with icons which saved me a lot of time and pain.

 

You can download plug in source here

More

Since I've seen code like this before and am also guilty of writing code like this, I thought I'd blog about an easier way to grab a single element from a LINQ query that Bill Wagner told me about at last night's AADND meeting.

Consider the following class:

   1: public class Person
   2: {
   3:     public string Name { get; set; }
   4:     public int Age { get; set; }
   5:     public bool Leader { get; set; }
   6: }

And let's load up some sample data:

   1: Person[] people = new Person[] {
   2:     new Person { Name = "Blue", Age = 25, Leader = true },
   3:     new Person { Name = "Gold", Age = 16, Leader = false },
   4:     new Person { Name = "Red", Age = 27, Leader = false },
   5:     new Person { Name = "Green", Age = 14, Leader = false}
   6: };

Now what we could do to find the leader (the assumption is that there is always only one leader):

   1: Person leader = people.Where(p => p.Leader == true).ToArray()[0];

The result of the people.Where() is an IEnumerable<Person>.  And you can't just index the first element of that – so you convert it to an array and index that instead.

LINQ provides two methods to perform this type of query without the need of having an intermediate array -- "First" and "Single":

   1: Person leader2 = people.First(p => p.Leader == true);
   2: Person leader3 = people.Single(p => p.Leader == true);

The difference between the two is that First grabs the first item it finds.  The Single method expects only a single matching item and will throw an exception if it finds more than one.  In this case, there is only one Person in the array that has Leader set to true so both of these lines of code produce the same result.

However, in the situation below:

   1: Person firstChild1 = people.First(p => p.Age < 18);
   2: Person firstChild2 = people.Single(p => p.Age < 18);

The first line will succeed.  The second line will fail since there are two people that are under 18.

 

Tuesday, February 10, 2009

In today’s post I’m going to share a problem I solved this week. The solution was to use the framework’s System.IO.Compression namespace and the GZipStream object.

The Problem

In my current project we save Xml data in our database. The field to save the data was of type varchar(6000). This way of saving the data raised a problem of big Xml data (over 8000 kb for every Xml data) which were saved in the database and for the long run could raise space and performance problems.

The Solution

Use the compression abilities of .NET framework, compress the Xml data and save the data in a binary form. We needed to change the field type in the database to binary type and compress the Xml data before inserting it to the database. After the binary data was retrieved from the database a reverse process of decompress returns the original Xml string.

The Code

I first built a console application to write the code and test it. Then, I wired the zip and unzip methods I wrote to the part that needed the compression. The following code is the console application’s zip and unzip methods I used to compress the Xml data.

01.static void Main(string[] args)

02.{

03.    string data = "<Root><Child></Child>data1<Child>data2</Child><Child>data3</Child><Child>data4</Child><Child>data5</Child></Root>";

04.    Console.WriteLine(data);

05.  

06.    byte[] zipped = ZipDocumentData(data);

07.    Console.WriteLine(Encoding.UTF8.GetString(zipped));

08.  

09.    data = UnZipDocumentData(zipped);

10.    Console.WriteLine(data);

11.  

12.    Console.Read();

13.}

14.  

15.private static byte[] ZipDocumentData(string documentData)

16.{

17.    byte[] byteArray = Encoding.UTF8.GetBytes(documentData);

18.    string result = string.Empty;

19.  

20.    using (MemoryStream ms = new MemoryStream())

21.    {

22.        using (GZipStream stream = new GZipStream(ms, CompressionMode.Compress))

23.        {

24.            //Compress

25.            stream.Write(byteArray, 0, byteArray.Length);

26.        }

27.        return ms.ToArray();

28.    }

29.}

30.  

31.private static string UnZipDocumentData(byte[] zippedDocumentData)

32.{            

33.    string result = string.Empty;

34.  

35.    //Prepare for decompress

36.    using (MemoryStream ms = new MemoryStream(zippedDocumentData))

37.    {

38.        using (GZipStream stream = new GZipStream(ms, CompressionMode.Decompress))

39.        {

40.            //Reset variable to collect uncompressed result

41.            byte[] byteArray = new byte[4096];

42.  

43.            //Decompress

44.            int rByte = stream.Read(byteArray, 0, byteArray.Length);

45.  

46.            result = Encoding.UTF8.GetString(byteArray);

47.        }

48.    }

49.    return result;

50.}

Some things that should be concerned if you are going to use this code:

  • The encoding of the strings I use are in UTF8 format. If you use other formats you should change the Encoding.UTF8 code to the format you use.
  • In the decompress process I use a fixed array of 4096 bytes. This is only for the testing application in the real method I save the original size of the array.

Summary

Lets sum up, I used a compression method to compress Xml data in the database. I showed the code to do that using the GZipStream object which is part of System.IO.Compression namespace. I hope the code will help you when you’ll ever need to compress data.