I have a component which is able to to check it controller’s action is accessible for currently logged user.
public class ControllerAccessChecker : IControllerAccessChecker
{
ILifetimeScope _scope;
public ControllerAccessChecker(ILifetimeScope scope)
{
_scope = scope;
}
public bool IsAccessible<TController>(Expression<Action<TController>> action, RequestContext requestContext) where TController : Controller
{
var actionName = GetActionName(action);
var result = false;
using (var childScope = _scope.BeginLifetimeScope())
{
var controller = childScope.Resolve<TController>();
result = HasActionPermission(requestContext, actionName, controller);
}
return result;
}
private static string GetActionName(LambdaExpression actionExpression)
{
object operand;
if (actionExpression.Body as UnaryExpression != null)
{
operand = ((UnaryExpression)actionExpression.Body).Operand;
}
else
{
operand = actionExpression.Body;
}
MethodCallExpression methodCallExpression = (MethodCallExpression)operand;
return methodCallExpression.Method.Name;
}
private static bool HasActionPermission(RequestContext requestContext, string actionName, ControllerBase controller)
{
var controllerContext = new ControllerContext(requestContext, controller);
var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
return ActionIsAuthorized(controllerContext, actionDescriptor);
}
private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
return false;
AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);
foreach (Filter authFilter in FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor).Where(x => x.Instance is IAuthorizationFilter))
{
((IAuthorizationFilter)authFilter.Instance).OnAuthorization(authContext);
if (authContext.Result != null)
return false;
}
return true;
}
}
As you can see I am injecting ILifetimeScope into constructor and I am not sure if this is good practice. Also I am not sure if I am doing right thigs inside IsAccessible method. This Autofac’s scope hierarchy approach is really confusing for me.
I was thinking about some sort of abstract factory for controllers but it seems to be not easy to implement with Autofac. I found some articles and answers here about using IIndex<,> and registration with .Keyed() but I don’t think this is really what should be use in case like this. I mean I know what type I need so it should be easy just to ask for it.
Rather than injecting the lifetime scope, you can use
AutofacDependencyResolver.Current.RequestLifetimeScope– Unless you’re using this outside a web context, I’d probably go with that.One thing on the design of this: in
IsAccessibleyou’re resolving the actual controller, which also means you’re resolving and instantiating all of its dependencies, all the way down the chain. If you’re doing this in a menu system or somewhere that you’re doing a lot of these on every request, that’s a lot of controllers (and controller dependencies). And if your controller dependencies are, say, database connections or WCF service proxies… well, that could get pretty expensive just to check accessibility. Something to consider.