Creating 2 sides relationship between a model to the default ApplicationUser of a basic MVC 5 project. Working with 2 dbContext classes - asp.net-mvc-5

I believe it's suppose to be a trivial issue, but I didn't find any relevant answer.
I am working on a MVC 5 project (created by the default VS template with Identity).
I have a Post model. Only an Authorized "user" ("user" is the default ApplicationUser class provided by the EF template when creating the project) can create a post.
I need to Extend the post and the user models in that way:
-Every Post will have the user (who created it)
-Every user will have all the Posts were created by him.
In other words, I need a one to many relationship between Post and users.
(my goal is that only post's owner will be able to edit it. Moreover, every "user" can get a list of his posts )
I use EntityFramework 6.1.2 with Identity 2.0 (or 1.0.. not sure how to check?).
The problem:
I have 2 dbContext classes pointing to two different db tables:
1) ApplicationDbContext - The default DbContext provided by the EF when the project was created and points to the AspNetUsers table.
2) MrSaleDB - The DbContext which points to the Posts db-table (and other tables in my model like Galleries and etc. )
So how should I extend the Post and user classes?
How can I compare the current user identity to the user who was saved in the extended Post class (in the PostController while user edit a post) ?
Everything I tried didn't work )-;
Here are some code from my project, thanks:
Models:
namespace MrSaleBeta01.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
**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;
}
//ADDED:
public ApplicationUser ()
{
Posts = new List<Post>();
}
public ICollection Posts { get; set; }
}
**public class Post**
{
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public virtual Gallery Gallery { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
// Navigation property:
public virtual Category Category { get; set; }
//ADDED:
public virtual ApplicationUser PublishedBy { get; set; }
}
}
DbContexts:
namespace MrSaleBeta01.Models
{
/*
* First (default) DbContext, points to users tables:
*/
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
...
}
/*
* Second DbContext, points to my models tables:
*/
public class MrSaleDB : DbContext
{
public MrSaleDB() : base("name=MrSaleDB")
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<AreaCode> AreaCodes { get; set; }
public DbSet<Gallery> Galleries { get; set; }
...
}
}
PostsController:
// POST: Posts/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Edit(Post post)
{
try
{
if (ModelState.IsValid)
{
...
...
...
//General: save post:
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DbEntityValidationException ex)
{
}
}

I don't think its a good idea having this application spread across 2 DbContexts.
Take a look at this similar article posted on here :-
ASP.NET Identity DbContext confusion

So I want to answer my own question based on the excellent help I got here (stackoverflow is a life saver.. ):
1) I followed # Steve Greene and Derek suggestion for not having 2 dbcontexts but to merge them to one which inherit from IdentityContext in the following steps:
1A) Deleting any old migrations and dbs
1B) In Web.config file, under connectionStrings element: remove the 2 lines of the old connectionStrings and add your new connectionString as follow:
<connectionStrings>
<add name="SaleConn" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=SaleConn; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
</connectionStrings>
1C) Merge the 2 dbcontexts into one class as this:
namespace MrSaleBeta01.Models
{
public class MrSaleDB : IdentityDbContext<ApplicationUser>
{
public MrSaleDB()
: base("SaleConn", throwIfV1Schema: false)
{
}
...
public DbSet<Post> Posts { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<AreaCode> AreaCodes { get; set; }
public DbSet<Gallery> Galleries { get; set; }
...
}
}
2) Establishing one-to-many relationship by adding to the ApplicationUser class this property:
public virtual ICollection<Post> Posts { get; set; }
and by adding to my model class (Post) the following properties:
[ForeignKey("ApplicationUser")]
public string ApplicationUserId { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
3) Enabling Migrations for project by writing to the PM console:
>Enable-Migrations
>add-migration init
>update-database
4) In the Post controller add using Microsoft.AspNet.Identity;
for enabling you to get the user-id in the next step. more info..
5) Update the Create method of the Post controller for saving the user id to the new post:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Create( Post post)
{
try
{
if (ModelState.IsValid)
{
post.ApplicationUserId = User.Identity.GetUserId();
...
...
...
}
}
}
6) Update the Edit method of the Post controller to check whether the current user is the owner of the edited post:
[Authorize]
public ActionResult Edit(int? id)
{
try
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Post post = db.Posts.Find(id);
if (post == null)
{
return HttpNotFound();
}
if( !User.IsInRole("Admin") && (post.ApplicationUserId != User.Identity.GetUserId()) )
{
return RedirectToAction("Index");
}
....
}
}
}
That's it. thanks again for everyone here. You helped me so much (-;

