An Investigation into the Umbraco Membership APIs

Since the release of v6.2.0, the membership APIs available within Umbraco have been given a long overdue spring clean, with the addition of several fancy new ones. The reasons for this are to generally make them more consistent with the latest frontend and backend APIs (UmbracoHelper and Services, respectively), as well as the previous membership APIs not being the most performant or easy to use.

Unfortunately, whilst the new APIs are greatly improved to what came before, there are still a myriad of different options, each with advantages, weird corner cases and inconsistencies that may perplex you as a developer. Should I use the MemberService to create a member, or MembershipHelper? Where do the standard ASP.NET Membership APIs sit within all of this? How do I just get my darn CRUD' on?

Different to Lee's awesome 24 Days in Umbraco article regarding membership last year, I hope to compare and contrast the different approaches so you can find the one that fits best for you. Membership has long been a dark, unexplored corner to me within Umbraco, so it's a good opportunity to learn and get stuck into the Umbraco source code as well!

Please note that at the time of writing, I'm using the latest and greatest version of Umbraco, v7.3.2.

Creating a Member

Let's start by running through some of the different ways you can create members.

MembershipHelper

Certainly the most expedient way of creating a new member is to use the MembershipHelper. It's available out of the box on the frontend as the Members property, as long as your view is derived from UmbracoViewPage<T> somewhere in its inheritance chain. MembershipHelper also offers a few public constructors if you need to use it independently of a view.

To start, we need to encapsulate the new member's details. To this end, we use the Umbraco.Web.Models.RegisterModel type. Let's see some code!

using Umbraco.Web.Models;

RegisterModel newMember = this.Members.CreateRegistrationModel("MyMemberTypeAlias");

Constructing an instance of RegisterModel

There are other ways of instantiating the type, as demonstrated below:

// Nay! Assumes that a default member type for a baked in alias exists!
newMember = this.Members.CreateRegistrationModel();

// Yay for mutability! >_>

/* Nay! Attempts to manually construct an instance of MembershipHelper based off the 
 * current context and also assumes that a default member type for a baked in alias exists! */
newMember = new RegisterModel(); 

/* Nay! Creates a completely vanilla instance of the model and also assumes that a default 
 * member type for a baked in alias exists! */
newMember = RegisterModel.CreateModel();

The different ways of constructing an instance of RegisterModel

I'd always recommend using the overload of MembershipHelper.CreateRegistrationModel which expects an explicit member type alias to construct a new instance of RegisterModel.

The other methods assume that the member type alias to be set on the RegisterModel is to be "Member", which is baked into the source of Umbraco itself and the alias of the default member type shipped with a vanilla instance of Umbraco.

The default member type alias set on the underlying UmbracoMembershipProvider, configured via the site web.config does not affect the aforementioned default member type alias. I do believe this could be a remnant of the older membership API. 

If a member type with this default alias isn't present, constructing a new instance of RegisterModel via the parameterless instance constructor or parameterless overload of MembershipHelper.CreateRegistrationModel will lead to an invalid operation exception being thrown. Blork!

Using the parameterless instance constructor should just be generally avoided altogether. It is marked with the Obsolete attribute and calling it leads to an attempt to manually construct MembershipHelper from the current context, which is not a guaranteed operation and may leave the newly created instance in a invalid state. It performs the same business logic lookups as both CreateRegistrationModel overloads on MembershipHelper (to be exact, setting a collection of member properties based on the member type alias to be used with the instance of RegisterModel), but in a different order as it has to construct MembershipHelper first. Just, uh, avoid it.

The static CreateModel method on the RegisterModel type itself should only be used to create a default, vanilla instance with none of the aforementioned business logic lookups. I fail to see how this will be useful in most cases, so again, avoid!

You can use RegisterModel directly as a form model in a view, though I'd recommend having a less specific, generalised model. This will increase the level of indirection and encourage you to keep logic mapping complex member properties to both the view and Umbraco itself separate.

To register a member based off a created RegisterModel, we need to call MembershipHelper.RegisterMember. There a few things we need to be aware of firstly, however.

The NameEmail and Password properties of the returned instance of RegisterModel must be set in order for the instance to be used with the MembershipHelper.RegisterMember method successfully, otherwise you'll run into non-descript null reference exceptions whilst trying to register the member.

