Umbraco

Getting to know Umbraco Relations

I'm not talking about the relations who will be showing up at your place for Christmas in a few weeks time. I'm talking about the little known API that has been part of Umbraco since the early days but is seldom used.

Up until recently Umbraco Relations were not easy to work with. They are not very well documented and required some database hacking. As of version 4.9 this is no longer the case as relation types can be managed via the back office.

Umbraco Relations allow us to relate almost any object in Umbraco to another Umbraco object.  For example Member to Content, Content to Media. Many of you will say "whoop-de-doo I can already do that with a picker" (tree, member, media) to form a one way relationship between items. This is where Umbraco Relations are different.  They allow you to create two way relationships and query the parent for the children and the children for the parent. More on this later.

In this blog post I am going to create a way for logged in members to mark pages as favourites using relations.

I will be working with a site based on Umbraco 4.11.1 with the Standard Website MVC for Umbraco starter kit installed using SQL CE.  This provides us with some basic content and the membership login etc already implemented.

So lets get to know Umbraco Relations :o)

Relation Types

As with almost every part of Umbraco we begin with a type.  Relation Types define the types of relationships that we can create in our Umbraco installation. In version prior to 4.9 you were required to create a new database record in the umbracoRelationType table.  This record was made up of the following parts.

dual = Boolean value to specify whether this relationship could be queried two ways.
parentObjectType = GUID of the parent object type.
childObjectType = GUID of the child object type.
name = The friendly name for the relation type.
alias = String alias of the relation type.

You are probably going "whoa there.  GUIDS???!!!? you gotta be kidding!" and that is pretty much why many people discounted relation types as being too difficult to use.  For a start you needed to know what the GUIDs of the objects were.  Fortunately someone had the smart idea of creating a wiki article listing the Umbraco object GUIDs.

Fast forward to 4.9+ this is no longer the case.  You no longer are required to manipulate the database yourself but can create your Relation Types via the back office.

Steps to create a new Relation Type in 4.9+

  1. Navigate to the developer section in the back office.
  2. Expand the Relation Types folder, you should see the default Relation Type for "Relate Document On Copy".



    This relation type is used by the little checkbxo on the content copy dialog that says "Relate copied item to original". Now you know what that was all about :o)

  3. Right click on the Relation Types folder and click create.
  4. The following dialog will apear.



    For our example I am going to create a simple relationship between Member and Document to record what the member has favourited the page.

    So here are the settings we will use in the create dialog.

    Name = Member Favourite
    Alias = memberFavourite
    Direction = BiDirectional (this allows us to quiery the relationship either from the parent or the child)
    Parent = Member
    Child = Document

    Click Create

So now our relationship type is setup now what? Well, for our example we are going to write some code. This will allow us to show you the Relations API which is where the real magic happens.

Create a relationship via the API

In this task we are going to setup a link on the page that when a logged in member clicks it they will "Favourite" the page or create a relationshp of type Member Favourite between their Member object and the Document.

We are going to do a little AJAX via /Base and a little bit of JQuery so the action is performed asynchronously rather than on a postback.

So lets begin by creating a new class called MemberFavourites.

To begin with we will need to include a couple of name spaces to work with favourties and Members and Base.

using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.relation;
using Umbraco.Web.BaseRest;

Then in the body of our class we are going to create a new Method called SetFavourite which takes in a PageId and has a return type of String.

public static bool SetFavourite(int pageId)
{
}

In the body of the method we are going to first load the relationType that we created earlier.

var relType = RelationType.GetByAlias("memberFavourite");

This will retrieve our RelationType via the alias that we gave it in the earlier task.
We will also need our currently logged in Member.

var currentMember = Member.GetCurrentMember();

next we are going to create or delete the relationship between the objects depending on if they are already related. For this we are going to use the Relation.MakeNew method. This method takes in the following parameters, Parent Object Id, Child Object Id, Relation Type and a Comment.

HINT: The comment can be used to store things other than just a simple string. You could store small amounts of JSON or XML in this field also. But be warned that the default database setting for this field is nVarChar and can only hold a limited amount of information. You might want to consider changing its type to Text or increasing the number of chars the field can hold.

// try and load a relationship between these objects.
var rel = Relation.GetRelations(pageId, relType).FirstOrDefault(x => x.Child.Id == currentMember.Id);
// if a relationship doesnt exists we want to create one.
if (rel == null)
{
    rel = Relation.MakeNew(pageId, currentMember.Id, relType,
         "Page with ID: " + pageId + " favourited by member with ID: " +
         currentMember.Id);
}
else
{
    rel.Delete();
} 

And that is about it!  You now have a relationship between the page and the member. Well not quite  because we need to write it up so that it works with base and we also need to return something to the page.

For the return type we are going to return a simple Boolean value.  We are going to use another part of the API to return this value. IsReleated is a method that will allow us to check whether two objects are related.  We are also able to pass a relationship type to filter what type of relationship exists as the objects may be related in some other way.

return Relation.IsRelated(currentMember.Id, pageId, relType);

Finally we need to decorate or class and method to expose them via /base as rest extensions.  Our completed class should look something like this.

using System.Linq;
using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.relation;
using Umbraco.Web.BaseRest;
namespace RelationsDemo.BaseExtensions
{
    [RestExtension("favourites")]
    public class MemberFavourites
    {
        [RestExtensionMethod(AllowType = "client", ReturnXml = "false")]
        public bool SetFavourite(int pageId)
        {
            //load the relationship type
            var relType = RelationType.GetByAlias("memberFavourite");

            //get the currentmember
            var currentMember = Member.GetCurrentMember();

            //if the current member is null then they are no logged in and cant favourite
            if (currentMember == null)
            {
                return false;
            }
            // try and load a relationship between these objects.
            var rel = Relation.GetRelations(currentMember.Id, relType).FirstOrDefault(x => x.Child.Id == pageId);           
            // if a relationship doesnt exists we want to create it.
            if (rel == null)
            {
                rel = Relation.MakeNew(currentMember.Id, pageId, relType,
                                       "Page with ID: " + pageId + " favourited by member with ID: " +
                                       currentMember.Id);
            }
            else
            {
                rel.Delete();
            }
            //return the value for whether or not they are related.
            return Relation.IsRelated(currentMember.Id, pageId, relType);
        }
    }
}

Now on the page some where all we need is a simple link on the page.

<a href="#" class="favouriteLink">Favourite</a>

And some jQuery to wire it all up which changes the class on the link depending on whether or not the page is a favourite.

//DOM Ready
$(function() {
    //favourite a page
    //attach jQuery to all links with the class "favourite"
    $("a.favouriteLink").click(function(e) {
        //get the current link
        var link = $(this);
        //get the statusID from its rel attribute
        var pageId = link.attr("rel");

        //Call the SetFavourites url and update the link with the returned value
        $.post("/base/favourites/SetFavourite/" + pageId, function (data) {
            if (data == "True") {
                link.addClass("favourite");
            } else {
                link.removeClass("favourite");
            }
        });
        //stop the link from doing anything
        e.preventDefault();
    });
});

Going Further

In the download I have also included a check to see if a page is already a favourite and applies the class. You can also see how you could use these relationships to create a listing of favourite pages on the home page. Make sure you login first as you will not see any of the favourite features till you have.

Relations can be used for so many things and not just simple relationships.  As mentioned earlier you can record extended information in the comment.

This primer should give you enough to now go forth and relate!