Autofac Web Api过滤器 - 在运行时声明

时间:2015-12-29 13:17:25

标签: c# asp.net-web-api asp.net-web-api2 autofac

我有一个MVC项目,我们使用派生自System.Web.Mvc.AuthorizeAttribute的自定义属性实现安全性。这是我们希望通过源自System.Web.Mvc.Controller的MVC控制器上的自定义授权来保护的方法。我们还使用Autofac for DI(IoC)将自动化服务注入我们的自定义安全过滤器。 我们现在也在向我们的项目添加Web API控制器,它来自System.Web.Http.ApiController。我遇到的情况是,当您想在使用Web API时在过滤器中包含DI时,您似乎必须使用其流畅的api向Autofac DI注册每个过滤器实例或整个控制器。有关详细信息,请参阅autofac documentation

对于MVC控制器,我们只需添加属性,无需额外注册。 现在对于ApiControllers,我们需要使用流畅的方法注册该属性(过滤器)实例,不需要将此属性应用于方法/类本身。 我希望通过仅将这些过滤器应用为属性而不使用流畅的方法来保持一致性,更不用说更容易维护了。这是一种更清洁的方法恕我直言,特别是在一个更大的解决方案中,可能有超过100个安全实现。

我一直在尝试创建一个方法,它可以在Web API控制器上注册所有应用过滤器的实例,但似乎Autofac实现不适合这一点。 任何人都可以建议如何解决此限制或在运行时注册过滤器的替代方法,而不必为每个过滤器实例编写重复的代码?

示例代码

  • ModuleAccessAuthorizationAttribute - 这是实现Autofac.Integration.WebApi.IAutofacAuthorizationFilter
  • 的自定义授权属性
  • MyController - 这是一个实现System.Web.Http.ApiController
  • 的控制器

此作品

builder.Register(c => new ModuleAccessAuthorizationAttribute(c.Resolve<IAuthenticationFactory>())) // the constructor actually takes much more information which varies each time it is applied but that is out of the scope of this question
   .AsWebApiAuthorizationFilterFor<MyController>(c => c.Get()) // Get() is one of the methods that has needs to have the filter applied to it
   .InstancePerRequest();

我想将其更改为

public class MyController : ApiController {
   [ModuleAccessAuthorizationAttribute]
   public IHttpActionResult Get()
}

public static void RegisterWebApiSecurityFilter(ContainerBuilder builder)
{
   var securedMembers = typeof(DependencyInjectionConfig).Assembly.ExportedTypes
      .Where(x => !x.IsAbstract // not abstract
               && x.Name.EndsWith("Controller", StringComparison.Ordinal) // name ends in controller
               && !x.GetCustomAttributes<ObsoleteAttribute>().Any(y => y.IsError) // not marked as obsolete
               && (x.IsSubclassOf(typeof(System.Web.Http.ApiController)))) // implements ApiController
      .SelectMany(x => x.GetMembers(BindingFlags.Public | BindingFlags.Instance)) // public instance members 
      .Where(x => x.GetCustomAttributes<ModuleAccessAuthorizationAttribute>().Any()) // marked with my custom attribute
      .Select(x => x)
      .ToList();

   foreach (var securedMember in securedMembers) // iterate over collection
   {
      var attribute = securedMember.GetCustomAttribute<ModuleAccessAuthorizationAttribute>(); // get the attribute instance
      var declaringClass = securedMember.DeclaringType; // get the declaring (class) type

      builder.Register(c => ModuleAccessAuthorizationAttribute(c.Resolve<IAuthenticationFactory>()))
         .AsWebApiAuthorizationFilterFor() // this is where I need help, it seems this is generic only and I cannot explicitly pass in my reflected type declaringClass
         .InstancePerRequest();
   }
}

2 个答案:

答案 0 :(得分:1)

不幸的是AsWebApiAuthorizationFilterFor没有带有controllerType参数的重载。 如果查看AsWebApiAuthorizationFilterFor方法的源代码,可以看到实现如下:

public static IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle>
    AsWebApiAuthorizationFilterFor<TController>(this IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle> registration)
        where TController : IHttpController
{
    return AsFilterFor<IAutofacAuthorizationFilter, TController>(registration, AutofacWebApiFilterProvider.AuthorizationFilterMetadataKey);
}

AsFilterFor是:

static IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle>
    AsFilterFor<TFilter, TController>(IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle> registration, string metadataKey)
        where TController : IHttpController
{
    if (registration == null) throw new ArgumentNullException("registration");

    var limitType = registration.ActivatorData.Activator.LimitType;

    if (!limitType.IsAssignableTo<TFilter>())
    {
        var message = string.Format(CultureInfo.CurrentCulture, RegistrationExtensionsResources.MustBeAssignableToFilterType,
            limitType.FullName, typeof(TFilter).FullName);
        throw new ArgumentException(message, "registration");
    }

    var metadata = new FilterMetadata
    {
        ControllerType = typeof(TController),
        FilterScope = FilterScope.Controller,
        MethodInfo = null
    };

    return registration.As<TFilter>().WithMetadata(metadataKey, metadata);
}

通过这些信息,您可以轻松地分叉RegistrationExtensions类并添加符合您需求的新重载。

即:

public static class MyRegistrationExtensions 
{
    public static IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle>
        AsWebApiAuthorizationFilterFor(this IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle> registration, Type controllerType)
    {
        return AsFilterFor<IAutofacAuthorizationFilter>(registration, AutofacWebApiFilterProvider.AuthorizationFilterMetadataKey, controllerType);
    }


    static IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle>
        AsFilterFor<TFilter>(IRegistrationBuilder<object, IConcreteActivatorData, SingleRegistrationStyle> registration, string metadataKey, Type controllerType)
    {
        if (registration == null) throw new ArgumentNullException("registration");
        if (controllerType == null) throw new ArgumentNullException("controllerType");
        if (!controllerType.IsAssignableTo<IHttpController>()) throw new ArgumentNullException("controllerType");

        var limitType = registration.ActivatorData.Activator.LimitType;

        if (!limitType.IsAssignableTo<TFilter>())
        {
            var message = string.Format(CultureInfo.CurrentCulture, RegistrationExtensionsResources.MustBeAssignableToFilterType,
                limitType.FullName, typeof(TFilter).FullName);
            throw new ArgumentException(message, "registration");
        }

        var metadata = new FilterMetadata
        {
            ControllerType = controllerType,
            FilterScope = FilterScope.Controller,
            MethodInfo = null
        };

        return registration.As<TFilter>().WithMetadata(metadataKey, metadata);
    }
}

答案 1 :(得分:1)

感谢您Cyril Durand的出色回复。我不希望分叉存储库,因为我不想维护其他代码。我选择不使用Autofac作为过滤器提供程序,而是依赖于默认的Web Api实现来获取过滤器,然后在过滤器的代码中使用依赖项解析器来获取我的依赖项实例。虽然这违背了使用DI / IoC背后的设计原则,但在这个特定情况下,我个人发现这个好处超过了必须分叉Autofac RegistrationExtensions并保持这一点的成本。

这是代码,如果有人感兴趣,虽然它是相当自我解释我想如何实现这一点。

public sealed class ModuleAccessAuthorizationAttribute : System.Web.Http.Filters.IAuthorizationFilter {
   public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation) {
      var resolver = actionContext.Request.GetDependencyScope();
      var authenticationFactory = resolver.GetService<IAuthenticationFactory>();
      // rest of implementation executing an authorization check
   }
}