views:

848

answers:

2

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!

+1  A: 

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).

Guffa
+1  A: 

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
Mark Brackett