Connecting AspNetUsers to customers - asp.net-mvc

I want administrators to be able to assign AspNetUser to a specific customer. The relationship is supposed to be that multiple logins (AspNetUsers) have a foreign key to the same customer. To do so i added the following property to my identityModel.
public virtual Customer Customer { get; set; }
The column is added to the db; so far so good.
When I try to update the ApplicationUser entity with an instance of an ApplicationUserManager no exception is thrown and the view is returned. Nonetheless no changes to the db. When debugging i see that the customer gets set for the user, so I'm wondering if there is anything I'm missing?
public async Task<ActionResult> Edit([Bind(Include = "Id,Email,Customer")] ApplicationUser user)
{
if (ModelState.IsValid)
{
ApplicationUserManager applicationUserManager = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
var customerName = Request.Form["customerSelect"];
var customer = db.Customers.SingleOrDefault(c => c.Name == customerName);
user.Customer = customer;
await applicationUserManager.UpdateAsync(user);
return RedirectToAction("index");
}
return View(user);
}
I thought of creating a new entity that maps AspNetUser.Id to Customer.Id, but i would prefer not to.

Related

ASP .NET MVC Identity users multiple Groups , multiple roles

I'm looking to create an ASP.NET 6 Core Identity solution where users can create groups.
The creator becomes admin of that group and can Crud other users to the group - (the other users may then become an admin of the group).
The creator also needs to exist in other groups with and without admin.
Is there an easy way to do this in Identity using roles as groups with policies/claims or are some new tables required? like this
https://www.codeproject.com/Tips/5276188/Implementing-User-Groups-using-Claims-in-ASP-NET-I?fid=1962554&df=90&mpp=25&sort=Position&view=Normal&spc=Relaxed&prof=True
Using claims by itself is enough for authorization (e.g. is this user an admin, can he promote this other user to admin status). But using claims limits you to string types, which could take arbitrary values. This puts the burden on you for making sure every value is as it's supposed to be.
To have a more sound DB schema, you should store memberships in a separate table. This means creating a many-to-many relation between User & Group to store membership, which would necessitate a lookup table, but seeing how you're using ASP.NET Core 5, and likely EF Core 5, it creates this table for you.
But likely you'd need to store when and by whom a user has been added to a group. This would mean storing some data on the lookup table. So if you need those, you should create an entity for that lookup table too.
As for the code, here's a starting point for you:
public record Group(string Name)
{
public const string AdminClaimName = "group_admin";
public Guid Id { get; init; } = Guid.Empty;
public List<IdentityUser> Members { get; set; } = new List<IdentityUser>();
}
[ApiController]
public class UserGroupController : ControllerBase
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ApplicationDbContext _db;
public UserGroupController(UserManager<IdentityUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_db = db;
}
public record GroupCreateRequest(string GroupName);
[HttpPost]
public async Task<IActionResult> CreateGroup(GroupCreateRequest request,
CancellationToken cancellationToken = default)
{
var user = await _userManager.GetUserAsync(User);
var group = new Group(request.GroupName);
await _db.Set<Group>().AddAsync(group, cancellationToken);
await _db.SaveChangesAsync(cancellationToken);
await _userManager.AddClaimAsync(user, new Claim(type: Group.AdminClaimName, value: group.Id.ToString()));
return Ok(group);
}
public record GroupMembershipRequest(string MemberUserId);
[HttpPost("{id:guid}/membership")]
public async Task<IActionResult> AddUserToGroup(Guid groupId,
GroupMembershipRequest request,
CancellationToken cancellationToken = default)
{
var user = await _userManager.GetUserAsync(User);
var claims = await _userManager.GetClaimsAsync(user);
// check if the current user is the admin of that group
if (claims.Any(c => c.Type == Group.AdminClaimName && c.Value == groupId.ToString()))
{
return Forbid();
}
var member = await _userManager.FindByIdAsync(request.MemberUserId);
var group = await _db.Set<Group>().FindAsync(groupId);
group.Members.Add(member);
await _db.SaveChangesAsync(cancellationToken);
return NoContent();
}
public record UserPromotionRequest(Guid GroupId, string MemberUserId);
[HttpPost("promote")]
public async Task<IActionResult> PromoteUserToAdmin(UserPromotionRequest request,
CancellationToken cancellationToken = default)
{
var user = await _userManager.GetUserAsync(User);
var claims = await _userManager.GetClaimsAsync(user);
// check if the current user is the admin of that group
if (claims.Any(c => c.Type == Group.AdminClaimName && c.Value == request.GroupId.ToString()))
{
return Forbid();
}
var member = await _userManager.FindByIdAsync(request.MemberUserId);
var group = await _db.Set<Group>().FindAsync(request.GroupId);
await _userManager.AddClaimAsync(member, new Claim(type: Group.AdminClaimName, value: group.Id.ToString()));
return NoContent();
}
}
(I've omitted some validations & null checks, you need to add those yourself.)
Notice the difference between User property of the controller (the user currently logged in) and user instance fetched from the database.

How to cache and get properties from extended Identity, AspUser in ASP Identity 2.2

To support tenan/companies, I added/extended a new property to the AspUser Table called OrgId in ASP MVC 5, Identity 2.2 role management, I added the corresponding OrgId to some other tables, looked here but no answers
During User Login() and the UserSession
how do I cache, configure & retrieve the OrgId, so that I can perform DBConext filtering/CRUD of table for Org specific records?
Advice: is better to save this in the Claims, FormsToken or Session - and
how to set the tenanId context in session?
I know how to get user, but not the extended Org
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>().FindById(System.Web.HttpContext.Current.User.Identity.GetUserId());
Your customized user class should be like this:
public class CustomizedUser : IdentityUser
{
public int OrgId {get; set;}
public DateTime JoinDate { get; set; }
//...
// and other simple fields
//Fields related to other tables
public virtual ICollection<Article> Articles { get; set; } = new List<Article>();
//...
}
And your CustomizedApplicationDbContext class
public class CustomizedApplicationDbContext : IdentityDbContext<CustomizedUser>, IApplicationDbContext
{
public CustomizedApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static CustomizedApplicationDbContext Create()
{
return new CustomizedApplicationDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//your entity configurations go here if you have any
base.OnModelCreating(modelBuilder);
}
//These fields are neccessary in order to maintain the power of base class
public DbChangeTracker Changetracker => base.ChangeTracker;
public Database DatabBase => base.Database;
//your own dbsets go here except CustomizedUser, because the base class IdentityDbContext<CustomizedUser> handles it
public IDbSet<Article> Articles { get; set; }
//...
}
Now, Remember to replace every ApplicationDbContext references with CustomizedApplicationDbContext and every IdentityUser refrences with CustomizedUser in your code (specially in your ManageController created by mvc).
From now on, you can easily access users table just like other tables:
var db = new CustomizedApplicationDbContext();
var user = db.CustomizedUsers.First(x=> x.OrgId == 10 || Email == "sth#yahoo.com");
And to get the current user OrgId, you can use something like this(without querying db):
var currentUserOrgId = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>().FindById(User.Identity.GetUserId()).OrgId;
Hope this was helpful
You can get the current user in ASP.NET Identity as shown below:
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.FindById(System.Web.HttpContext.Current.User.Identity.GetUserId());
//If you use int instead of string for primary key, use this:
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.FindById(Convert.ToInt32(System.Web.HttpContext.Current.User.Identity.GetUserId()));
For getting custom properties from AspNetUsers table:
ApplicationUser user = UserManager.FindByName(userName);
string name = user.Name;
Hope this helps...

Submitting form data to multiple tables. NullReferenceException

Hey could someone please help me submit this form? Here is my ViewModel that I made. This is my third day trying to pick up MVC so I'm still new to this.
public class EmployeeAllData
{
public Employees Employees { get; set; }
public PermissionModel PermissionModel { get; set; }
}
Here is my controller for the form submit. I'm starting with the permission table and I'm not having any luck. It keeps giving me this error: NullReferenceException was unhandled by user code. I tried hard coding values and it updated the permissions table just fine. I can't find out why I'm not getting a value back from Employees.
[HttpPost]
public ActionResult Create(EmployeeAllData viewModel)
{
var permission = new PermissionModel
{
EmployeesId = Convert.ToByte(viewModel.Employees.Id),
TimeStamp = DateTime.Now,
PermissionVal = viewModel.Employees.Permissions
};
_context.PermissionModels.Add(permission);
_context.SaveChanges();
return RedirectToAction("EmployeeList", "Employee");
}
Any ideas?
UPDATE
I think my problem is with my ViewModel. The code below runs fine and creates a new employee in the database.
public ActionResult Create(Employees employees)
{
_context.Employeeses.Add(employees);
_context.SaveChanges();
return RedirectToAction("EmployeeList", "Employee");
}
I did not need to use a viewModel. Here is my final code that now works.
[HttpPost]
public ActionResult Create(Employees employees)
{
_context.Employeeses.Add(employees);
_context.SaveChanges();
var permission = new PermissionModel()
{
EmployeesId = Convert.ToByte(employees.Id),
TimeStamp = DateTime.Now,
PermissionVal = employees.Permissions
};
_context.PermissionModels.Add(permission);
_context.SaveChanges();
return RedirectToAction("EmployeeList", "Employee");
}

Editing some properties of View Model in ASP.NET MVC

I'm using Entity Framework Database First approach. Let's say I have a model class called Product and that class has a NumberOfViews property. In the Edit page I pass an instance of the product class to the controller.
The problem is I can't add #Html.EditorFor(model => model.NumberOfViews) in the Edit page, because it's supposed that NumberOfViews is updated with every visit to the product page, and NOT by the website Admin.
And I can't add it as #Html.HiddenFor(model => model.NumberOfViews), because if the Admin Inspected the element, he can edit it manually.
Also If I try to programmatically set the value on the server-side (e.g., Product.NumberOfViews = db.Products.Find(Product.Id).NumberOfViews;), I get the following error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
And if I don't add it to either the view or the controller, the value will be null, thus overriding any previous value.
So what should I do?
I have noticed a lot of people use the same model for their Entity Framework as they do for their MVC Controller. I generally discourage this practice. In my opinion, a database model is not the same as a view model.
Sometimes a view needs less information than what the database model is supplying. For example while modifying account password, view does not need first name, last name, or email address even though they may all reside in the same table.
Sometimes it needs information from more than one database table. For example if a user can store unlimited number of telephone numbers for their profile, then user information will be in user table and then contact information with be in contact table. However when modifying user profile, they may want to add/edit/delete one or more of their numbers, so the view needs all of the numbers along with first name, last name and email address.
This is what I would do in your case:
// This is your Entity Framework Model class
[Table("Product")]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string Name { get; set; }
public int NumberOfPageViews { get; set; }
}
// This is the model you will use in your Edit action.
public class EditProductViewModel
{
public int ProductId { get; set; }
public string Name { get; set; }
}
public class ProductController : Controller
{
IProductService service;
//...
[HttpGet]
public ActionResult Edit(int productId)
{
var product = service.GetProduct(productId);
var model = new EditProductViewModel()
{
ProductId = product.ProductId,
Name = product.Name
};
return View(model);
}
[HttpPost]
public ActionResult Edit(EditProductViewModel model)
{
if (ModelState.IsValid)
{
var product = service.GetProduct(model.ProductId);
product.Name = model.Name;
service.Update(product);
}
// ...
}
}

