【问题标题】:Autofac Web Api filters - declare at runtimeAutofac Web Api 过滤器 - 在运行时声明
【发布时间】:2016-04-03 09:19:39
【问题描述】:

我有一个 MVC 项目,我们使用派生自 System.Web.Mvc.AuthorizeAttribute 的自定义属性实现安全性。这放置在我们希望通过派生自System.Web.Mvc.Controller 的 MVC 控制器上的自定义授权来保护的方法上。我们还使用 Autofac for DI (IoC) 将 autohrization 服务注入我们的自定义安全过滤器。 现在,我们还将 Web API 控制器添加到我们的项目中,这些控制器源自 System.Web.Http.ApiController。我遇到的是,当您想在使用 Web API 时在过滤器中包含 DI 时,您似乎必须使用 Autofac DI 使用其流利的 api 注册每个过滤器实例或整个控制器。有关详细信息,请参阅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();
   }
}

【问题讨论】:

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


    【解决方案1】:

    不幸的是,AsWebApiAuthorizationFilterFor 的重载没有控制器类型参数。 如果你查看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);
    }
    

    利用这些信息,您可以轻松地 fork 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);
        }
    }
    

    【讨论】:

      【解决方案2】:

      感谢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
         }
      }
      

      【讨论】:

      • 您的回答帮助我意识到我不需要将依赖项传递给我的自定义授权属性,因为我可以使用 GetDependencyScope() 获取它们。谢谢
      猜你喜欢
      • 1970-01-01
      • 2014-02-05
      • 1970-01-01
      • 2021-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-31
      • 2018-09-12
      相关资源
      最近更新 更多