Is there a way to use AutoFac Web Api authorization filters through attributes instead of injection?

I have an Autofac Web Api authorization filter:

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

Right now, the only way I can use the Autofac Web Api Authorization Filter is to enter it in AutofacConfig.cs:

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

and it seems that the attribute is ignored unless I add it as stated above

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

Is there a way to use attributes / annotations for AutoFac Web Api authorization filters instead of injections through AutoFac, and are their attachments right?

+5
source share
2 answers

Unfortunately, if you want to use DI in your filters, you cannot use attributes. In the docs :

Unlike the filter provider in MVC, the Web API does not specify that filter instances should not be cached. This means that all filter attributes in the web API are in fact singleton instances that exist throughout the life of the application.

If you want to use attributes, the best you can do is use the service location in the standard Web API attributes. Extract the request pending area from the request message and manually enable the services you need.

+7
source

You can use your own extension for the ContainerBuilder class for the Autofac library:

 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; } } 

Then you can make one registration for the entire assembly, and not for each controller:

 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; } } 

Registration:

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

You can use your attribute on a controller or action:

 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" }; } } 

The whole demo project is on github

0
source

All Articles