19

I have an ASP.NET MVC 4 site based off the internet template. I am using the SimpleMembership which i set up with that template.

I can modify the Users table which has been creted for me but I am unsure as to the "correct" way to modify the extra fields I have added. I want Fullname, Email etc and have added them to the user table but there appears no way to update through the SimpleMembership WebSecurity.* static methods.

Are you supposed to just update those properties yourself using EF outside of the SimpleMembership API?

PlanetWilson
  • 361
  • 1
  • 2
  • 8

4 Answers4

22

1 - You need to enable migrations, prefereably with EntityFramework 5

2 - Move your

WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "EmailAddress", autoCreateTables: true); 

to your Seed method in your YourMvcApp/Migrations/Configuration.cs class

    protected override void Seed(UsersContext context)
    {
        WebSecurity.InitializeDatabaseConnection(
            "DefaultConnection",
            "UserProfile",
            "UserId",
            "UserName", autoCreateTables: true);

        if (!Roles.RoleExists("Administrator"))
            Roles.CreateRole("Administrator");

        if (!WebSecurity.UserExists("lelong37"))
            WebSecurity.CreateUserAndAccount(
                "lelong37",
                "password",
                new {Mobile = "+19725000000", IsSmsVerified = false});

        if (!Roles.GetRolesForUser("lelong37").Contains("Administrator"))
            Roles.AddUsersToRoles(new[] {"lelong37"}, new[] {"Administrator"});
    }

Now EF5 will be in charge of creating your UserProfile table, after doing so you will call the WebSecurity.InitializeDatabaseConnection to only register SimpleMembershipProvider with the already created UserProfile table, also tellling SimpleMembershipProvider which column is the UserId and UserName. I am also showing an example of how you can add Users, Roles and associating the two in your Seed method with custom UserProfile properties/fields e.g. a user's Mobile (number) and IsSmsVerified.

3 - Now when you run update-database from Package Manager Console, EF5 will provision your table with all your custom properties

For additional references please refer to this article with sourcecode: http://blog.longle.net/2012/09/25/seeding-users-and-roles-with-mvc4-simplemembershipprovider-simpleroleprovider-ef5-codefirst-and-custom-user-properties/

LeLong37
  • 3,031
  • 1
  • 20
  • 17
  • Nice... this really helped me solve a really difficult situation. :) More on this matter here: http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx – Leniel Maccaferri Oct 01 '12 at 20:51
  • 6
    this assumes you're using the DEMOWARE code from the Internet template. In a real world app, you'd have your EF dependencies in a data access project, and you probably wouldn't want that to take a dependency on the WebMatrix assemblies or any of System.Web. – Thiago Silva Dec 14 '12 at 21:58
  • I agree, but how do you get around using Webmatrix in your data access project? – nportelli Jul 26 '13 at 13:18
  • I have a question please, I did everything and I sucessfully create roles and users but when I try to affect users to roles I get exception because Users table is empty and my users data goes to UserTable so what's wrong exactly – TheSM Jun 23 '14 at 17:51
15

They made it easy to modify the profile with SimpleMembership. SimpleMembership is using the code first EF model and the user profile is defined in the file AccountModels.cs that is generated as part of the Internet template for MVC 4. Just modify the class UserProfile and add the new fields in the class definition. For example, adding a field for email would look something like this:

[Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
}

Here is an example on how you would access the email field:

var context = new UsersContext();
var username = User.Identity.Name;
var user = context.UserProfiles.SingleOrDefault(u => u.UserName == username);
var email = user.Email;

Here is what the database looks like after adding the email field.

enter image description here

There is a good blog that describes some of the changes in SimpleMembership here. You can also find more detailed information on customizing and seeding SimpleMembership here.

