Leveraging ASP.NET MVC 2 futures “ViewState”

Let’s start this blog post with a confession: yes, I abused a feature in the ASP.NET MVC 2 futures assembly to fire up discussion. In my previous blog post, I called something “ViewState in MVC” while it is not really ViewState. To be honest, I did this on purpose, wanting to see people discuss this possibly new feature in MVC 2. Discussion started quite fast: most people do not like the word ViewState, especially when it is linked to ASP.NET MVC. As Phil Haack pointed out in a comment on my previous blog post, I used this foul word where it was not appropriate.

(…) I think calling it ViewState is very misleading. (…) what your serializing is the state of the Model, not the View. (…)

That’s the truth! But… how should we call this then? There is already something called ModelState, and this is something different. Troughout this blog post, I will refer to this as “Serialized Model State”, or “SMS” in short. Not an official abbreviation, just something to have a shared meaning with you as a reader.

So, SMS… Let’s use this in a practical example.

kick it on DotNetKicks.com

Example: Optimistic Concurrency

Concurrency between old train and updated train. Every developer who has worked on a business application will definitely have come to deal with optimistic concurrency. Data retrieved from the database has a unique identifier and a timestamp, used for optimistic concurrency control. When editing this data, the identifier and timestamp have to be associated with the client operation, requiring a persistence mechanism. This mechanism should make sure the identifier and timestamp are preserved to verify if another user has updated it since it was originally retrieved.

There are some options to do this: you can store this in TempData, in a Cookie or in Session state. A more obvious choice, however, would be a mechanism like “SMS”: it allows you to persist the model state on your view, allowing to retrieve the state of your model whenever data is posted to an action method. The fact that it is on your view, means that is linked to a specific request that will happen in the future.

Let’s work with a simple Person class, consisting of Id, Name, Email and RowState properties. RowState will contain a DateTime value when the database record was last updated. An action method fetching data is created:

