views:

858

answers:

9

Is it possible to serialize an ASP.NET web form, including all data that a user has entered? I want to give the users the ability to save a half-completed form and was hoping I could accomplish this with serialization. Any simple examples would be greatly appreciated.

Edit: I want to avoid having to have separate data layer for the "incomplete" forms that mirrors the layers for the completed forms. I don't want to store the incomplete forms with the complete forms because they may not pass my DB constraints.

+8  A: 

Mike,

You dont have to serialize the whole asp.net form to capture the half filled data. Its better to capture the data entered in the fields on unload (or whaterver trigger) and save it in the db. and u simply re-assign the data to controls while loading the page again.. this is better than serializing the entire page...

EDIT::

i understand as per ur several comments in this post, ur requirement is to

  • persist the half filled data not along with complete data
  • and ur data is very sensitive
  • and u want to store the serialized form of page and render it directly whenener is needed...

but do u think serializing is recommented in this context and it will solve all ur problems .. NO..

consider the below facts

  1. if you are serializing the entire form, u may end up storing the datas thats not even required. (if your form contain 15 textboxes and user just filled only 2 entries. then wats the use of persisting the whole form object).
  2. And amount of data u r transferring will be higher (increates packet size, slow transmission rates).
  3. And your control over the page (to be rendered from serialized object) is less. consider if you wanted to show some notification on the serialized form.. ther might be some way to do it for sure.. but i know its not that simple..

consider all the facts given by ppl here who got experience in this and do some analysis urself and Choose the best approach.. all the best

EDIT

I just came across the JQuery AutoSave plugin.. and its doing the same what i suggest above, but in a quite simple manner....(which runs in a specified some time interval & saving the unsaved content. u can easily tweak this to meet ur neeeds) just take a look..

Cheers

Ramesh Vel

Ramesh Vel
Seems to be an viable choice. Agreed till here unless someone come up with a better answer.
Ramiz Uddin
I would like to explore the option of serialization. That's what my question was specifically about.
Mike C.
+2  A: 

You can't really avoid a separate storage place for incomplete forms. You need to persist them someplace and you can't put them with the complete ones because apparently you have some database constraints on the underlying database table(s) and you want to keep them. I would suggest as @Ramesh did encapsulating your form's data in a class.

public class FormData
{
  public int IntField { get;set;}
  public string StringField {get;set;}
  public decimal DecimalField {get;set;}
  public decimal DateTimeField {get;set;}
}

Bind FormData object to your UI controls. When the user wants to save incomplete data, serialize FormData object to xml since you really want to utilize serialization here.

XmlSerializer serializer = new XmlSerializer(typeof(FormData));
StringWriter writer = new StringWriter(new StringBuilder());
serializer.Serialize(writer,formDataObject);
string serializedFormDataObject = writer.GetStringBuilder().ToString();

You can then save serializedFormDataObject to a separate table. When the user loads the form, you can check the table to see if the form is incomplete. If it is, you can deserialize the data on form load event for example:

string serializedFormDataObject = GetFromDatabase(formId,userId); //you get the idea I guess
StringReader reader = new StringReader(serializedFormDataObject);
XmlSerializer serializer = new XmlSerializer(typeof(FormData));
formDataObject = serializer.Deserialize(reader) as FormData;

You can then bind formData to your controls. When the form is complete, you can remove the incomplete form entry and save the completed form to the table for the completed forms.

Mehmet Aras
+1  A: 

You say that you want to serialize the form in its partially completed state. From your question and various comments it sounds like you are trying to use serialization as a way to avoid using separate storage. However, that's not what serialization is about.

Serializing is turning some object, usually in memory, into a known format (for example, an XML file) so that it can be transmitted across a network or persisted in a database. Serialization implies nothing about what you do with an object in its serialized form, only that it is in a serialized form.

Sorry if this is cookie-cutter, but I think it's made pretty clear: http://en.wikipedia.org/wiki/Serialization

Edit: maybe you understand what I said above already - are you looking to, for example, put the serialized form into the session?

Edit 2: Okay, I think I get it. You're okay with saving a serialized form to a database but you'd rather not save individual form inputs to a DB - in the same way you would save a completed form. So, you're looking for a way to persist incomplete forms differently from completed ones. To this end, I would create a new table in your database for this exact thing. It could have a single data column (which stores the serialized data) or the same columns as your normal (completed form) table, just without the constraints you mentioned. Does this make sense?

Matt Ball
I understand what serialization is for. I'm looking for an easy way to grab the state of my page, wrap it up in a ball, and save it to the database with the assumption that it will never be used for any other reason that restoring itself back to the page at some point. I thought serialization would be a good, easy choice for something like this. I am open to other easier ideas.
Mike C.
A: 

