Применение атрибутов, не связанных с MVC, к действиям MVC

В нашем приложении есть понятие PermissionAttribute. Этот атрибут определен в базовом слое нашего приложения, и наши команды и запросы украшены этим атрибутом. Поскольку этот атрибут определен в базовом слое, мы не можем (и не хотим) позволить ему наследоваться от FilterAttribute или реализовать на нем System.Web.Mvc.IActionFilter.

Тем не менее, мы хотели бы применить этот атрибут к действиям контроллера следующим образом:

[Permission(Permissions.Administration.ManageUsers)]
public ActionResult Index()
{
    return this.View();
}

На основе этого атрибута должны применяться надлежащие проверки безопасности. Я просматривал кодовую базу MVC, чтобы найти подходящие хуки для настройки поведения MVC, позволяющие добавлять эти проверки безопасности на основе этого пользовательского атрибута. Я думал о создании пользовательского ControllerActionInvoker, который возвращал пользовательский ReflectedControllerDescriptor из своего метода GetControllerDescriptor, который возвращал бы FilterAttribute, который будет создан на основе существования PermissionAttribute, но это похоже на много работы, и я не уверен, что это правильный путь для прогулки.

Каким был бы эффективный и приятный способ настроить конвейер MVC, чтобы мы могли обрабатывать этот атрибут, не связанный с MVC?


person Steven    schedule 17.09.2014    source источник
comment
Не могли бы вы наследоваться от AuthoriseAttribute, поместить туда логику для проверки вашего PermissionAttribtue и добавить ее в свое приложение по всему миру?   -  person DavidG    schedule 17.09.2014
comment
Просто создайте глобальный фильтр в проекте mvc и проверьте атрибут Permission при выполняемом действии. :)   -  person dariol    schedule 17.09.2014
comment
@DavidG, но в этом случае мне нужно применить два атрибута к действию или нет? Это уродливо и подвержено ошибкам.   -  person Steven    schedule 17.09.2014
comment
@Steven Косвенно да, но вам нужна только 1 строка в файле FilterConfig.cs, чтобы применить ее ко всем действиям контроллера за один раз.   -  person DavidG    schedule 17.09.2014


Ответы (2)


Я бы сделал это так. Сначала создайте собственную реализацию AuthorizeAttribtues следующим образом:

public class PermissionAuthoriseAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        //Leaving the implementation of this to you, but you check if your
        //PermissionAttribute is assigned and call it's methods.
        if(...)
            return true;

        //You may want to check this first, depending on your requirements
        return base.AuthorizeCore(httpContext);
    }
}

Затем примените это к своему проекту, добавив эту строку в файл FilterConfig.cs:

filters.Add(new PermissionAuthoriseAttribute());
person DavidG    schedule 17.09.2014
comment
Да, это отличное решение. Я закончил реализацию IAuthorizationFilter напрямую, но ваш ответ достаточно близок. - person Steven; 17.09.2014

В итоге я сделал следующее:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
        filters.Add(new PermissionAuthorizationFilter(
            () => Global.Container.GetInstance<IUserPermissionChecker>()), 0);
        filters.Add(new HandleErrorAttribute());
    }
}

public sealed class PermissionAuthorizationFilter : IAuthorizationFilter
{
    private readonly Func<IUserPermissionChecker> userPermissionCheckerFactory;

    public PermissionAuthorizationFilter(
        Func<IUserPermissionChecker> userPermissionCheckerFactory) {
        this.userPermissionCheckerFactory = userPermissionCheckerFactory;
    }

    public void OnAuthorization(AuthorizationContext filterContext) {
        var attribute = filterContext.ActionDescriptor
            .GetCustomAttributes(typeof(PermissionAttribute), true)
            .OfType<PermissionAttribute>()
            .SingleOrDefault();

        if (attribute != null) {
            VerifyPermission(filterContext, attribute.PermissionId);
        }
    }

    private static void VerifyPermission(AuthorizationContext filterContext,
        Guid permissionId) {
        var permissionChecker = userPermissionCheckerFactory.Invoke();

        if (!permissionChecker.HasPermission(permissionId))
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
    }
}
person Steven    schedule 17.09.2014
comment
Хороший. Раздражает, что нет простого способа обойти расположение службы (но, как говорится в вашей статье в блоге, не делайте DI в атрибутах!) - person DavidG; 17.09.2014
comment
@Steven: вы можете внедрить фабрику для IUserPermissionChecker в конструктор PermissionAuthorizatinonFilter вместо разрешения через DependencyResolver. - person Ric .Net; 26.09.2014
comment
@Ric.Net: Да, это намного чище. Обновил мой ответ. - person Steven; 26.09.2014