2 min read

Modifications of the controllers to embed EF Core Global Query Filters functionality

I know I’ve said it before and I will say it again, maybe for the last time, this was a redesign project, so all the modifications had to be minimal in the architecture already in place.

So, to achieve this, we’ve added a utilities class that takes cares of the things that happen in the controllers, but keep in mind that we used this in an Odata API Implementation scenario. You can adapt the class if needed also to an normal API logic as the class reads only the headers params:

    public class OdataParamsCheck
    {

        private StringValues tenantqueryVal;
        private String deletedqueryVal;
        private String crosstenantqueryVal;

        public List<Guid> CheckAllParams(HttpContext httpContext, out int hashCode, out bool isAllActive)
        {

            hashCode = 0;
            isAllActive = false;
            var tenantIds = new List<Guid>();
            var allowedtenantIds = new List<Guid>();
            var request = httpContext.Request;
            var userTenantClaims = httpContext.User.Claims.Where(c => c.Type == "tenant_ids").ToList();

            tenantqueryVal = request.Query["tenant-Id"];
            deletedqueryVal = request.Query["include-deleted"];
            crosstenantqueryVal = request.Query["cross-tenant"];

            if (deletedqueryVal != null)
            {            
                if(bool.Parse(deletedqueryVal) == true)
                    {
                        isAllActive = true;
                        hashCode += deletedqueryVal.GetHashCode();
                }
            }

            if (crosstenantqueryVal != null )
            { 
                if(bool.Parse(crosstenantqueryVal) == true)
                    {

                      foreach (var userTenantClaim in userTenantClaims)
                       {
                        //check if Querry TenantId allowd in Claims
                                hashCode += userTenantClaim.Value.GetHashCode();
                                allowedtenantIds.Add(new Guid(userTenantClaim.Value.ToString()));
                        }

                    }
            }
            else
            { 

                tenantIds.Add(new Guid(tenantqueryVal.First()));

            
                foreach (var userTenantClaim in userTenantClaims)
                {
                    //check if Querry TenantId allowd in Claims
                    foreach (var tid in tenantIds)
                    {
                        if (tid.ToString() == userTenantClaim.Value.ToString())
                        {

                            hashCode += tid.GetHashCode();
                            allowedtenantIds.Add(new Guid(tid.ToString()));
                        }

                    }

                }
            }

            return allowedtenantIds;

        }

    }

This class manages all the heavy lifting that has to be done by Global Query Filters in a fashionable way and also adds some logic base on the parameters the request that reaches the controller will have like :

1. tenantqueryVal = request.Query[“tenant-Id”];

This query parameter handles the scenario in which a user has access to multiple tenants but requests data only for 1.

2. deletedqueryVal = request.Query[“include-deleted”];

This query parameter handles the scenario in which a user request all the data they have access to, regardless of the fact that some lines might be soft deleted.

3. crosstenantqueryVal = request.Query[“cross-tenant”];

This query parameter handles the scenario in which a user request all the data they have access to from all the tenants he has access to.

All this so a normal GET controller would have only 2 rows in addition to what is was before:

OdataParamsCheck odataParamsCheck = new OdataParamsCheck();
_DBContext._tenantIds = odataParamsCheck.CheckAllParams(this.HttpContext, out _DBContext.hashCode, out _DBContext.isAllActive);

And even more, for the POST, PUT and PATCH methods there are no modifications needed because we have already achieved that by overwriting the SaveChangesAsync() method.

I really hope this was a fun ride, and we helped you understand better the topics we have approached together. If there are any questions, use the section below and will reach out to you.

Cheers, Consurs