U4-5287 - Real Member's Name not available in events when a member is first created/persisted due to membership provider logic when they are created.

Created by Sebastiaan Janssen 28 Jul 2014, 17:53:07 Updated by Mads Krohn 23 Apr 2018, 16:34:59

Subtask of: U4-11015

See: http://our.umbraco.org/forum/developers/api-questions/55037-Get-actual-name-of-member

There's no way to get the Member name from the Created or Saving (on initial save) of a Member when they are created because we always need to use the membership provider for the initial creation - which automatically just uses the username. We then apply the real name after the initial save when we detect that an Umbraco membership provider is used.

We can make this process work better but it requires a breaking change to modify the abstract method in the UmbracoMembershipProviderBase.PerformCreateUser method.

Comments

Sebastiaan Janssen 28 Jul 2014, 17:53:46

Complication: people are already using the .Name property :(


Shannon Deminick 29 Jul 2014, 03:45:21

I have replied to the Our thread. Everything is working 'correctly' the problem is two things:

  • The event they are using shouldn't be used for what they are trying to do
  • The logic that executes behind the scenes for members with membership providers

I think I can make it work better without breaking stuff so will give it a go.


Shannon Deminick 29 Jul 2014, 17:53:31

Nope, I can't make it work without breaking compatibility in the class:

Umbraco.Core.Security.UmbracoMembershipProviderBase

as I'd need to modify the abstract method PerformCreateUser


Sebastiaan Janssen 30 Jul 2014, 08:01:09

No worries, there's a workaround, and in fact a better workaround so we'll fix it for v8.


Mads Krohn 22 Apr 2018, 23:18:34

Just ran into this issue. Tried the work around mentioned in the Our post:

var m = umbraco.cms.businesslogic.member.Member.GetMemberFromLoginName(e.Entity.Username); var name = m.Text;

But that also gives me the username. Did I miss another work around, or? Right now I have to spawn some kind of task that delays for a few seconds then get the member again, do work, then save, not exactly the prettiest :P


Mads Krohn 22 Apr 2018, 23:20:16

Would it perhaps be possible to introduce a new temporary RealName property on the Member class or perhaps on one of the IDirty interfaces?


Simon Dingley 23 Apr 2018, 10:22:45

Also battling this issue at the moment to send personalised welcome emails out to members.


Shannon Deminick 23 Apr 2018, 10:40:39

I think i mentioned in the Our thread that you can probably create a custom member property like 'emailSent', check if that property exists or is false on the Saved event (once it's persisted) and by this stage you will also have the name, then set the property to true so you don't resend an email every time they are saved.


Mads Krohn 23 Apr 2018, 10:49:37

Shannon, I'm not sure I follow. In the Saved event, when the member is created, the real name is not available. How would checking a custom property for a value help with that?


Shannon Deminick 23 Apr 2018, 10:55:24

In reference to the Our thread and what Simon just wrote with welcome emails. If you want to send a welcome email when the member is created (first saved) but the initial Saved event which is raised with the membership provider, the real member name is not available, it is their username. You can check if their name == username and assume that this is the initial Saved event (you could also make this more robust by setting a different flag during the Saving event like: 'initSave' / 'savedWithName'), the next one that triggers will probably contain the real name since that one is raised by the normal member saving process. In that case, you'd then send the email, save to a custom field that you've sent the email, so in future cases when the member is saved, you don't send the email.


Simon Dingley 23 Apr 2018, 10:56:19

@sniffdk I think in the Saved event the properties are then available, just not before that stage. I've just posted on the Our thread with my understanding of how the workaround should be implemented.


Mads Krohn 23 Apr 2018, 11:16:06

@ProNotion Do let me know if you get anything to work, can't just wrap my head around how to implement Shannons suggestions, since as soon at I call sender.Save(member, false) in the Saved event, the real name is overriden by the username and then lost.


Simon Dingley 23 Apr 2018, 11:53:42

I keep crashing the application pool at the moment because it seems that calling SavePassword on the MemberService (which I need to do in order to email the user their initial password), subsequently triggers the Saving event on the Member service again and therefore creates an infinite loop. So at the moment, I'm not really any further forward.


Shannon Deminick 23 Apr 2018, 12:04:58

Never perform any service operation in an "ing" event, like Saving. Those events are purely to modify the model before being persisted, or canceling.

I haven't tested any of this stuff myself, i'm only just describing the steps that i would take to achieve this.


Mads Krohn 23 Apr 2018, 12:40:43

@Shandem so, a new temporary RealName property on the Member class or something like that won't happen?


Simon Dingley 23 Apr 2018, 12:44:22

ok, thanks for the tip @Shandem but I still end up with the same scenario in the Saved event so seem to be going around in circles at the moment. In the MemberService.Saving event handler I am doing the following:

if (!member.HasIdentity)
{
    member.SetValue("SendWelcomeEmail", true);
}

And then in the MemberService.Saved EventHandler I am doing the following:

foreach (var member in e.SavedEntities) { // We can skip this iteration if the meber has not yet been persisted or they have already received the email if (!member.HasIdentity || !member.GetValue("SendWelcomeEmail")) continue;

    // Generate a new password that meets minimum requirements
    var password = Membership.GeneratePassword(
        Membership.MinRequiredPasswordLength,
        Membership.MinRequiredNonAlphanumericCharacters);

    // Send the member a welcome email with the initial password
    Members.SendWelcomeEmail(member, password);

    // Update property so we don't send the welcome email again!
    member.SetValue("SendWelcomeEmail", false);

    // Save the password. This should REALLY happen before we send the email but
    // because of the order of events currently it can't
    memberSvc.SavePassword(member, password);

    // Save the member with the updated property value
    memberSvc.Save(member, false);
}

The SendWelcomeEmail property value never seems to get updated and so once again creates an infinite loop.


Shannon Deminick 23 Apr 2018, 13:09:17

It's because SavePassword doesn't have an overload to not raise events (Side note that SavePassword is deprecated more or less since it won't work in most scenarios apart from creating members with an empty password https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Core/Services/IMemberService.cs#L127 ... but that still won't help in this situation) Also note that member.HasIdentity will always be true in Saved.

Are you doing this with an event because you want these emails sent in case admin's are manually creating members? Just wondering if this could 'just' be done in your normal app code when you have members sign up.

If you want to do all of this in an event you're going to have to do this in phases:

  • Have a custom property, let's call it 'MemberStatus'
  • In Saving **check !member.HasIdentity, this will mean that the member isn't created yet, do member.SetValue("MemberStatus", "initialCreate");. **I'm assuming that the 2nd member save is with their real name and not just their username, otherwise you'll have to do another phase 2 check: username == name then track another status member.SetValue("MemberStatus", "initialProviderSave"); **Else, set another state: member.SetValue("MemberStatus", "initialServiceSave"); which should mean you now have the real Name
  • In Saved **check member.GetValue<string>("MemberStatus") == "initialServiceSave", in this case you'll want to generate a new password and save it, so first, update the status member.SetValue("MemberStatus", "generatedPassword"); and save without raising events memberSvc.Save(member, false);, then generate & save the password and send the email. Since we can't tell it to not raise events, it will again end up in Saving/Saved handlers. **check member.GetValue<string>("MemberStatus") == "generatedPassword", if that is the case, it means the password has just been saved, so exit since we don't want to do anything ... and then the logic will return to where you used SavePassword and you can continue sending your email


Simon Dingley 23 Apr 2018, 13:16:12

Are you doing this with an event because you want these emails sent in case admin's are manually creating members?

Yes, exactly that. On this site, members have to pay for membership but some do it offline which is when the admins manually create the member. I will give your idea a shot and let you know how I get on.


Mads Krohn 23 Apr 2018, 13:40:35

Almost thought I had it working. It does indeed hit the Saving/Saved events 3 times. The third time, however, when I would expect status to be 'initProviderSave', the value of my 'memberStatus' property is just null. I have no idea why, but alas.

private static void MemberService_Saving(IMemberService sender, SaveEventArgs saveEventArgs) { foreach (var member in saveEventArgs.SavedEntities) { if (!member.HasIdentity) { member.SetValue("memberStatus", "initCreated"); } else { var status = member.GetValue("memberStatus"); if (status == "initCreated") { member.SetValue("memberStatus", "initProviderSave"); }
else if (status == "initProviderSave") { member.SetValue("realName", member.Name); member.SetValue("memberStatus", "initServiceSave"); } } } }


Simon Dingley 23 Apr 2018, 14:34:14

Nailed it!!! Thanks @Shandem

In the MemberService.Saving event handler I am doing the following:

if (!member.HasIdentity) { member.SetValue("MemberCreationStatus", MemberStatus.InitialCreate.ToString()); } else if (member.GetValue("MemberCreationStatus").Equals(MemberStatus.InitialCreate)) { if (member.Username.Equals(member.Name)) { member.SetValue("MemberCreationStatus", MemberStatus.InitialProviderSave.ToString()); } else { member.SetValue("MemberCreationStatus", MemberStatus.InitialServiceSave.ToString()); } }

And in the member.Saved event I am doing the following:

foreach (var member in e.SavedEntities) { // We can skip this iteration if the member has not yet been persisted or they have already received the email if (member.GetValue("MemberCreationStatus") .Equals(MemberStatus.PasswordGenerated)) continue;

if (member.GetValue<MemberStatus>("MemberCreationStatus").Equals(MemberStatus.InitialServiceSave))
{
	member.SetValue("MemberCreationStatus", MemberStatus.PasswordGenerated.ToString());
	memberSvc.Save(member, false);

	// Generate a new password that meets minimum requirements
	var password = Membership.GeneratePassword(
		Membership.MinRequiredPasswordLength,
		Membership.MinRequiredNonAlphanumericCharacters);

	// Save the password to persist it before we send the email
	memberSvc.SavePassword(member, password);

	// Send the member a welcome email with the initial password
	Members.SendWelcomeEmail(member, password);
}

}

I now have access to all the properties I was previously missing when sending the email and also the name is correct.


Mads Krohn 23 Apr 2018, 14:41:30

Good to hear Simon! I would still argue though, that we should have an easier workaround until v8..


Simon Dingley 23 Apr 2018, 14:43:12

I agree that this is far from ideal but I'm happy that it has at least removed a bottleneck for me right now.


Mads Krohn 23 Apr 2018, 15:41:46

@ProNotion which version of Umbraco are you using? I can't seem to get around the problem that on the third try, which should be the initServiceSave, my memberStatus property is null.


Simon Dingley 23 Apr 2018, 15:49:50

For this particular project I'm on 7.8.1 right now but it is soon to be updated to the latest. Is the issue present using my code?


Mads Krohn 23 Apr 2018, 15:59:26

Ok, running 7.10.3 here, and yes, it is. For me, at least, in the Saving event on the line: else if (member.GetValue("MemberCreationStatus").Equals(MemberStatus.InitialCreate)) The 'MemberCreationStatus' property is null on the third time it runs through, thus I'm not able to query the status for anything meaningful.


Simon Dingley 23 Apr 2018, 16:04:12

Weird, so it has a value then it doesn't? Going to be a few more days before I can get onto the upgrade to test it in the latest version but will let you know how I get on.


Mads Krohn 23 Apr 2018, 16:34:59

Exactly! Super weird :| Do let me know when you get there!


Priority: Normal

Type: Bug

State: Open

Assignee:

Difficulty: Normal

Category:

Backwards Compatible: False

Fix Submitted:

Affected versions: 6.2.1, 7.1.4, 7.8.1

Due in version: 8.0.0

Sprint:

Story Points:

Cycle: