How to create custom additional fields in UserProfile in MVC4
Asked Answered
S

5

40

I faced with new ASP MVC 4 feature, it shipped with new membership db schema and new initialization. In mvc 3 and old versions developer able to create custom user profile fields using specifications in web.config, but now i faced with method in filters namespace in default mvc 4 project:

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

and user profile table:

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

But the method InitializeDatabaseConnection generate only UserName and UserId i need to generate other additional fields.

I have good experience in EF codeFirst approach, and in that case i try to edit UserProfile Class:

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

But when i regenerate database, i havent see any changes, Custom Db fields not generated. Help me please, how can i create custom user fields?

Siddur answered 15/9, 2012 at 6:55 Comment(1)
One solution which i found is creating fields in table manually, it nice work, but i want to know is any way to generate same fields from code.Siddur
P
52

Elaborating from the answer above

The WebSecurity.InitializeDatabaseConnection Method help states that

If you want to use a database table that contains user profile information (user names, email addresses, and so on), you specify a connection string and table name that the membership system uses to connect to that information. If you do not want to use an existing user profile table, you can specify that the InitializeDatabaseConnection() method should automatically create the user profile table. (A database for the user profile table must already exist.)

So if we want more fields into the UserProfile table we just need to make sure we are creating a profile table and run the InitializeDatabaseConnection method after the table is already in place.

In the standard MVC4.0 project template from VS2012 I've commented out the Account controller

[Authorize]
//[InitializeSimpleMembership]
public class AccountController : Controller
{

and moved InitializeDatabaseConnection into the EF Code First Database Initializer

public class MyDatabaseInit: DropCreateDatabaseAlways<MyDatabaseContext>
{
    protected override void Seed(MyDatabaseContext context)
    {
        SeedMembership();
    }

    private void SeedMembership()
    {
        WebSecurity.InitializeDatabaseConnection("MyDatabaseContext",
            "UserProfile", "UserId", "UserName", autoCreateTables: true);
    } 
}

ensuring that the InitializeDatabaseConnection runs once the table is already in place.

Added the UserProfile class to my EF Code First model

public class MyDatabaseContext : DbContext
{
    public DbSet<UserProfile> UserProfiles { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

Added the extra field in the UserProfile table

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

All you need now is to set the database initialization strategy when the application starts and also call a query on the database the make sure it gets created at that point, before any authorization/authentication code is called.

protected void Application_Start()
{
   AreaRegistration.RegisterAllAreas();

   WebApiConfig.Register(GlobalConfiguration.Configuration);
   FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
   RouteConfig.RegisterRoutes(RouteTable.Routes);
   BundleConfig.RegisterBundles(BundleTable.Bundles);
   AuthConfig.RegisterAuth();

   Database.SetInitializer<MyDatabaseContext>(new MyDatabaseInit());
   new MyDatabaseContext().UserProfile.Find(1);
}
Pollinate answered 17/9, 2012 at 14:28 Comment(9)
Is the Database System.Data.Entity.Database or WebMatrix.Data.Database?Chough
It's System.Data.Entity, We are working with Entity Framework and not Webmatrix's Dynamic database access layer.Sourdine
Although I've done all that IMLiviu has said, my added field [MobilePhone] is still left as NULL! Is the answer missing something?Extempore
@Ciwan you need to populate the fields through your view as well. You can extend out the registration view with the model binder - think "model => model.MobilePhone" when you populate.Feuillant
Why did you decide to use DropCreateDatabaseAlways as DB initializer? Wouldn't it remove all database data every time the application is restarted?Filiform
@Shedal - yes that will recreate the database every time. The initialization strategy selected is not important, any strategy will work.Pollinate
@Pollinate Yes, it is important, since the strategy that would fit my needs the best is MigrateDatabaseToLatestVersion. But it does not contain a Seed() method that I could override. And I don't think dropping the database on application restart is a good idea in production environment.Filiform
@Shedal - then you might want to try something like: Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourDbContext, Configuration>()); where Configuration : DbMigrationsConfiguration<YourDbContext> and has a Seed method to override.Pollinate
+1 Thanks for all the useful tips, but leaving "delete everything first" as the answer was unfortunate. I built a prototype based on your answer and your final comments and have added that below so others can benefit from your actual solution without the pain I just went through :)Eddieeddina
E
18

Non-destructive version based on IMLiviu's answer and comments:

Just came across this one problem and had to unknot the correct way to do it from the answer + comments (+ trial and error), so thought I would share the results you can cut & paste. This is based on the answer by IMLiviu, so full credit to them. It modifies the existing UserProfile and UserContext classes as they appear directly compatible with EF as-is:

I was horrified to see a suggestion that involved completely removing a database, just to add a few tables so after reading all the comments and creating a prototype, here's the result.

1 - Stop Webmatrix creation

Stop the standard webmatrix creation of the membership tables (comment out the [InitializeSimpleMembership] attribute).

[Authorize]
//[InitializeSimpleMembership]
public class AccountController : Controller

2 - Create a migration configuration

Create a migration configuration class like the one below:

public class MigrationConfiguration : DbMigrationsConfiguration<UsersContext> 
{
    public MigrationConfiguration()
    {
        this.AutomaticMigrationsEnabled = true;  // This is important as it will fail in some environments (like Azure) by default
    }

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

3 - Remove pluralisation from EF creation

Change the AccountModel.cs file UsersContext class to remove the pluralisation option (added the OnModelCreating event):

public class UsersContext : DbContext
{
    public UsersContext() : base("DefaultConnection")
    {
    }