How to check in Razor view if ApplicationUser.Roles contains certain role?

I have 3 roles in my webapp: Admin,Moderator,User.
I have a user #model WebApplication2.Models.ApplicationUser I want to check inside Razor view if user.Roles contains role Moderator. How to do so? I tried #if(#Model.Roles.Contains(DON'T_KNOW_WHAT_TO_WRITE_HERE){}.
NOTE: I am not asking how to check if currently authorized user is in certain role.
What you could do is create an extension method on IPrincipal that operates the same way as User.IsInRole(...)
public static bool IsInAppRole(this IPrincipal user, string role)
{
using(var db = new MyEntities())
{
var dbUser = db.Users.Find(user.Identity.GetUserId());
return dbUser.Roles.Any(r => r.RoleName == role)
}
}
Import extensions into a view
#using MyApplication.Web.Extensions
Use like you would IsInRole()
#if(User.IsInAppRole("Admin"))
{
}
Though, not sure why you'd do this as the user's roles can be put into their Identity object.
The simplest way that I could find is to use:
#Model.Roles.SingleOrDefault().RoleId
This, of course, requires you to work with the ID rather than the name in your comparison. Without creating a new ViewModel, I have not found a good way to get direct access to the name.
EDIT: If multiple roles are assigned, you should be able to do something like this:
#{
var isMod = false;
}
foreach (var r in Model.Roles)
{
if(r.RoleId == "1")
{
isMod = true;
}
}
I think you should use User.IsInRole(...) in your view code
Why don't you just print them out to preview possible values?
Your code seems to have minor bug to me. I believe it should be
#if(Model.Roles.Contains(DON'T_KNOW_WHAT_TO_WRITE_HERE){}
(just one '#')
EDIT:
Since Model.Roles are just plain Many-To-Many references, you need to call UserManager to obtain user roles. For example:
public class UserDetailsModel {
public string Id { get; set; }
public ApplicationUser User { get; set; }
public IList<string> Roles { get; set; }
}
and Details action in controller:
public ActionResult Details(string id) {
var model = new UserDetailsModel
{
Id = id,
User = UserManager.FindById(id),
Roles = UserManager.GetRoles(id)
};
return View(model);
}
You can get UserManager from OwinContext or inject it in controller:
private readonly ApplicationUserManager _userManager;
public UsersController(){}
public UsersController(ApplicationUserManager userManager) {
_userManager = userManager;
}
public ApplicationUserManager UserManager {
get {
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
}

Resources