Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

User Profile

Wednesday, 8 December, 2010 @ 10:29 PM < Adam Boddington
Tags: Architecture, ASP.NET MVC, Building Neno, Concurrency Control, NHibernate

This is post #15 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.

The ying to register's yang, I need to let users modify their registration details through a profile screen.

Action

The update story is very similar to the create story (see the register actions). Normally the only differences are:

  • The object is pulled from the database rather than created from scratch.
  • The version of the object is set (which doesn't matter when creating).

An additional difference here is there is no return URL, so I'm redirecting back to the profile action to let the user make another change if they need to. It's important to redirect after a successful postback action so that a browser refresh doesn't resubmit. It doesn't matter where the redirect goes, the important part is that a redirect happens.

[Registered]
public ActionResult Profile()
{
    User user = Container.Get<IUserService>().GetCurrentUser();

    return View(user);
}

[HttpPost]
[Registered]
public ActionResult Profile(FormCollection collection)
{
    User user = Container.Get<IUserService>().GetCurrentUser();

    try
    {
        TryUpdateModel(user, new[] { "Version", "DisplayName", "Email", "Website" });

        if (!ModelState.IsValid)
            return View(user);

        Container.Get<IUserService>().UpdateUser(user);
        Messages.Add("Profile updated successfully.");

        return RedirectToAction("profile");
    }
    catch (Exception ex)
    {
        Messages.Add(ex);

        return View(user);
    }
}

Notice the object is re-retrieved from the database in the postback action. It's not serialised or stored anywhere by the get action, apart from displaying some of its details on the screen. This is generally good practice -- serialisation to the UI might be a security risk, and session storage might expire. Re-retrieving the object is more in line with the disconnected stateless reality of web applications.

To compensate, the version of the object has to go into a hidden input in the form to keep optimistic concurrency alive. If the version isn't included in the form and isn't set in the postback action, optimistic concurrency is dead and changes can be overwritten without users seeing them. This is the real weakness of this approach -- remembering to include and set the version on every update and delete form.

Cut!

There's a bug here. Suppose I put a bad value into my profile screen, like a really long display name.

Invalid 1

See the bad display name in the top right hand corner of the screen? That's because I'm working on the same user object the rest of the screen is using -- which probably isn't a good idea. I don't want to affect the current user object until the changes have been validated and saved to the database. What I really need here is some way to work on a copy of the current user object.

In NHibernate there is the concept of eviction from the session. Normally each object hangs on to its session for lazy loading, and the session hangs on to each object for caching. Eviction severs the relationship between the two completely and is exactly what I need here. That will give me an instance of the current user object that no other part of the application will know about it. When they ask for the current user object, NHibernate will see it doesn't have one in cache and will instantiate a new one. Wait, will they have the copy or will I? Neither, we'll both just have separate instances of the same object.

This is only an issue because I'm hooked into my domain model directly. If I was using a view model, this wouldn't be a problem.

Hooking directly into NHibernate in my controller to evict something would be bad form. This needs to belong somewhere generic instead -- like IContext.

namespace StackingCode.Moja.Repositories
{
    public interface IContext
    {
        ITransaction BeginTransaction();
        ITransaction BeginTransaction(IsolationLevel isolationLevel);
        void Evict(object entity);
    }
}

You could argue hooking into IContext in my controller is also bad form, and you'd be right. But it's less so than hooking into NHibernate -- and my imagination is failing me, so it will do for now.

The NHibernate implementation of IContext looks just like this.

namespace StackingCode.Moja.Repositories.NHibernate
{
    public class Context : IContext
    {
        protected ISession Session { get; set; }

        #region IContext Members

        // ...

        public void Evict(object entity)
        {
            Session.Evict(entity);
        }

        #endregion
    }
}

Take Two, Action

Now I can modify my controller to work on a separate instance of the current user object when it needs to.

[HttpPost]
[Registered]
public ActionResult Profile(FormCollection collection)
{
    User user = Container.Get<IUserService>().GetCurrentUser();

    try
    {
        TryUpdateModel(user, new[] { "Version", "DisplayName", "Email", "Website" });

        if (!ModelState.IsValid)
        {
            // Isolate this instance.
            Container.Get<IContext>().Evict(user);

            return View(user);
        }

        Container.Get<IUserService>().UpdateUser(user);
        Messages.Add("Profile updated successfully.");

        return RedirectToAction("profile");
    }
    catch (Exception exception)
    {
        // Isolate this instance.
        Container.Get<IContext>().Evict(user);

        Messages.Add(exception);

        return View(user);
    }
}

Which lets me safely enter crappy data and not have it affect the rest of the screen.

Invalid 2

That's a Wrap

Yes it is. In summary...

  1. Optimistic concurrency requires some attention when working on a disconnected stateless application.
  2. Editing a domain object directly can be a special case when it's used elsewhere in the request/response.

Just a couple things to keep in mind.

EDIT: A later refactor with the use of view models eliminated the need for IContext.Evict.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
Speed Bumps

Newer
Optimistic Concurrency and NHibernate

Older
Speed Bumps

Newer
Optimistic Concurrency and NHibernate

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM