Getting data in and out of complex Archetype data types

If you haven't already discovered Archetype then you should give yourself an early Christmas present and install it in your current Umbraco project - it's awesome!

What does it do? Well it allows you to build your own custom data types that contain other data types. You can even get really ambitious (like I did) and create Archetype data types that contain other Archetype data types! It's like the movie Inception is actually happening right in front of your eyes, except it's in code version and nobody (hopefully) dies.

The drawback of creating your own Hollywood blockbuster style data types is that once you've created your data type and added your data to it, you then have to somehow get the data out and onto your web page! And if you're even more ambitious (like I was) you will have to update that data and save it back to your Archetype data type!

"Surely something that complex isn't possible!" you scream in horror. Well relax - I've done it and I'm still here to tell the tale. Also I'm going to show you in this very article how you too can create and use these complex Archetype data types. This is my Christmas present to you. Something to wow the relatives with over your Christmas turkey, or impress the guests at your New Year's Eve soiree!

First a bit of background - the project I've been working on recently uses a number of opinion polls. These opinion polls typically have a question and two or three possible answers. Here's what one of those opinion polls could possibly look like:

Sample Opinion Poll

The number of answers for any opinion poll can vary and I didn't want to hard-code these as properties, so I needed a data type where you could dynamically add 3, 4 or 10 answers to an opinion poll without any hassle.

Also the votes for the opinion polls needed to be easily viewable by the client, which meant they had to be accessible through Umbraco. I could have done this by storing the answers to a database, building a custom Admin section where you could see each opinion poll and the associated answers, and adding that into the Umbraco back end, but that sounded like a lot of work and a somewhat over-engineered solution. So I decided to store the opinion poll results in the actual opinion poll questions themselves, and that's why I chose to use Archetype.

First I created the Archetype data type for my Answer, that I called "Opinion Poll Answer" (clever eh?). This consists of an Textstring property called "Answer" which contains a possible answer for the poll, and a Numeric property called "Votes" which is used to record how many users have chosen that specific answer:

Answer Archetype

I enabled "Multiple Fieldsets" on this data type (in the data type editor if you toggle the Advanced Options you will see the option there) which enables the editor creating a new opinion poll in Umbraco to add as many answers as they like.

Next I created the Archetype data type for my Question, which contained a Textstring property called "Question" to hold the poll question text, and the Opinion Poll Answer data type that I just created:

Question Archetype

So now I had my data type set up and added as a property into my Opinion Poll document type, I could create exciting looking polls like this one:

Opinion Poll Content Item

As you can see, the votes for each answer are stored within the actual answers, and there's a "Total Votes" property that I used to work out voting percentages.

Now came the fun part - I had to get the data out of my awesome data type and on to the page! Are you ready for some code?

I do this all in a helper class rather than clogging up my Surface Controller, so first off I retrieve the poll content item:

var poll = new umbraco.NodeFactory.Node(pollId);

Then I deserialize the "answers" property of that content item into an ArchetypeModel object:

var answers = 
  JsonConvert.DeserializeObject<ArchetypeModel>(poll.GetProperty("answers").Value);

Finally I loop through the Fieldsets of that ArchetypeModel object and add the "Answer" and "Votes" values to my view model:

foreach (var answer in answers.Fieldsets.Where(x => x != null && x.Properties.Any()))
  {
	var thisAnswer = answer.GetValue("answer");
	var thisVotes = answer.GetValue<double>("votes");

	model.Answers.Add(new OpinionPollAnswer
	{
	  Answer = thisAnswer,
	  PercentageVotes = (Convert.ToInt32(totalVotes) != 0) 
	    ? (int)Math.Round((thisVotes / totalVotes) * 100) : 0
	});
  }

So that's not too tricky really, once you know what you need to do to get the data out. Getting the data back in is the tricky part, because first you need to get the data out (i.e. repeat what I just did there), then you need to update the data, and finally you need to put the data back in to your content item!

So once a user chooses an answer to my opinion poll and clicks 'submit', I get the poll content item that I want to update and put it into the "pollToUpdate" variable:

var pollToUpdate = _contentService.GetById(model.PollId);

Then I increment the total votes property by 1:

var totalVotesProperty = pollToUpdate.Properties.FirstOrDefault(x => x.Alias == "totalVotes");

if (totalVotesProperty != null)
{
  var totalVotes = (!string.IsNullOrEmpty(totalVotesProperty.Value.ToString())) 
    ? Convert.ToInt32(totalVotesProperty.Value) : 0;
  
  totalVotes++;

  pollToUpdate.SetValue("totalVotes", totalVotes);
}

Next comes the big finale - I deserialize the "Answer" property of the opinion poll content item into an ArchetypeModel object. I then loop through the ArchetypeModel object's Fieldsets and find the Answer that matches the one that has been submitted.

Then I retrieve the "Votes" property of that answer and increment it by 1 before assigning it back to the "Votes" property.

I then put the data back into the pollToUpdate object by serializing my ArchetypeModel object, and using the "SetValue" method of my content item:

var selectedAnswer = model.SelectedAnswer;

var pollProperties = pollToUpdate.Properties.FirstOrDefault(x => x.Alias == "answers");
  
if (pollProperties != null)
{
  var answerToUpdate = 
    JsonConvert.DeserializeObject<ArchetypeModel>(pollProperties.Value.ToString());

  foreach (var answer in answerToUpdate.Fieldsets)
  {
	if (answer.GetValue("answer") == selectedAnswer)
	{
	var answerVote = answer.GetValue<int>("votes");
	  
	answerVote++;

	var answerProperty = answer.Properties.FirstOrDefault(x => x.Alias == "votes");

	if (answerProperty != null)
	  answerProperty.Value = answerVote;
    }
  }

  pollToUpdate.SetValue("answers", JsonConvert.SerializeObject(answerToUpdate));
}

Finally I save my updated content item back to Umbraco using the ContentService's SaveAndPublishWithStatus method:

_contentService.SaveAndPublishWithStatus(pollToUpdate);

So there you go - that's how you get data in and out of complex Archetype Data Types! As with most code things, if you haven't done it before it can take you quite some time to figure out what to do. That's why I've shared this article - to hopefully help somebody somewhere to do the same thing at some point in the future and save themselves a load of hassle trying to figure it out! I hope you found reading this interestina and useful, or failing that I hope that reading it has kept you busy enough to avoid having to peel the sprouts on Christmas Day!

Maff Rigby

Maff is on Twitter as