开发者

Linq to SQL with Unity Interception

开发者 https://www.devze.com 2023-03-14 07:53 出处:网络
I am using Linq-to-SQL with Unity in a Repository pattern.I am trying to add an in开发者_开发知识库terceptor for object security on the repository method [Securable]IQueryable<TEntity> List<T

I am using Linq-to-SQL with Unity in a Repository pattern. I am trying to add an in开发者_开发知识库terceptor for object security on the repository method [Securable]IQueryable<TEntity> List<TEntity>() that intercepts the call and returns only the entities that the user has rights to.

public class SecurableAttribute : HandlerAttribute
{...}

public class SecurableHandler : ICallHandler
{
    ...
    IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        var message = getNext()(input, getNext);
        var returnType = message.ReturnValue.GetType();
        if (typeof(IQueryable).IsAssignableFrom(returnType))
        {
            var entityType = returnType.GetGenericArguments().Single();
            var securableAttribute = entityType.GetAttribute<SecurableTypeAttribute>();
            if(securableAttribute != null)
            {
                //Build expression to filter the list from the attribute and primary key of the entity
                //Return the new IQueryable
            }
        }
        return message;
    }
}

I have built an expression, but I can't do message.ReturnValue.Where(expression) since the message.ReturnValue is object (message.ReturnValue is actually a System.Data.Linq.Table<TEntity>, but I don't want to be too tied to L2S), and it is at runtime so I can't cast it back to a generic and replace message.ReturnValue.

Alternatively, I tried

public interface ISecurable<TKey>
{
    TKey SecurityId { get; }
}

on the entity, which locks me in a bit, but I am OK with that if I could separate the remaining security aspects. This allows me to do the following in IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) where I build the expression above:

if(typeof(ISecurableType).IsAssignableFrom(entityType))
{
    var secured = ((IQueryable<ISecurable>)message.ReturnValue).Where(expression);
    //Need to return secured as IQueryably<TEntity>
}

I now have to cast secured to IQueryable<ISecurable> but typeof(IQueryable<TEntity>).IsAssignableFrom(secured.GetType()) returns false, and swapping out the return value throws an exception, but it does seem to work with delayed execution as far as I can tell. (Also, I don't know TEntity at design time in SecurableHandler, but I do know the reflected type - but I have tried using the class declaration that I know it is in testing.)

Is there any way to modify the return results somehow? I am stuck needing to return a generic that I don't know at design time, thus making that impossible, but I also can't modify the expression (((IQueryable)message.ReturnType).Expression is declared as Expression Expression { get; }).

Is there any brilliance out there that could point me in a way that works?

tl;dr Need to return an IQueryable<TEntity> at runtime from an object that is a Table<TEntity> : IQueryable<TEntity> with an additional .Where(expression).


You can try creating a dynamic expression at runtime. You shouldn't have to explicitly cast the IQueryable back to it's generic type, as long as you don't change the element types with a "Select".

Example:

    public class SecurityHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        var message = getNext()(input, getNext);
        var returnType = message.ReturnValue.GetType();
        if (typeof(IQueryable).IsAssignableFrom(returnType))
        {
            var entityType = returnType.GetGenericArguments().Single();

            var securableAttribute = entityType.GetAttribute<SecurableTypeAttribute>();
            if (securableAttribute != null)
            {
                //Build expression to filter the list from the attribute and primary key of the entity
                //Return the new IQueryable
                message.ReturnValue = AddWhereExpression(
                    (IQueryable)message.ReturnValue, 
                    securableAttribute.FilterValues,
                    securableAttribute.FilterPropertyName);
            }
        }
        return message;
    }

    public int Order { get; set; }

    private static IQueryable AddWhereExpression(IQueryable query, IEnumerable ids, string filterPropertyName)
    {
        // Build this expression:
        // item => ids.Contains(item.[PrimaryKeyPropertyName])

        var itemParameter = Expression.Parameter(query.ElementType, "item");

        var itemParameterProperty = Expression.Property(itemParameter, filterPropertyName);

        var listParameter = Expression.Constant(ids);

        var containsExpression = Expression.Call(
            typeof(System.Linq.Enumerable),
            "Contains",
            new[] { typeof(int) },
            listParameter,
            itemParameterProperty);

        var delegateTypeExpression = Expression.GetFuncType(new[] { query.ElementType, typeof(bool) });

        var whereExpression = Expression.Lambda(
            delegateTypeExpression,
            containsExpression,
            new[] { itemParameter }
            );

        Expression callWhere = Expression.Call(
                                     typeof(Queryable),
                                     "Where",
                                     new Type[] { query.ElementType },  // type args for Where<T>()
                                     query.Expression,
                                     whereExpression
                                     );

        return query.Provider.CreateQuery(callWhere);
    }
}

I am assuming your attribute will provide some array of allowable values.

Here are some extension methods that will help with this process:

public static class TypeExtensions
{       

    public static TAttribute GetAttribute<TAttribute>(this Type type)
    {
        var attributes = type.GetCustomAttributes(typeof(TAttribute), true);
        if (attributes.Length == 0) return default(TAttribute);
        return (TAttribute)attributes[0];
    }      

    public static PropertyInfo GetPropertyWithAttributeValue<TAttribute>(
        this IEnumerable<PropertyInfo> properties,
        Func<TAttribute, bool> findPredicate)
        where TAttribute : Attribute
    {
        var property = from p in properties
                       where p.HasAttribute<TAttribute>() &&
                       findPredicate.Invoke(p.GetAttribute<TAttribute>())
                       select p;

        return property.FirstOrDefault();
    }

    public static bool HasAttribute<TAttribute>(this PropertyInfo propertyInfo)
    {
        return propertyInfo.GetCustomAttributes(typeof(TAttribute), true).Any();
    }

    public static TAttribute GetAttribute<TAttribute>(this PropertyInfo propertyInfo)
    {
        var attributes = propertyInfo.GetCustomAttributes(typeof(TAttribute), true);
        if (attributes.Length == 0) return default(TAttribute);
        return (TAttribute)attributes[0];
    }
}

I haven't tried running this myself, but hopefully it's enough to get you started.

0

精彩评论

暂无评论...
验证码 换一张
取 消