Ninject 到简单注入器:使用控制器属性(带参数)注册 ActionFilter

Ninject to Simple Injector: Register ActionFilter with Controller Attribute (with params)

我有一个使用 Simple Injector 的 WebApi 应用程序,我正在尝试使用控制器属性(带参数)配置特定过滤器。我在另一个使用 Ninject 的项目中使用此配置,但我不知道如何在 Simple Injector 上执行此操作。

public enum UserType {
    Director,
    Developer,
    Leader
}

我的控制器:

[RequiresAtLeastOneOfUserTypes(UserType.Developer, UserType.Leader)]
public class MyController : Controller
{
    ...
}

我的属性:

public sealed class RequiresAtLeastOneOfUserTypesAttribute : Attribute
{
    public UserType[] TypesToBeVerified { get; set; }

    public RequiresAtLeastOneOfUserTypesAttribute(params UserType[] typesToBeVerified)
    {
        TypesToBeVerified = typesToBeVerified;
    }
}

我的过滤器:

public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
    private readonly IUser _user;
    private readonly UserType[] _typesToBeVerified;

    protected RequiresAtLeastOneOfUserTypesFilter(IUser user, params UserType[] typesToBeVerified)
    {
        _user = user;
        _typesToBeVerified = typesToBeVerified;
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        bool authorized = _user.HasAtLeastOneOfTypes(_typesToBeVerified);
        if (!authorized)
        {
            throw new ForbiddenUserException();
        }
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // do nothing
    }
}

最后是我的 Ninject 配置:

this.BindFilter<RequiresAtLeastOneOfUserTypesFilter>(FilterScope.Controller, 0)
    .WhenControllerHas<RequiresAtLeastOneOfUserTypesAttribute>()
    .WithConstructorArgumentFromControllerAttribute<RequiresAtLeastOneOfUserTypesAttribute>(
        "typesToBeVerified",
         attribute => attribute.typesToBeVerified);

我的问题是:如何使用 Simple Injector 进行此配置?

Simple Injector Web API 集成包不像 Ninject 的集成包那样包含操作过滤器的集成功能。但是这样的集成可以用几行代码构建。

这里有几个选项。第一个选项是恢复为直接从操作过滤器内部解析服务,如 documentation 中所示。当您只有一个过滤器 class 时,这种方法很好,但不是最干净的方法,并且会迫使您对已创建的过滤器属性进行更改。

因此,作为第二个选项,您可以创建一个操作过滤器代理 class,它能够将调用转发到您的真实过滤器 class,然后可以通过简单注入器解决:

public class ActionFilterProxy<T> : IActionFilter
    where T : IActionFilter
{
    public ActionFilterProxy(Container container) => _container = container;

    public void OnActionExecuting(ActionExecutingContext filterContext) =>
        _container.GetInstance<T>().OnActionExecuting(filterContext);

    public void OnActionExecuted(ActionExecutedContext filterContext) =>
        _container.GetInstance<T>().OnActionExecuted(filterContext);
}

使用此代理,您可以进行以下配置:

GlobalConfiguration.Configuration.Filters.Add(
    new ActionFilterProxy<RequiresAtLeastOneOfUserTypesFilter>(container));

container.Register<RequiresAtLeastOneOfUserTypesFilter>();

这仍然迫使您对 RequiresAtLeastOneOfUserTypesFilter 进行更改,因为 Simple Injector 无法向 RequiresAtLeastOneOfUserTypesFilter 的构造函数提供属性信息(UserType[])。相反,您可以将 RequiresAtLeastOneOfUserTypesFilter 更改为以下内容:

public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
    private readonly IUser _user;

    public RequiresAtLeastOneOfUserTypesFilter(IUser user) => _user = user;

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Get the attribute from the controller here
        var attribute = filterContext.ActionDescriptor.ControllerDescriptor
            .GetCustomAttribute<RequiresAtLeastOneOfUserTypesAttribute>();

        bool authorized = _user.HasAtLeastOneOfTypes(attribute.TypesToBeVerified);
        if (!authorized)
        {
            throw new ForbiddenUserException();
        }
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
    }
}

要使用的第三个选项是 documentation 中提到的选项,在 这篇博客 post 中进行了描述,其中讨论了一个模型您将过滤器置于特定于应用程序的抽象之后,并允许它们自动注册。它使用类似的代理方法。当您有 multiple/many 个需要应用的过滤器(它们的执行顺序无关紧要)时,此方法很有用。