Two years ago (how time flies!) I last wrote a blog post on how to create a Members Surface Controller to allow for a Members’ section on websites in Umbraco. The tutorial was deliberately simple and wasn’t feature complete. It gets a lot of traffic and the odd comment and I think it’s about time I did the final step to add some of the standard functionality that websites require – e.g. validating the user’s email before allowing login, adding a forgotten password function and sending the necessary emails. As with all code - this is just the way I do it – there might be better ways, I’d be keen to hear them though!
Following writing this the Umbraco community provided a few pointers and corrected a few mistakes!
Features:
This example uses the jQuery dependant Ubobtrusive validation – I often leave this out as I find it a bit annoying. But to install it in your project run the Nuget command
Install-Package Microsoft.jQuery.Unobtrusive.Validation
Then just add to your master template near the closing body tag:
<script src="~/scripts/jquery-1.8.0.min.js"></script> <script src="~/scripts/jquery.validate.min.js" type="text/javascript"></script> <script src="~/scripts/jquery.validate.unobtrusive.min.js"></script> </body>
Add the following custom properties to the member (Members > Member Types > Member ). Note the different data type (e.g. textbox, true /false and DateTime). The validate GUID expiry date time field will allow us to create links for emails that will expire (e.g. for forgotten passwords).
Add the following doc types (with a template);
For the account processes;
For the emails (without templates);
Add the necessary permissions and create the content nodes so that your content tree matches something like the structure below – you’ll note I’ve put all the account nodes under an Account node and a Reset Password, Register child and Already Registered Email Template node underneath the Site Settings. If you name the nodes differently you may need to adjust some hard coded paths.
The Already Registered email is used so that we don't show users error messages if they use an email address that is already tied to a member account. This is to protect user's privacy - instead that user receives an email (so that if they have forgotten they've already registered they know to just log in or reset their account) - thanks to Sebastiaan Janssen for recommending I add this as per this blog post.
I tend to always add a site settings node (either at the root level to keep it away from the content or for multilingual sites just under the homepage node. When you create this take a note of the ID of the node (you can get this from the Properties tab).
You’ll need to update line 21 of the /Helpers/Emalhelper.cs file to "get" the correct node by ID (yours is unlikely to also be 1074!).
In the Site Settings doctype I’ve created a multi-line text field (called a text area now) property called “Email Master Template”. I’ve inserted the email template markup from http://leemunroe.github.io/responsive-html-email-template/email.html - the only thing I’ve changed is to remove the content between the content comments (lines 188-215 at time of writing) and replaced it with a [[CONTENT]] field that I use to replace the email body with (which is different depending on the email being sent, I also remove the unsubscribe link (we’re only sending emails the user needs – a Mailchimp integration would be recommended to do the mailing list subscription).
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Really Simple HTML Email Template</title> <style> /* ------------------------------------- GLOBAL ------------------------------------- */ * { font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; font-size: 100%; line-height: 1.6em; margin: 0; padding: 0; } img { max-width: 600px; width: auto; } body { -webkit-font-smoothing: antialiased; height: 100%; -webkit-text-size-adjust: none; width: 100% !important; } /* ------------------------------------- ELEMENTS ------------------------------------- */ a { color: #348eda; } .btn-primary { Margin-bottom: 10px; width: auto !important; } .btn-primary td { background-color: #348eda; border-radius: 25px; font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-size: 14px; text-align: center; vertical-align: top; } .btn-primary td a { background-color: #348eda; border: solid 1px #348eda; border-radius: 25px; border-width: 10px 20px; display: inline-block; color: #ffffff; cursor: pointer; font-weight: bold; line-height: 2; text-decoration: none; } .last { margin-bottom: 0; } .first { margin-top: 0; } .padding { padding: 10px 0; } /* ------------------------------------- BODY ------------------------------------- */ table.body-wrap { padding: 20px; width: 100%; } table.body-wrap .container { border: 1px solid #f0f0f0; } /* ------------------------------------- FOOTER ------------------------------------- */ table.footer-wrap { clear: both !important; width: 100%; } .footer-wrap .container p { color: #666666; font-size: 12px; } table.footer-wrap a { color: #999999; } /* ------------------------------------- TYPOGRAPHY ------------------------------------- */ h1, h2, h3 { color: #111111; font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-weight: 200; line-height: 1.2em; margin: 40px 0 10px; } h1 { font-size: 36px; } h2 { font-size: 28px; } h3 { font-size: 22px; } p, ul, ol { font-size: 14px; font-weight: normal; margin-bottom: 10px; } ul li, ol li { margin-left: 5px; list-style-position: inside; } /* --------------------------------------------------- RESPONSIVENESS ------------------------------------------------------ */ /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */ .container { clear: both !important; display: block !important; Margin: 0 auto !important; max-width: 600px !important; } /* Set the padding on the td rather than the div for Outlook compatibility */ .body-wrap .container { padding: 20px; } /* This should also be a block element, so that it will fill 100% of the .container */ .content { display: block; margin: 0 auto; max-width: 600px; } /* Let's make sure tables in the content area are 100% wide */ .content table { width: 100%; } </style> </head> <body bgcolor="#f6f6f6"> <!-- body --> <table class="body-wrap" bgcolor="#f6f6f6"> <tr> <td></td> <td class="container" bgcolor="#FFFFFF"> [[CONTENT]] </td> <td></td> </tr> </table> <!-- /body --> <!-- footer --> <table class="footer-wrap"> <tr> <td></td> <td class="container"> </td> <td></td> </tr> </table> <!-- /footer --> </body> </html>
In the Email Template doc type there is a text area property called Email Body and a text string called Email Subject.
You need an email template under the Register and the Reset Password nodes. You may prefer to have these under the site settings – I think it makes more sense for editors to have the email on the page that the email is sent from.
<!-- content --> <div class="content"> <table> <tr> <td> <p>Hi [[FIRSTNAME]],</p> <p>Thank you for registering with This Website.</p> <p>To complete your registration please click on the following link to validate your email:</p> <h4><a href="http://[[DOMAIN]]/account/validate?email=[[EMAIL]]&validateGUID=[[VALIDATEGUID]]">http://[[DOMAIN]]/my-account/validate?email=[[EMAIL]]&validateGUID=[[VALIDATEGUID]]</a></h4> <p>Thanks</p> <p>Website Owners</p> </td> </tr> </table> </div> <!-- /content -->
<!-- content --> <div class="content"> <table> <tr> <td> <p>Hi [[FIRSTNAME]],</p> <p>Someone requested a password reset on the WEBSITE .</p> <p>To complete the password reset please click on the following link:</p> <h4><a href="http://[[DOMAIN]]/account/reset-password?email=[[EMAIL]]&validateGUID=[[VALIDATEGUID]]">http://[[DOMAIN]]/my-account/reset-password?email=[[EMAIL]]&validateGUID=[[VALIDATEGUID]]</a></h4> <p>Thanks</p> <p>Website Owners</p> </td> </tr> </table> </div> <!-- /content -->
<!-- content --> <div class="content"> <table> <tr> <td> <p>Hi [[FIRSTNAME]],</p> <p>Someone, hopefully you, has tried to register on This Website.</p> <p>If it was you we already have an account. Please try logging in using this address. If you've forgotten your password then you can reset it using the link below.</p> <h4><a href="http://[[DOMAIN]]/account/reset-password">http://[[DOMAIN]]/account/reset-password</a></h4> <p>Thanks</p> <p>Website Owners</p> </td> </tr> </table> </div> <!-- /content -->
@Html.Action("MemberEditProfileRenderForm", "MemberEditProfileSurface")
@Html.Action("MemberLoginRenderForm", "MemberLoginSurface")
@Html.Action("MemberRegisterRenderForm", "MemberRegisterSurface")
- See the code in the repository as there is some logic here.
@Html.Action("MemberValidate", "MemberValidateSurface")
And that is it!
Try now hitting the register page and register a user. Ensure you can register, login, log out and reset your password!
Now you just need to add the various links to the desired place in your menu / header etc
If you get any errors check you've correctly named the custom properties on the member, you've remembered to update the Site Settings node ID in the code and you've named the email template nodes *exactly* as per the example above!
Any comments, suggestions for improvements very welcome!
If you spot any typos or issues with this tutorial please leave a comment below or email me steve@SiempreSolutions.co.uk
Siempre Solutions Limited is a company registered in England and Wales with company number 09019307