Why my Entity Framework Code First Entities are being detached? - ef-code-first

I am using the code first (EF5) approach to create my domain classes. I perform a query to pull a Parent record w/ a collection of child's. I need to edit my parent and perform CRUD operations on childs.
The problem that when saving my objects (parent/child) the entities state is Detached thus without putting an extra logic I don't now if the objects where modified, deleted or inserted.
How can I enable EF to keep traking of my entities or not detaching my entities?
Here is my code:
public class myDbContext : DbContext
{
#region ConnectionStrings
public myDbContext()
: base(ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["ActiveDB_my"]].ToString())
{
this.Configuration.LazyLoadingEnabled = true;
Database.SetInitializer<myDbContext>(null);
//this.Configuration.ProxyCreationEnabled = true;
//this.Configuration.AutoDetectChangesEnabled = true;
}
#endregion
#region EntityFramework
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Entity Framework Configurations
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
//Load Entity Configurations
dbomyAnotations.Load(ref modelBuilder);
base.OnModelCreating(modelBuilder);
}
#endregion
#region EntityRegistrations
//Client
public DbSet<ClientLocation> ClientLocations { get; set; }
//Templates
public DbSet<Template> Templates { get; set; }
#endregion
}
namespace App.myTrak.BL
{
public class Templates
{
public static ClientLocation getTemplatesForClientLocation(int clientLocationId)
{
ClientLocation entity = null;
using (myDbContext db = new myDbContext())
{
entity = db.ClientLocations
.Where(c => c.ClientLocationId == clientLocationId)
.Include(t => t.Templates)
.FirstOrDefault();
}
return entity;
}
public static void saveForTemplates(ClientLocation clientLocations)
{
int Id = clientLocations.ClientLocationId;
var db = new myDbContext();
//Client Location
db.ClientLocations.Attach(clientLocations);
db.Entry(clientLocations).State = System.Data.EntityState.Unchanged;
db.Entry(clientLocations).Property(x => x.ShowPresoldProducts).IsModified = true;
//App Templates
foreach (var template in clientLocations.Templates)
{
db.Templates.Attach(template);
db.Entry(template).State = System.Data.EntityState.Unchanged;
if (template.DeleteDate != null)
{
db.Entry(template).Property(x => x.DeleteUserId).IsModified = true;
db.Entry(template).Property(x => x.DeleteDate).IsModified = true;
template.DeleteDate = Shared.Common.DateTimeUTC();
}
else if (template.TemplateId == 0)
db.Entry(template).State = System.Data.EntityState.Added;
else
{
//Modified
db.Entry(template).Property(x => x.TemplateName).IsModified = true;
db.Entry(template).Property(x => x.Description).IsModified = true;
db.Entry(template).Property(x => x.AppTypeId).IsModified = true;
db.Entry(template).Property(x => x.ModifyUserId).IsModified = true;
db.Entry(template).Property(x => x.ModifyDate).IsModified = true;
template.ModifyDate = Shared.Common.DateTimeUTC();
template.ModifyUserId = CurrentUserId;
}
}
db.SaveChanges();
db.Database.Connection.Close();
}
}
}

Related

Resolution failed with error: No public constructor is available for type IHttpContextAccessor

I recently upgraded my app to .net core 6 and now I am getting this error when trying to get a service using this code:
IUnityContainer container = HangfireUnityConfig.GetConfiguredContainer();
var authService = container.Resolve<IAuthService>();
I read some other posts that mentioned adding HttpContextAccessor in my ConfigureServices() method but none of the ways ive tried fixed the error.
services.AddHttpContextAccessor();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Another person mentioned adding the line in my Program.cs but still getting the error.
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
If I add RegisterType<IHttpContextAccessor, HttpContextAccessor>() to RegisterTypes() in my HangFireUnityConfig class the error goes away but throws a new error later on so Im not sure if thats the right fix.
public static void RegisterTypes(IUnityContainer container)
{
// register hangfire dependencies
container.RegisterType<IHttpContextAccessor, HttpContextAccessor>()
}
AuthService.cs
using MyApp.Entities.DTOs;
namespace MyApp.Service.Auth
{
public class AuthService : IAuthService
{
private UserDto currentUser = null;
private readonly IHttpContextAccessor _context;
public AuthService(IHttpContextAccessor ctx)
{
_context = ctx;
currentUser = parseClaimsUser();
}
public bool isInRole(string role, List<string> roleList)
{
return true;
}
public UserDto parseClaimsUser()
{
ClaimsPrincipal currentClaim = _context.HttpContext.User;
UserDto parsedUser = new UserDto();
bool isAdmin = false;
if (currentClaim == null || !currentClaim.Identity.IsAuthenticated)
{
return parsedUser;
}
//return user id from token properties
parsedUser.userID = currentClaim.Claims.Where(claim => claim.Type == ClaimTypes.NameIdentifier).Select(v => v.Value).FirstOrDefault<string>();
// retrieve groups from token properties --- this is only retrieved upon login. Users will have to log out and log back in to see any changes in groups
var currentGroupsIDs = currentClaim.HasClaim(claim => claim.Type == ClaimTypes.Role) ?
currentClaim.Claims.Where(t => t.Type == ClaimTypes.Role).Select(y => int.Parse(y.Value)).ToList<int>()
: new List<int>();
var adminString = currentClaim.Claims.Where(claim => claim.Type == ClaimTypes.AuthorizationDecision)
.Select(v => v.Value)
.SingleOrDefault<string>();
adminString = adminString == null ? "False" : adminString;
isAdmin = bool.Parse(adminString);
//parsedUser.userGrp = currentGroups;
parsedUser.userGrpIDs = currentGroupsIDs;
parsedUser.isAuthenticated = currentClaim.Identity.IsAuthenticated;
parsedUser.displayName = currentClaim.Identity.Name;
parsedUser.email = currentClaim.Claims.Where(w => w.Type == ClaimTypes.Email).Select(v => v.Value).SingleOrDefault<string>();
//parsedUser.currentToken = tokenExtract;
parsedUser.isAdmin = isAdmin;
var isUS = currentClaim.Claims.Where(claim => claim.Type == "us_citizen").Select(v => v.Value).SingleOrDefault<string>();
if (isUS != null)
{
parsedUser.isUSCitizenAndJPLEmployee = bool.Parse(isUS);
}
return parsedUser;
}
public void initUser()
{
currentUser = parseClaimsUser();
}
public UserDto getCurrentUser(bool includeToken = false)
{
if (currentUser == null || currentUser.userID == null)
{
currentUser = parseClaimsUser();
}
if (!includeToken)
{
currentUser.currentToken = null;
}
return currentUser;
}
public bool userIsAdmin()
{
return true;
}
}
}
I was able to figure out the error I was getting with my last implementation. The error was with not being able to cast my dbContext to type IObjectContextAdapter.
public List<KeyValuePair<string, long>> GetKeys(EntityEntry entry)
{
var keys = new List<KeyValuePair<string, long>>();
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
if (objectStateEntry.EntityKey.EntityKeyValues != null)
{
keys.AddRange(objectStateEntry.EntityKey.EntityKeyValues.Select(key => new KeyValuePair<string, long>(key.Key, Convert.ToInt64(key.Value))));
}
return keys;
}
I refactored the code to look like this and got no errors.
public List<KeyValuePair<string, long>> GetKeys(EntityEntry entry)
{
//this gets an array of the key names
var keyNames = entry.Metadata.FindPrimaryKey()
.Properties
.Select(p => p.Name)
.ToArray();
var keys = new List<KeyValuePair<string, long>>();
if (keyNames != null)
{
//creates the KeyValuePairs
keys.AddRange(keyNames.Select(key => new KeyValuePair<string, long>(key, Convert.ToInt64(entry.Property(key).CurrentValue))));
}
return keys;
}

Grouping data in a collection-view using DynamicData Xamarin forms prism

