Add property to Identity in MVC 5, using Entity framework - asp.net-mvc

I'm using MVC 5 and EF 6 (Datafirst),using msssql management studio.
I created a new mvc project, which came up with built database (AspNetUsers etc)
I also created a new table called UserDetails, which it purpose to contain more details about the user by it's Id (so I created a link between AspNetUsers id column to UserDetails UserId column)
therefore I added the following code
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("FirstName", FirstName.ToString()));
return userIdentity;
}
//Extended Propeties
public string FirstName { get; set; }
}
But of course it's not working, I looked over the internet for over 4 hours now, can someone please guide me ? I'm new to MVC, everything seems to be complicated much.
I also have the following static method :
public static class IdentityExtensions
{
public static string GetFirstName(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("FirstName");
// Test for null to avoid issues during local testing
return (claim != null) ? claim.Value : string.Empty;
}
}
in order to get it in the view and display it..
my goal is to display data from another table (UserDetails) in the view based on connection of 1-1 from AspNetUsers (UserDetails.UserId == AspNetUsers.Id)

All you have to do is extend the IdentityUser class, then add your custom properties like FirstName etc.. then since you are using EntityFramework Database first you need to enable the migrations with this command in your package manager console enable-migrations, then add an initial migration like add-migration initialMigration, after that update the database using migrations with this command update-database, the AspNetUsers table in your database will now have the new columns you added. Use migration to keep your database in sync with your models

if there is correct connection between both table you can use Eagerly Loading to get the details of one entity from another.

Related

How to allow soft-deleted email to be reused again with ASP.NET UserIdentity

I have an ASP.NET application where users are authenticated using the UserIdentity class. Recently, I have just implemented a soft-delete feature by adding 'ActiveStatus' to the ApplicationUser class.
The issue arises where the user cannot re-register with the soft-deleted email address as a new account. Can someone help me with this?
I've just managed to achieve this in my MVC application using the instructions and sample code from https://www.codeguru.com/csharp/csharp/soft-deleting-entities-cleanly-using-entity-framework-6-interceptors.html posted by Rakesh Babu Paruchuri on August 28th, 2015
The sample code link from that blog entry is https://github.com/rakeshbabuparuchuri/EFExpensionPoints
In case those links become unavailable here are the key points:
It uses a custom attribute "SoftDeleteAttribute" with an Entity Framework Interceptor.
The key elements that I included in my own project were:
a class for the SoftDeleteAttribute inherited from System.Attribute
a SoftDeleteQueryVisitor class that inherits from System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DefaultExpressionVisitor
a SoftDeleteInterceptor class that inherits from System.Data.Entity.Infrastructure.Interception.IDbCommandTreeInterceptor
Then you register the interceptor - in my case I put the following code in the same file as my ApplicationDbContext (inherited from IdentityDbContext):
public class ApplicationDbConfiguration : DbConfiguration
{
public ApplicationDbConfiguration()
{
AddInterceptor(new Helpers.SoftDeleteInterceptor());
}
}
And override OnModelCreating to add a convention for dealing with the SoftDeleteAttribute:
var conv = new AttributeToTableAnnotationConvention<SoftDeleteAttribute, string>(
"SoftDeleteColumnName",
(type, attributes) => attributes.Single().ColumnName);
modelBuilder.Conventions.Add(conv);
The final step was adding the SoftDeleteAttribute to my ApplicationUser class.
[SoftDelete("IsDeleted")]
public class ApplicationUser : IdentityUser<int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>, IUser<int>
{
//some code removed to emphasise the important bit
[StringLength(150)]
public string Forenames { get; set; }
[StringLength(50)]
public string Surname { get; set; }
public bool IsDeleted { get; set; }
}
In addition to this I've also dropped and re-created the unique index on the Username column of my users table in the database so that it uses a condition so that I can re-use the usernames of deleted users (not recommended but I'm using an existing database):
CREATE UNIQUE NONCLUSTERED INDEX [UserNameIndex]
ON [dbo].[tbl_user] ([UserName] ASC)
WHERE ([IsDeleted]=(0))
I also ran some migrations - I'm not sure if that migrations step is important for getting it to work, I've literally only done this myself today so haven't had a chance to try it against a manually-created database.
With these changes I can soft-delete users and then create new users with the same username and/or email address
I also found a similar solution at http://marisks.net/2016/02/27/entity-framework-soft-delete-and-automatic-created-modified-dates/ which also uses command interceptors, but replaces the SoftDelete Attribute with a fixed column name and has the code arranged a little differently. He does also include updating Created and Modified columns as well as the soft-delete flag. That article references's Rakesh's article which helped me find it :)