Serialization won't help you, unless you are happy to store the serilized data somewhere, maybe as an xml fragment in a db table or as a file (yuk). It seems like the wrong approach to me but if you really wanted to use serialization there are many options. The simplest I can think of would be to serialize request.form collection to xml, either by using the xmlserializer or by looping though it and building an xml string yourself.

My suggestion would be...

If you are happy to use javascript, why not try and do this on the client?

You could collect the data from the form on the click of a 'save' button, for example and save it in a cookie.

When the user comes back you can check for the cookie and reload the data from the cookie.

You'd only post the form data back to the server when the form is complete. This would avoid your DB constraints issue.

Try this: http://www.bigresource.com/Tutorial/PHP/Saving%5FForm%5FData%5FInto%5FA%5FCookie.htm

UPDATE

Following comments: OK, if you want to take the approach of serializing your form you need to store the data somewhere.

Based on what I think you want to do:

If your form data fits in one single table I'd suggest that you have a 'replica table' without the db constraints and call it something like formDataHolding (where your original was fomrData). You'd read and write from this table and only migrate the data when the form data is complete, I use the word migrate meaning that data leaves the holding table and goes into the complete table somehow. If your form comprises of data that fits in several tables, then serialization may well be the right answer but be prepared to store this data somewhere, in a way that might not sit well with your existing data model, or be prepared to create 'replica tables' without the constraints, for all the tables that store the data.

Serializing the data doesn't necassarily mean that you are creating xml, but I'll assume this is what you want. I'd take the approach of getting back the NameValueCollection from Request.Form and then serializing it. You may want to do this different if you're using webforms.

Here is a url for someone who has serialized a NameValueCollection: http://nayyeri.net/blog/Serialize-NameValueCollection/

Lewis
I do not want to store the half finished data in a cookie. This form contains sensitive data and I do not want to open the security risk of a cookie getting left where somebody else can get at it. I also want to give the administrators of system the ability to view the incomplete data in the same page.
Mike C.
OK, but from your responses you say you "I don not want xyz".In my response I mentioned if you really want to 'serialize' the form you can store it on the server in many ways, an example would be as an xml fragment.I mention, alternatively you could do this in a cookie, if you have securoty concerns why not use public / private key encryption ti encrypt the data in the cookie making it secure.Looks liek you've got several answers to you original questions and now answers to several others!
Lewis
A: 

Consider developing for google gears which allows the user to work offline. Basically, that means that the form data gets saved locally until getting committed to the server. While google itself isn't interested in empowering Microsoft developers, there are those in the developer community who are. Here is a tutorial on getting an ASP.NET application to take advantage of google gears.

Glenn
This form contains sensitive data and I do not want to open the security risk of a cookie getting left where somebody else can get at it. I also want to give the administrators of system the ability to view the incomplete data in the same page.
Mike C.
http://stackoverflow.com/questions/259461/method-to-serialize-objects-from-appserver-to-web-server-without-code-duplication/259572#259572
Glenn
A: 

I haven't gone through this big answers but to me looks like simply need to persist viewstate and use persisted viewstate instead of creating new one when need half entered form.

No need for any additional logic, viewstate is designed to be used just like this.

Any post back is technically new page render but uses value from viewstate and op is exactly trying to do same thing only not with postback but some other time but need same thing

mamu
How do I persist viewstate to a database?
Mike C.
When you do postback, ViewState property of page has all information regarding what ever has been entered by user. That can be serialized and will give you one string to save into database
mamu
Can you show a code snippet or refer a link on how to serialize a viewstate and how to unserialize and restore it?
Mike C.
+1  A: 

Viewstate is unfortunately not enough; you also need to consider post data and query parameters to be able to accurately save and restore page state. Here is a very comprehensive write-up about the issues involved, and it includes working code.

Vinay Sajip
A: 

An approach I have taken with a number of forms (mainly multipage but same applies for single page) is for each form page to inherit from a common base class, which has a number of properties. One of the properties would be FormData and could be defined as:

public FormData FormDataStore
{
    get
    {
        if (Session["FormDataStore"] == null)
        {
            Session["FormDataStore"] = new FormData();
        }

        return (FormData)Session["FormDataStore"];
    }
    set
    {
        Session["FormDataStore"] = value;
    }
}

FormData is a class which holds all of the properties (fields of the form), e.g.

public class FormData
{
  public string name {get; set;}
  public string email {get; set;}
}

Each form page has a 'LoadData' and 'SaveData' function which is responsible for populating the form with data from the FormDataObject object and saving data to the FormDataObject object respectively.

The base class also contains 2 methods, SaveDraft and LoadDraft. SaveDraft serializes FormDataObject to Xml and saves to a DB table (which can be as simple as 2 columns, ID and XmlData):

            StringWriter sw_wf = new StringWriter();
            XmlSerializer xs_wf = new XmlSerializer(FormData.GetType(), new System.Type[] { typeof(additionalObjects)});
            xs_wf.Serialize(sw_wf, FormDataObject);
            string WebFormData = sw_wf.ToString();
            sw_wf.Close();

            //write WebFormData to database