[code:c#]

[HttpGet]
public ActionResult Edit(int id)
{
    // Simulate fetching Person from database
    Person initialPersonFromDatabase = new Person
    {
        Id = id,
        FirstName = "Maarten",
        LastName = "Balliauw",
        Email = "",
        RowVersion = DateTime.Now
    };
    return View(initialPersonFromDatabase);
}

[/code]

The view renders an edit form for our person:

[code:c#]

<h2>Concurrency demo</h2>

<% Html.EnableClientValidation(); %>
<%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>

<% using (Html.BeginForm()) {>
    <%=Html.Serialize("person", Model)%>

    <fieldset>
        <legend>Edit person</legend>
        <%=Html.EditorForModel()%>
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>

<% } %>

[/code]

Let’s have a look at this view markup. <%=Html.EditorForModel()%> renders an editor for our model class Person. based on templates. This is a new feature in ASP.NET MVC 2.

Another thing we do in our view is <%=Html.Serialize("person", Model)%>: this is a HtmlHelper extension persisting our model to a hidden form field:

[code:c#]

<input name="person" type="hidden" value="/wEymwIAAQAAAP
////8BAAAAAAAAAAwCAAAARE12YzJWaWV3U3RhdGUsIFZlcnNpb249MS4wL
jAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsBQEA
AAAbTXZjMlZpZXdTdGF0ZS5Nb2RlbHMuUGVyc29uBAAAABo8Rmlyc3ROYW1
lPmtfX0JhY2tpbmdGaWVsZBk8TGFzdE5hbWU+a19fQmFja2luZ0ZpZWxkFj
xFbWFpbD5rX19CYWNraW5nRmllbGQbPFJvd1ZlcnNpb24+a19fQmFja2luZ
0ZpZWxkAQEBAA0CAAAABgMAAAAHTWFhcnRlbgYEAAAACEJhbGxpYXV3BgUA
AAAAqCw1nBkWzIgL" />

[/code]

Yes, this looks ugly and smells like ViewState, but it’s not. Let’s submit our form to the next action method:

[code:c#]

[HttpPost]
public ActionResult Edit([Deserialize]Person person, FormCollection form)
{
    // Update model
    if (!TryUpdateModel(person, form.ToValueProvider()))
        return View(person);

    // Simulate fetching person from database
    Person currentPersonFromDatabase = new Person
    {
        Id = person.Id,
        FirstName = "Maarten",
        LastName = "Balliauw",
        Email = "[email protected]",
        RowVersion = DateTime.Now
    };

    // Compare version with version from model state
    if (currentPersonFromDatabase.RowVersion > person.RowVersion)
    {
        // Concurrency issues!
        ModelState.AddModelError("Person", "Concurrency error: person was changed in database.");

        return View(person);
    }
    else
    {
        // Validation also succeeded
        return RedirectToAction("Success");
    }
}

[/code]

Let’s see what happens here…The previous model state is deserialized from the hidden field we created in our view, and passed into the parameter person of this action method. Edited form values are in the FormCollection parameter. In the action method body, the deserialized model is updated first with the values from the FormCollection parameter. Next, the current database row is retrieved, having a newer RowVersion timestamp. This indicates that the record has been modified in the database and that we have a concurrency issue, rendering a validation message.

Conclusion

“ViewState” (or “SMS” or whatever it will be called) is really a useful addition to ASP.NET MVC 2, and I hope this blog post showed you one example usage scenario where it is handy. Next to that, you are not required to use this concept: it’s completely optional. So if you still do not like it, then do not use it. Go with Session state, Cookies, hidden fields, …

kick it on DotNetKicks.com

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

12 responses

  1. Avatar for mogadanez
    mogadanez October 8th, 2009

    why need serialize entire Person, if really we need only Id and Timestamp for optimistic locking?

  2. Avatar for maartenba
    maartenba October 9th, 2009

    I was expecting this question :-) Sure, you can easily use hidden fields to store id and/or timestamp, feel free to do so. You can also use a "PersonConcurrency" class containing only ID and Timestamp properties. The advantage of this one is that no once can tamper with data once you also encrypt and sign the "SMS". You can do this with the third parameter to Html.Serialize().

  3. Avatar for mogadanez
    mogadanez October 9th, 2009

    Encryption has additional setup to provide persistent MAC. elsewhere sometimes you get an error like with ViewState -> "ViewState is not valid."

  4. Avatar for Haacked
    Haacked October 9th, 2009

    Also, you might be working with an object that doesn't have a timestamp property. For example, if it's a true POCO object. In that case, you would need to serialize the whole object.

  5. Avatar for Jaco Pretorius
    Jaco Pretorius October 9th, 2009

    I suppose it's a nice feature but I can't really see myself using it - I prefer fine-grained control over the generated html. Maybe I'll change my mind at a later stage...

    I just wanted to point out that your implementation of RowVersion seems a little fishy. The concurrency field should always be implemented as a Timestamp and NOT as a DateTime. Timestamp != DateTime. I guess you were doing this for demonstration purposes, but it might be worth mentioning.

    Nice article.

  6. Avatar for maartenba
    maartenba October 9th, 2009

    Agreed on the timestamp thingy.

    There still are other situations where persisting model state on the client is a better choice than pushing stuff in session.

  7. Avatar for mogadanez
    mogadanez October 9th, 2009

    If _Model_ object does not have a timestamp, that is not mean that it is absent in DB, so I can Fetch it from DB by Model identity.
    if no timestamp in DB, I probably want to have a Checksum instead of Entire object.

    I Think "SMS" More approaches for wizards, when i not want store data while i reach end of wizard.

  8. Avatar for Neil Kerkin
    Neil Kerkin October 15th, 2009

    Just wondering what benefits this approach has over the alternatives: "TempData, in a Cookie or in Session state"?

  9. Avatar for Maarten
    Maarten October 15th, 2009

    None, except that there is no dependency on session state. This is also good when having multiple browser windows sharing one session.

  10. Avatar for Boriscallens
    Boriscallens October 15th, 2009

    I'm having a hard time seeing the merit here and I tend to agree with mogadanez.
    If it's for concurrency, there is always "something" (dtstamp, id, stateNr even hash) to compare it with. That something should normally be more interesting to save then a whole object.

    For times where you want to have an object's life span multiple requests without saving it to your repository (like in a wizard) I can imagine this is a nice way to do it without having to have some kind of session.

  11. Avatar for Igor Loginov
    Igor Loginov October 21st, 2009

    And I'd rather vote for such serialization. The problem with ViewState in WebForms was not in keeping serialized objects in a hidden field. The problem, that there ViewState is automatic and therefore not manageable. Switching it off actually breaks the event-based server code. So, if MVC team offers "Managable ViewState" (I mean, of developer's choice where and how to use it), it becomes just a helper for a widely used technique for storing some data in a hidden field.

  12. Avatar for Wout
    Wout September 19th, 2010

    Hi Maarten,

    The serialization is very cool, I use it for almost the exact same purpose. Thank you for your post! (Can't believe it's not in the regular ASP.NET MVC!) However, your concurrent update check ultimately has to be done by the database itself, because after your check there is still room for another update to happen, just before you save. What I do is do an UPDATE in the db, with all the old fields in the WHERE clause. If nothing was updated, then somebody else updated first. So the old fields you can get from the deserialized model. The updated model you could create from the deserialized model, and then updating the clone (so not updating the original!).

    About the designs using time stamp/row version, that's just an alternative way of doing it (the choice depends on your priorities). I tend to just compare the whole record and not introduce an extra column. Updates are usually infrequent, and queries are frequent, so not needing an extra column makes queries a little bit faster.

    - Wout