I'm working on a Xamarin forms app using Prism. I'm trying to manipulate(Sort, filter, etc..) and display grouped data using Dynamic data. I'm following this
https://www.xamboy.com/2021/01/20/using-dynamic-data-in-xamarin-forms-part-1/
My issue is that my Readonlyobservablecollection _getUsers is not getting populated please see below:
private SourceCache<User, string> _sourceCache = new SourceCache<User, string>(x =>x.Id);
private ReadOnlyObservableCollection<ObservableGroupedCollection<string,User, string>> _getUsers;
public ReadOnlyObservableCollection<ObservableGroupedCollection<string, User, string>> GetUser { get; set; }
public string Gender { get { return _gender; } set { SetProperty(ref _gender, value); OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(nameof(Gender))); } }
private string _gender;
private async Task GetUsers()
{
try
{
var users = await _apiService.GetUsers();
_sourceCache.AddOrUpdate(users);
Func<User, bool> searchFilter(string text) => user =>
{
return string.IsNullOrWhiteSpace(text) || user.PhoneNumber.ToLower().Contains(text.ToLower()) || user.UserName.ToLower().Contains(text.ToLower()) || user.Name.ToLower().Contains(text.ToLower());
};
Func<User, bool> genderFilter(string gender) => user =>
{
return gender == "All" || user.Gender == gender;
};
Func<User, bool> statusFilter(string status) => user =>
{
if(status == "Live")
{
return user.Active == true;
}
if (status == "Suspended")
{
return user.Active == false;
}
return status == "None" || user.Payment.StatusMessage.Replace(" ","") == status;
};
var genderPredicate = this.WhenAnyValue(x => x.Gender)
.Select(genderFilter);
var filterPredicate = this.WhenAnyValue(x => x.SearchText)
.Select(searchFilter);
var statusPredicate = this.WhenAnyValue(x => x.Status)
.Select(statusFilter);
var sortPredicate = this.WhenAnyValue(x => x.SortBy)
.Select(x => SortExpressionComparer<User>.Ascending(a => a.Name));
_cleanUp = _sourceCache.Connect()
.Filter(genderPredicate)
.Group(x => x.Race)
.Transform(g => new ObservableGroupedCollection<string, User, string>(g, genderPredicate, sortPredicate))
.Bind(out _getUsers)
.DisposeMany()
.Subscribe();
Gender = "All";
OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(nameof(Gender)));
}
catch (Refit.ApiException ex)
{
if(ex.StatusCode != System.Net.HttpStatusCode.NotFound)
{
await HandleExceptionAsync(ex);
}
}
catch (Exception ex)
{
await HandleExceptionAsync(ex);
}
}
The generic class
public class ObservableGroupedCollection<TGroupKey, TObject, TKey> : ObservableCollectionExtended<TObject>, IDisposable
{
public TGroupKey Key { get; }
public ObservableGroupedCollection(IGroup<TObject, TKey, TGroupKey> group, IObservable<Func<TObject, bool>> filter, IObservable<IComparer<TObject>> comparer)
{
this.Key = group.Key;
//load and sort the grouped list
var dataLoader = group.Cache.Connect()
.Filter(filter)
.Sort(comparer)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(this) //make the reset threshold large because xamarin is slow when reset is called (or at least I think it is #erlend, please enlighten me )
.Subscribe();
_cleanUp = new CompositeDisposable(dataLoader);
}
public void Dispose()
{
_cleanUp.Dispose();
}
private readonly IDisposable _cleanUp;
}
I'm really struggling with this. Please if anyone can help, I'd really appreciate it.
You can use this to populate _getUsers.
public ReadOnlyObservableCollection<ObservableGroupedCollection<string, User, string>> GetUser {
get
{
return _getUsers;
}
set
{
if (_getUsers != value)
{
_getUsers = value;
OnPropertyChanged(nameof(GetUser));
}
}
}

DbContext in Service triggered by Hangfire

I have a .NET 6 Razor Pages app that triggers background tasks and then informs the user of their status via SignalR.
I'm trying to use Database1 context in the PerformBackgroundJob method, but it's disposed. What technique should I use to inject Database1 context in PerformBackgroundJob, or how else can I get this to work?
namespace Toolkat.Pages
{
public class ProcessModel : PageModel
{
private readonly Database1Context _context;
private readonly ToolkatContext _tkcontext;
private IConfiguration configuration;
private readonly IQueue _queue;
private readonly IHubContext<JobHub> _hubContext;
static ServerConnection conn;
static Server server;
static Job job;
public ProcessModel(
Database1Context context,
ToolkatContext tkcontext,
IConfiguration _configuration,
IQueue queue,
IHubContext<JobHub> hubContext)
{
_context = context;
_tkcontext = tkcontext;
configuration = _configuration;
_queue = queue;
_hubContext = hubContext;
}
public IList<CustomFileImport> CustomFileImport { get; set; } = default!;
[BindProperty]
public CustomFileImport CustomFileImportNumberTwo { get; set; } = default!;
public async Task OnGetAsync()
{
if (_context.CustomFileImports != null)
{
CustomFileImport = await _context.CustomFileImports
.Include(c => c.FileImportType)
.Include(c => c.FileImportStatus)
.Where(i => i.FileImportStatusId.Equals(1))
.ToListAsync();
}
}
public async Task<IActionResult> OnPostAsync(int[] fileImportId)
{
//Generate GUID
Guid jobId = Guid.NewGuid();
//Update FileImportItems with GUID
foreach (var id in fileImportId)
{
if (/*id == null ||*/ _context.CustomFileImports == null)
{
return NotFound();
}
var customfileimport = await _context.CustomFileImports.FirstOrDefaultAsync(m => m.FileImportId == id);
if (customfileimport == null)
{
return NotFound();
}
customfileimport.ProcessId = jobId;
await _context.SaveChangesAsync();
}
_queue.QueueAsyncTask(() => PerformBackgroundJob(jobId));
return RedirectToPage("./Result", new { jobId });
}
private async Task PerformBackgroundJob(Guid jobId /*CancellationToken cancellationToken*/)
{
await _hubContext.Clients.Group(jobId.ToString()).SendAsync("progress", "PerformBackgroundJob Started");
/*
var customFileImports = await _context.CustomFileImports
.Include(c => c.FileImportType)
.Where(i => i.ProcessId.Equals(jobId))
.ToListAsync();
*/
Debug.WriteLine("ProviderName:" + _context.Database.ProviderName);
/*
foreach (var f in customFileImports)
{
await _hubContext.Clients.Group(jobId.ToString()).SendAsync("progress", WebUtility.HtmlEncode(f.FileName));
}
*/
}
}
}
I had to combine lessons from lots of articles to figure this out. Hangfire has a nice way of approaching this.
Replace
_queue.QueueAsyncTask(() => PerformBackgroundJob(jobId));
With
BackgroundJob.Enqueue<ProcessFilesService>(x => x.DoWork());
Passing dependencies
and create this class
public class ProcessFilesService
{
IServiceProvider _serviceProvider;
public ProcessFilesService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoWork()
{
using var scope = _serviceProvider.CreateScope();
var ctx = scope.ServiceProvider.GetRequiredService<MyDatabaseContext>();
using var hubScope = _serviceProvider.CreateScope();
var _hubContext = hubScope.ServiceProvider.GetRequiredService<JobHub>();
Debug.WriteLine(ctx.Database.ProviderName);
}
}
Hmm...I didn't need to register it as a service in program.cs and it appears to still be working. Will have to learn more about that.

ASP.NET Core multiple (types) authorization requirements in single policy

