Asp.Net Button Event on refresh fires again??? GUID? - asp.net

The obvious issue here is that on refresh a button event is recalled and duplicate posts are created in the database. I have read other questions on this site about the very same issue.
Apparently the answer is for every post create a GUID and check to make sure the GUID is unique? I am not really sure how to implement this.
Does that mean on refresh, it will try and create a duplicate post with the same GUID?
How do you implement a GUID into your database? Or if this is not the answer, what is?
Thank you!

The idea is that you create a unique number for the form, and when the form is posted you save this unique number in the database in the record that you are editing/creating. Before saving you check if that number has already been used, in that case it's a form that has been reposted by refreshing.
If you are updating a record, you only have to check if that record has been saved with the same unique number, but if you are adding a new record you have to check if any other record has that number.
A Guid is a good number to use as it's very unlikely that you get a duplicate. A 31 bit random number that the Random class can produce is also pretty unlikely to give duplicates, but the 128 bits of a Guid makes it a lot more unlikely.
You don't have to create the Guid value in the database, just use Guid.NewGuid() in the code that initialises the form. You can put the Guid in a hidden field in the form. In the database you only need a field that can store a Guid value, either a Guid data type if available or just a text field large enough to hold the text representation of the Guid.
You can use the ToString method to get the string representation of a Guid value (so that you can put it in the form). Using id.ToString("N") gives the most compact format, i.e. 32 hexadecimal digits without separators. Using id.ToString("B") gives the more recognisable format "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}". To get the Guid back from a string (either format), you just use new Guid(str).