newMember.Name = "Mike Bowen";
newMember.Email = "milquetoastable@gmail.com";

newMember.Password = "24DaysInUmbraco"; 

newMember.UsernameIsEmail = false;  
newMember.Username = "mike";

newMember.LoginOnSuccess = false;

Setting the required properties on an instance of RegisterModel

The UsernameIsEmail property needs to be set to false on an instance of RegisterModel to be able set a custom username on the Username property. Just setting the property isn't enough. If you try to be funny and set UsernameIsEmail to false and not set a username, the supplied email address will be used. So don't try it. Sneaky.

Setting custom properties that are defined on your member type, as configured within Umbraco is relatively simple, if rather inelegant. Please note that a property must be marked as "Member can edit" within the Umbraco interface to be accessible within the collection of UmbracoProperty.

// The "favouriteLanguage" property has been marked as "Member can edit" within Umbraco.
UmbracoProperty favouriteLanguage = newMember.MemberProperties.Single(p => p.Alias == "favouriteLanguage");
favouriteLanguage.Value = "Crystal";

Setting custom properties on an instance of RegisterModel

I'd personally recommended creating an extension method on the RegisterModel type that tidys up this slightly cumbersome bit of code.

public static void SetPropertyValue(this RegisterModel model, string alias, object value)
{
    model.MemberProperties.Single(p => p.Alias == alias).Value = value.ToString();
}

newMember.SetValue("favouriteLanguage", "F#");

Extension methods on RegisterModel

The above provides a similar interface to other Umbraco APIs we all know and love for dealing with setting and getting property values.

MembershipHelper.RegisterMember uses the underlying UmbracoMembershipProvider. The provided email address, username and password contained on the supplied instance of RegisterModel are subject to validation which can be configured through the site web.config as you would a standard ASP.NET membership provider. The duo of RegisterModel and MembershipHelper.RegisterMember doesn't quite fully encapsulate everything the UmbracoMembershipProvider offers, at least directly, however.

Now, for the life of me, I just can't figure out how to set some standard membership properties such as the password retrieval question and answer using this approach. These are not exposed on an instance of RegisterModel directly, even via its collection of member properties.

MembershipUser does expose one such method, ChangePasswordQuestionAndAnswer, but this can not be called unless the requiresQuestionAndAnswer attribute has been set to true on the UmbracoMembershipProvider configuration stored within the web.config. However, we can then not create a new member using MembershipHelper.RegisterMember as a password retrieval question and answer are required to be set before the new member is registered. A chicken and egg situation - answers on a postcard please!

The Umbraco abstractions end after calling MembershipHelper.RegisterMember, where upon you're left with instances of MembershipCreateStatus and MembershipUser, both belonging to the System.Web.Security namespace. MembershipCreateStatus should be used to verify the success of the registration. I feel it's somewhat unfortunate that after dealing with these rather lovely Umbraco abstractions, we are thrown back to murky waters of the underlying .NET types. It's not a massive issue, but slightly jarring nonetheless.

using System.Web.Security

MembershipCreateStatus status = new MembershipCreateStatus();
MembershipUser member = this.Members.RegisterMember(newMember, out status);

Creating a new member using MembershipHelper

It's here where the frontend APIs throw back the curtain and reveal what they were all along; a thin layer over both the UmbracoMembershipProvider and the Umbraco Services layer.

Once you've registered a member, you may want to add them to an Umbraco member group, or a role in ASP.NET parlance. Neither RegisterModel or MembershipHelper exposes a way of doing this, so you'll have to resort to the ASP.NET membership APIs.

Roles.AddUserToRole(newMember.Username, "MyMemberGroup");

Adding member groups (roles) to a member

Members created using MembershipHelper are automatically approved. If you want to disapprove the member, after the member has been persisted you'll have to use the native ASP.NET membership APIs.

member.IsApproved = false;
Membership.UpdateUser(member);

Disapproving a member

MemberService

Despite the availability and relative ease of using the front end APIs to create a new member, there is fair amount of complexity that is just as easy to find yourself wading through. The front end APIs essentially wrap a good portion of the MemberService for actually creating and persisting a member, using the UmbracoMembershipProvider for validation and general data sanity.

