有没有办法通过属性而不是注入来使用 AutoFac Web Api 授权过滤器?

Is there a way to use AutoFac Web Api Authorization Filters through Attributes instead of injection?

我有一个 Autofac Web Api 这样的授权过滤器:

public class MyAuthorizationFilter : IAutofacAuthorizationFilter
{
    public void OnAuthorization(HttpActionContext actionContext){}
}

public class MyAuthorizationAttribute : Attribute
{
    public MyAuthorizationAttribute() { }
}

现在我拥有 Autofac Web Api 授权过滤器的唯一方法是将其注入 AutofacConfig.cs:

builder.RegisterType<MyAuthorizationFilter>()
.AsWebApiAuthorizationFilterFor<MyController>(
    c => c.MyMethod(default(MyModel))
).InstancePerDependency();

如果我没有像上面那样注入它,该属性似乎会被忽略

public MyController : ApiController {

    [MyAuthroziationFilter] // ignored
    [POST("")]
    public HttpResponseMessage MyMethod(MyModel myModel) { 
        [...]
    }
}

有没有一种方法可以使用 attributes/annotations 用于 AutoFac Web Api 授权过滤器而不是通过 AutoFac 注入并正确注入它们的依赖项?

不幸的是,如果您想在过滤器中使用 DI,则不能使用属性。 Per the docs:

Unlike the filter provider in MVC, the one in Web API does not allow you to specify that the filter instances should not be cached. This means that all filter attributes in Web API are effectively singleton instances that exist for the entire lifetime of the application.

如果您想使用属性,最好的办法是使用标准 Web API 属性中的服务位置。从请求消息中获取请求生命周期范围并手动解析您需要的服务。

您可以为 Autofac 库的 ContainerBuilder class 使用自定义扩展:

public static class ContainerBuilderExtensions
{
    public static void RegisterWebApiFilterAttribute<TAttribute>(this ContainerBuilder builder, Assembly assembly) where TAttribute : Attribute, IAutofacAuthorizationFilter
    {
        Type[] controllerTypes = assembly.GetLoadableTypes()
            .Where(type => typeof(ApiController).IsAssignableFrom(type)).ToArray();
        RegisterFilterForControllers<TAttribute>(builder, controllerTypes);
        RegisterFilterForActions<TAttribute>(builder, controllerTypes);
    }

    // We need to call 
    // builder.RegisterType<TFilter>().AsWebApiAuthorizationFilterFor().InstancePerDependency()
    // for each controller marked with TAttribute
    private static void RegisterFilterForControllers<TAttribute>(ContainerBuilder builder, IEnumerable<Type> controllerTypes) where TAttribute : Attribute, IAutofacAuthorizationFilter
    {
        foreach (Type controllerType in controllerTypes.Where(c => c.GetCustomAttribute<TAttribute>(false)?.GetType() == typeof(TAttribute)))
        {
            GetAsFilterForControllerMethodInfo(controllerType).Invoke(null, new object[] { builder.RegisterType(typeof(TAttribute)) });
        }
    }

    // We need to call 
    // builder.RegisterType<TFilter>().AsWebApiAuthorizationFilterFor(controller => controller.Action(arg)).InstancePerDependency()
    // for each controller action marked with TAttribute
    private static void RegisterFilterForActions<TAttribute>(ContainerBuilder builder, IEnumerable<Type> controllerTypes)
        where TAttribute : Attribute, IAutofacAuthorizationFilter
    {
        foreach (Type controllerType in controllerTypes)
        {
            IEnumerable<MethodInfo> actions = controllerType.GetMethods().Where(method => method.IsPublic && method.IsDefined(typeof(TAttribute)));
            foreach (MethodInfo actionMethodInfo in actions)
            {
                ParameterExpression controllerParameter = Expression.Parameter(controllerType);
                Expression[] actionMethodArgs = GetActionMethodArgs(actionMethodInfo);
                GetAsFilterForActionMethodInfo(controllerType).Invoke(null,
                    new object[] {
                        builder.RegisterType(typeof(TAttribute)),
                        Expression.Lambda(typeof(Action<>).MakeGenericType(controllerType), Expression.Call(controllerParameter, actionMethodInfo, actionMethodArgs),
                            controllerParameter)
                    });
            }
        }
    }

    private static Expression[] GetActionMethodArgs(MethodInfo actionMethodInfo)
    {
        return actionMethodInfo.GetParameters().Select(p => Expression.Constant(GetDefaultValueForType(p.ParameterType), p.ParameterType)).ToArray<Expression>();
    }

    /// <summary>
    ///     Returns info for <see cref="Autofac.Integration.WebApi.RegistrationExtensions" />.AsWebApiAuthorizationFilterFor()
    ///     method
    /// </summary>
    private static MethodInfo GetAsFilterForControllerMethodInfo(Type controllerType)
    {
        return GetAsFilterForMethodInfo(controllerType, parametersCount: 1);
    }

    /// <summary>
    ///     Returns info for <see cref="Autofac.Integration.WebApi.RegistrationExtensions" />
    ///     .AsWebApiAuthorizationFilterForerFor(actionSelector) method
    /// </summary>
    private static MethodInfo GetAsFilterForActionMethodInfo(Type controllerType)
    {
        return GetAsFilterForMethodInfo(controllerType, parametersCount: 2);
    }

    private static MethodInfo GetAsFilterForMethodInfo(Type controllerType, int parametersCount)
    {
        return typeof(RegistrationExtensions).GetMethods()
            .Single(m => m.Name == nameof(RegistrationExtensions.AsWebApiAuthorizationFilterFor) && m.GetParameters().Length == parametersCount)
            .MakeGenericMethod(controllerType);
    }

    private static object GetDefaultValueForType(Type t)
    {
        return typeof(ContainerBuilderExtensions).GetMethod(nameof(GetDefaultGeneric))?.MakeGenericMethod(t).Invoke(null, null);
    }

    private static T GetDefaultGeneric<T>()
    {
        return default;
    }
}

那么您就可以为整个组件进行一次注册,而不是为每个控制器进行一次注册:

public class LoggerFilterAttribute : Attribute, IAutofacAuthorizationFilter
{
    private readonly ILog _logger;
    //if you don't want empty constructor you can use separate "marker" attribute and current class will not have to inherit Attribute, but you will need to modify ContainerBuilderExtensions
    public LoggerFilterAttribute() { }
    public LoggerFilterAttribute(ILog logger)
    {
        _logger = logger;
    }

    public Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        _logger.Trace($"Authorizing action '{actionContext.ControllerContext.ControllerDescriptor.ControllerName}.{actionContext.ActionDescriptor.ActionName}'");
        return Task.CompletedTask;
    }
}

报名人数:

builder.RegisterWebApiFilterAttribute<LoggerFilterAttribute>(Assembly.GetExecutingAssembly());

您可以在控制器或操作上使用您的属性:

public class AuthorsController : ApiController
{
    // GET api/authors
    [LoggerFilter]
    public IEnumerable<string> Get()
    {
        return new string[] { "author1", "author2" };
    }        
}

[LoggerFilter]
public class BooksController : ApiController
{
    // GET api/books
    public IEnumerable<string> Get()
    {
        return new string[] { "book1", "book2" };
    }
}

整个演示项目在github