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

*爱你&永不变心* 提交于 2019-12-04 13:18:09

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.

  1. The User "IsInRole" method in the HttpContext will work but it will not be tenant-sensitive so you can no longer use it.
  2. 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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!