В проекте, над которым я работаю, есть два пользовательских класса ActionFilterAttribute, которые внедряются с помощью нинъектов BindFilter:
kernel.BindFilter<LogErrorsAttribute>(FilterScope.Last, 0);
kernel.BindFilter<CriticalErrorAttribute>(FilterScope.Last, 1);
Они работали нормально.
Я создал собственный фильтр IAuthorizationFilter, который также вводится с помощью BindFilter:
kernel.BindFilter<AuthorizationFilter>(FilterScope.Action, null).WhenActionMethodHas<Authorise>().WithPropertyValueFromActionAttribute<Authorise>("Roles", n => n.Roles).WithPropertyValueFromActionAttribute<Authorise>("Years", n => n.Years);
Сам по себе, это тоже отлично работает.
Я только что обнаружил, что если я применю тег Authorize к действию, два пользовательских класса ActionFilterAttribute больше не будут вызываться.
Я озадачен, почему это произойдет. Мой пользовательский IAuthorizationFilter выглядит так:
public class AuthorizationFilter : IAuthorizationFilter
{
private readonly string[] RolesHaveAccessToApplication;
public AuthorizationFilter()
{
//put roles which should allow user to see application, hardcoded for now, but later
//this can be generated from the database
var configRoles = ConfigurationManager.AppSettings["ApplicationRoles"];
if(string.IsNullOrEmpty(configRoles))
throw new Exception("The ApplicationRoles value has not been defined in the web.config file.");
RolesHaveAccessToApplication = configRoles.Split(',');
}
[Inject]
public IUserServices userService { get; set; }
public string Roles { get; set; }
public string Years { get; set; }
protected bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
if(!Roles.HasContent() && !Years.HasContent())
{
return RolesHaveAccessToApplication.Any(role => RolesHaveAccessToApplication.Any(n => n == role));
}
var AuthenticatedUserRoles = System.Web.Security.Roles.GetRolesForUser();
bool isAuthorised = false;
//first, lets check against to see if the user has any roles related to the application
isAuthorised = RolesHaveAccessToApplication.Any(role => AuthenticatedUserRoles.Any(n => n == role));
//if they don't, we throw them to access denied page
if (!isAuthorised)
return false;
#region CheckRoles
if (!string.IsNullOrEmpty(Roles) && AuthenticatedUserRoles.HasContent())
{
var authRoles = Roles.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries);
isAuthorised = authRoles.Any(role => AuthenticatedUserRoles.Any(n => n == role));
}
#endregion
#region CheckYears
if (!string.IsNullOrEmpty(Years) && AuthenticatedUserRoles.HasContent())
{
if (AuthenticatedUserRoles.Any(n => n == "Student"))
{
var yearRoles = Years.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var user = userService.FetchUser(httpContext.User.Identity.Name);
if (user != null)
{
isAuthorised = yearRoles.Any(n => n == user.Year);
}
}
}
#endregion
return isAuthorised;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
if(filterContext == null)
throw new Exception("filtercontext is null");
if (!filterContext.HttpContext.Request.IsAuthenticated)
HandleUnauthorizedRequest(filterContext);
if (AuthorizeCore(filterContext.HttpContext))
SetCachePolicy(filterContext);
else
HandleUnauthorizedRequest(filterContext);
}
protected void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
filterContext.Result = new RedirectResult(new UrlHelper(filterContext.RequestContext).Action("Denied", "Home", new {Area = ""}));// new ViewResult { ViewName = "~/Home/Denied", View = new RazorView("Home") //ViewData = viewData };
else
filterContext.Result = new HttpUnauthorizedResult();
}
protected void SetCachePolicy(AuthorizationContext filterContext)
{
..snip..
}
}
//Used as a filter for actions, and ninject is configured to bind AuthorizationFilter to this
public class Authorise : ActionFilterAttribute
{
public string Roles { get; set; }
public string Years { get; set; }
}
Буду признателен за любую помощь в решении этой проблемы.
Edit:
Это один из других фильтров:
public class CriticalErrorAttribute : ActionFilterAttribute
{
[Inject]
public IErrorServices ErrorService { private get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{ //if the request is an ajax request, we don't want a redirect to happen
//the controller dealing with the ajax request can fetch the critical
//errors and pass them back to the user for display
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
var criticalErrors = ErrorService.FetchCriticalErrors();
if (criticalErrors.HasContent())
{
var helper = new UrlHelper(filterContext.RequestContext);
var url = helper.Action("Error", "Home", new { area = "" });
filterContext.Controller.TempData["CriticalErrorList"] = criticalErrors;
filterContext.Result = new RedirectResult(url);
}
}
base.OnActionExecuted(filterContext);
}
}
Если объект домена не может быть гидратирован, он регистрирует критическую ошибку. Этот фильтр проверяет наличие таких ошибок, если они возникают, он направляет пользователя на страницу ошибок.
Решено:
Оказывается, Дарин был прав! Но проблема была скрыта моей конфигурацией моих фильтров.
Во-первых, у меня было [Authorize] для элементов навигации, а во-вторых, я связывал CriticalErrorAttribute с каждым действием.
Таким образом, каждый раз, когда создавалось меню (welcome, left, nav, sub) - этот фильтр срабатывал. В какой-то момент во время этой цепочки вызовов фильтра результаты применялись к filterContext.Result - более поздние результаты скрывали более ранний (правильный) результат.
Чтобы преодолеть эту проблему, я настроил строку конфигурации BindFilter для CriticalError. Атрибут для этого:
kernel.BindFilter<CriticalErrorAttribute>(FilterScope.Last, 0).When( (context, ad) =>
context.RouteData.DataTokens["action"] != null && context.RouteData.DataTokens["action"] !=
"Error" && context.RouteData.DataTokens["controller"] != "Navigation");
Теперь все отлично работает!