Error: DataContext has been changed, even though I am not changing anything in mvc.net code first.

I am using code first to develop web application using mvc.net.
I have added two fields to IdentityUser like this
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public string ScreenName { get; set; }
public string UserType { get; set; }
}
I use this fields on my first controller
UserId = User.Identity.GetUserId(),
NewUserId = Genrate.GenrateUserId(),
NewUserIdWithString = "Unspecified"
Apart from this, all application works fine but I don't know after adding this things, nothing works and it says, datacontext has been changed, may be there is/are another issue/s .
You changed the database model by adding these two lines:
public string ScreenName { get; set; }
public string UserType { get; set; }
The database does no longer match your code and your application crashes.
It is however very easy to update said database to include these 2 new fields.
Go to the Package Manager console (bottom of VS in default layout) and type
add-migration The console will prompt you for a name for the migration. Fill in a name and next type update-database
The first time you do this you might need to enter Enable-Migrations first
The database will then update to your new schema (note that it doesn't remove any present data and adds default values to non-nullable fields)
You said that you delete the Migrations folder, you can't do it.
Migrations work with comparation, it compare the changes with the last migration, if you have no last migration, the new migration will be empty.
In order to solve that, do the following:
Comment the model;
Comment the lines you added to ApplicationUser;
Comment the new parameters that you added to register or another
page;
Add new migration and update the database;
Uncomment all;
Add new migration and update database.
If you need to enable migrations: Enable-Migrations
To add new migration: Add-Migration <string>
To update the database: Update-Database

MVC - unable to query Identity tables

I am trying to query the AspNetUsers table.
I have set up the db variable as follows
public ApplicationDbContext db = new ApplicationDbContext();
However, the intellisense for db does not list any of the Identity tables, but it does the others created for the web site (which are listed in IdenityModels.cs) e.g .......
public System.Data.Entity.DbSet<FeatureRequestMVC.Models.Feature> Features { get; set; }
How can I get the Identity tables (like AspNetUsers) listed in the db intellisense ?
Thanks
You can use the following code to instantiate the UserManager Class,
UserManager<ApplicationUser> userManager;
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
Once you have added all the using statements required, you can call all sorts of methods to deal with Identity users.
// find user by id, also available for email and username
var user = await userManager.FindByIdAsync(Id param);
// with the user object above you can delete the referenced user
await userManager.DeleteAsync(user);
// Update user
await userManager.UpdateAsync(user);
All methods above use the async and await keyword
MSDN UserManager
Hope this helps.
The identity wants you to go through the UserManager for data, however there are cases where you need to query the identity tables. You can do something like
var sqlQuery = #"SELECT Users.UserName FROM AspNetUserClaims AS Claims
INNER JOIN AspNetUsers AS Users ON Claims.UserId = Users.Id
WHERE Claims.ClaimType = {0} AND Claims.ClaimValue = {1}";
var userName = dbContext.Database.SqlQuery<string>(sqlQuery, "MyClaimType", theClaimValue).ToList().FirstOrDefault();
This is pulling a single UserName and setting the variable as a string. You can create a model and pass that model into SqlQuery<T> as long as the property names match the column or alias names in your query.

ASP.NET MVC - How does WebSecurity work?

I have an ASP.NET MVC 4 project which I have successfully connected to a MySQL database. I have done this by adding a ADO.NET/EntityFramework class which created a Model.edmx object.
Within the database, I have created a table called user which holds what you should expect in a User table such as Email, UserName, Password, FirstName. etc etc.
I have created some dummy records and added the following code to the Login method within the AccountController:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var database = new Database();
user user = database.SelectByUserName(model.UserName).FirstOrDefault<user>();
var hash = Utilities.HashPassword(model.Password, user.Salt);
if (hash == user.Password && WebSecurity.Login(user.UserName, user.Password))
{
//Correct Login Details!
RedirectToAction("About", "Home");
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
For some reason, the WebSecurity.Login method returns false and the user isn't redirected to the Home page.
Why is it returning false? What am I missing and how would the WebSecurity.Login even know what credentials are required i.e. How does it even know that it should look inside the user table which I created?
WebSecurity doesn't default to looking at your database, it will actually make it's own tables using the DefaultConnection that is defined in Web.Config. To work around this you need to add a new connection string Web.Config and then during app initialization force WebSecurity to look at that connection.
The easiest way to accomplish this, assuming you have a MySQL specific connection string in your Web.Config named "AccountConnection" is by adding the following to your Application_Start()
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
And then you'll need the following fields and function:
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
// Overload is: Web.Config Connection string by name, user table name, user id column name, user name column name, auto create missing tables
WebSecurity.InitializeDatabaseConnection("AccountConnection", "UserProfile", "UserId", "Email", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The Membership database could not be initialized.", ex);
}
}
}
Whether you can make WebSecurity work with MySQL I have no idea, though I believe I've read some place that it is supported.
Note: the UserContext should have been auto generated when you installed WebSecurity into your solution. If not it's a CodeFirst model that you can easily add.
There are one of two reasons your code will not work. Understand that WebSecurity and SimpleMembershipProvider (assuming you are using it) uses PBKDF2 algorithm to populate the password field when you call WebSecurity.CreateUserAndAccount or WebSecurity.CreateAccount.
So Either:
You did not use one of these two methods to create the user, in which case WebSecurity.Login will almost always fail (99.99%).
or
You did use one of the methods above and the code in Utilities.HashPassword() (which seems redundant since the Create Account methods listed above hash passwords anyway...) does not hash the password Exactly the same way WebSecurity does so hash == user.Password will always fail.

