1

What I am trying to do is simple in principle, but the lifecycle of ASP.NET pages is throwing a bucket of cold water into my day.

Here is the problem

We have implemented URL Redirection and I've inherited code that reads like this in Global.ASAX, on Sub Application_BeginRequest:

    dv.Table = CommonFunctions.ConvertXmlFileToDataSet("/XmlData/WebAppFolders.xml").Tables("Application")
    dv.RowFilter = "'" + fullOrigionalPath + "' LIKE '%'+ folder + '%'"
    If dv.Count > 0 Then 'match on key to redirect
        If fullOrigionalPath.EndsWith(dv(0)("folder") + "/") Then 
            Context.RewritePath(dv(0)("basePage"), True)
        Else 'missing page in directory --> redirect
            HttpContext.Current.Items("Raise404") = "true"
            Response.Redirect("/" + dv(0)("folder"))
        End If
        Return
    End If

Basically we are reading a large XML file that contains the URL redirects. That's working fine. The problem happens in the line...

HttpContext.Current.Items("Raise404") = "true"

In the context of Application_BeginRequest, the session object is not yet available, so I could not use it to store a flag which I named Raise404. Instead I had to resort to using the Items collection.

The problem occurs when the redirect takes place. The new page lifecycle destroys the Items array and overwrites it with a new empty one.

By the time I try to use my flag Raise404, it no longer exists on my page PreRender event.

To complicate matters we use master pages, and so I was asked to place the code that we want to execute in the master page

Ideally, if the Items array wasn't being destroyed, this code would work:

Private Sub Page_PreRender(sender As Object, e As System.EventArgs) Handles Me.PreRender
    If HttpContext.Current.Items("Raise404") IsNot Nothing AndAlso HttpContext.Current.Items("Raise404").Equals("true") Then
        Response.StatusCode = 404
        HttpContext.Current.Items("Raise404") = Nothing
    End If
End Sub

I am not sure what kind of variable I could use to store my flag and allow it to survive the redirect.

Any ideas?

Update: The problem is that the HTTP Handler servicing my request is System.Web.DefaultHTTPHandler, which does not implement IRequiresSessionState, and so when my request is being handled inside Global ASAX there is no session created. So, it seems like the solution will be to write a custom HTTPHandler that implements IRequiresSessionState, and use that for all my .aspx files. Even then, a session state is not created in Global.ASAX until PreRequestHandlerExecute is raised. So, putting it all together I think I need to write a custom HTTP Handler that implements IRequiresSessionState, and delay the redirection of the page until PreRequestHandlerExecute is raised where I will be able to store my flag into the Session state, and only after that, redirect my page.

Not very elegant, and I wonder if there will be any performance implications.

2 Answers2

1

Have you considered using the query string for that?

Response.Redirect("/" & dv(0)("folder") & "?Raise404=true")

Then, in your Master Page, simply check QueryString("Raise404") and act accordingly.

The only drawback I can see is that a malicious client could deliberately add Raise404=true to the query string, which is not possible with your current solution. However, I do not see how that could do any harm.

Heinzi
  • 167,459
  • 57
  • 363
  • 519
0

As I suspected, my problem was directly tied to the fact that IIS 8 will avoid using an HTTPHandler that implements IRequiresSessionState if for performance reasons it detects that a session object is not needed, and so because the redirect code was happening during the Application_BeginRequest event, IIS was handling up to that point my requests using System.Web.DefaultHTTPHandler.

So, upon further research, I found this which was tremendous help: IRequiresSessionState - how do I use it?

And I also documented myself on this article: http://forums.iis.net/t/1094546.aspx

What solved my problem was to write a dummy HTTPHandler class. I added a new class to my app_code folder:

Imports System.Web

    Public Class HTTPRequestHandler : Implements IHttpHandler, IRequiresSessionState

        Private OriginalHandler As IHttpHandler

        Public Sub New(handler As IHttpHandler)
            Me.OriginalHandler = handler
        End Sub

        Public Sub ProcessRequest(context As HttpContext) Implements IHttpHandler.ProcessRequest
            Throw New InvalidOperationException("HTTPRequestHandler cannot process requests.")
        End Sub

        Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
            Get
                Return True
            End Get
        End Property

        Public ReadOnly Property Handler() As IHttpHandler
            Get
                If Me.OriginalHandler IsNot Nothing Then
                    Return Me.OriginalHandler
                End If
                Return HttpContext.Current.Handler
            End Get
        End Property
    End Class

Now, during Application_BeginRequest, session is not available but HttpContext.Current.Items is available. So, as the second article suggests, during PostMapRequestHandler I put a condition:

If HttpContext.Current.Items("Raise404") IsNot Nothing Then
    Context.Handler = New MyNameSpace.HTTPRequestHandler(Context.Handler)
End If

And finally, that created the session object that I could finally use during PreRequestHandlerExecute:

    HttpContext.Current.Session("Raise404") = "true"
    Response.Redirect(HttpContext.Current.Items("Redirect"))

I will definitely not argue that this is not elegant, but it works and I can raise my 404 error during Page_PreRender on my master page.

I hope this may help others.

Cheers.

Community
  • 1
  • 1