Here's a RefreshValidator control that I use. Just drop it on your page, and check Page.IsValid before saving to the database. You can add an error message like other validators, or catch the Refreshed event if you want to do something special. Since it's a Validator, GridViews and the like will already take notice of it - except for Delete or Cancel actions (yeah, I have a custom GridView that solves that too...)
The code is pretty simple - store a GUID into ControlState, and a copy in Session. On load, compare the 2. If they're not the same - then it's a refresh. Rinse, repeat, and create a new GUID and start over.
''' <summary>
''' A validator control that detects if the page has been refreshed
''' </summary>
''' <remarks>If <see cref="SessionState.HttpSessionState" /> is not available or is reset, validator will return Valid</remarks>
Public Class RefreshValidator
Inherits BaseValidator
Private isRefreshed As Boolean
Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
MyBase.OnInit(e)
Page.RegisterRequiresControlState(Me)
End Sub
Protected Overrides Function SaveControlState() As Object
Dim obj As Object = MyBase.SaveControlState()
Return New System.Web.UI.Pair(_pageHashValue, obj)
End Function
Protected Overrides Sub LoadControlState(ByVal savedState As Object)
Dim pair As System.Web.UI.Pair = TryCast(savedState, System.Web.UI.Pair)
If pair IsNot Nothing Then
_pageHashValue = TryCast(pair.First, String)
MyBase.LoadControlState(pair.Second)
Else
MyBase.LoadControlState(savedState)
End If
End Sub
Private _pageHashValue As String
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
If HttpContext.Current Is Nothing OrElse HttpContext.Current.Session Is Nothing Then
isRefreshed = False
Return
End If
' Get hash value from session
Dim currHashValue As String = CType(HttpContext.Current.Session(Me.UniqueID & ":pageHashValue"), String)
If _pageHashValue Is Nothing OrElse currHashValue Is Nothing Then
' No page hash value - must be first render
' No current hash value. Session reset?
isRefreshed = False
ElseIf currHashValue = _pageHashValue Then
' Everything OK
isRefreshed = False
Else
' Was refreshed
isRefreshed = True
End If
' Build new values for form hash
Dim newHashValue As String = Guid.NewGuid().ToString()
_pageHashValue = newHashValue
HttpContext.Current.Session(Me.UniqueID & ":pageHashValue") = newHashValue
End Sub
Protected Overrides Function ControlPropertiesValid() As Boolean
Return True
End Function
Protected Overrides Function EvaluateIsValid() As Boolean
If isRefreshed Then OnRefreshed(EventArgs.Empty)
Return Not isRefreshed
End Function
Protected Overridable Sub OnRefreshed(ByVal e As EventArgs)
RaiseEvent Refreshed(Me, e)
End Sub
''' <summary>
''' Fires when page is detected as a refresh
''' </summary>
Public Event Refreshed As EventHandler
End Class

Related

How can I store the data in memory and use by the other Button click event to display the data?

Here is the code, but the datatable is NULL in ButtonExport click event, how can i pass the DataTable to Sub ButtonExport_Click ? I dont want to store in Session as the data is too big
Here is the class clsGlobalVarriable
Public Class clsGlobalVariable
Private _gdt As DataTable
Public Property globalDataTable As DataTable
Get
Return _gdt
End Get
Set(ByVal value As DataTable)
_gdt = value
End Set
End Property
End Class
Here is the From frmTest code:
Public Class frmTest
Inherits System.Web.UI.Page
Private gdt As New clsGlobalVariable
Protected Sub ButtonInactivePC_Click(sender As Object, e As EventArgs) Handles ButtonInactivePC.Click
Try
Dim func As New clsFunction
Dim command As String = "Get-ADComputer -Filter { OperatingSystem -NotLike '*Windows Server*'} -Property * | select Name, CanonicalName, operatingSystem, LastLogonDate, Description, whenChanged | Where {($_.LastLogonDate -lt (Get-Date).AddDays(-90)) -and ($_.LastLogonDate -ne $NULL)}"
Dim arr As New ArrayList
arr.Add("Name")
arr.Add("CanonicalName")
arr.Add("operatingSystem")
arr.Add("LastLogonDate")
arr.Add("whenChanged")
arr.Add("Description")
gdt.globalDataTable = func.PSObjectToDataTable(command, arr)
Me.GridView1.DataSource = gdt.globalDataTable
Me.GridView1.DataBind()
Catch ex As Exception
Me.LabelDebug.Text = "Button Click" + ex.Message
End Try
End Sub
Protected Sub ButtonExport_Click(sender As Object, e As EventArgs) Handles ButtonExport.Click
Dim func As New clsFunction
Dim dt As New DataTable
dt = (DirectCast(Me.GridView1.DataSource, DataTable))
Me.LabelDebug.Text = "Global Data Table Count = " & dt.Rows.Count
End Sub
When working with webpages that show data to the user, and the user takes some action on that data you either need to store the data somewhere in their computer, your computer (the server) or rely on the fact that it's still stored in the computer you got it from. As a process you have undertaken:
You generate a grid from querying AD
You send the grid to the customer's computer - so it's stored there as a visual representation (and maybe also ViewState)
It's still stored in AD, where you got it
You could also store it locally on the server somehow - Session, DB, text file, whatever
Decide on which of these to use when the user clicks Export:
Dig it out of the viewstate or other data that was sent to the user - for this you'll have to code things up so it comes back from the user
Get it out of AD again - simple to do; you did it once and sent it to the user in HTML. Getting it again and sending it to the user again this time as a CSV isn't really any different from the first time you did it
Restore it from wherever you kept it on the server
Choose the first if your user is going to modify the data or choose to export only some of it - the data he sends back to you should indicate which bits he wants exporting.
Choose the second option if you want an easy life, and it's just a straight export, no editing or subset of data. Write one method that gets the data out of AD and then use it in either place, one to form HTML/fill a grid, in the other to send a file to the user. Don't get hung up on "well I already got this data once, it's a waste to get it again" - no-one writes a Login Page and thinks "i'll only ever look up a user from the DB once, then get the server to remember the login data forever more and use it next time there is a login request" - they store the data in the db, and look it up every time there is a login. DBs store data and perform the same queries over and over again. This is no different
You probably wouldn't choose the third option, for reasons already mentioned
I decided to use alternative for the Excel Export, i am not going to pass the DataTable, instead i pass the GridView to the Export to Excel function
Add the following sub right after Page_load, this is to avoid the GridView error
Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control)
End Sub
Here is the Code:
Public Sub ExportFromGridview(ByVal gv As GridView, ByVal response As HttpResponse
response.Clear()
response.Write("<meta http-equiv=Content-Type content=text/html;charset=utf-8>")
response.AddHeader("content-disposition", "attachment;filename=" & Now & ".xls")
response.ContentType = "application/vnd.xls"
Dim stringWrite As System.IO.StringWriter = New System.IO.StringWriter()
Dim htmlWrite As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(stringWrite)
gv.RenderControl(htmlWrite)
response.Write(stringWrite.ToString())
response.End()
End Sub

Validation Summary

I use a login entrance in my Asp.Net project
And I use validationSummary for User Name and password.
Everything goes well but.
What I want is to know if the ValidationSummary has errors to show me or not before the appearance of the errors window
I use vb.net to build the project
I don't have any code to show. And also I can't find anything relative in on the Internet to assist me on this issue.
You are probably using the ValidationSummary method in your Razor views, which - as per MSDN
Returns an unordered list (ul element) of validation messages in the ModelStateDictionary object.
So, if you want to know if there will be any errors shown by the ValidationSummary method, you can check this ModelStateDictionary in your controller before delivering your response to the browser. Doing this is described i.e. here (in C#).
In your controller method you can access ModelState.IsValid if you want to know if there are any errors which will be displayed.
This does directly answer your question, but this might not be the optimal way to achieve what you want when looking at the bigger picture. If you want to i.e. do something special if the login fails in your controller you should check directly if the login failed, not if some other method added model errors. To provide an answer, which might be more on point, you need to clarify your question and add more details about what you specifically want to do and possibly add some of your code too.
The idea to use the code I post is finally correct.
Public Sub IsGroupValid(ByVal sValidationGroup As String, ByVal sender As Object, ByVal e As EventArgs)
For Each validator As BaseValidator In Validators
If validator.ValidationGroup = sValidationGroup Then
Dim fValid As Boolean = validator.IsValid
Dim CtrlToValidate As String = validator.ControlToValidate
validator.DataBind()
If Not fValid And CtrlToValidate = ServerHandler.UName Then
validator.Validate()
fValid = validator.IsValid
ModelState.AddModelError(CtrlToValidate, validator.ID)
ElseIf Not fValid And CtrlToValidate = "Password" And validator.ID = ServerHandler.PwdRq Then
validator.Validate()
fValid = validator.IsValid
ModelState.AddModelError(CtrlToValidate, validator.ID)
ElseIf Not fValid And CtrlToValidate = "Password" And validator.ID = ServerHandler.PwdRegEx Then
validator.Validate()
fValid = validator.IsValid
ModelState.AddModelError(CtrlToValidate, validator.ID)
End If
End If
Next
End Sub
But has condition that someone or something give him the error list from ValidationSummaryGroup
And this is done with the following code
Public Function LoadModel(ByVal sender As Object, ByVal e As EventArgs) As Boolean
Dim retVal As New Boolean
Try
If Not ModelState.IsValid Then
Dim result As StringBuilder = New StringBuilder()
For Each item In ModelState
Dim key As String = item.Key
Dim errors = item.Value.Errors
For Each [vError] In errors
ModelAnswer.Add(key & "^" & [vError].ErrorMessage)
retVal = True
Next
Next
End If
ModelState.Clear()
Catch ex As Exception
Environment.AssemblyInfo.ErrorAnswer = ServerHandler.ErrHandler.GetError(3, Nothing, Nothing, ex, Nothing)
Environment.AssemblyInfo.ErrorAnswer = Environment.AssemblyInfo.ErrorAnswer & "\r\n ifExistConsistencyRecord "
ServerHandler.ErrProperty._InnerError = Environment.AssemblyInfo.ErrorAnswer
Environment.AssemblyInfo.errorCall = True
retVal = False
End Try
Return retVal
End Function
Of course ModelAnswer is an ArrayList and declared as Public
And all this under the very basic prerequisite, all the processes to work within the main page and NOT in a "class"
Thank you very much for those who helped to solve this puzzle

VB.Net exception: Object reference not set to an instance of an object

I'm currently working on coding a web page for a school project. This site is supposed to be a simple online store where people can order prints of artwork. The specific page I'm working on has a Drop Down List (ddlArt) that is bound to my database and displays a list of the different art pieces available. When the user selects one of the items, all the information on that item is pulled from the database and displayed on the page in a variety of labels and such. The only thing is that I'm getting a null reference exception error saying "Object reference not set to an instance of an object" when I go to try to run the page. I got the same error on a homework assignment earlier in the year and managed to get it fixed, but I can't remember what I did and I can't get help from school until next week, so I thought I'd try my luck on here. Here's my code:
Private selectedArt As Art
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
ddlArt.DataBind()
End If
selectedArt = Me.GetSelectedArt
lblArtID.Text = selectedArt.ArtID()
lblArtName.Text = selectedArt.ArtName()
lblCaption.Text = selectedArt.Caption()
lblDescription.Text = selectedArt.Description()
imgArt.ImageUrl = "~/images/" & selectedArt.FileName()
End Sub
Private Function GetSelectedArt() As Art
Dim artTable As DataView = CType(SqlDataSource1.Select(DataSourceSelectArguments.Empty), DataView)
artTable.RowFilter = "ArtID = '" & ddlArt.SelectedValue & "'"
Dim artRow As DataRowView = artTable(0)
Me.imgArt.ImageUrl = "~/images/" & artRow("FileName")
Dim art As New Art
art.ArtID = artRow("ArtID").ToString
art.ArtName = artRow("ArtName").ToString
art.Caption = artRow("Caption").ToString
art.Description = artRow("LongDescription").ToString
art.FileName = artRow("FileName").ToString
Return art
End Function
And here's the code for the Art class, in case anybody is interested:
Public Class Art
Public Property ArtID As Integer
Public Property ArtName As String
Public Property ArtType As String
Public Property Caption As String
Public Property FileName As String
Public Property Description As String
End Class
When I get the error, it highlights the artTable.RowFilter = "ArtID = '" & ddlArt.SelectedValue & "'" line in the GetSelectedArt function. I've tried comparing it to my corrected homework assignment that I mentioned, but I can't seem to find the problem. My VB is a little fuzzy because it's been awhile since I actually took the class. Any suggestions? Thanks a bunch!
If I understand your comment above correctly, on the initial page load there is nothing in the ddlArt, because the user must first choose an art type.
If that is correct, then your answer to my question is your answer.
For whatever reason (and without seeing at least the Select statement), artTbl is not getting instantiated, which is why you're seeing the Object reference not set to an instance of an object error.
One way to fix this (without knowledge of your SqlDataSource it's hard to give a precise answer) is to modify your Page Load method so that GetSelectedArt is only called when the user has selected an item from the drop down list. Right now GetSelectedArt is called every time the page loads.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
ddlArt.DataBind()
Else
selectedArt = Me.GetSelectedArt
lblArtID.Text = selectedArt.ArtID()
lblArtName.Text = selectedArt.ArtName()
lblCaption.Text = selectedArt.Caption()
lblDescription.Text = selectedArt.Description()
imgArt.ImageUrl = "~/images/" & selectedArt.FileName()
End If
End Sub
However, the above modification will only prevent GetSelectedArt from being called on the initial page load. If your SqlDataSource.Select command is still returning nothing, then you're still going to have this problem.
A better solution would be to call the GetSelectedArt on the ddlArt.SelectedIndexChanged event handler. This way you'll know that you have (or should have) a valid SelectedValue from ddlArt.
Also, if you don't populate the drop down until the user selects an art type from the radio button list, why are you binding the drop down list on the initial page load (and what are you binding it to)? Or is the drop down list and detail information on a different page from the radio button list?
May be .. with ArtID as integer
artTable.RowFilter = "ArtID = " & format(ddlArt.SelectedValue)

ASP.NET - Populate a page with input entered from a previous page

on my website I have an "Enroll" page where users submit some basic information, there's some basic validation on that page then the user is taken to an "Enrollment Confirmed" page (which i don't want to display the info from previous page). On the confirmation page there is link to a "Print Enrollment Confirmation" page which, on this page, I want to contain the information entered from the "Enroll" page. So basically, I want the input entered on "Page 1" put into labels on "Page 3". I've seen some examples of transferring information from "Page 1" to "Page 2" but I have an extra page users need to go through before hitting the page with their previously entered data.
Can someone give me an explanation on how I could do this without using query strings? Thank you.
You could create an class with properties for each form field then store it in the session. Then after you populate what you need on page 3 remove it from the session.
Example
Class:
<Serializable()>
Public Class Input
Private _FirstName As String = String.Empty
Private _LastName As String = String.Empty
Public Property FirstName As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property
Public Property LastName As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property
Public Sub New()
End Sub
End Class
Storing data:
Private Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
Dim FormData As New Input()
FormData.FirstName = txtFirstName.Text
FormData.LastName = txtLastName.Text
Session("InputData") = FormData
End Sub
Retrieving it:
If Not IsNothing(Session("InputData")) Then
Dim FormData As Input = DirectCast(Session("InputData"), Input)
txtFirstName.Text = FormData.FirstName
txtLastName.Text = FormData.LastName
Session.Remove("InputData")
End If
You could use the button.postbackurl property to post the data to another page:
http://msdn.microsoft.com/en-us/library/ms178140.aspx
In the intermediary pages, you could store the data in hidden fields from page 1, so the data would be in the posted results for page 3, when another button posts the data from page 2 to page 3.
HTH.
This pretty much sums up your choices:
http://msdn.microsoft.com/en-us/library/6c3yckfw.aspx
I would store the values in hidden input fields on page 2 and if page 3 is called as a form submit, then the values will be available through Request.Form.

How to invalidate a single data item in the .net cache in VB

I have the following VB.NET code to set and read objects in cache on a per user basis (i.e. a bit like session)
Public Shared Sub CacheSet(ByVal Key As String, ByVal Value As Object)
Dim userID As String = HttpContext.Current.User.Identity.Name
HttpContext.Current.Cache(Key & "_" & userID) = Value
End Sub
Public Shared Function CacheGet(ByVal Key As Object)
Dim returnData As Object = Nothing
Dim userID As String = HttpContext.Current.User.Identity.Name
returnData = HttpContext.Current.Cache(Key & "_" & userID)
Return returnData
End Function
I use these functions to hold user data that I don't want to access the DB for all the time. However, when the data is updated, I want the cached item to be removed so it get created again.
How do I make an Item I set disappear or set it to NOTHING or NULL?
Taking an item out of the cache is as simple as calling HttpContext.Current.Cache.Remove(Key) so in your case you'd probably want:
Public Shared Sub CacheRemove(ByVal key As String)
Dim userID As String = HttpContext.Current.User.Identity.Name
HttpContext.Current.Cache.Remove(Key & "_" & UserID)
End Sub
Since you say that when the data is updated you want the cached item to be removed so it gets re-cached with the updated data, you might want to take a look at using a SqlCacheDependency object. This wires up the cache to your database so that when your data changes, the cached item is automatically updated to keep it in sync.

Resources