Related

How to get a subset of Users from ASP.NET Identity 2

Using a slightly modified version of the default ASP.NET MVC 5 template (with Individual Accounts), I am trying to get a subset of users based on an intermediary table. I have already built up an administration UI that can return a list of all users, but now I need to limit the set of users returned based on the currently logged in user's access privileges defined in the intermediary table.
Essentially, each user will have access to 1 or more clinics, so there will be one record for each clinic to which they have access.
If the currently logged in user belongs to a given role (e.g., "Clinic Admin"), then they should have the ability to retrieve a list of any users who belong to any of the clinics to which they have access.
Can anyone help point me in the right direction? This is my first Anything.NET application, so please feel free to explain like I'm five. :-)
Thank you in advance for any help you can offer.
Additional information:
Visual Studio 2013 Update 5
Entity Framework 6
MS SQL Server 2008 R2
Here is the intermediary table's class (ClinicUser):
[Table("clinic_users")]
public class ClinicUser
{
[Key]
public virtual ApplicationUser ApplicationUsers { get; set; }
[Required]
public string Id { get; set; }
[Required]
public System.Guid provider_id { get; set; }
[Required]
public System.Guid health_system_id { get; set; }
[Required]
public System.Guid clinic_id { get; set; }
}
Here is my ApplicationUser class:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return FirstName + " " + LastName; }
}
[ForeignKey("ClinicUsers")]
public override string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
public virtual ClinicUser ClinicUsers { get; set; }
public IEnumerable<SelectListItem> RolesList { get; set; }
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.AddClaims(ClinicClaimsProvider.GetClaims(userIdentity));
return userIdentity;
}
}
In case it wasn't clear, what I'm really trying to do is narrow the list of ApplicationUsers to return only the list of users to which I have access to based on the clinics we have have in common.
If I were writing this as a SQL query, this would be one way to accomplish what I want (I just can't seem to quite get what I want with LINQ):
SELECT *
FROM AspNetUsers au
WHERE Id IN (
SELECT Id
FROM clinic_users
WHERE clinic_id IN (
SELECT clinic_id
FROM clinic_users
WHERE Id = 'CurrentUserId'
)
)
First of all do not user much properties in ApplicationUser class, you can manage user profiles table and connect it with application user class, so you can put lot of information about user in profile table.
Next task is organize table of clinics, branches etc... and asociate application users with them.
Next you have 2 ways:
1. asociate application users with clinics or branches.
or
2. Manage them with roles.
Here is example with Application users:
[Table("Clinics")]
public class Clinic
{
[Key]
public string Id { get; set; }
public virtual ICollection<ClinicUser> ClinicUsers { get; set; }
}
[Table("ClinicUsers")]
public class ClinicUser
{
[Key]
[Column(Order = 0)]
public string ClinicId { get; set; }
[Key]
[Column(Order = 1)]
public string UserId { get; set; }
}
So next you need Other ViewModels to display them hope this help.
UPDATE
// GET: ClinicUsers by Clinic
public async Task<ActionResult> ViewCurrentClinicUsers(string id) // This is clinis ID
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Clinic clinic = await db.clinic.FindAsync(id); // Get Selectedclinic
if (clinic == null)
{
return HttpNotFound();
}
ClinicUsers model = new ClinicUsers() // ClinicUsers model
{
clinic = clinic, // View Currentclinic
ClinicUsers = await db.ClinicUsers.Were(x => x.clinicid == clinic.id)ToListAsync()) // Get Users that asigned to current clinic
};
return View(model);
}
UPDATE 2
And Finaly if you want display clinics were is assigned current loged user
// GET: Clinics by currentuser
public async Task<ActionResult> ViewClinicsWithCurrentUserAccess()
{
var currentuserId = User.Identity.GetUserId(); // This gets currentloged user id
var currentuser = await db.Users.SingleOrDefaultAsync(x => x.Id == myUserId); // This gets currentloged user virtual
return View(await db.Clinics.Were(x => x.clinicuserid == currentuserId).ToListAsync());
}
I solved this a while back, but I thought I had better come back here and update my question with an answer, in case this might help someone else.
I updated my Clinic and ClinicUser classes accordingly:
Clinic.cs
[Table("clinics")]
public class Clinic
{
[Key]
public System.Guid ClinicId { get; set; }
public List<ClinicUser> ClinicUsers { get; set; }
}
ClinicUser.cs
[Table("clinic_users")]
public class ClinicUser
{
[Key, Column(Order = 0)]
public string UserId { get; set; }
[Key, Column(Order = 1)]
public System.Guid ClinicId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser ApplicationUser { get; set; }
[ForeignKey("ClinicId")]
public Clinic Clinic { get; set; }
}
Also, I updated the following excerpt of my ApplicationUser class from this:
[ForeignKey("ClinicUsers")]
public override string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
public virtual ClinicUser ClinicUsers { get; set; }
to this:
public List<ClinicUser> ClinicUsers { get; set; }
Finally, in my ApplicationUsersController's Index() action, I was able to use this:
public async Task<ActionResult> Index()
{
if (User.IsInRole("Admin")) return View(await UserManager.Users.ToListAsync());
var userId = User.Identity.GetUserId();
//Get the Ids of the current user's clinics
var userClinics = db.ClinicUsers.Where(cu => cu.UserId == userId).Select(cu => cu.ClinicId).ToList();
//Get all userIds of the user at the current user's clinics
var clinicUserIds = db.ClinicUsers.Where(cu => userClinics.Contains(cu.ClinicId)).ToList().Select(cu => cu.UserId);
var users = UserManager.Users.Where(u => clinicUserIds.Contains(u.Id));
return View(await users.ToListAsync());
}
In essence, if the user has the "Admin" role, then they will get a list of all users in the database. If they aren't, they will only get a list of the users that also belong to the clinics they have in common.
It may not be perfect, but it works. If anyone has any suggestions on how to improve this, I would be glad to hear it.
Again, my thanks to Archil (https://stackoverflow.com/users/4089212/archil-labadze) for his helpful responses.

CustomPrincipal.IsInRole in ASP.NET MVC5

I am working in ASP.NET MVC 5 and I am using ASP.NET Identity. I have followed LukeP's solution here to get access to my ApplicationUser custom properties (e.g. User.DisplayUsername or User.DOB). Like Luke has suggested, I now have a custom IPrincipal implementation (basically exact same code as him).
This has a problem however, and I suspect it is do with with this line of code on the CustomPrincipal class:
public bool IsInRole(string role) { return false; }
I have a controller called ReviewController and on there I have this:
[Authorize(Roles = "Admin")]
public class ReviewController : Controller
{
// controller stuff
}
This isn't working. Even though the user I am logged in as is of role Admin. So I tried improving the code by doing this to the IsInRole method:
public class CustomPrincipal : ICustomPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role)
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new BBContext()));
return roleManager.Roles.All(r => r.Name == role);
}
public CustomPrincipal(string email)
{
this.Identity = new GenericIdentity(email);
}
public string Id { get; set; }
public string DisplayUsername { get; set; }
public DateTime DOB { get; set; }
}
This has improved in the sense that I am now served the ReviewController. However it is still wrong because even user that are not in the Admin role are also allowed access. I know why that is too, but just don't know how to fix this.
How can I get it to work as it should?

Merge ApplicationDbContext and DataContext into a single context

I am having a problem merging these 2 contexts.
I deleted this line of code from IdentityModels.cs:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
I implemented this inside my DataContext.cs
public class DataContext : DbContext
{
public DataContext()
: base("name=DefaultConnection")
{
}
// Account tables
public DbSet ApplicationUsers { get; set; }
public DbSet Roles { get; set; }
public DbSet Tokens { get; set; }
public DbSet UserClaims { get; set; }
public DbSet UserLogins { get; set; }
public DbSet UserManagements { get; set; }
public DbSet UserRoles { get; set; }
public DbSet UserSecrets { get; set; }
// App Models
public DbSet<Course> Courses { get; set; }
public DbSet<Student> Students { get; set; }
}
Problem:
When I "update-database" it only created the Courses and Students tables.
Question
If I successfully implement this method will I lose all of the nice methods that IdentityDbContext interface offers for example:
var rm = new RoleManager<IdentityRole>(
new RoleStore<IdentityRole>(new ApplicationDbContext()));
return rm.RoleExists(name);
Ok here is the solution that I found posted by Olav Nybo in this topic How can one put application users in the same context as the rest of the objects?.
Go to his sample project on github: https://github.com/onybo/Asp.Net-Identity-sample-app/tree/master/CustomUser/CustomUser
Download the configurations folder from the Models folder and place the folder inside your models folder.
Inside your DataContext file you will put this snippet of code which will call these configuration files to build out your database.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (modelBuilder == null)
throw new ArgumentNullException("modelBuilder");
modelBuilder.Configurations.AddFromAssembly(typeof(Generic_Repo_Template.Models.Configurations.ApplicationUserConfiguration).Assembly);
}
Now that your database is created you still do not have access to these tables through the datacontext object. In order to be able to access these tables through your datacontext is to include these lines of code:
public virtual IDbSet<ApplicationUser> Users { get; set; }
public virtual IDbSet<IdentityRole> Roles { get; set; }
public virtual IDbSet<IdentityUserClaim> Claims { get; set; }
So the full DataContext file will look something like this:
public class DataContext : DbContext
{
public DataContext()
: base("name=DefaultConnection")
{
}
// Account tables
public virtual IDbSet<ApplicationUser> Users { get; set; }
public virtual IDbSet<IdentityRole> Roles { get; set; }
public virtual IDbSet<IdentityUserClaim> Claims { get; set; }
// App Models
public DbSet<Course> Courses { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (modelBuilder == null)
throw new ArgumentNullException("modelBuilder");
modelBuilder.Configurations.AddFromAssembly(typeof(AspNetRoleBasedSecurity.Models.Configurations.ApplicationUserConfiguration).Assembly);
}
}
You have to use:
DbSet<Model> Name {get;set;}
Instead of
DbSet Model {get;set;}
As the answer to your question. The RoleStore needs a DbContext (IdentityDbContext inherits from DbContext). So you should be able to still use the roleManager and more...

DDD/CQRS, Entity has access to Query, Command?

public class PageRoleService
{
public void SetRoles(Page page, User activeUser)
{
var rb = page.Project.ProjectType.GetRoleFor(activeUser.UserType);
page.RolesForPage.Add(activeUser, rb);
var managers = GetAllManagersOf(activeUser);
foreach (var m in managers)
{
page.RolesForPage.Add(m, rb);
}
}
}
public class Project : Entity
{
public ProjectType ProjectType { get; set; }
public IList<Page> Pages { get; set; }
}
public class Page : Entity
{
public string Name { get; set; }
public Project Project { get; set; }
public IDictionary<User, RoleBehaviour> RolesForPage { get; set; }
}
public class ProjectType : Entity
{
public IQueryProcessor QueryProcessor { get; set; }
public IList<RoleBehaviour> RoleBehaviours { get; set; }
public RoleBehaviour GetRoleFor(USerType userType)
{
var behaviour = return QueryProcessor.Execute(new GetRolesByUserAndProjectTypeQuery() {
ProjectType = this,
UserType = userType
});
// Filter behaviour attributes for project type properties, business rules, etc...
// FilterBehaviour(behaviour);
return behaviour;
}
}
public class GetRolesByUserAndProjectTypeQuery
{
public UserType UserType { get; set; }
public ProjectType ProjectType { get; set; }
}
public class GetRolesByUserAndProjectTypeQueryHandler
{
public Db Db { get; set; }
public RoleBehaviour Execute(GetRolesByUserAndProjectTypeQuery query)
{
return Db.FirstOrDefault(r => r.UserType == query.UserType && r.ProjectType == query.projectType);
}
}
public class RoleBehaviour : Entity
{
public Role ROleForArea1 { get; set; }
public Role ROleForArea2 { get; set; }
public UserType UserType { get; set; }
public ProjectType ProjectType { get; set; }
public IDictionary<string, string> Attributes { get; set; }
}
public enum UserType
{
A,
B,
C,
D
}
public class Role : Entity
{
public IList<string> Permissions { get; set; }
}
I don't use repository, no need data abstraction, I use CQRS for crud operations. (CreateProjectCommand, GetRolesByUserAndProjectTypeQuery, etc..)
Users related a lot of project and page. Users have more than role for each Page Entity and is dynamically created when user (client) request to fetch All projects page or single page item.
My Page Role Service determinates page roles for active user and its managers. My MVC Controller use PageRoleService.
PageRoleService is Application Service or Domain Service or .....?
QueryProcessor in Entity (ProjectType) is invalid approach? How can handle this/their problems without lazy or eager loading?
RoleBehaviour is Entity or Value Object?
PageRoleService is a service or business logic in domain?
I know that I'm some years later, but:
I would put away the base class Entity, because it looks that this are just Dtos returned by the queryhandler (infact GetRolesByUserAndProjectTypeQueryHandler.Execute returns a RoleBehaviour).
Given this, I think that:
PageRoleService is a simple service that completes a Dto, hence it looks a kind of factory
Given that ProjectType here has two different roles (a Dto and Entity, and this is against CQRS), if:
it's a Dto, then use a service/factory/ORM to load extra data on it
it's an Entity, try to load all the data that's needed by it. This because there're great changes that you'll need it on the way to execute your command (great explanation about DDD and entities).
The object has it's own identity? Has it an Id that, even if things will change, remains the same? Looking at it, it looks just a Dto, with nothing really interesting (at business level).
see 1.

MVC 4 EF5 - Add sub-entities error

I am getting an Object reference not set to an instance of an object error when trying to add multiple entity levels to my EF context.
Take the following three-level example class structure:
public class Forum
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Blog> Blogs { get; set; }
}
public class Blog
{
public int ID { get; set; }
public string Name { get; set; }
public int ForumID { get; set; }
public virtual Forum Forum { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int ID { get; set; }
public string Name { get; set; }
public int BlogID { get; set; }
public virtual Blog Blog { get; set; }
}
For a given Forum, I want to add a new Blog with a new Post:
Forum MyForum = context.Forums.Find(1);
Blog MyBlog = new Blog { Name = "My New Blog" };
Post MyPost = new Post { Name = "My New Post" };
MyForum.Blogs.Add(MyBlog); // This WORKS
MyBlog.Posts.Add(MyPost); // This FAILS
context.SaveChanges(); // We never make it this far
I've tried every possible order combination, including placing context.SaveChanges() immediately after .Add(MyBlog). It seems like it's choking because there is no Blog.ID to use for Post.BlogID, but EF generates temporary key values for use in this situation.
Any ideas?
Hints at the answer (and the root problem) can be found at:
Entity Framework Uninitialised Collection
Entity Framework 4.1 Code First - Should many relationship ICollections be initialised
The "simple" solution is to manually initialize the Blog.Posts collection:
Blog MyBlog = new Blog { Name = "My New Post", Posts = new List<Post>() };
Alternatively, you can build this logic into the class constructor as recommended by Ladislav in the second link.
Basically, when you create a new object, the collection is null and not initialized as a List<>, so the .Add() call fails. The Forum.Blogs collection is able to lazy-load because it derives from the database context. Blog.Posts, however, is created from scratch, and EF can't help you, so the collection is null by default.

Resources