moving mvc5 account controller code to separate project

I have a solution that contains a WebApi2,MVC5 & DAL project (all RTM).
I am wanting to use the new membership bits that are now baked-in, but I don't like all the account stuff being all in the account controller. Doing a file new project (asp.net) has all of the membership stuff coupled to the account controller.
Within my DAL I am using EF6 as I like the ideal of code-first as it suits what I am trying to do. I am trying to take the account controller code and move it into my separate project.
My context within the DAL is nice and simple (taken from the MVC site)
public class ApplicationUser : IdentityUser
{
//a user can belong to multiple stores
public virtual ICollection<StoreModel> Stores { get; set; }
}
public class DataContext : IdentityDbContext<ApplicationUser>
{
public DataContext(): base("name=DefaultConnection")
{
}
public DbSet<Business> Businesses { get; set; }
public DbSet<ConsumerModel> Consumers { get; set; }
public DbSet<StoreModel> Stores { get; set; }
}
From my account controller within my login actionresult I try
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
It throws an error with User.FindAsync
The entity type ApplicationUser is not part of the model for the
current context.
What do I need to do to allow ApplicationUser to be used in the current context?
I have done something similar. In order to implement separation of concerns, I fetch the UserManager from my Repository and then use it in the Presentation layer. Repository internally creates the UserManager from UserStore using the internal LoginDbContext. That way, the DbContext and Store are separated from the controller.
If you create WebApi project or somthing with VisualStudio template,
please carefully see UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())); in Startup.Auth.cs file.
You might miss (new ApplicationDbContext()) part. By default, it has empty parameter.
You need to create a UserManager which takes in the userstore which takes in your dbcontext
public UserController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
public UserManager<ApplicationUser> UserManager { get; private set; }
public UserController(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
I believe this will help. I am pretty new to MVC5 and I have been wanting to separate my model layer from my MVC website, mainly because I imagine I will want to share the models across my various projects. I have been unable to follow all the programming mumbo jumbo I have found on the various help sites. I always end up with a lot of errors which I am unable to resolve with my limited knowledge. However, I have found an easy way to move my ApplicationDbContext out of my MVC5 model and with hardly any errors. All the work is done by the wizards already provided by Microsoft. I would like to share my little find with everyone. This is what you do (step by step):
1. Create a MVC5 project with authentication. Call it ModelProject.
2. Exclude everything from the project except
a. Properties
b. References
c. Models
d. packages.config
3. The ModelProject will hold all your models (even ApplicationDbContext.) Rebuild it.
4. Now, create a new MVC5 project with authentication. Call this Mvc5Project
5. Import the ModelProject project into Mvc5Project .
6. Wire the ModelProject into this project i.e. link in the reference.
7. Exclude the following from the MVc5Project from the Models folder
a. AccountViewModels.cs
b. IdentityModels.cs
c. ManageViewModels.cs
8. If you rebuild now, you will get a bunch of errors. Just go to the errors and resolve them using the right click method to get the new namespace from ModelProject. The namespace will show if you have wired the project in correctly.
9. Also, dont forget to go to View/Manage and Views/Account of Mvc5Project and change the models in there to the new location otherwise you will get some weird cryptic errors.
That's it! Now you have a project with the models all separated out (including the applicationDbContext) -And NO ERRORS!! Good luck!

Resources