Oh wait… this is awful. AWRUCHKA. Right dry heaving done with.
It's a good job so few websites want to authenticate users and collect data on them otherwise we'd constantly have to write the same code ove… what's that? Oh my! Everyone is going through this.
Jesus no wonder people bang on about RoR. It makes this easier in comparison
Anyway - I'll forget how to do this before I have to do it again
So
- fire up a new MVC3 web application
- Jump into nuget and Install-Package System.Web.Providers
- Sort out a connection string for SQL CE
- Add a key to make sure the login link always points to
LogOn
Now my web.config
looks like this (edited out parts I haven't touched for something approximating brevity)
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=152368
-->
<configuration>
<connectionStrings>
<add name="users" connectionString="Data Source=|DataDirectory|users.sdf;"
providerName="System.Data.SqlServerCe.4.0"/>
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="1.0.0.0" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="loginUrl" value="~/Account/LogOn" />
</appSettings>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<clear />
<add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="users" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
<profile defaultProvider="DefaultProfileProvider">
<providers>
<clear />
<add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="users" applicationName="/" />
</providers>
</profile>
<roleManager enabled="false" defaultProvider="DefaultRoleProvider">
<providers>
<clear />
<add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
<add name="DefaultRoleProvider" type="System.Web.Providers.DefaultRoleProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="users" applicationName="/" />
</providers>
</roleManager>
<sessionState mode="InProc" customProvider="DefaultSessionProvider">
<providers>
<add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
</providers>
</sessionState>
</system.web>
</configuration>
Now start a debug session for the web app. Click logon. Click Register. Fill in the form. Register. Click Logoff and stop the debug session in Visual Studio
You can see the new SQL CE database and have a look at the schema. The Memberships and Users tables have a new row. The new user.
Hurrah - all the information you'll ever need is collected.
What?! You want to know more than name and email. Now that's a turn up for the books.
It turns out you can store key-value pairs in the profiles table. I think that anyone that wrote ASP dot Net websites will be old-hand at this but I've never had to do that or this…
While you can do magic up a key-value pair whenever you feel the need to in your code it's probably better to use one of these new fangled Class thing-a-ma-bobs
using System;
using System.Web.Profile;
namespace AcrHack.Models
{
public class CustomProfile : ProfileBase
{
//magic string
public static string ADDRESS = "address";
public string Address
{
get { return this[ADDRESS] as String; }
set { this[ADDRESS] = value; }
}
}
}
Now a quick edit to the web config above so that the providers opening tag becomes
<profile defaultprovider="DefaultProfileProvider" inherits="AcrHack.Models.CustomProfile"/>
which makes the Profile Provider aware of the new Profile class
Next step is to find the RegisterModel
(this could be the CreateModel
or some other model) and add an Address field
public class RegisterModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
//Added Address field
[Required]
public string Address { get; set; }
}
and edit the Register method in the controller
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
//Changes here
//Create loads or creates a profile based on searching for username
var userProfile = ProfileBase.Create(model.UserName) as CustomProfile;
userProfile.Address = model.Address;
userProfile.Save();
//End of changes
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
and finally edit the view to add an editor field for the new property. (I'll leave that as an exercise for the reader)
Now we can go back to the Register page
Register and then have a look in the profile table.
Ta da!
So there's a mechanism for extending the default profile.
Honestly, it feels messy and since at this point if there's a need for any data access layer then since there'll be a link on user name or user id anyway it's likely a better idea to have the additional data in the DAL and fangle the authentication and user models together in a ViewModel
.
Having gone away and checked some code committed on another project by the lovely OrangeTentacle that's what he's done. So having figured it out for myself I'll probably go and crib off that much tidier code
Additional Reading: