ASP.NET Core - Dynamic Policy Based Authorization - asp.net

I'm developing a application that's relatively large. I want to implement permission level authorization in my application. I've developed this code which works for one permission but doesn't work for multiple permission.
Here is sample code:
Below enum indicates features that my application will provide:
public enum CppFeature
{
NotDefined = 0,
DistributorManagement = 1,
ChannelPartnerManagement = 2,
}
Below code indicates what permissions are supported in my application:
public enum CppPermission
{
NotDefined = 0,
View = 1,
Invite = 2,
Deactivate = 3,
List = 4,
Edit = 5,
Create = 6,
Approve = 7,
}
Finally a mapping is created in database which says which permissions are applicable in which feature...
|------------------------|------------------|
| Feature | Permission |
|------------------------|------------------|
| DistributorManagement | View |
|------------------------|------------------|
| DistributorManagement | Invite |
|------------------------|------------------|
|ChannelPartnerManagement| Create |
|------------------------|------------------|
A user then gets the ability to create a dynamic role and select permission from the mapping.
Later in my code, I've configured custom policies as below:
public class CppFeaturePermissionCheck
{
public CppFeature Feature { get; set; }
public List<CppPermission> Permission { get; set; }
}
public class CustomPolicyProvider : IAuthorizationPolicyProvider
{
public CustomPolicyProvider (IOptions<AuthorizationOptions> options)
{
DefaultPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public DefaultAuthorizationPolicyProvider DefaultPolicyProvider { get; }
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (!string.IsNullOrEmpty(policyName))
{
var policy = new AuthorizationPolicyBuilder();
policy.AddRequirements(new CppAuthorizeRequirement(JsonConvert.DeserializeObject<List<CppFeaturePermissionCheck>>(policyName)));
return Task.FromResult(policy.Build());
}
return DefaultPolicyProvider.GetPolicyAsync(policyName);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return DefaultPolicyProvider.GetDefaultPolicyAsync();
}
}
public class CppAuthorizeRequirement : IAuthorizationRequirement
{
public CppAuthorizeRequirement(List<CppFeaturePermissionCheck> userPermission)
{
UserPermissions = userPermission;
}
public List<CppFeaturePermissionCheck> UserPermissions { get; set; }
}
public class CppAuthorizeAttribute : AuthorizeAttribute
{
public CppAuthorizeAttribute(CppFeature feature, CppPermission permission)
: this(new List<CppFeaturePermissionCheck>()
{
new CppFeaturePermissionCheck()
{
Feature = feature,
Permission = new List<CppPermission>()
{
permission,
},
},
})
{
}
// This ctor doesn't work since attributes can only have primitive types as parameter.
public CppAuthorizeAttribute(List<CppFeaturePermissionCheck> featurePermissions)
{
Policy = JsonConvert.SerializeObject(featurePermissions, Formatting.None);
}
}
[CppAuthorize(CppFeature.ChannelPartnerManagement, CppPermission.View)]
[HttpPost("channelpartnerlist")]
public async Task<IActionResult> PartnerList([FromBody]ChannelPartnerListRequestModel_V1 model)
{
try
{
//some work.
}
catch (CPPException ioe)
{
}
}
The problem is:
1. The attribute doesn't support parameter type of Array, List or Dictionary which would allow me to specify multiple permissions
Is there a better way by which I can specify whether a specific action is supported for specific permission in specific feature?

Related

How to define authorization policy?

I get some idea about the policy based authorization in .NET 6.0 based on Microsoft article.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-6.0
The article mentioned about to hard code the policy in the authorization attribute. I have REST API' and I want to assign permissions to them in some configuration for example in file and how can I can define the policy in the configuration what ingredients it should include so that I can load the policy from the file and then apply on startup to the authorization attribute. How to apply it to authorization attribute I see the following link Bind AuthorizationPolicy to Controller/Action without using AuthorizeAttribute
I am here only interested how I can define the polices in the configuration file(appsettings.json) what template or fields it should have. I know It will move it to database later but I need it for the proof of concepts. I am not sure do we really need to define the policy or we can define the permissions per API and then create policy automatically based on the API permission? Any help in this context will be appreciated.
Regards,
IK
I tried as below :
var policylist = new List<AuthOption>();
Configuration.GetSection("PolicyList").Bind(policylist);
services.AddAuthorization(options => {
policylist.ForEach(x =>
{
options.AddPolicy(x.PolicyName, policy =>
{
x.Requirement.ForEach(y =>
{
Type type = Type.GetType(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace+"."+y.RequirementName);
if (y.Inputs!=null)
{
var requirement = (IAuthorizationRequirement)Activator.CreateInstance(type,y.Inputs);
policy.AddRequirements(requirement);
}
else
{
var requirement = (IAuthorizationRequirement)Activator.CreateInstance(type);
policy.AddRequirements(requirement);
}
});
});
});
});
added some class:
public class AuthOption
{
public AuthOption()
{
Requirement = new List<Requirement>();
}
public string PolicyName { get; set; }
public List<Requirement> Requirement { get; set; }
}
public class Requirement
{
public string RequirementName { get; set; }
public string Inputs { get; set; }
}
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public MinimumAgeRequirement(string minimumAge) =>
MinimumAge = minimumAge;
public string MinimumAge { get; }
}
public class AnotherRequirement : IAuthorizationRequirement
{
}
in appsettings.json:
"PolicyList": [
{
"PolicyName": "policy1",
"Requirement": [
{
"RequirementName": "MinimumAgeRequirement",
"Inputs": "21"
},
{
"RequirementName": "AnotherRequirement"
}
]
},
{
"PolicyName": "policy2",
"Requirement": [
{
"RequirementName": "AnotherRequirement"
}
]
}
]
Result:

EF Core with Lazy Loading tracks unreachable Objects?

I am currently having troubles with entity framework core.
The application I am developing is supposed to help users plan their next business year by increasing/decreasing the quantity of a service they want to provide in the next year.
Based on their input the "worth" of a service is distributed pro rata to other "mini-services" that are contained in the changed service.
To do so I load the affected entries of the main service and the "mini-services" from Database via a repository which then uses Entity Framework.
public IEnumerable<OpsDistributionEntry> FilteredOpsDistributionEntries(int catalogId, IEnumerable<OpsDistributionEntry> filterEntries)
{
return _context.OpsDistributionEntries.FromSqlRaw(
$"SELECT * FROM OpsDistributionEntries WHERE (Id IN (SELECT OpsEntriesId FROM DistributionCatalogOpsDistributionEntry WHERE CatalogsId = {catalogId}) " +
$"AND EntityId IN ({string.Join(",", filterEntries.Select(x => x.EntityId))}))").ToList();
}
I then map those database objects to my domain objects via constructor.
var opsDistributionEntries = new OpsDistributionEntriesFromDatabaseObjects(
_repository.FilteredOpsDistributionEntries(_distCatalogId, _filterEntries));
public class OpsDistributionEntriesFromDatabaseObjects : IOpsDistributionEntries
{
private readonly IOpsDistributionEntries _distribution;
public OpsDistributionEntriesFromDatabaseObjects(IEnumerable<DatabaseObjects.OpsDistributionEntry> distribution)
{
_distribution = new OpsDistributionEntries(distribution.Select(x => new OpsDistributionEntryFromDatabaseObject(x)));
}
public IEnumerator<IOpsDistributionEntry> GetEnumerator()
{
return _distribution.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class OpsDistributionEntryFromDatabaseObject : IOpsDistributionEntry
{
public OpsDistributionEntryFromDatabaseObject(DatabaseObjects.OpsDistributionEntry opsDistributionEntry)
: this(opsDistributionEntry.Id, opsDistributionEntry.TotalCases, opsDistributionEntry.TotalEffectiveWeight, opsDistributionEntry.Provide,
opsDistributionEntry.Freeze,
new OpsFromDatabaseObject(opsDistributionEntry.Entity),
new DrgDistributionsFromDatabaseObjects(opsDistributionEntry.DrgDistribution))
{
}
private OpsDistributionEntryFromDatabaseObject(int id, int totalCases, double totalEffectiveWeight, bool provide, bool freeze, IOps ops,
IDrgDistributions drgDistribution)
{
Id = id;
TotalCases = totalCases;
TotalEffectiveWeight = totalEffectiveWeight;
Provide = provide;
Freeze = freeze;
Ops = ops;
DrgDistribution = drgDistribution;
}
public int Id { get; }
public int TotalCases { get; }
public double TotalEffectiveWeight { get; }
public bool Provide { get; }
public bool Freeze { get; }
public IOps Ops { get; }
public IDrgDistributions DrgDistribution { get; }
}
public sealed class OpsFromDatabaseObject : IOps
{
public OpsFromDatabaseObject(DatabaseObjects.Ops ops) : this(ops.Id, ops.Code, ops.Description, ops.Year)
{
}
private OpsFromDatabaseObject(int id, string code, string description, int year)
{
Id = id;
Code = code;
Description = description;
Year = year;
}
public int Id { get; }
public string Code { get; }
public string Description { get; }
public int Year { get; }
}
I pass the database objects on to different levels, but finally every value is assigned and every possible navigation property is mapped to an domain object.
With those mapped domain objects I recalculate the new "worth" of the service and the correlated "mini-services".
After calculation I again map my Domain Objects to DatabaseObjects.
DatabaseObjects.OpsDistributionEntry ToDatabaseObject() => new DatabaseObjects.OpsDistributionEntry
{
Id = Id,
EntityId = Ops.Id,
Freeze = Freeze,
Provide = Provide,
TotalCases = TotalCases,
TotalEffectiveWeight = TotalEffectiveWeight,
DrgDistribution = DrgDistribution.Select(x => x.ToDatabaseObject()).ToImmutableList(),
};
When I want to add those "updated" Objects to the context via repository
public void UpdateDistributionEntries(IEnumerable<OpsDistributionEntry> opsDistributionEntries)
{
if (opsDistributionEntries == null) throw new ArgumentNullException(nameof(opsDistributionEntries));
_context.OpsDistributionEntries.UpdateRange(opsDistributionEntries);
}
I am getting an Error that the Entities I want to updated are already being tracked by Entity Framework.
After some debugging I think that EF is still tracking the database objects I loaded for mapping the domain objects. I just use the database objects to map values to the domain objects and do not store any reference for them (as far as I understand).
Can any of you maybe tell me why they are still being tracked even if they are "unreachable". Or am I thinking wrong? Might this be because of Lazy Loading?
I've been debugging for almost 14 hours now :D Please someone give me a hint :D
Many thanks in advance

Asp.Net IdentityUser add custom List Property

I am fairly new to coding with asp.net so there might be an obvious answere to my question but I haven't found one yet.
So currently I am developing a site for project management and I want the users to get notified when an event happens, eg. they were added to a new project, a project has been updated etc.
For that I have expanded the IdentityUser Model with a new property List
public class CojectUser : IdentityUser
{
public List<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Message { get; set; }
public bool Seen { get; set; }
}
When an event happens I add them to the user's notification list and update the user via the userManager.
public class EventBroker<T> : IEventBroker<T>
{
private readonly UserManager<CojectUser> userManager;
public EventBroker(UserManager<CojectUser> userMgr, IUserValidator<CojectUser> userValid)
{
userManager = userMgr;
}
public async Task NotifyAsync(Message<T> message, List<UserRole> recipients)
{
foreach (var user in recipients)
{
var cojectUser = await userManager.FindByNameAsync(user.Name);
if (cojectUser != null)
{
if (cojectUser.Notifications == null)
{
cojectUser.Notifications = new List<Notification>();
}
cojectUser.Notifications.Add(new Notification
{
Message = message.Information,
Seen = false
});
IdentityResult result = await userManager.UpdateAsync(cojectUser);
if (!result.Succeeded)
{
throw new UserUpdateFailException();
}
}
}
}
}
}
I am able to save the custom data to the database, but I am unable to load it again from database.
When I want to display the user's notifications userManager retrieves an user object with null as notification list. Even though the data is stored in database.
public async Task<IActionResult> Index()
{
CojectUser user = await userManager.GetUserAsync(User);
if(user.Notifications == null)
{
user.Notifications = new List<Notification>();
}
return View(user);
}
Data in database:
Can anybody tell me what I am doing wrong?
UserManager don't eager load properties by default.
You should use DatabaseContext directly.
var user = _context.Users.Include(c => c.Notifications).Where(u => u.Id == user.Id).ToList();

EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType

I am working with Entity Framework Code First and MVC 5. When I created my application with Individual User Accounts Authentication I was given an Account controller and along with it all the required classes and code that is needed to get the Indiv User Accounts authentication to work.
Among the code already in place was this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
But then I went ahead and created my own context using code first, so I now have the following too:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
}
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<IdentityRole> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Paintings> Paintings { get; set; }
}
Finally I have the following seed method to add some data for me to work with whilst developing:
protected override void Seed(DXContext context)
{
try
{
if (!context.Roles.Any(r => r.Name == "Admin"))
{
var store = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(store);
var role = new IdentityRole { Name = "Admin" };
manager.Create(role);
}
context.SaveChanges();
if (!context.Users.Any(u => u.UserName == "James"))
{
var store = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(store);
var user = new ApplicationUser { UserName = "James" };
manager.Create(user, "ChangeAsap1#");
manager.AddToRole(user.Id, "Admin");
}
context.SaveChanges();
string userId = "";
userId = context.Users.FirstOrDefault().Id;
var artists = new List<Artist>
{
new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId },
};
artists.ForEach(a => context.Artists.Add(a));
context.SaveChanges();
var paintings = new List<Painting>
{
new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true, ApplicationUserId = userId }
};
paintings.ForEach(p => context.Paintings.Add(p));
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
My solution builds fine, but when I try and access a controller that requires access to the database I get the following error:
DX.DOMAIN.Context.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.
DX.DOMAIN.Context.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.
What am I doing wrong? Is it because I have two contexts?
UPDATE
After reading Augusto's reply, I went with Option 3. Here is what my DXContext class looks like now:
public class DXContext : DbContext
{
public DXContext() : base("DXContext")
{
// remove default initializer
Database.SetInitializer<DXContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Painting> Paintings { get; set; }
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Role>().ToTable("Roles");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
I also added a User.cs and a Role.cs class, they look like this:
public class User
{
public int Id { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
public class Role
{
public int Id { set; get; }
public string Name { set; get; }
}
I wasn't sure if I would need a password property on the user, since the default ApplicationUser has that and a bunch of other fields!
Anyways, the above change builds fine, but again I get this error when the application is ran:
Invalid Column name UserId
UserId is an integer property on my Artist.cs
In my case I had inherited from the IdentityDbContext correctly (with my own custom types and key defined) but had inadvertantly removed the call to the base class's OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // I had removed this
/// Rest of on model creating here.
}
Which then fixed up my missing indexes from the identity classes and I could then generate migrations and enable migrations appropriately.
The problem is that your ApplicationUser inherits from IdentityUser, which is defined like this:
IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser
....
public virtual ICollection<TRole> Roles { get; private set; }
public virtual ICollection<TClaim> Claims { get; private set; }
public virtual ICollection<TLogin> Logins { get; private set; }
and their primary keys are mapped in the method OnModelCreating of the class IdentityDbContext:
modelBuilder.Entity<TUserRole>()
.HasKey(r => new {r.UserId, r.RoleId})
.ToTable("AspNetUserRoles");
modelBuilder.Entity<TUserLogin>()
.HasKey(l => new {l.LoginProvider, l.ProviderKey, l.UserId})
.ToTable("AspNetUserLogins");
and as your DXContext doesn't derive from it, those keys don't get defined.
If you dig into the sources of Microsoft.AspNet.Identity.EntityFramework, you will understand everything.
I came across this situation some time ago, and I found three possible solutions (maybe there are more):
Use separate DbContexts against two different databases or the same database but different tables.
Merge your DXContext with ApplicationDbContext and use one database.
Use separate DbContexts against the same table and manage their migrations accordingly.
Option 1:
See update the bottom.
Option 2:
You will end up with a DbContext like this one:
public class DXContext : IdentityDbContext<User, Role,
int, UserLogin, UserRole, UserClaim>//: DbContext
{
public DXContext()
: base("name=DXContext")
{
Database.SetInitializer<DXContext>(null);// Remove default initializer
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
public static DXContext Create()
{
return new DXContext();
}
//Identity and Authorization
public DbSet<UserLogin> UserLogins { get; set; }
public DbSet<UserClaim> UserClaims { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
// ... your custom DbSets
public DbSet<RoleOperation> RoleOperations { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Configure Asp Net Identity Tables
modelBuilder.Entity<User>().ToTable("User");
modelBuilder.Entity<User>().Property(u => u.PasswordHash).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.Stamp).HasMaxLength(500);
modelBuilder.Entity<User>().Property(u => u.PhoneNumber).HasMaxLength(50);
modelBuilder.Entity<Role>().ToTable("Role");
modelBuilder.Entity<UserRole>().ToTable("UserRole");
modelBuilder.Entity<UserLogin>().ToTable("UserLogin");
modelBuilder.Entity<UserClaim>().ToTable("UserClaim");
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimType).HasMaxLength(150);
modelBuilder.Entity<UserClaim>().Property(u => u.ClaimValue).HasMaxLength(500);
}
}
Option 3:
You will have one DbContext equal to the option 2. Let's name it IdentityContext. And you will have another DbContext called DXContext:
public class DXContext : DbContext
{
public DXContext()
: base("name=DXContext") // connection string in the application configuration file.
{
Database.SetInitializer<DXContext>(null); // Remove default initializer
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
// Domain Model
public DbSet<User> Users { get; set; }
// ... other custom DbSets
public static DXContext Create()
{
return new DXContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
modelBuilder.Entity<User>().ToTable("User");
}
public DbQuery<T> Query<T>() where T : class
{
return Set<T>().AsNoTracking();
}
}
where User is:
public class User
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
[Required, StringLength(128)]
public string SomeOtherColumn { get; set; }
}
With this solution, I'm mapping the entity User to the same table as the entity ApplicationUser.
Then, using Code First Migrations you'll need to generate the migrations for the IdentityContext and THEN for the DXContext, following this great post from Shailendra Chauhan: Code First Migrations with Multiple Data Contexts
You'll have to modify the migration generated for DXContext. Something like this depending on which properties are shared between ApplicationUser and User:
//CreateTable(
// "dbo.User",
// c => new
// {
// Id = c.Int(nullable: false, identity: true),
// Name = c.String(nullable: false, maxLength: 100),
// SomeOtherColumn = c.String(nullable: false, maxLength: 128),
// })
// .PrimaryKey(t => t.Id);
AddColumn("dbo.User", "SomeOtherColumn", c => c.String(nullable: false, maxLength: 128));
and then running the migrations in order (first the Identity migrations) from the global.asax or any other place of your application using this custom class:
public static class DXDatabaseMigrator
{
public static string ExecuteMigrations()
{
return string.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(),
ExecuteDXMigrations());
}
private static string ExecuteIdentityMigrations()
{
IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration();
return RunMigrations(configuration);
}
private static string ExecuteDXMigrations()
{
DXMigrationConfiguration configuration = new DXMigrationConfiguration();
return RunMigrations(configuration);
}
private static string RunMigrations(DbMigrationsConfiguration configuration)
{
List<string> pendingMigrations;
try
{
DbMigrator migrator = new DbMigrator(configuration);
pendingMigrations = migrator.GetPendingMigrations().ToList(); // Just to be able to log which migrations were executed
if (pendingMigrations.Any())
migrator.Update();
}
catch (Exception e)
{
ExceptionManager.LogException(e);
return e.Message;
}
return !pendingMigrations.Any() ? "None" : string.Join(", ", pendingMigrations);
}
}
This way, my n-tier cross-cutting entities don't end up inheriting from AspNetIdentity classes, and therefore I don't have to import this framework in every project where I use them.
Sorry for the extensive post. I hope it could offer some guidance on this. I have already used options 2 and 3 in production environments.
UPDATE: Expand Option 1
For the last two projects I have used the 1st option: having an AspNetUser class that derives from IdentityUser, and a separate custom class called AppUser. In my case, the DbContexts are IdentityContext and DomainContext respectively. And I defined the Id of the AppUser like this:
public class AppUser : TrackableEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
// This Id is equal to the Id in the AspNetUser table and it's manually set.
public override int Id { get; set; }
(TrackableEntity is the custom abstract base class that I use in the overridden SaveChanges method of my DomainContext context)
I first create the AspNetUser and then the AppUser. The drawback with this approach is that you have ensured that your "CreateUser" functionality is transactional (remember that there will be two DbContexts calling SaveChanges separately). Using TransactionScope didn't work for me for some reason, so I ended up doing something ugly but that works for me:
IdentityResult identityResult = UserManager.Create(aspNetUser, model.Password);
if (!identityResult.Succeeded)
throw new TechnicalException("User creation didn't succeed", new LogObjectException(result));
AppUser appUser;
try
{
appUser = RegisterInAppUserTable(model, aspNetUser);
}
catch (Exception)
{
// Roll back
UserManager.Delete(aspNetUser);
throw;
}
(Please, if somebody comes with a better way of doing this part I appreciate commenting or proposing an edit to this answer)
The benefits are that you don't have to modify the migrations and you can use any crazy inheritance hierarchy over the AppUser without messing with the AspNetUser. And actually, I use Automatic Migrations for my IdentityContext (the context that derives from IdentityDbContext):
public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
public IdentityMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(IdentityContext context)
{
}
}
This approach also has the benefit of avoiding to have your n-tier cross-cutting entities inheriting from AspNetIdentity classes.
By Changing The DbContext As Below;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
Just adding in OnModelCreating method call to base.OnModelCreating(modelBuilder); and it becomes fine. I am using EF6.
Special Thanks To #The Senator
For those who use ASP.NET Identity 2.1 and have changed the primary key from the default string to either int or Guid, if you're still getting
EntityType 'xxxxUserLogin' has no key defined. Define the key for this EntityType.
EntityType 'xxxxUserRole' has no key defined. Define the key for this EntityType.
you probably just forgot to specify the new key type on IdentityDbContext:
public class AppIdentityDbContext : IdentityDbContext<
AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppIdentityDbContext()
: base("MY_CONNECTION_STRING")
{
}
......
}
If you just have
public class AppIdentityDbContext : IdentityDbContext
{
......
}
or even
public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
......
}
you will get that 'no key defined' error when you are trying to add migrations or update the database.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
// relationship.DeleteBehavior = DeleteBehavior.Restrict;
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles");
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");
}
}
My issue was similar - I had a new table i was creating that ahd to tie in to the identity users. After reading the above answers, realized it had to do with IsdentityUser and the inherited properites. I already had Identity set up as its own Context, so to avoid inherently tying the two together, rather than using the related user table as a true EF property, I set up a non-mapped property with the query to get the related entities. (DataManager is set up to retrieve the current context in which OtherEntity exists.)
[Table("UserOtherEntity")]
public partial class UserOtherEntity
{
public Guid UserOtherEntityId { get; set; }
[Required]
[StringLength(128)]
public string UserId { get; set; }
[Required]
public Guid OtherEntityId { get; set; }
public virtual OtherEntity OtherEntity { get; set; }
}
public partial class UserOtherEntity : DataManager
{
public static IEnumerable<OtherEntity> GetOtherEntitiesByUserId(string userId)
{
return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity);
}
}
public partial 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;
}
[NotMapped]
public IEnumerable<OtherEntity> OtherEntities
{
get
{
return UserOtherEntities.GetOtherEntitiesByUserId(this.Id);
}
}
}

a request level singleton object in asp.net

I trying to write a kind of pseudo singleton implementation. I want it to work similar to how HttpContext does work, where I can get an instance to the context doing something as simple as:
var ctx = HttpContext.Current;
So my implementation goes something like this:
public class AppUser
{
public string Username { get; set; }
public string[] Roles { get; set; }
public AppUser()
{
var appuser = HttpContext.Session["AppUser"] as AppUser;
if(appuser == null)
throw new Exception("User session has expired");
Username = appuser.Username;
Roles = appuser.Roles;
}
}
public class WebAppContext
{
const string ContextKey = "WebAppContext";
WebAppContext() { } //empty constructor
public static WebAppContext Current
{
get
{
var ctx = HttpContext.Current.Items[ContextKey] as WebAppContext;
if(ctx == null)
{
try
{
ctx = new WebAppContext() { User = new AppUser() };
}
catch
{
//Redirect for login
}
HttpContext.Current.Items.Add(ContextKey, ctx);
}
return ctx;
}
}
public AppUser User { get; set; }
}
And I try to consume this object as follows:
var appuser = WebAppContext.Current.User;
Now does the above line guarantee I get the user associated with the correct request context; not some other user which is associated with another concurrent http request being processed?
Apart from the fact that I can't understand why would you need to barely copy the user information from the Session container to the Items container, the answer to your question should be - yes, if the Session data is correct then the same data will be available from your static property.
I wrote a blog entry on that once
http://netpl.blogspot.com/2010/12/container-based-pseudosingletons-in.html

Resources