Kevin Junghans
  • 17,475
  • 4
  • 45
  • 62
  • two questions from that though. 1) does adding a property here changed the DB automatically? and how to get the currently logged in user's email from the API? (assuming the properties above) – Eonasdan Sep 10 '12 at 14:45
  • It will change the DB schema on creation so you will need to remove any existing DB for this to work. I updated the answer to show you would access the email. It is not an API like the old MembershipProvider. – Kevin Junghans Sep 10 '12 at 15:07
  • Now I updated that UserProfile class and deleted my database tables but upon recreation the new fields did not appear? – PlanetWilson Sep 10 '12 at 18:19
  • Also when I decompile the WebSecurity.InitializeDatabaseConnection then I can see it only does UserName and UserId. There is no code in ther to do extra columns. I think you must have to create them yourself. – PlanetWilson Sep 10 '12 at 18:28
  • It automatically creates the new column in the database for me by just modifying the UserProfile class as you can see in my updated answer. What version of MVC 4 and VS are you using? This was done using the latest release of MVC 4 (not beta or RC) running on the latest release of VS 2012. – Kevin Junghans Sep 10 '12 at 18:50
  • Also note that SimpleMembership uses a different database than the one created by the ASP.NET Web Site Administration Tool previously used for user profile information. – Kevin Junghans Sep 10 '12 at 18:59
  • i am using latest VS 2012 and build in MVC 4, start new project using internet template and default AccountController. After adding new property to AccountModel's UserProfile class, the database didn't generate the field for my new property. I have deleted the database many times and repeat the process, but the new property i added never get created in my database – Quad Coders Jan 04 '13 at 22:32
  • suhendri - Are you sure you are looking at the correct database. The are two, one for the standard ASP.NET security, and the other for SimpleMembership. Hard to tell what is going on for you without code examples and screen shots. You may want to open your own QA with the details to get some assistance. – Kevin Junghans Jan 05 '13 at 15:43
  • Somebody can help to update the Manage.cshtml view + controller so that the user can edit his own email as per example above? – firepol Feb 04 '13 at 12:21
  • Look at this blog post [http://kevin-junghans.blogspot.com/2013/02/adding-email-confirmation-to.html] which shows how to add email confirmation to the registration process. This will show you how to capture the email in Register.cshtml. The same concepts would apply to Manage.cshtml. – Kevin Junghans Feb 04 '13 at 21:18
4

if you look right around line 273 of the accountcontroller you'll find this line

db.UserProfiles.Add(new UserProfile { UserName = model.UserName });

Looks like even OOTB they (MS) are doing just as you suggested and using EF to update.

I too, am looking for the "correct" way of updating and accessing these properties.

Edit:

Here's my solution (I'm happy if someone says there's an OOTB way to do this).

wrap UserProfile (the .net Entity from SimpleMembership) in a session class.

public static class sessionHelpers {
     public static UserProfile userProfile
        {
            get
            {
                if (HttpContext.Current.Session["userProfile"] != null)
                {
                    return HttpContext.Current.Session["userProfile"] as UserProfile; 
                }
                else
                {
                    using (UsersContext db = new UsersContext())
                    {
                        HttpContext.Current.Session["userInfo"] =
                        db.UserProfiles.Where(x => x.UserName == 
                            HttpContext.Current.User.Identity.Name).FirstOrDefault();

                   return db.UserProfiles.Where(x => x.UserName == 
                       HttpContext.Current.User.Identity.Name).FirstOrDefault();
                    }
                }
            }
            set { HttpContext.Current.Session["userProfile"] = value; }
        }
}

From this you can access the profile table by doing

string foo = sessionHelpers.userProfile.FIELDNAME;

where sessionHelpers is my wrapper class. The if block just ensures that if it hasn't been set in the current session that accessing it will attempt to get it.

Eonasdan
  • 7,563
  • 8
  • 55
  • 82
  • 2
    hmmm...i sense some serious code smell here...you're co-mingling DB access code with web code. I would highly recommend refactoring ALL entity framework references into an isolated Data Access project, then using the repository pattern to allow callers to retrieve data for the app. – Thiago Silva Dec 14 '12 at 22:01
  • With this pattern, how do you ensure that changes to the profile are written back to the database? – Eric J. Feb 09 '13 at 21:40
  • I would cast what's in the session to UserProfile rather than use the `as` keyword. If you accidentally put some other type in there, your getter will just return *null*. It's better to fail immediately than try and diagnose downstream problems caused by a null UserProfile. – Eric J. Feb 09 '13 at 21:42
  • The code queries the DB twice with the same query. Although the query is probably cached, it's still extra load. – Eric J. Feb 09 '13 at 21:50
1

You need to add them to your Database ( done according to description )

add them to the view ( edit , add , delete , and view ) unless modified

add them to your model in UserProfiles

Then it will work.

Pakk
  • 1,299
  • 2
  • 18
  • 36