With the new ASP.NET MVC 5 Preview released, how do I configure the Users context/table?
In MVC 4 I would just use my own User class and then point the WebSecurity initialize to it, tike this:
WebSecurity.InitializeDatabaseConnection(connectionString, "System.Data.SqlClient", userTableName, userIdColumn, userNameColumn, autoCreateTables);
I wish to add additional properties to the Users class - how?
I think, this can solve your issue:
In Models \ IdentityModels.cs you can redefine your own User model:
public class ApplicationUser : IdentityUser
{
/* identity field from database */
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[Required]
public bool Internal { get; set; }
public string UserFullName { get; set; }
public string UserEmail { get; set; }
public ApplicationUser()
: base()
{
Internal = false;
}
public ApplicationUser(string userName)
: base(userName)
{
Internal = false;
}
}
now you can change mapping of defaults AspNet tables using OnModelCreating() overridding and ToTable() methode:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Change the name of the table to be Users instead of AspNetUsers
modelBuilder.Entity<IdentityUser>().ToTable("User");
modelBuilder.Entity<ApplicationUser>().ToTable("User");
modelBuilder.Entity<IdentityRole>().ToTable("Role");
modelBuilder.Entity<IdentityUserClaim>().ToTable("User_Claim");
modelBuilder.Entity<IdentityUserLogin>().ToTable("User_Login");
modelBuilder.Entity<IdentityUserRole>().ToTable("User_Role");
}
}
Finally you will see in the database the following tables:
User, Role, User_Role, User_Claim, User_Login instead of AspNetUsers, AspNetRoles, AspNetUsersRoles, AspNetUsersClaims, AspNetUserLogins.
Of course the User table will contain additional fields: UserId (int identity), Internal, UserFullName and UserEmail.
The UserStore and User classes are there to make EF based implementations easier, but you can always drop down and implement your own custom IUserStore and pass in your own DbContext.
I can provide a more detailed example if you need.
You can download a sample from https://github.com/rustd/AspnetIdentitySample. This is based on the ASP.NET MVC template that shipped with ASP.NET and Web Tools 2013 Preview Refresh (Supports English version of VS2013 Preview only) Once you have this Preview Refresh installed you can do the same for ASP.NET Web Forms and SPA applications.
Following are the steps to Run this project
Open the solution
Build and run
Register a user ---- Notice that the user registration field only has user name and password
Let's ask for a birthdate option from the user while registering an account.
Goto Nuget Package Manager console and run "Enable-Migrations"
Goto Models\AppModel.cs and uncomment BirthDate property in the MyUser class
Goto Models\AccountViewModels.cs and uncomment BirthDate property in RegisterViewModel
Goto AccountController and in Register Action and have the following code var user = new MyUser() { UserName = model.UserName,BirthDate=model.BirthDate }; //var user = new MyUser() { UserName = model.UserName };
Goto Views\Account\Register.cshtml and uncomment the HTML markup to add a BirthDate column
Goto Nuget Package Manager console and run "Add-Migration BirthDate"
Goto Nuget Package Manager console and run "Update-Database"
Run the application
When you register a user then you can enter BirthDate as well
Related
I am wondering how to create and assign roles in Razor Pages 2.1. application.
I have found how to make them for MVC application (How to create roles in asp.net core and assign them to users and http://hishambinateya.com/role-based-authorization-in-razor-pages), however it does not work for razor pages as I have no IServicesProvider instance.
What I want is just to create admin role and assign it to seeded administrator account. Something similar has been done in this tutorial https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data?view=aspnetcore-2.1, but it seems be sutied for MVC and does not work properly after I applied it to my application. Please help me to understand how to create and seed roles in Razor Pages.
Will be very greatfull for help!
I handle the task next way. First, I used code proposed by Paul Madson in How to create roles in asp.net core and assign them to users. Abovementioned method I have inserted into Startup.cs. It creates administrator role and assigned it to seeded user.
private void CreateRoles(IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
Task<IdentityResult> roleResult;
string email = "someone#somewhere.com";
//Check that there is an Administrator role and create if not
Task<bool> hasAdminRole = roleManager.RoleExistsAsync("Administrator");
hasAdminRole.Wait();
if (!hasAdminRole.Result)
{
roleResult = roleManager.CreateAsync(new IdentityRole("Administrator"));
roleResult.Wait();
}
//Check if the admin user exists and create it if not
//Add to the Administrator role
Task<ApplicationUser> testUser = userManager.FindByEmailAsync(email);
testUser.Wait();
if (testUser.Result == null)
{
ApplicationUser administrator = new ApplicationUser
{
Email = email,
UserName = email,
Name = email
};
Task<IdentityResult> newUser = userManager.CreateAsync(administrator, "_AStrongP#ssword!123");
newUser.Wait();
if (newUser.Result.Succeeded)
{
Task<IdentityResult> newUserRole = userManager.AddToRoleAsync(administrator, "Administrator");
newUserRole.Wait();
}
}
}
Then, in the same file in Configure method I add argument (IServiceProvider serviceProvider), so you should have something like Configure(..., IServiceProvider serviceProvider). In the end of Configure method I add
CreateRoles(serviceProvider).
To make this code work create ApplicationUser class somwhere, for example in Data folder:
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sobopedia.Data
{
public class ApplicationUser: IdentityUser
{
public string Name { get; set; }
}
}
Finally, inside ConfigureServices method substitute
services.AddIdentity<ApplicationUser>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
with
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
As a result, after programm starts in table AspNetRoles you will get a new role, while in table AspNetUsers you will have a new user acuiering administrator role.
Unfortunatelly, after you add the following code
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<SobopediaContext>()
.AddDefaultTokenProviders();
pages Login and Registration stop working. In order to handle this problem you may follow next steps:
Scaffold Identity following (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-2.1&tabs=visual-studio).
Then substitute IdentityUser for ApplicationUser in entire solution. Preserv only IdentityUser inheritance in ApplicationUser class.
Remove from Areas/identity/Pages/Account/Register.cs all things related to EmailSernder if you have no its implementation.
In order to check correctness of the roles system you may do as follows. In the end of ConfigureServices method in Startup.cs add this code:
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/Contact","RequireAdministratorRole");
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
If it does not worki then just add [Authorize(Roles = "Administrator")] to Contact Page model, so it will look something like this:
namespace Sobopedia.Pages
{
[Authorize(Roles = "Administrator")]
public class ContactModel : PageModel
{
public string Message { get; set; }
public void OnGet()
{
Message = "Your contact page.";
}
}
}
Now, in order to open Contact page you should be logged in with login someone#somewhere.com and password _AStrongP#ssword!123.
Building a custom IUserPasswordStore to connect to a legacy system's username/password table. The password is hashed with custom code in the table so I need to write custom code for PasswordSignInAsync.
Do I need to override PasswordSignInAsync or is there a method I can provide that just does the hashing of the password? If I do override the entire PasswordSignInAsync is there sample code somewhere showing me what needs to be done in the method?
That was easier than I thought.
Override CheckPasswordAsync in UserManager.
For someone who wants to see the complete setup in .NET 6, this is how it looks like:
Step 1:
Add CustomUserManager to override CheckPasswordAsync:
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
public class CustomUserManager<TUser> : UserManager<TUser> where TUser : IdentityUser
{
public CustomUserManager(IUserStore<TUser> store, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<TUser> passwordHasher, IEnumerable<IUserValidator<TUser>> userValidators,
IEnumerable<IPasswordValidator<TUser>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<TUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer,
errors, services, logger)
{
}
// THIS IS ONLY CALLED FOR USERS STORED IN YOUR IDENTITY DATABASE
public override Task<bool> CheckPasswordAsync(TUser user, string password)
{
// Add custom check using user.UserName and password
return Task.FromResult(true); // Replace this with your custom check
}
}
Step 2:
Register it in your Program.cs
builder.Services
.AddDefaultIdentity<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
})
.AddUserManager<CustomUserManager<ApplicationUser>>() <----- THIS GUY
.AddEntityFrameworkStores<ApplicationDbContext>();
ApplicationUser and ApplicationDbContext look like this:
public class ApplicationUser : IdentityUser
{
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
Step 3:
Try to Login using this:
var result = await _signInManager.PasswordSignInAsync("SomeUserNameInYourIdentityDatabase", "SomePassword", isPersistent: true, lockoutOnFailure: false);
I have a class in my MVC5 application that deals with some user related functionality and has a dependency on HttpContext.Current.User as shown below
public interface IUser
{
// return roles of currently logged in user
string[] GetRoles;
}
public Class User : IUser
{
private HttpContext context;
// constructor
public User(HttpContext user)
{
this.context = user
}
// get roles
public string[] GetRoles()
{
string username = this.context.User.Identity.Name;
// get roles through some DB calls
string[] roles = someDbCalls();
return roles;
}
}
I have it setup for dependency injection using Ninject in NinjectWebCommon.cs as
kernel.Bind<IUser>().To<User>().WithConstructorArgument("user", x => HttpContext.Current);
This works fine if called from anywhere in my code except in my custom RolesProvider which is setup as shown below
public class CustomRoleProvider : RoleProvider
{
[Inject]
public IUser user {get; set;}
public override string[] GetRolesForUser(string username)
{
return this.user.GetRoles();
}
}
The call to GetRoles() from my custom role provider fails because HttpContext.Current.User injected by Ninject under this case is null. Any idea on what I may be doing wrong?
Edit:
On further testing, it appears that the problem is with the way I am using Ninject in my custom Roles provider. Using the attribute injection as shown below
[Inject]
public IUser user {get; set;}
works only the first time and subsequent calls fail with HttpContext.Current.User is null error. I have fixed it in a hacky way by forcing the injection to happen each time I call the GetRoles method as shown below
public class CustomRoleProvider : RoleProvider
{
private IUser user;
public override string[] GetRolesForUser(string username)
{
// force ninject to inject a new instance of my interface
var user = DependencyResolver.Current.GetService<IUser>();
return user.GetRoles();
}
}
Not sure why this works and so I am leaving this question open if someone can provide an explanation.
It appears that by the time the role provider is called, the HTTPContext.Current is not yet set. This leads to other issues with custom RolesProvider (like the Null Reference Exception due to EtwTracing bug see: SqlRoleProvider on IIS8 Express
).
If you really need the HTTPContext.Current instead of using the Thread's PrincipalIdentity, you can setup your app to use compatibility mode. This appears to resolve the problem by setting up the HttpContext.Current sooner:
https://social.msdn.microsoft.com/Forums/en-US/8ee88c92-5e8a-4c66-ace7-887eb500e1cb/httpcontextcurrent-always-been-null
So I am trying to grasp EF6 and it's use of Identity 2.0 for making a many to many relationship. It is Visual Studio 2013 and the MVC 5 template.
I have a fresh MVC app with the following models:
public class Meeting
{
public Guid MeetingID { get; set; }
public string Title { get; set; }
public virtual ICollection<ApplicationUser> Attendees { get; set; }
}
public class ApplicationUser : IdentityUser
{
public ICollection<Meeting> Meetings { get; set; }
}
Then I scaffold a controller and views for Meetings. Now, for instance, if I just wanted to add every user as an attendee to my meeting, I would imagine that I could modify the Create action to look like the following:
public ActionResult Create(Meeting meeting)
{
if (ModelState.IsValid)
{
meeting.MeetingID = Guid.NewGuid();
db.Users.ForEachAsync(u => meeting.Attendees.Add(u));
db.Meetings.Add(meeting);
db.SaveChanges();
return RedirectToAction("Index");
}
else...
}
However I don't think it's working because I don't see it in my LocalDB and if I add this to the detail view for a meeting I get no results:
#{foreach (var item in Model.Attendees)
{
<li>#item.UserName</li>
}}
As a final note, I have two users in the LocalDB, test and test2.
What tutorial or documentation will allow me to make this work?
* Edit *
So I have tried your suggestion (I'll admit, I am unfamiliar with async and await and how to implement it), and I had to modify the controller to allow me to use await so I'm not sure if I'm doing this correctly now, but I got the following to compile and I get run time error of 'object reference not set to an instance of an object' :
public async Task<ActionResult> Create(Meeting meeting)
{
if (ModelState.IsValid)
{
meeting.MeetingID = Guid.NewGuid();
await db.Users.ForEachAsync(u => meeting.Attendees.Add(u));
db.Meetings.Add(meeting);
db.SaveChanges();
(is it possible I'm missing some setup of my model on Entity Framework? The project is exactly the code shown above plus defaults.)
You're going to kick yourself :)
(Drumroll)
You forgot to add await before your ForEachAsync line:
await db.Users.ForEachAsync(u => meeting.Attendees.Add(u));
Without await the application happily continues on and saves the record, all before that async process has completed.
UPDATE
Most likely you haven't initialized the Attendees collection. Just set it to a new List<ApplicationUser> in your constructor.
I recently updated the nuget package for Microsoft.WindowsAzure.Storage to the 3.0 package which also included updates to WCF Data Services Client and it's dependencies. Since the update I get an error when the query is resolving stating:
"There is a type mismatch between the client and the service. Type
'ShiftDigital.Flow.Data.RouteDiagnostic' is not an entity type, but
the type in the response payload represents an entity type. Please
ensure that types defined on the client match the data model of the
service, or update the service reference on the client."
I've done nothing but update the packages and both my application along with a test script I setup in LinqPad generate this exception.
Here is the definition of the entity I've been returning just fine before the update
public class RouteDiagnostic : TableEntity
{
public long? LeadRecipientRouteId { get; set; }
public bool Successful { get; set; }
public int Duration { get; set; }
public string Request { get; set; }
public string Response { get; set; }
public RouteDiagnostic()
: base()
{
this.Timestamp = DateTimeOffset.Now;
this.PartitionKey = GetPartitionKey(this.Timestamp.Date);
this.RowKey = Guid.NewGuid().ToString();
}
public static string GetPartitionKey(DateTime? keyDateTime = null)
{
return string.Format("{0:yyyyyMM}", keyDateTime ?? DateTime.Now);
}
}
Here is the code performing the query
var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse("...");
var tableClient = storageAccount.CreateCloudTableClient();
var tableContext = new Microsoft.WindowsAzure.Storage.Table.DataServices.TableServiceContext(tableClient);
var diagnostics =
tableContext.CreateQuery<RouteDiagnostic>("RouteDiagnostic")
.Where(rd => rd.PartitionKey == "0201401")
.ToList();
Has something changed that in the latest update or a different way to structure the entities when using data service queries?
Turns out with the update to WCF Data Services 5.6 I needed to add the following attribute to my type:
[DataServiceKey("PartitionKey", "RowKey")]
Once I added the DataServiceKey attribute, all was well again.
When using WCF Data Services, please make your class inherit from TableServiceEntity rather than TableEntity, which already has the DataServiceKey attribute defined. TableEntity is used for the new Table Service Layer in the Windows Azure Storage Client Library. For more information on the new Table Service Layer, please see our blog post.