问题描述
|
我在存储库模式中将Linq-to-sql与Unity一起使用。我试图在存储库方法“ 0”上添加一个用于对象安全的拦截器,以拦截调用并仅返回用户有权访问的实体。
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;
}
}
我已经构建了一个表达式,但是我不能做message.ReturnValue.Where(expression)
,因为message.ReturnValue
是object
(message.ReturnValue
实际上是System.Data.Linq.Table<TEntity>
,但是我不想太受L2S的束缚),并且它在运行时所以我可以\不要将其转换回通用名称并替换“ 3”。
或者,我尝试了
public interface ISecurable<TKey>
{
TKey SecurityId { get; }
}
在实体上,这使我有些困难,但是如果可以将其余的安全方面分开,我可以接受。这使我可以在IMethodReturn Invoke(IMethodInvocation input,GetNextHandlerDelegate getNext)
中执行以下操作,在其中构建上面的表达式:
if(typeof(ISecurableType).IsAssignableFrom(entityType))
{
var secured = ((IQueryable<ISecurable>)message.ReturnValue).Where(expression);
//Need to return secured as IQueryably<TEntity>
}
现在,我必须将secured
转换为IQueryable<ISecurable>
,但typeof(IQueryable<TEntity>).IsAssignableFrom(secured.GetType())
返回false,换出返回值会引发异常,但就我所知,它似乎确实可以延迟执行。 (此外,我在设计时不了解Entity14ѭ中的TEntity,但我确实知道反射类型-但是我尝试使用我知道它在测试中的类声明。)
有什么办法可以修改返回结果吗?我被迫需要返回设计时不知道的泛型,从而使之不可能,但我也无法修改表达式(((IQueryable)message.ReturnType).Expression
被声明为Expression Expression { get; }
)。
有没有能以一种可行的方式向我指出的辉煌?
tl; dr在运行时需要从object
(即Table<TEntity> : IQueryable<TEntity>
和附加.Where(expression)
)返回IQueryable<TEntity>
。
解决方法
您可以尝试在运行时创建动态表达式。只要您不使用\“ Select \”更改元素类型,就不必显式将IQueryable强制转换回其通用类型。
例:
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);
}
}
我假设您的属性将提供一些允许值的数组。
以下是一些有助于此过程的扩展方法:
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];
}
}
我没有尝试自己运行此程序,但希望它足以使您入门。