Imports Microsoft.VisualBasic
Imports System.Security.Principal
Imports System.IO
Imports System.Web.Configuration
Imports System.Security.Permissions
''' <summary>
''' FormSaverModule Version 1.0
''' -Mastro: Not my orignal idea, found this in use out on MSDN site. I manually converted it to VB
''' as the orignaly is in C#. No comments, so I'll add my own.
'''
''' This class will handle every new request coming in and PostMapRequest.
''' If user has Forms Authentication has timed out, class will save the state of the form into cache
''' with a Unique ID of that state to the user's cookie.
'''
''' When user logs back in, this class will check if they have the cookie set, if so it will then
''' load that state back to their page and load the webcontent by generating a runtime transit page that
''' post submits the data back to the destination page and pre-fills in the values.
''' </summary>
''' <remarks></remarks>
<AspNetHostingPermission(SecurityAction.LinkDemand, Level:=System.Web.AspNetHostingPermissionLevel.Minimal)> _
<AspNetHostingPermission(SecurityAction.InheritanceDemand, Level:=AspNetHostingPermissionLevel.Minimal)> _
Public Class FormSaverHTTPModule
Implements IHttpModule
#Region "Private Variables"
Private Const CookieName As String = "#FormStateSaverModule/FormRestoreId"
Private Shared ReadOnly StateCacheDuration As TimeSpan = TimeSpan.FromMinutes(40)
Private Shared ReadOnly _AnonymousUser As IPrincipal = New GenericPrincipal(New GenericIdentity(String.Empty), Nothing)
Private _FormsCookieName As String
Private _LoginUrl As String
#End Region
''' <summary>
''' This class is required from the IHttpModule contract
''' </summary>
''' <remarks></remarks>
Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
End Sub
''' <summary>
''' This is the first method to run on application start
''' </summary>
''' <param name="Context"></param>
''' <remarks></remarks>
Public Sub IHttpModuleInit(ByVal Context As HttpApplication) Implements System.Web.IHttpModule.Init
Me.Init(Context) 'Lets call our Init and send the Application state along with it
End Sub
''' <summary>
''' We call this Init Method from the IHttpModuleInit and send it the Application State
''' and then turn on Events to capture
''' </summary>
''' <param name="Context">Hold's the Application State</param>
''' <remarks></remarks>
Protected Overridable Sub Init(ByVal Context As HttpApplication)
If Context Is Nothing Then
Throw New ArgumentNullException("context")
End If
_LoginUrl = FormsAuthentication.LoginUrl 'Store the Login page name
_FormsCookieName = FormsAuthentication.FormsCookieName 'Store the Forms Authentication Cookie Name
If Not String.IsNullOrEmpty(_LoginUrl) Then 'If LoginUrl Exists....
Dim index As Integer = _LoginUrl.IndexOf("?"c) 'Search for a ? for query string
If -1 <> index Then 'If ? was found....
_LoginUrl = _LoginUrl.Substring(0, index) 'Grab the first part of the URL before the ? and set LoginUrl
End If
End If
'Add Handlers that now raise events on every Application BeginRequest... and Application PostMapRequest
AddHandler Context.BeginRequest, New EventHandler(AddressOf Application_BeginRequest)
AddHandler Context.PostMapRequestHandler, New EventHandler(AddressOf Application_PostMapRequestHandler)
End Sub
''' <summary>
''' A Delegate receiver method for the Application.BeginRequest Event
''' </summary>
''' <param name="sender">Object</param>
''' <param name="e">EventArgs</param>
''' <remarks></remarks>
Protected Overridable Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
Dim application As HttpApplication = DirectCast(sender, HttpApplication)
Dim context As HttpContext = application.Context
Dim cookie As HttpCookie = context.Request.Cookies(CookieName)
If cookie IsNot Nothing AndAlso Not String.IsNullOrEmpty(cookie.Value) Then 'If Cookie exist and has value....
Dim state As FormState = FormState.Load(cookie.Value) 'Lets try and load the state via the CookieID
'Lets see if the State is valid now that it's loaded and it's originaly location matches where the user is trying to go
If state IsNot Nothing AndAlso String.Equals(state.Path, context.Request.Path, StringComparison.OrdinalIgnoreCase) Then
FormState.SetCurrent(context, state) 'State is valid and it's for this request so lets create the object of the state to be used later by Application_PostMapRequestHandler
Return
End If
End If
If IsPost(context) AndAlso Not IsAccessingLoginPage(context, _LoginUrl) Then 'If user is doing a Post back and it's not to the login page...
cookie = context.Request.Cookies(_FormsCookieName) 'Load Authenication cookie
If cookie IsNot Nothing Then
Try
Dim ticket As FormsAuthenticationTicket = FormsAuthentication.Decrypt(cookie.Value)
If ticket IsNot Nothing AndAlso ticket.Expired AndAlso Not HasAnonymousAccess(context) Then 'If the user has expired.....
Dim state As FormState = FormState.Create(context) 'Store current state into cache
If state IsNot Nothing Then
cookie = New HttpCookie(CookieName, state.StateId) 'save cache ID into user's cookie
cookie.HttpOnly = True
context.Response.Cookies.Add(cookie)
End If
End If
Catch generatedExceptionName As ArgumentException
End Try
End If
End If
End Sub
Protected Overridable Sub Application_PostMapRequestHandler(ByVal sender As Object, ByVal e As EventArgs)
Dim application As HttpApplication = DirectCast(sender, HttpApplication)
Dim context As HttpContext = application.Context
Dim state As FormState = FormState.GetCurrent(context) 'Get the context from the cache if it's there
If state IsNot Nothing AndAlso state.Form IsNot Nothing AndAlso 0 <> state.Form.Count Then 'if the context was there....
context.Handler = New FormStateSaverHandler(state) 'Then load the runtime transit page and post it back to destination page
End If
End Sub
''' <summary>
''' Returns Boolean if request is a Post request to page.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Protected Shared Function IsPost(ByVal context As HttpContext) As Boolean
Return String.Equals("POST", context.Request.HttpMethod, StringComparison.OrdinalIgnoreCase)
End Function
''' <summary>
''' Return Boolean after it checks to see if content matches the LoginURL. Basically is the user
''' going to the login page.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Protected Shared Function IsAccessingLoginPage(ByVal context As HttpContext, ByVal loginUrl As String) As Boolean
Dim result As Boolean = False
If Not String.IsNullOrEmpty(loginUrl) Then
If String.Equals(context.Request.Path, loginUrl, StringComparison.OrdinalIgnoreCase) Then
result = True
ElseIf -1 <> loginUrl.IndexOf("%"c) Then
Dim temp As String = HttpUtility.UrlDecode(loginUrl)
If String.Equals(context.Request.Path, temp, StringComparison.OrdinalIgnoreCase) Then
result = True
Else
temp = HttpUtility.UrlDecode(loginUrl, context.Request.ContentEncoding)
If String.Equals(context.Request.Path, temp, StringComparison.OrdinalIgnoreCase) Then
result = True
End If
End If
End If
End If
Return result
End Function
''' <summary>
''' Returns Boolean if the destination URL in question has anonymouse access.
''' </summary>
''' <param name="context"></param>
''' <returns></returns>
''' <remarks></remarks>
Protected Shared Function HasAnonymousAccess(ByVal context As HttpContext) As Boolean
Return UrlAuthorizationModule.CheckUrlAccessForPrincipal(context.Request.Path, _AnonymousUser, context.Request.HttpMethod)
End Function
''' <summary>
''' This class becomes the actual FormState the user was on and is Serialzied and then saved
''' </summary>
''' <remarks></remarks>
<Serializable()> _
Protected NotInheritable Class FormState
Private Shared ReadOnly FormRestoreKey As New Object()
Private ReadOnly _id As String
Private ReadOnly _path As String
Private ReadOnly _form As NameValueCollection
Private Sub New(ByVal id As String, ByVal path As String, ByVal form As NameValueCollection)
_id = id
_path = path
_form = form
End Sub
Public ReadOnly Property StateId() As String
Get
Return _id
End Get
End Property
Public ReadOnly Property Path() As String
Get
Return _path
End Get
End Property
Public ReadOnly Property Form() As NameValueCollection
Get
Return _form
End Get
End Property
''' <summary>
''' Loads the state that was set by Setcurrent and tries to return it as a FormState object.
''' Returns nothing if it's invalid.
''' </summary>
''' <param name="context"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function GetCurrent(ByVal context As HttpContext) As FormState
If context Is Nothing Then
Return Nothing
End If
Return TryCast(context.Items(FormRestoreKey), FormState)
End Function
''' <summary>
''' Takes the state sent and creates a valid formstate object
''' </summary>
''' <param name="context"></param>
''' <param name="state"></param>
''' <remarks></remarks>
Public Shared Sub SetCurrent(ByVal context As HttpContext, ByVal state As FormState)
If context IsNot Nothing Then
context.Items(FormRestoreKey) = state
End If
End Sub
''' <summary>
''' Takes the Context of the current Request Form and stores it into Cache with a Unique ID
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function Create(ByVal context As HttpContext) As FormState
Dim result As FormState = Nothing
If context IsNot Nothing AndAlso context.Request.Form IsNot Nothing AndAlso 0 <> context.Request.Form.Count Then
Dim id As String = Guid.NewGuid().ToString()
result = New FormState(id, context.Request.Path, context.Request.Form)
HttpRuntime.Cache.Add("FormStateSaver_" + id, result, Nothing, Cache.NoAbsoluteExpiration, StateCacheDuration, CacheItemPriority.Normal, _
Nothing)
End If
Return result
End Function
''' <summary>
''' Loads the Context stored in Cache using the Unique ID
''' </summary>
''' <param name="id">Unique GUID</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function Load(ByVal id As String) As FormState
Dim result As FormState = Nothing
If Not String.IsNullOrEmpty(id) Then
result = TryCast(HttpRuntime.Cache("FormStateSaver_" + id), FormState)
End If
Return result
End Function
''' <summary>
''' Deletes the stored Context from Cache
''' </summary>
''' <remarks></remarks>
Public Sub Delete()
HttpRuntime.Cache.Remove("FormStateSaver_" + _id)
End Sub
End Class
''' <summary>
''' This class is responsible for generating the Transit page on the fly.
''' Basically a page that loads all the values then submits them back to the destination
''' page with the previous viewstate settings so that the form reloads the controls to where
''' they were prior.
'''
''' This page quickly shows when the user is logging back in and loading a previous web form.
'''
''' </summary>
''' <remarks></remarks>
Protected Class FormStateSaverHandler
Implements IHttpHandler
Private ReadOnly _state As FormState
Public Sub New(ByVal state As FormState)
_state = state
End Sub
Protected ReadOnly Property FormState() As FormState
Get
Return _state
End Get
End Property
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
Public Overridable Sub ProcessRequest(ByVal context As HttpContext) Implements System.Web.IHttpHandler.ProcessRequest
Try
Using writer As HtmlTextWriter = CreateHtmlTextWriter(context.Response.Output, context.Request.Browser)
Me.Render(writer)
End Using
Finally
_state.Delete()
End Try
End Sub
Protected Overridable Sub Render(ByVal writer As HtmlTextWriter)
writer.RenderBeginTag(HtmlTextWriterTag.Html)
writer.RenderBeginTag(HtmlTextWriterTag.Head)
writer.RenderBeginTag(HtmlTextWriterTag.Title)
writer.Write("Restoring form")
writer.RenderEndTag()
' TITLE
writer.RenderEndTag()
' HEAD
writer.AddAttribute("onload", "document.forms[0].submit();")
writer.RenderBeginTag(HtmlTextWriterTag.Body)
writer.AddAttribute("method", "post")
writer.RenderBeginTag(HtmlTextWriterTag.Form)
Dim form As NameValueCollection = Me.FormState.Form
For Each name As String In form.Keys
RenderHiddenField(writer, name, form(name))
Next
'writer.AddAttribute(HtmlTextWriterAttribute.Align, "center")
'writer.RenderBeginTag(HtmlTextWriterTag.P)
'writer.Write("You should be redirected in a moment.")
'writer.WriteFullBeginTag("br")
'writer.Write("If nothing happens, please click ")
'RenderSubmitButton(writer, "Submit")
'writer.RenderEndTag()
' P
writer.RenderEndTag()
' FORM
writer.RenderEndTag()
' BODY
writer.RenderEndTag()
' HTML
End Sub
Protected Shared Sub RenderHiddenField(ByVal writer As HtmlTextWriter, ByVal name As String, ByVal value As String)
writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden")
writer.AddAttribute(HtmlTextWriterAttribute.Name, name)
writer.AddAttribute(HtmlTextWriterAttribute.Value, value)
writer.RenderBeginTag(HtmlTextWriterTag.Input)
writer.RenderEndTag()
' INPUT
End Sub
Protected Shared Sub RenderSubmitButton(ByVal writer As HtmlTextWriter, ByVal text As String)
writer.AddAttribute(HtmlTextWriterAttribute.Type, "submit")
writer.AddAttribute(HtmlTextWriterAttribute.Value, text)
writer.RenderBeginTag(HtmlTextWriterTag.Input)
writer.RenderEndTag()
' INPUT
End Sub
Protected Shared Function CreateHtmlTextWriter(ByVal writer As TextWriter, ByVal browser As HttpCapabilitiesBase) As HtmlTextWriter
If browser Is Nothing Then
Return New HtmlTextWriter(writer)
End If
Return browser.CreateHtmlTextWriter(writer)
End Function
End Class
End Class