The MemberService is a lower level API, but should be comfortable to those familiar to the other, similarly veined APIs available in the Services family.

The obvious issue is that MemberService hits the database agnostic of any validation supplied by the UmbracoMembershipProvider. This is a gain in that it allows very fine grained control of the registration process, but it's not exactly a win to duplicate pre-built functionality.

The service itself resides within the Umbraco.Core.Services namespace and is accessible via the current Umbraco ServiceContext, itself in the Umbraco.Core namespace as an interface (IMemberService). The concrete type is MemberService.

You can create a new instance of MemberService, but you'll almost always want to use the current Umbraco application context rather than do this. It's certainly a lot more involved than initialising a new instance of MembershipHelper.

Anyhoo, let's register a new member!

using Umbraco.Core.Models;
using Umbraco.Core.Services;

IMemberService service = ApplicationContext.Current.Services.MemberService;
    
IMember newMember = service.CreateMember("milquetoastable@gmail.com", "milquetoastable@gmail.com", "Mike Bowen", "MyMemberType");
newMember = service.CreateMemberWithIdentity("milquetoastable@gmail.com", "milquetoastable@gmail.com", "Mike Bowen", "MyMemberType");
newMember = service.CreateWithIdentity("milquetoastable@gmail.com", "milquetoastable@gmail.com", "Mike Bowen", "MyMemberType");

Using MemberService to create and / or persist a new member

The above methods called on the IMemberService expose a bunch of different overloads, allowing you to pass in either a MemberType object or alias for the type of the newly created member as well as an optional member name.

There is no functional difference between the IMemberService.CreateMemberWithIdentity and IMemberService.CreateWithIdentity methods; the former is implemented from Umbraco.Core.Services.IMembershipMemberService whilst the latter is implemented from Umbraco.Core.Services.IMembershipMemberService<IMember>

IMemberService.CreateMember creates a new member object (of type Umbraco.Core.Models.IMember, the concrete type being Umbraco.Core.Models.Member) without immediately registering the new member, allowing you to set additional properties before hitting the database.

Custom properties can be easily set using the IMember.SetValue method. Since the concrete instance of IMember inherits from Umbraco.Core.Models.ContentBase just like Umbraco.Core.Models.Content, you may be already familiar with this approach! Please note that every custom property is available, regardless of whether it has been marked as "Member can edit" within the Umbraco interface.

newMember.SetValue("favouriteLanguage", "Ruby");

Setting custom properties on an instance of IMember

Interesting aside; whilst the main overload of the SetValue method you'll be using accepts object as the type of the value you want to set on a member (or content), it binds to one of several SetPropertyValue overloads, defined on the concrete type of Umbraco.Core.Models.ContentModelBase dynamically. There are overloads of ContentModelBase.SetPropertyValue for string, int, long, bool and DateTime. Basically, you can't just pass anything despite the signature accepting object.

Properties related to the UmbracoMembershipProvider itself are exposed directly, and can be edited as such.

newMember.Comments = "Here's an member";
newMember.IsApproved = false;
newMember.PasswordQuestion = "Awesome?";
newMember.RawPasswordAnswerValue = "AWESOME";

Setting properties on IMember

The member can then be persisted by passing the IMember object to IMemberService.Save.

Slightly jarringly, we have to set member's password after it has been persisted. If the member does not exist, say as a result by creating the member by calling IMemberService.CreateMember first, a obscure SQL exception is thrown. The below call entails a separate database hit, unfortunately.

service.Save(newMember);
service.SavePassword(newMember, "24DaysInUmbraco");

Calling SavePassword on the MemberService

Roles or Umbraco member groups can be applied to a member using the MemberService like so:

service.AssignRole(newMember.Id, "MyMemberGroup");

// or multiple role assignment.
    
service.AssignRoles(new [] { newMember.Id }, new [] { "MyMemberGroup", "MyOtherMemberGroup" });

Adding member groups (roles) to a member

Which to use?

In my first draft of this article I actually choose MemberService. Perhaps controversial, but I feel the API as whole is more consistent, familiar and requires less uncomfortable hoop jumping than the front end APIs.