Is there any way to have something like this?
options.AddPolicy("IsEducationOwner", policy =>
{
// Eather first OR second policy requirement needs to be true
policy.Requirements.Add(new EducationOwnerRequirement()); // My custom requirement that has one handler
policy.RequireRole("CatalogAdmin"); // Role based requirement
});
I found that this is working. Requirements needs to have additional handler that checks for role in user claim so the code looks like this.
Additional information can be found on this MSDN page or in this article
My example:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options => {
options.AddPolicy("IsEducationOwner", policy =>
{
policy.Requirements.Add(new EducationOwnerRequirement());
});
});
services.AddTransient<IAuthorizationHandler, IsEducationOwnerHandler>();
services.AddTransient<IAuthorizationHandler, HasCatalogAdminRoleHandler>();
}
}
public class EducationOwnerRequirement : IAuthorizationRequirement
{
}
public class HasCatalogAdminRoleHandler : AuthorizationHandler<EducationOwnerRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EducationOwnerRequirement requirement)
{
if (context.User.IsInRole("CatalogAdmin"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class IsEducationOwnerHandler : AuthorizationHandler<EducationOwnerRequirement>
{
private PerformaContext _db;
public IsEducationOwnerHandler(PerformaContext db)
{
_db = db;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EducationOwnerRequirement requirement)
{
var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext == null || !context.User.HasClaim(c => c.Type == ClaimTypeNaming.oid))
{
return Task.CompletedTask;
}
var path = mvcContext.HttpContext.Request.Path.Value;
var educationId = path.Substring(path.IndexOf("/api/educations/") + 16, path.Length - path.IndexOf("/api/educations/") - 16);
var userExternalId = context.User.FindFirst(ClaimTypeNaming.oid).Value;
var userId = _db.GetUserByExternalId(userExternalId).Select(x => x.Id).FirstOrDefault();
if(userId == Guid.Empty)
{
return Task.CompletedTask;
}
var educationOwners = _db.GetOwnersForEducation(Guid.Parse(educationId)).Select(x => x.UserId).ToList();
if (educationOwners.Contains(userId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}

How to overload UserManager.AddToRoleAsync(string userId, string role)

I'm using Asp.net Identity Framework 2.1. I implement customized ApplicatoinUser, ApplicationRole, ApplicationUserRole, because I want to add support to multi-tenant, that is each user belongs to different companies, but I have 3 roles among all these companies, they are User, Admin and Approver.
My ApplicationUserRole derived from IdentityUserRole, and have one more property: CompanyId. This property will indicate the user's role in this particular company. My code for these customized classes attached in bottom.
My question is when I try to override ApplicationUserManager(Yes, it derived from UserManager too)'s AddToRoleAsync , IsInRoleAsync , I don't know how to deal with the new CompanyId, looks like the existing function doesn't receive these companyId(or tenantId).
Then when I'm trying to overload these functions with companyId included, I can't find the db context either in ApplicatoinUserManager nor its base class.
Am I on the right track of adding tenantId/companyId to the application Role?
I've referenced this answer: SO linkes, and this blog.ASP.NET Web Api and Identity 2.0 - Customizing Identity Models and Implementing Role-Based Authorization
My IdentityModels:
public class ApplicationUserLogin : IdentityUserLogin<string> { }
public class ApplicationUserClaim : IdentityUserClaim<string>
{
}
public class ApplicationUserRole : IdentityUserRole<string>
{
public string CompanyId { get; set; }
}
// 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<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>//, IAppUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public virtual string CompanyId { get; set; }
public virtual List<CompanyEntity> Company { get; set; }
public DateTime CreatedOn { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
return userIdentity;
}
}
// Must be expressed in terms of our custom UserRole:
public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
{
public ApplicationRole() {}
public ApplicationRole(string name) : this()
{
this.Name = name;
}
// Add any custom Role properties/code here
public string Description { get; set; }
}
// Most likely won't need to customize these either, but they were needed because we implemented
// custom versions of all the other types:
public class ApplicationUserStore: UserStore<ApplicationUser, ApplicationRole, string,ApplicationUserLogin, ApplicationUserRole,ApplicationUserClaim>, IUserStore<ApplicationUser, string>, IDisposable
{
public ApplicationUserStore()
: this(new IdentityDbContext())
{
base.DisposeContext = true;
}
public ApplicationUserStore(DbContext context)
: base(context)
{
}
}
public class ApplicationRoleStore
: RoleStore<ApplicationRole, string, ApplicationUserRole>,
IQueryableRoleStore<ApplicationRole, string>,
IRoleStore<ApplicationRole, string>, IDisposable
{
public ApplicationRoleStore()
: base(new IdentityDbContext())
{
base.DisposeContext = true;
}
public ApplicationRoleStore(DbContext context)
: base(context)
{
}
}
My IdentityConfig:
public class ApplicationUserManager
: UserManager<ApplicationUser, string>
{
public ApplicationUserManager(IUserStore<ApplicationUser, string> store)
: base(store) { }
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var manager = new ApplicationUserManager(
new UserStore<ApplicationUser, ApplicationRole, string,
ApplicationUserLogin, ApplicationUserRole,
ApplicationUserClaim>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
//RequireNonLetterOrDigit = true,
//RequireDigit = true,
//RequireLowercase = true,
//RequireUppercase = true,
};
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
// add sms and email service provider
manager.SmsService = new EMaySmsServiceProvider();
manager.EmailService = new ConcordyaEmailServiceProvider();
return manager;
}
public string GetCurrentCompanyId(string userName)
{
var user = this.FindByName(userName);
if (user == null)
return string.Empty;
var currentCompany = string.Empty;
if (user.Claims.Count > 0)
{
currentCompany = user.Claims.Where(c => c.ClaimType == ConcordyaPayee.Core.Common.ConcordyaClaimTypes.CurrentCompanyId).FirstOrDefault().ClaimValue;
}
else
{
currentCompany = user.CurrentCompanyId;
}
return currentCompany;
}
public override Task<IdentityResult> AddToRoleAsync(string userId, string role, string companyId)
{
return base.AddToRoleAsync(userId, role);
}
#region overrides for unit tests
public override Task<bool> CheckPasswordAsync(ApplicationUser user, string password)
{
return base.CheckPasswordAsync(user, password);
}
public override Task<ApplicationUser> FindByNameAsync(string userName)
{
return base.FindByNameAsync(userName);
}
#endregion
}
public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole, string> roleStore)
: base(roleStore)
{
}
public static ApplicationRoleManager Create(
IdentityFactoryOptions<ApplicationRoleManager> options,
IOwinContext context)
{
return new ApplicationRoleManager(
new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
}
}
First of all, I would like to say thanks for taking it this far. It gave me a great start for my multi-tenant roles solution. I'm not sure if I'm 100% right, but this works for me.
Firstly, you cannot override any of the "RoleAsync" methods, but you can overload them. Secondly, the UserStore has a property called "Context" which can be set to your DbContext.
I had to overload the "RoleAsyc" methods in both my UserStore and UserManager extended classes. Here is an example from each to get you going:
MyUserStore
public class MyUserStore : UserStore<MyUser, MyRole, String, IdentityUserLogin, MyUserRole, IdentityUserClaim> {
public MyUserStore(MyDbContext dbContext) : base(dbContext) { }
public Task AddToRoleAsync(MyUser user, MyCompany company, String roleName) {
MyRole role = null;
try
{
role = Context.Set<MyRole>().Where(mr => mr.Name == roleName).Single();
}
catch (Exception ex)
{
throw ex;
}
Context.Set<MyUserRole>().Add(new MyUserRole {
Company = company,
RoleId = role.Id,
UserId = user.Id
});
return Context.SaveChangesAsync();
}
}
MyUserManager
public class MyUserManager : UserManager<MyUser, String>
{
private MyUserStore _store = null;
public MyUserManager(MyUserStore store) : base(store)
{
_store = store;
}
public Task<IList<String>> GetRolesAsync(String userId, int companyId)
{
MyUser user = _store.Context.Set<MyUser>().Find(new object[] { userId });
MyCompany company = _store.Context.Set<MyCompany>().Find(new object[] { companyId });
if (null == user)
{
throw new Exception("User not found");
}
if (null == company)
{
throw new Exception("Company not found");
}
return _store.GetRolesAsync(user, company);
}
}
From here a couple scary things happen and I don't know a better way to manage them.
The User "IsInRole" method in the HttpContext will work but it will not be tenant-sensitive so you can no longer use it.
If you use the "Authorize" attribute, the same idea for "scary thing 1" applies, but here you can just extend it and make things happy for your system. Example below:
MyAuthorizeAttribute
public class MyAuthorizeAttribute : AuthorizeAttribute {
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (null == httpContext)
{
throw new ArgumentNullException("httpContext");
}
HttpSessionStateBase session = httpContext.Session;
IList<String> authorizedRoleNames = Roles.Split(',').Select(r => r.Trim()).ToList();
if (!httpContext.User.Identity.IsAuthenticated)
{
return false;
}
if (null == session["MyAuthorize.CachedUsername"])
{
session["MyAuthorize.CachedUsername"] = String.Empty;
}
if (null == session["MyAuthorize.CachedCompanyId"])
{
session["MyAuthorize.CachedCompanyId"] = -1;
}
if (null == session["MyAuthorize.CachedUserCompanyRoleNames"])
{
session["MyAuthorize.CachedUserCompanyRoleNames"] = new List<String>();
}
String cachedUsername = session["MyAuthorize.CachedUsername"].ToString();
int cachedCompanyId = (int)session["MyAuthorize.CachedCompanyId"];
IList<String> cachedUserAllRoleNames = (IList<String>)session["MyAuthorize.CachedUserAllRoleNames"];
IPrincipal currentUser = httpContext.User;
String currentUserName = currentUser.Identity.Name;
int currentCompanyId = (int)session["CurrentCompanyId"];//Get this your own way! I used the Session in the HttpContext.
using (MyDbContext db = MyDbContext.Create())
{
try
{
MyUser mUser = null;
ICollection<String> tmpRoleIds = new List<String>();
if (cachedUsername != currentUserName)
{
session["MyAuthorize.CachedUsername"] = cachedUsername = String.Empty;
//Reload everything
mUser = db.Users.Where(u => u.Username == currentUserName).Single();
session["MyAuthorize.CachedUsername"] = currentUserName;
session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = -1; //Force Company Reload
cachedUserCompanyRoleNames.Clear();
}
if (cachedUserCompanyRoleNames.Count != db.Users.Where(u => u.Username == currentUserName).Single().Roles.Select(r => r.RoleId).ToList().Count)
{
cachedUserCompanyRoleNames.Clear();
if (0 < currentCompanyId)
{
if(null == mUser)
{
mUser = db.Users.Where(u => u.Username == cachedUsername).Single();
}
tmpRoleIds = mUser.Roles.Where(r => r.Company.Id == currentCompanyId).Select(r => r.RoleId).ToList();
session["MyAuthorize.CachedUserCompanyRoleNames"] = cachedUserCompanyRoleNames = db.Roles.Where(r => tmpRoleIds.Contains(r.Id)).Select(r => r.Name).ToList();
session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = currentCompanyId;
}
}
if (cachedCompanyId != currentCompanyId)
{
cachedUserCompanyRoleNames.Clear();
//Reload company roles
if (0 < currentCompanyId)
{
if(null == mUser)
{
mUser = db.Users.Where(u => u.Username == cachedUsername).Single();
}
tmpRoleIds = mUser.Roles.Where(r => r.Company.Id == currentCompanyId).Select(r => r.RoleId).ToList();
session["MyAuthorize.CachedUserCompanyRoleNames"] = cachedUserCompanyRoleNames = db.Roles.Where(r => tmpRoleIds.Contains(r.Id)).Select(r => r.Name).ToList();
session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = currentCompanyId;
}
}
}
catch (Exception ex)
{
return false;
}
}
if (0 >= authorizedRoleNames.Count)
{
return true;
}
else
{
return cachedUserCompanyRoleNames.Intersect(authorizedRoleNames).Any();
}
}
}
In closing, as I said, I'm not sure if this is the best way to do it, but it works for me. Now, throughout your system, make sure you used your overloaded methods when dealing with Roles. I am also thinking about caching the Roles in a MVC BaseController that I wrote so that I can get similar functionality to User.IsInRole in all of my MVC Views.

Resources