LoadDraft simply loads the Xml and deserialises back into an object which the forms will automatically pick up and populate the fields. The way LoadDraft works is dependant on the nature of the iste and the sensitivity of the data - e.g. if the form is being completed by a logged in user or it could be anonymous and a unique ID stored in the DB and in a cookie.

I build a lot of sites that submit to 1 or more web services and this approach works very well when building a stub from a WSDL file, and using this as the FormData class.

Macros
A: 

Hi Mike

One way ive used in the past is to capture the POST values from the Form Object and stored that using a momemto class that is serializable using the soapformatter. When required to restore, all I do is GET to a HttpHandler class with querystring parameters that parses the intended id and deserilizes the state for restoration which renders hidden fields to simulate the user doing a post from the original page. Here are the two classes.

NOTE that you are capturing the state of the FORM so when you restore the form you will restore the state to a state in which you are asking for a restore, you may need to handle this if you are finding a problem.

''' <summary>
''' This class encapsulates the form state
''' </summary>
<Serializable()> _
Public NotInheritable Class FormState

    Private _path As String
    Private _form As NameValueCollection

    ''' <summary>
    ''' Constructor.
    ''' </summary>
    ''' <param name="path">The path of the original form post request.</param>
    ''' <param name="form">The form to save.</param>
    Public Sub New(ByVal path As String, ByVal form As NameValueCollection)
        _path = path
        _form = form
    End Sub


    ''' <summary>
    ''' Serializer Ctor.
    ''' </summary>
    ''' <remarks></remarks>
    Sub New()
    End Sub


    ''' <summary>
    ''' The path of the original form post request.
    ''' </summary>
    Public ReadOnly Property Path() As String
        Get
            Return _path
        End Get
    End Property

    ''' <summary>
    ''' The saved form.
    ''' </summary>
    Public ReadOnly Property Form() As NameValueCollection
        Get
            Return _form
        End Get
    End Property

End Class

    ''' <summary>
    ''' This http handler will render a small page that will reconstruct the form as it was before session timeout, using hidden fields.
    ''' The page will submit itself upon loading, meaning that the user will barely see it.
    ''' </summary>
    Public Class FormStateRestoreHandler
        Implements IHttpHandler, IRequiresSessionState

        Private _state As FormState
        Private _dealGuid As Guid

        ''' <summary>
        ''' The form state.
        ''' </summary>
        Protected ReadOnly Property FormState() As FormState
            Get
                Return _state
            End Get
        End Property

        ''' <summary>
        ''' Gets a value indicating whether another request can use this IHttpHandler instance.
        ''' </summary>
        Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
            Get
                Return False
            End Get
        End Property


        ''' <summary>
        ''' Processes the web request - this is where the page is rendered.
        ''' </summary>
        ''' <param name="context"></param>
        Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
            Dim Id = context.Request.QueryString("id")

            If Id Is Nothing Then Return

            _state = LoadFormState(Id)

            Using writer As HtmlTextWriter = CreateHtmlTextWriter(context.Response.Output, context.Request.Browser)
            Me.Render(writer)
        End Using

   End Sub

   ''' <summary>
   ''' Loads the specified FormState by id
   ''' </summary>
   ''' <param name="id">The unique id of the saved form state.</param>
   ''' <returns></returns>
   Private Shared Function LoadFormState(ByVal id As Guid) As FormState
       Dim _storageProvider = ConfigurationFacade.GetUnityContainer.Resolve(Of IDealProvider)()
       Dim result As FormState = Nothing
       Dim bytes = _storageProvider.LoadUserState(id)

       Dim soapFormatter = New SoapFormatter
       Using ms = New IO.MemoryStream(bytes)
           result = soapFormatter.Deserialize(ms)
           ms.Close()
       End Using


       Return result
   End Function



   ''' <summary>
   ''' Renders a small page that will resubmit the saved form
   ''' </summary>
   ''' <param name="writer"></param>
   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.AddAttribute("action", Me.FormState.Path)

       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

   ''' <summary>
   ''' Renders a hidden field.
   ''' </summary>
   ''' <param name="writer">The writer to use.</param>
   ''' <param name="name">The name of the hidden field.</param>
   ''' <param name="value">The value of the hidden field.</param>
   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

   ''' <summary>
   ''' Renders a submit button.
   ''' </summary>
   ''' <param name="writer">The writer to use.</param>
   ''' <param name="text">The text of the button.</param>
   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

   ''' <summary>
   ''' Gets a HtmlTextWriter to write output to, based on a TextWriter.
   ''' </summary>
   ''' <param name="writer">The Text writer holding the output stream.</param>
   ''' <param name="browser">The browser capabilities of the client browser.</param>
   ''' <returns></returns>
   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
almog.ori