    public DbSet<UserProfile> UserProfiles { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

4 - Add new fields to UserProfile

Add the extra fields you need to UserProfile:

[Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
    public string UserEmail { get; set; }  // <<<<<<<< E.G. THIS ADDED
}

5 - Migrate the tables on app start

Now when the app starts you set the database init strategy and trigger it with a read:

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();

        Database.SetInitializer(new MigrateDatabaseToLatestVersion<UsersContext, MigrationConfiguration>());
        new UsersContext().UserProfiles.Find(1);
    }

Obviously you will need to add various using statements to get this all to work, but the right click resolve option will do that for you.

Additional notes:

If you decide (as I did) to use a table other than UserProfile for your users, you need to change several entries to match.

  • In your SimpleMembershipInitializer class you need to reference the new table and column names
  • In the account controller and the various Login and Register views you need to reference your new model fields (if the names have changed)
  • In the UsersContext class you can leave the Userprofile classname as-is, but you need to change the Table attribute to match your table name.
  • In the UserProfile class you need to rename the fields to match your new table field names.
  • In the database you need to remove the relationship between webpages_UsersInRoles and UserProfile and add a relationship between your new user table and webpages_UsersInRoles or the old referential integrity checks will break you at run-time. (I strongly recommend you delete the existing UserProfile table and check it is not recreated. if it is you have something left behind in your code).
Eddieeddina answered 9/10, 2013 at 15:21 Comment(4)
I get an error in the Application_Start() method. It says: 'MigrationConfiguration' cannot be used as type parameter 'TMigrationsConfiguration' in the generic type or method 'System.Data.Entity.MigrateDatabaseToLatestVersion<TContext,TMigrationsConfiguration>'. There is no implicit reference conversion from 'MigrationConfiguration' to 'System.Data.Entity.Migrations.DbMigrationsConfiguration<Context>'.Boarding
@Josh Barber: this answer was used with EF 5 from memory. I am guessing you are using EF 6 and things have changed (yet again)|Eddieeddina
Does migrating tables on app start happen every time or just the first time?Annelleannemarie
@ParthShah: It only does anything if they do not exist, so first time, or after deleting your database tables :)Eddieeddina
A
6

Add a new table to the Database and call it User_Details or similar, on creating a user you can then retrieve the ID of the user and force the details into the new table. This is One simple option.

Accidence answered 7/1, 2013 at 16:27 Comment(0)
N
3

Have a look at the MVC4 internet project template that shipped with VS2012 or on VS2010. You need to make sure the database is not created before you modify the columns of your userprofile class. you can add more properties in your POCO classes and then regenerate your database. if you add properties after the database is generated then please make sure that you are using EF migrations to add those new properties to the database

Nonprofessional answered 16/9, 2012 at 20:35 Comment(0)
E
3

Of course you probably know that you have to make your RegisterModel identical to UserProfile Model. What I like to do is use Migrations prior to initializing the database and put the following in the Configuration.cs file:

    protected override void Seed(UsersContext context)
    {

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

        if (!WebSecurity.UserExists("yardpenalty"))
            WebSecurity.CreateUserAndAccount(
                "yardpenalty",
                "password",
                new
                {
                    Email = "[email protected]",
                    ImageUrl = "/Content/Avatars/yourname",
                    DateJoined = DateTime.Now
                },
                false);  
           //... rest of model.Builder stuff 
   }

Then I use the Packet Manager Console by these three easy to remember commands:

  1. Enable-Migrations
  2. Add-Migration MigrationName
  3. Update-Database // will seed from Configuration.cs

I agree with TrueBlueAussie , and would go as far as saying LazyInitialization is not very helpful anymore and you really don't have to call it they way you did anymore. Anyways, All you have to do is then change you Register Action in your Account Controller to call the Method like this:

  WebSecurity.CreateUserAndAccount(model.UserName, model.Password, propertyValues: new { model.Email, model.ImageUrl, model.DateJoined });

NOTE: MVC5 uses OWIN

Engdahl answered 14/12, 2013 at 19:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.