Custom member properties when using the ASP.NET login controls?
Hi!
I'm using the ASP.NET login controls to allow website visitors to sign up and login. I have added the ASP.NET controls to my own user controls and in order to get the markup that I want I have converted them so they use templates for rendering - that way I can work with my own translations (Swedish) and get rid of the ugly table based layout of the forms. All this is working great and the users can sign up and login.
Now I need to do two more things:
Add custom properties to my sign up-form since the user profiles will have to contain more info than just the regular username, password etc.
I want to access the propery "Name" (Member.Text) to allow the user to enter their full name. The default value seems to be the username (so login and name contains the same value)
If possible I'd like to continue using the membership provider style of coding.
One more thing that I'm having trouble understanding is how the role based protection works. When I look at the memberI have created it looks like it doesn't belong to any group and still the member can access the protected pages? I thought I had to add the member to a group before he/she could log in an access the pages?
Do I need to add the members I create to a group at the same time that I create them? How do I do that?
As far as I can see, in order to write custom properties to members and add them to groups I have to write my own code for creating a new member. Is that correct?
There is a great forum post here with a link to a blog post about the subject. But it seems as they have had some problems with the solution. I have only code where the user can login and edit their info but here is a small part of it. Dont know if its wrong to work directly with the umbraco Member API or i should have used some .NET membership to abstract the work going on in umbraco, but here goes :)
Member member = Member.GetCurrentMember(); Member.getProperty( "CompanyName" ).Value = companyName; Member.Save(); //Remove from cache Member.ClearMemberFromClient( Member.Id ); Member.RemoveMemberFromCache( Member.Id );
I had a solution where I'd use the .NET membership functions to patch it all together since everyone has adviced me against interacting directly with the Umbraco Member API. But since there is no coherent documentation and I don't know all the inner workings of .NET membership, I may have to abandon the solution I have deveoped today and start over again, working directly with the Umbraco API.
I have read that too, not to interact directly with it but the blog post i refereed to says it saves for every property you put a new value in. So if the user changes 10 properties it saves 10 times to the sql.
I dont know 100% if the umbraco Member is doing that also, but dont hope so. Can anyone confirm what it does?
Anders: Do you have a link to this blog post? Right now I'm desperate at getting my hands on every little piece of documentation regarding how to work with the umbraco membership provider (or why I should not).
. . .
Continuing with my own thoughts on the matter:
One way of doing it could be to first create the member with the ASP.NET control and then, perhaps in a second view/form ask the member to enter the remaining (custom) properties. Or I could attach my own code to an event that is triggered after the member has been created and in this event I work directly with the old umbraco member API. The solution in one of Mortens blogposts suggests a solution along those lines:
Still it feels odd to use two different "ways" when adding data to a member profile. It would be cleaner if I could write data to my custom member properties using the standard ASP.NET CreateUser-way. But if building my own membership provider is the only way, I will have to pass. That's a huge task and not worth it. If it could be done by inheriting umbracos membership provider and just adding the extra stuff that I need, it might be easier, but I'm not sure that I'm up to the challenge.
Indeed I would create the member through the .net membership provider model. The extra properties is the ones hard to figure out how to do. One from the core team or one that has done it before could help for that question.
The standard ASP.NET registration control doesn't handle the properties which are set on the profile provider, it's a problem I've come across in the past.
So you'll have to make your own registration form regardless, but there's nothing that should stop you from creating a profile provider, adding all the properties to that and mapping them to their Umbraco counterparts and using the standard ASP.NET way of creating a member and profile.
You would then tie into the "OnCreated" event with something like this to set the custom properties:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
CreateUserWizard1.CreatedUser += new EventHandler(CreateUserWizard1_CreatedUser);
}
// event call to add the user role...
void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
Member registeredMember = Member.GetMemberFromLoginName(CreateUserWizard1.UserName);
// If we returned a member from the above call, then let's try to save the rest of the props...
if (registeredMember != null && registeredMember.Id > 0) {
try {
//Update the member record with custom props...
UpdateMemberProfile(registeredMember);
} catch {
Log.Add(LogTypes.Custom, User.GetUser(0), Node.GetCurrent().Id,
new StringBuilder().Append("Error while saving member profile details '").Append(registeredMember.Text).Append("' (").Append(registeredMember.Id).Append(")!").ToString());
}
}
CreateUserWizard1.LoginCreatedUser = false;
}
// check to make sure that the email is not in use (to avoid having multiple accounts with the same username)
protected void CreateUserWizard1_CreatingUser(object sender, EventArgs e)
{
// check to make sure that the email address isn't already in use..
Panel errRow = (Panel)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("errRow");
Literal errorMsg = (Literal)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("ErrorMsg");
TextBox email = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("UserName");
Member m = Member.GetMemberFromEmail(email.Text);
if (m!=null)
{
errRow.Visible = true;
errorMsg.Text = "The email you specified is already taken. Please choose another email.";
return;
}
}
private void UpdateMemberProfile(Member member)
{
//Get profile info from regsitration wizard
TextBox phoneTB = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Phone"); // example
bool isFreePoster = false;
if (subscriptionType.SelectedValue.IndexOf("Free")!=-1) isFreePoster = true;
// I store my member info in a custom table, but you could just as easily set member properties here (if you've added them to your member types in Umbraco)
StaticMember sm = new StaticMember();
sm.Phone = !string.IsNullOrEmpty(phoneTB.Text) ? phoneTB.Text : string.Empty;
//Set user friendly name for member node in admin backend
string text = string.Empty;
if (nameTextBox != null)
text = nameTextBox.Text;
//E-mail is set to username as e-mail is used as username in registration wizard
member.Email = member.LoginName;
//Set user friendly name in backend
if (!string.IsNullOrEmpty(text))
member.Text = text;
// add member to group (one specified in Umbraco UI
MemberGroup addToMemberGroup = MemberGroup.GetByName(this._memberGroupName);
member.AddGroup(addToMemberGroup.Id);
// save the member
member.Save(); // save umbraco props....
}
This contains some additional code (like checking if a username is already in use), but hopefully it will get you where you need to be.
@slace - he could do that and its the best solution to work with the asp.net membership and profile, but what i read in the blog post and forum topic that i have linked to in a previous post they talk about that it isnt working or only some times and also that for every custom field you have on the profile you will need to call save and going into the DB 10 times if you have 10 custom properties, and that not so great :)
When I read it through, everything fell into place. I altered some of the stuff related to the member parameters to suit my specific needs. I also added the property that specifies the member group name to my usercontrol class and set it as a parameter on the macro:
private string _memberGroupName;
public string MemberGroupName { get { return _memberGroupName; } set { _memberGroupName = value ;} }
Nik - a follow up question: I noted that you don't display a message to the user if the function inside the try/catch statement should throw an error. I normally like to keep all my error messages in one place so I use a ValidationSummary at the top of the form where I display all messages that the validation controls want to display.
Wouldn't it be nice using a custom validation control whose IsValid parameter is set to false if an error is thrown inside the try/catch statement? I tried using it, but Visual Studio wouldn't acknowlege the fact that the user control had a custom validation control(?)
I also noted that the create user wizard contains a literal called ErrorMessage. It would be nice if the message that it displays could be displayed among the other messages, up in the validation summary.
I found that creating custom templates to the member controls didn't remove all the ugly table code - my CSS friendly templates where still encapsulated inside a table that the control itself generated. The solution was to download CSS Friendly Control Adapters and use them in the project instead. And it worked like a charm!
Regardning the performance of the multiple saves, I guess there's no way around it for now?
Thomas, glad it was helpful. I do use a ValidationSummary yin my control but only for field errors. In my 'real' code I simply log application exceptions and continue on with the process, not always ideal but that is something I am working on too. Getting a real error handler library that would be extensible and easy to work with.
Nice idea on the CSS Friendly Control Adaptors. I'll look into that as I had the same problem with output.
@Slace - I had no idea that it did repeated DB saves either! Thanks for pointing that out.
Yeah sorry guys it's not something being addressed in 4.x. Basically due to the design of Umbraco's Data Type system it's next-to-impossible to have a single SQL query 'to rule them all' (hehe). Because a data type may not even say to the database it's up to the implementor of the data type to get around this.
I have dome "some" improvements in 4.1, but they only apply to documents, which allows for deferred saving (ie - all the properties are stored in a Dictionary and then assigned to the data type during the .Save() method call). It still means lots of (potential) DB writes, but you know when they are happening this time. But it only occurs when you're creating Documents yourself, it was too complex to do it any other way :(.
There are some DB improvements in 4.1, which we'll document as the time comes.
@All - I think in this instance (unless you're starting the next Amazon) creating users is not going to be that system resource intense. I think we're OK setting properties on the member like in the above example. At least in my cases (so far).
Custom member properties when using the ASP.NET login controls?
Hi!
I'm using the ASP.NET login controls to allow website visitors to sign up and login. I have added the ASP.NET controls to my own user controls and in order to get the markup that I want I have converted them so they use templates for rendering - that way I can work with my own translations (Swedish) and get rid of the ugly table based layout of the forms. All this is working great and the users can sign up and login.
Now I need to do two more things:
If possible I'd like to continue using the membership provider style of coding.
Thanks in advance!
/Thomas Kahn
One more thing that I'm having trouble understanding is how the role based protection works. When I look at the memberI have created it looks like it doesn't belong to any group and still the member can access the protected pages? I thought I had to add the member to a group before he/she could log in an access the pages?
Do I need to add the members I create to a group at the same time that I create them? How do I do that?
/Thomas Kahn
As far as I can see, in order to write custom properties to members and add them to groups I have to write my own code for creating a new member. Is that correct?
/Thomas
There is a great forum post here with a link to a blog post about the subject. But it seems as they have had some problems with the solution. I have only code where the user can login and edit their info but here is a small part of it. Dont know if its wrong to work directly with the umbraco Member API or i should have used some .NET membership to abstract the work going on in umbraco, but here goes :)
Member member = Member.GetCurrentMember();
Member.getProperty( "CompanyName" ).Value = companyName;
Member.Save();
//Remove from cache
Member.ClearMemberFromClient( Member.Id );
Member.RemoveMemberFromCache( Member.Id );
The member class is in the cms.dll of umbraco
Hi Anders!
I had a solution where I'd use the .NET membership functions to patch it all together since everyone has adviced me against interacting directly with the Umbraco Member API. But since there is no coherent documentation and I don't know all the inner workings of .NET membership, I may have to abandon the solution I have deveoped today and start over again, working directly with the Umbraco API.
Regards,
Thomas K
I have read that too, not to interact directly with it but the blog post i refereed to says it saves for every property you put a new value in. So if the user changes 10 properties it saves 10 times to the sql.
I dont know 100% if the umbraco Member is doing that also, but dont hope so. Can anyone confirm what it does?
Anders: Do you have a link to this blog post? Right now I'm desperate at getting my hands on every little piece of documentation regarding how to work with the umbraco membership provider (or why I should not).
. . .
Continuing with my own thoughts on the matter:
One way of doing it could be to first create the member with the ASP.NET control and then, perhaps in a second view/form ask the member to enter the remaining (custom) properties. Or I could attach my own code to an event that is triggered after the member has been created and in this event I work directly with the old umbraco member API. The solution in one of Mortens blogposts suggests a solution along those lines:
http://www.mortenbock.dk/blog/2009/04/01/setting-up-membership-in-umbraco.aspx
Still it feels odd to use two different "ways" when adding data to a member profile. It would be cleaner if I could write data to my custom member properties using the standard ASP.NET CreateUser-way. But if building my own membership provider is the only way, I will have to pass. That's a huge task and not worth it. If it could be done by inheriting umbracos membership provider and just adding the extra stuff that I need, it might be easier, but I'm not sure that I'm up to the challenge.
/Thomas K
Indeed I would create the member through the .net membership provider model. The extra properties is the ones hard to figure out how to do. One from the core team or one that has done it before could help for that question.
Here is the link
http://our.umbraco.org/forum/developers/api-questions/5240-Using-ProfileProvider-during-registration?sort=newest
The standard ASP.NET registration control doesn't handle the properties which are set on the profile provider, it's a problem I've come across in the past.
So you'll have to make your own registration form regardless, but there's nothing that should stop you from creating a profile provider, adding all the properties to that and mapping them to their Umbraco counterparts and using the standard ASP.NET way of creating a member and profile.
Thomas, here is something that may help you....
Using the CreateUserWizard like so:
<asp:CreateUserWizard ID="CreateUserWizard1" OnCreatingUser="CreateUserWizard1_CreatingUser" RequireEmail="false" runat="server"> ... </asp:CreateUserWizard>You would then tie into the "OnCreated" event with something like this to set the custom properties:
protected override void OnInit(EventArgs e) { base.OnInit(e); CreateUserWizard1.CreatedUser += new EventHandler(CreateUserWizard1_CreatedUser); } // event call to add the user role... void CreateUserWizard1_CreatedUser(object sender, EventArgs e) { Member registeredMember = Member.GetMemberFromLoginName(CreateUserWizard1.UserName); // If we returned a member from the above call, then let's try to save the rest of the props... if (registeredMember != null && registeredMember.Id > 0) { try { //Update the member record with custom props... UpdateMemberProfile(registeredMember); } catch { Log.Add(LogTypes.Custom, User.GetUser(0), Node.GetCurrent().Id, new StringBuilder().Append("Error while saving member profile details '").Append(registeredMember.Text).Append("' (").Append(registeredMember.Id).Append(")!").ToString()); } } CreateUserWizard1.LoginCreatedUser = false; } // check to make sure that the email is not in use (to avoid having multiple accounts with the same username) protected void CreateUserWizard1_CreatingUser(object sender, EventArgs e) { // check to make sure that the email address isn't already in use.. Panel errRow = (Panel)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("errRow"); Literal errorMsg = (Literal)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("ErrorMsg"); TextBox email = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("UserName"); Member m = Member.GetMemberFromEmail(email.Text); if (m!=null) { errRow.Visible = true; errorMsg.Text = "The email you specified is already taken. Please choose another email."; return; } } private void UpdateMemberProfile(Member member) { //Get profile info from regsitration wizard TextBox phoneTB = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("Phone"); // example bool isFreePoster = false; if (subscriptionType.SelectedValue.IndexOf("Free")!=-1) isFreePoster = true; // I store my member info in a custom table, but you could just as easily set member properties here (if you've added them to your member types in Umbraco) StaticMember sm = new StaticMember(); sm.Phone = !string.IsNullOrEmpty(phoneTB.Text) ? phoneTB.Text : string.Empty; //Set user friendly name for member node in admin backend string text = string.Empty; if (nameTextBox != null) text = nameTextBox.Text; //E-mail is set to username as e-mail is used as username in registration wizard member.Email = member.LoginName; //Set user friendly name in backend if (!string.IsNullOrEmpty(text)) member.Text = text; // add member to group (one specified in Umbraco UI MemberGroup addToMemberGroup = MemberGroup.GetByName(this._memberGroupName); member.AddGroup(addToMemberGroup.Id); // save the member member.Save(); // save umbraco props.... }This contains some additional code (like checking if a username is already in use), but hopefully it will get you where you need to be.
Thanks,
Nik
@slace - he could do that and its the best solution to work with the asp.net membership and profile, but what i read in the blog post and forum topic that i have linked to in a previous post they talk about that it isnt working or only some times and also that for every custom field you have on the profile you will need to call save and going into the DB 10 times if you have 10 custom properties, and that not so great :)
Manually assigning the properties like you're doing also writes to the DB each time. In fact the .Save() method call does little more than raise a few events, as I point out here - http://www.aaron-powell.com/blog/july-2009/the-great-umbraco-api-misconception.aspx ;)
Hi Nik!
Thanks for posting your code sample!
When I read it through, everything fell into place. I altered some of the stuff related to the member parameters to suit my specific needs. I also added the property that specifies the member group name to my usercontrol class and set it as a parameter on the macro:
...and in the macro
Works great! Thanks!
Nik - a follow up question: I noted that you don't display a message to the user if the function inside the try/catch statement should throw an error. I normally like to keep all my error messages in one place so I use a ValidationSummary at the top of the form where I display all messages that the validation controls want to display.
Wouldn't it be nice using a custom validation control whose IsValid parameter is set to false if an error is thrown inside the try/catch statement? I tried using it, but Visual Studio wouldn't acknowlege the fact that the user control had a custom validation control(?)
I also noted that the create user wizard contains a literal called ErrorMessage. It would be nice if the message that it displays could be displayed among the other messages, up in the validation summary.
Any thoughts on this would be much appreciated!
/Thomas
@slace - WOW! I didnt knew that it saved all the time. Thats horrible for performance! :) First in v5 it will be a fix or a 4.x thing?
I found that creating custom templates to the member controls didn't remove all the ugly table code - my CSS friendly templates where still encapsulated inside a table that the control itself generated. The solution was to download CSS Friendly Control Adapters and use them in the project instead. And it worked like a charm!
Regardning the performance of the multiple saves, I guess there's no way around it for now?
/Thomas
Thomas, glad it was helpful. I do use a ValidationSummary yin my control but only for field errors. In my 'real' code I simply log application exceptions and continue on with the process, not always ideal but that is something I am working on too. Getting a real error handler library that would be extensible and easy to work with.
Nice idea on the CSS Friendly Control Adaptors. I'll look into that as I had the same problem with output.
@Slace - I had no idea that it did repeated DB saves either! Thanks for pointing that out.
Cheers,
Nik
Yeah sorry guys it's not something being addressed in 4.x. Basically due to the design of Umbraco's Data Type system it's next-to-impossible to have a single SQL query 'to rule them all' (hehe). Because a data type may not even say to the database it's up to the implementor of the data type to get around this.
I have dome "some" improvements in 4.1, but they only apply to documents, which allows for deferred saving (ie - all the properties are stored in a Dictionary and then assigned to the data type during the .Save() method call). It still means lots of (potential) DB writes, but you know when they are happening this time.
But it only occurs when you're creating Documents yourself, it was too complex to do it any other way :(.
There are some DB improvements in 4.1, which we'll document as the time comes.
@Slace - thanks for chiming in.
@All - I think in this instance (unless you're starting the next Amazon) creating users is not going to be that system resource intense. I think we're OK setting properties on the member like in the above example. At least in my cases (so far).
Thanks,
Nik
is working on a reply...
This forum is in read-only mode while we transition to the new forum.
You can continue this topic on the new forum by tapping the "Continue discussion" link below.