Just instantiating and configuring RegisterModel correctly is an awkward process hiding a few alarming edge cases. Far too much is dependant on the state of the model and what properties may or may not be set when using it in conjunction with the MembershipHelper.RegisterMember method.

Using the MemberService also just feels more natural. Creating members should logically be a "backend" operation, rather than something exposed on an frontend API.

As awkward as it is, the combination of MembershipHelper and RegisterModel does provide validation for the new member using the UmbracoMembershipProvider. Replicating this functionality yourself is a pretty large ask, you may as well be making your own membership provider.

MemberService does expose almost too much flexibility; there are plenty of properties on IMember that, whilst they mirror those available on MembershipUser, aren't immediately relevant to registering a member. It's very much an administration API.

The situation isn't ideal. I'd definitely like to see an intermediate backend service that utilises the UmbracoMembershipProvider but is semantically similar to the MemberService, is as simple as MembershipUser and that has most of the flexibility and power of MemberService. A RegisterService, if you will.

Regardless of what API you use, whether you mix and match or even disregard both and simply use System.Web.Security.Membership, I'd mostly just recommended wrapping the logic up as a separate module, whether that's a class or class library that's up to you!

Displaying a Member's Profile

Now that we've created a member, it's best we find an appropriate API to retrieve that member and their information.

MembershipHelper

Our old friend MembershipHelper exposes a multitude of different ways of dealing with members on the frontend of an Umbraco website. There are operations for not only registering, logging in and out, but updating and obtaining the currently logged in member. Compared to using MemberService for frontend operations involving members, MembershipHelper feels much nicer, succinct and logical. As previously stated, it's available out of the box on view pages inheriting Umbraco.Web.Mvc.UmbracoViewPage<T>, abstracts parts of the underlying
membership provider and genuinely feels like a true, usable counterpart to the UmbracoHelper.

There are a couple of caveats, but there's really no convincing reason not to use it in favor of another API. It's not complete picture however, and I'd certainly recommended using it in conjunction with custom functionality that helps smooth the rougher edges.

I must stress that as MembershipHelper is very much a frontend API; that is it concerns itself most with dealing with a single, logged in member. If you want to perform batch operations on members, you'll have to use the MemberService.

if (this.Members.IsLoggedIn() && this.Members.IsMemberAuthorized(allowTypes: new [] { "MyMemberType" }))
{
    string currentUserName = this.Members.CurrentUserName;
    int currentMemberId = this.Members.GetCurrentMemberId();
    
    IPublishedContent member = this.Members.GetCurrentMember();
    ProfileModel profile = this.Members.GetCurrentMemberProfileModel();
}

Simple operations defined on MembershipHelper to access the currently logged in member

The above methods are relatively self explanatory. Of note is MembershipHelper.IsMemberAuthorized, which provides a variety of different ways via optional parameters to confirm that the currently logged in member is allowed to view the current content. The above example uses member types to authorise the member; how this works with regards to Umbraco protected pages I'm not sure - you can only protect a page by member group or a specific member.

The main point of interest is how we request a more complex object representing the currently logged in member. MembershipHelper exposes two different methods that deliver such functionality. MembershipHelper.GetCurrentMemberProfileMode maps the currently logged in member to an instance of Umbraco.Web.Models.ProfileModel, which we will disregard for the time being.

MembershipHelper.GetCurrentMember returns the currently logged in member as instance of Umbraco.Core.Models.IPublishedContent.

You'll most likely be familiar with IPublishedContent. It is the interface that represents a chunk content within Umbraco on the frontend. That the concrete type that represents a logged member implements the same interface allows us to treat it exactly the same way. Lovely!

Except in situations where a member is not like content. That is where the abstraction falls down.

string email = member.GetPropertyValue<string>("uhhh, what's the alias for the member's email address?");
        
// Oddly, the below don't throw an exception.
var firstChild = member.FirstChild();
var descendants = member.Descendants();

// Throws not supported exception!
string templateAlias = member.GetTemplateAlias();

IPublishedContent properties

Odd. Certain properties and methods throw not supported exceptions, whilst others fail silently. We could argue that if a type is throwing such exceptions  or failing silently within a interface method, then it is only implementing the interface to please the compiler and the type shouldn't implement said interface. Regardless of the approach taken, just general consistency would be nice.

The main stumbling block you may come across will be how to acquire standard membership properties such as the member's email address or username. IPublishedContent.GetPropertyValue<T> won't help here. Instead we have to cast the instance of IPublishedContent to its underlying concrete type, Umbraco.Web.PublishedCache.MemberPublishedContent.

using Umbraco.Web.PublishedCache;

MemberPublishedContent actualMember = member as MemberPublishedContent;
        
string email = actualMember.Email;
string username = actualMember.UserName;

MemberPublishedContent

The other approaches...

There are two other Umbraco APIs that can be used to display a member's details. Either MembershipHelper.GetCurrentMember (covered above) or MembershipHelper.GetCurrentMemberProfileMode should be used if you're trying to access details pertaining to a currently logged in member, as there's nothing on MemberService that will help with this.

There are methods to retrieve a member based on id, username or provider key, but you'll have to call one of the appropriate methods on MembershipHelper to use these in a meaningful way. Conversely MembershipHelper exposes nothing to help with a bunch of members, so if you're trying to build a members directory or something similar, you'll have to use MemberServiceIMember plenty of ways to obtain information and properties on a member.

Where you can, and unless you have very specific needs, stick with MembershipHelper and MembershipHelper.GetCurrentMember. It really is a wonderful API for dealing with plenty of common tasks related to members, not just displaying information pertaining to them.

We haven't covered MembershipHelper.GetCurrentMemberProfileMode, mainly because its main purpose isn't purely for displaying details about a member. Rather it is updating.

Updating a Member's Profile

We've come to the final part of our investigation. How do we update a member's profile in a meaningful way?

You can either use the MemberService directly as demonstrated above, or use MembershipHelper.GetCurrentMemberProfileMode. Calling this method returns the currently logged in member mapped to an instance of Umbraco.Web.Models.ProfileModel.

ProfileModel looks and feels very similar to RegisterModel. In fact, it can be constructed in very much the same as RegisterModel; via a static method call on the type itself, a parameterless instance constructor and with the aforementioned method on MembershipHelper. The same edge cases and oddities that occur with constructing RegisterModel make an re-appearance with ProfileModel.

Regardless, it's the best way of updating a member's details. Again, as a part of the MembershipHelper suite of methods, it's consistent and holds less potential pitfalls than using MembershipHelper for registering a member. Since MembershipHelper is attuned to dealing with the currently logged in member, you're stuck with MemberService if you want to batch update members.

Updating custom properties is awkward, just as it is on RegisterModel, but can easily be cleaned up with a extension method or two. 

Once a ProfileModel has been updated with the member's new details, it needs to be passed to Members.UpdateMemberProfile to be persisted.

Once again, I wouldn't recommend using a ProfileModel directly as a form model. Use a generalised model that can be mapped to a ProfileModel for update operations. That way you can also make it interchangeable with the MemberService if needs be.

Conclusion

This concludes our whirlwind tour of the Umbraco membership APIs for now. What have we learnt? Well, Membership is hard. But apart from that, if you know your way around the APIs Umbraco offers, it's actually a much easier, streamlined process. Registration is only the spot that causes headaches and where the APIs could be clearer. I think the old Python adage that there should only be one simple way of doing things rings especially true here.

  • When registering a member, use either the MemberService and (ouch!) write your own validation or use MembershipHelper and beware of its issues.
  • To display details about the currently logged in member, use MembershipHelper.
  • To perform batch operations or get information about multiple members, use MemberService.
  • To update a member, use MembershipHelper. It provides validation and is more consistent with the rest of the frontend APIs.
  • Try to avoid using either RegisterModel or ProfileModel directly as form models. You'll have more control and flexibility if use an intermediate model.

Hopefully I will write a future article that will go into further details about the other membership services that Umbraco provides, as well as custom membership providers.

Please leave a comment if you have any feedback, queries, corrections, insights or any of the alike or you can tweet me at @milquetoastable.

Here is a link to a gist with some of the above code examples.

Thanks to Blake Clerke-Smith for allowing me to provide an article! 

Godspeed!

Mike Bowen

Mike is on Twitter as