Transparent entity collection filtering

Posts   
 
    
Kjelli
User
Posts: 27
Joined: 11-May-2006
# Posted on: 10-Jan-2007 13:35:58   

Hi

Is there a single place I can add a custom filter (group names from Active Directory) to a entitycollection that will make it filter no matter what method (GetMulti(...)) is called?

And to avoid the changes to be overwritten on next DAL generation? (base class ?)

In other words I have a method that returns i.e. 5 strings of AD group names(role1, role2, etc). The entitycollection should return only entities where RoleEntity.RoleName matches any of the strings from the AD method.

Using v2 selfservicing.

kjelli

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 10-Jan-2007 15:33:46   

I'm afraid I didn't understand your question. Would you please elaborate more? also please post the related database schema to be easier to imagine the issue.

Kjelli
User
Posts: 27
Joined: 11-May-2006
# Posted on: 10-Jan-2007 15:40:41   

I'm sorry for the poor explanation I gave.

Maybe some code will enlighten the issue:


        public bool GetMulti(ITransaction containingTransaction, IEntityCollection collectionToFill, long maxNumberOfItemsToReturn, ISortExpression sortClauses, IEntityFactory entityFactoryToUse, IPredicateExpression filter, IEntity countryInstance, IEntity logoPictureInstance, IEntity mapPictureInstance, int pageNumber, int pageSize)
        {
            base.EntityFactoryToUse = entityFactoryToUse;
            IEntityFields fieldsToReturn = EntityFieldsFactory.CreateEntityFieldsObject(CMS.DAL.EntityType.CustomerEntity);
            IPredicateExpression selectFilter = CreateFilterUsingForeignKeys(countryInstance, logoPictureInstance, mapPictureInstance, fieldsToReturn);
            if(filter!=null)
            {
                selectFilter.AddWithAnd(filter);
            }

            bool res = base.PerformGetMultiAction(containingTransaction, collectionToFill, maxNumberOfItemsToReturn, sortClauses, selectFilter, null, pageNumber, pageSize);

            System.Collections.Generic.List<string> customers = CMS.DAL.Roles.GetCompaniesForUser();

            IEntity[] backup = new IEntity[collectionToFill.Count];
            collectionToFill.CopyTo(backup, 0);

            foreach(IEntity item in backup)
                if(item is CustomerEntity)
                {
                    //check if customer is found in permitted collection. If not remove from result set
                    if(!customers.Contains(((CustomerEntity) item).Customer))
                        collectionToFill.Remove(item);
                }

            return res;
        }

This doesn't work, but illustrates what I'm trying to do. Whenever the customercollection is fetched I want to eliminate the entities the user does NOT have access to.

Did this help any?

I case you wonder what that external method is:


        public static List<string> GetCompaniesForUser(DirectoryEntry user)
        {
            //get group references
            //we use the collection in order to 
            //batch the request for translation
            IdentityReferenceCollection irc = ExpandTokenGroups(user).Translate(typeof(NTAccount));

            List<string> items = new List<string>();

            foreach (NTAccount account in irc)
            {
                if (account.Value.Contains(CMS.DAL.Roles.CompanyPrefix))
                {
                    items.Add(account.Value.Substring(account.Value.LastIndexOf("\\") + 1).Replace(CompanyPrefix, ""));
                }
            }

            return items;
        }
        public static List<string> GetCompaniesForUser()
        {
            //Get logged on user's AD entry
            DirectoryEntry user = GetCurrentUserDirectoryEntry();

            return GetCompaniesForUser(user);
        }
        private static string SidToHex(SecurityIdentifier sid)
        {

            int binLength = sid.BinaryLength;

            byte[] bt = new byte[binLength];

            sid.GetBinaryForm(bt, 0);

            System.Text.StringBuilder retval = new System.Text.StringBuilder(binLength * 2, binLength * 2);

            for (int cx = 0; cx < binLength; cx++)

                retval.Append(bt[cx].ToString("X2"));

            return retval.ToString();

        }

        //Sample Helper Function
        private static IdentityReferenceCollection ExpandTokenGroups(
          DirectoryEntry user)
        {
            user.RefreshCache(new string[] { "tokenGroups" });

            IdentityReferenceCollection irc =
              new IdentityReferenceCollection();

            foreach (byte[] sidBytes in user.Properties["tokenGroups"])
            {
                irc.Add(new SecurityIdentifier(sidBytes, 0));
            }
            return irc;
        }

thanks, kjelli

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 10-Jan-2007 16:05:59   

You may use the .NET 2.0 partial classes filter to extend the entity class in hand and to override the GetMulti methods, and the new file containing the extension code won't be overwritten when regenerating the code.

Kjelli
User
Posts: 27
Joined: 11-May-2006
# Posted on: 11-Jan-2007 08:57:52   

I follow you somewhat, but my attempts to catch this GetMulti method still fails.

This is the statements I use to get all customers:


            CustomerCollection collection = new CustomerCollection();
            collection.GetMulti(null);

I created the partial class you mentioned:


    public partial class CustomerCollection : EntityCollectionBase<CustomerEntity>
    {
        protected new bool GetMulti(IPredicate selectFilter)
        {
            //this point is never reached!!
            return base.GetMulti(selectFilter);
        }
    }

My "new" method is never called (cannot override since not virtual). Seems like I "new" the wrong method. There are some things about the generated code I don't get. I can find only one GetMulti method, and that is the one in CustomerDAO.cs. There are however 9 abstract definitions in EntityCollectionBase<TEntity>, but I cannot find any implementation of those.

I hope you see what I'm trying to do now as it is actually simple: (pseudo)

- Get all groups (strings) from AD where this user is a member, directly or indirectly.
-Foreach item returned from GetMulti base implementation
{
  if current CustomerEntity.CustomerName does not exists in the string[] from AD, remove from CustomerCollection
  else continue
}

return reduced CustomerCollection

Thanks for your patience!

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 11-Jan-2007 15:15:39   

Oh, Yes. GetMulti() methods are implemented in the EntityCollectionBase<TEntity> class.

So you can extend the collection class inhand with the partial keyword, and now instead of overriding the GetMulti, just create a new method of your own that should wrap the GetMulti (i.e it calls the base.GetMulti() method)

Kjelli
User
Posts: 27
Joined: 11-May-2006
# Posted on: 24-Jan-2007 09:55:58   

There seems to be many places to override GetMulti().

So all I did was this (single override) and now it works for all methods for this class:


    public partial class CustomerCollection : EntityCollectionBase<CustomerEntity>
    {
        private System.Collections.Generic.List<string> customers;

        protected override void PerformAddToActiveContext(CustomerEntity entity)
        {
            if (customers == null)
            {
                customers = CMS.DAL.Roles.GetCompaniesForUser();

                //if previous method returned null make sure a real collection is present
                if (customers == null)
                    customers = new List<string>();
            }

            //check if customer is found in permitted collection. If not remove from result set
            if (!customers.Contains(entity.Customer))
                Remove(entity);
            else
                base.PerformAddToActiveContext(entity);
        }
    }

Thanks for your time!