Concurrency Checking Content Records

Topics: Customizing Orchard
Nov 21, 2011 at 8:26 AM

There doesn't appear to be any built-in concurrency checking of content changes.  I also can't find a module or setting to enable this.  Anyone know of anything that exists already?  I would be willing to implement this on my own and share it if it doesn't already exist.  Surprising if this really doesn't exist yet though since I would imagine it's a very common need.  

Nov 21, 2011 at 12:53 PM

I've created a module that solves this for me.  Anyone think it's worth putting in the public feed?

Nov 21, 2011 at 2:50 PM

Sounds interesting; what exactly does it do, how does it work?

Nov 21, 2011 at 9:23 PM

The desired behavior would be this...

  • User A and User B open the same content record for editing.
  • User A makes some changes and saves successfully.
  • User B makes some changes and tries to save, but instead of overwriting User A's data, User B receives a message that tells them there is a newer version of the record and they will need to refresh.

In my module, I made the conflict resolution rely on an event so I can modify or extend the behavior of the system in the event of a version conflict.  This could allow some more rich behavior for resolution like doing actual merges or something.  

The implementation was really simple thanks to Orchard being pretty flexible.  What I did was I created a Content Part that could be added to any content type which would effectively add the concurrency checking behavior.  I ended up with a driver that looked something like this....

public class ConcurrentPartDriver : ContentPartDriver<ConcurrentPart> {
        private readonly IEnumerable<IConcurrencyEvents> _concurrencyEvents;

        public ConcurrentPartDriver(IEnumerable<IConcurrencyEvents> concurrencyEvents) {
            _concurrencyEvents = concurrencyEvents;
        }

        //GET
        protected override DriverResult Editor(ConcurrentPart part, dynamic shapeHelper)
        {

            return ContentShape("Parts_Concurrent_Edit",
                () => shapeHelper.EditorTemplate(
                    TemplateName: "Parts/Concurrent",
                    Model: part,
                    Prefix: Prefix));
        }
        
        //POST
        protected override DriverResult Editor(ConcurrentPart part, IUpdateModel updater, dynamic shapeHelper) {
            var dbVersion = part.ContentVersion;
            updater.TryUpdateModel(part, Prefix, null, null);
            var postedVersion = part.ContentVersion;
            if (postedVersion != dbVersion) {
                var context = new ConflictEventContext();
                context.Content = part.ContentItem;
                context.DbVersion = dbVersion;
                context.PostedVersion = postedVersion;
                context.Updater = updater;
                foreach (var e in _concurrencyEvents) {
                    e.OnConflict(context);
                }
                }
            part.ContentVersion += 1;
            return Editor(part, shapeHelper);
        }

There is a hidden field in the editor that holds the ContentVersion property value.  There is a default event implementation that adds a model error on conflicts.  This is all I needed to accomplish my original goal, but could easily be built upon.  

When I first started I considered relying on NHibernate's version checking features by naming one of my record properties "Version" and having the AutoMapping conventions pick it up.  This half worked, but I don't like that way for a bunch of reasons.  

Anyway, what do you think?  Is this something other people will need?  I'm creating some content types that could easily get overwritten with the number of users who contribute to the same record. 

Nov 21, 2011 at 10:28 PM

Sounds very useful to me - it's not something that's come up yet, but I'm sure it'll happen sooner or later once we have multiple editors on our website.

In an ideal world it'd be great to have an ability display the two versions side-by-side so you could pick the correct changes. There might be some Content Manager limitations that prevent this right now (not being able to pass in a unique prefix) but I'll have a way around that soon with a custom version of the BuildEditor functionality.

Nov 22, 2011 at 8:04 AM

Ya that would definitely be the ideal way to resolve a conflict I think.  I wanted to go ahead and put this module on the gallery, but I'm not sure if I did it right.  I uploaded it and filled out the details.  It says I've registered the package, but I don't see anything in my contributions and it doesn't show up in the gallery for me.  I must be missing something.  

Nov 22, 2011 at 8:31 AM

Not having any luck publishing my module.  I'm using the direct upload method to the gallery.  I got a message saying I should see the module in about a minute or so, but it's not there.  

Nov 22, 2011 at 9:09 AM

Nevermind!  It's published just fine.  Looks like the gallery website just isn't updated as of right now.  It's in the feed though.

Nov 22, 2011 at 1:00 PM

Yes I had the same thing with a new package, it's a problem since the server move, I mentioned it to Bertrand so he's aware of it.

Jan 16, 2012 at 5:23 AM

BTW, here's the link to this module.  http://gallery.orchardproject.net/List/Modules/Orchard.Module.SoNerdy.ConcurrentContent