Constructor Dependency Injection WebApi Atributos

He estado buscando una opción de inyección sin parámetros para los atributos de WebApi.

Mi pregunta es simplemente si esto es realmente posible usando Structuremap?

He estado buscando en Google pero sigo generando inyecciones de propiedades (que prefiero no usar) o supuestas implementaciones de inyección de constructores que hasta ahora no he podido replicar.

Mi contenedor de elección es Structuremap; sin embargo, cualquier ejemplo de esto será suficiente, ya que puedo convertirlo.

¿Alguien alguna vez logró esto?

Sí, es posible. Usted (como la mayoría de las personas) está siendo lanzado por la comercialización de Microsoft de los atributos de filtro de acción, que se colocan convenientemente en una sola clase, pero para nada aptos para DI.

La solución es dividir el atributo de filtro de acción en 2 partes como se demuestra en esta publicación :

  1. Un atributo que no contiene ningún comportamiento para marcar sus controladores y métodos de acción.
  2. Una clase DI-friendly que implementa IActionFilter y contiene el comportamiento deseado.

El enfoque es usar IActionFilter para probar la presencia del atributo y luego ejecutar el comportamiento deseado. El filtro de acción se puede suministrar con todas las dependencias (a través del constructor) y luego inyectarse cuando se compone la aplicación.

IConfigProvider provider = new WebConfigProvider(); IActionFilter filter = new MaxLengthActionFilter(provider); config.Filters.Add(filter); 

NOTA: si necesita que alguna de las dependencias del filtro tenga una vida útil más corta que el singleton, necesitará usar un GlobalFilterProvider como en esta respuesta .

Para conectar esto con StructureMap, deberá devolver una instancia del contenedor desde su módulo de configuración DI. El método Application_Start sigue siendo parte de la raíz de la composición, por lo que puede usar el contenedor en cualquier lugar dentro de este método y aún no se considera un patrón de localización de servicios. Tenga en cuenta que no muestro aquí una configuración completa de WebApi, porque supongo que ya tiene una configuración DI trabajando con WebApi. Si necesitas uno, esa es otra pregunta.

 public class DIConfig() { public static IContainer Register() { // Create the DI container var container = new Container(); // Setup configuration of DI container.Configure(r => r.AddRegistry()); // Add additional registries here... #if DEBUG container.AssertConfigurationIsValid(); #endif // Return our DI container instance to the composition root return container; } } public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { // Hang on to the container instance so you can resolve // instances while still in the composition root IContainer container = DIConfig.Register(); AreaRegistration.RegisterAllAreas(); // Pass the container so we can resolve our IActionFilter WebApiConfig.Register(GlobalConfiguration.Configuration, container); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); } } public static class WebApiConfig { // Add a parameter for IContainer public static void Register(HttpConfiguration config, IContainer container) { config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable return type. // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries. // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712. //config.EnableQuerySupport(); // Add our action filter config.Filters.Add(container.GetInstance()); // Add additional filters here that look for other attributes... } } 

La implementación de MaxLengthActionFilter se vería así:

 // Used to uniquely identify the filter in StructureMap public interface IMaxLengthActionFilter : System.Web.Http.Filters.IActionFilter { } public class MaxLengthActionFitler : IMaxLengthActionFilter { public readonly IConfigProvider configProvider; public MaxLengthActionFilter(IConfigProvider configProvider) { if (configProvider == null) throw new ArgumentNullException("configProvider"); this.configProvider = configProvider; } public Task ExecuteActionFilterAsync( HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation) { var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor); if (attribute != null) { var maxLength = attribute.MaxLength; // Execute your behavior here (before the continuation), // and use the configProvider as needed return continuation().ContinueWith(t => { // Execute your behavior here (after the continuation), // and use the configProvider as needed return t.Result; }); } return continuation(); } public bool AllowMultiple { get { return true; } } public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor) { MaxLengthAttribute result = null; // Check if the attribute exists on the action method result = (MaxLengthAttribute)actionDescriptor .GetCustomAttributes(typeof(MaxLengthAttribute), false) .SingleOrDefault(); if (result != null) { return result; } // Check if the attribute exists on the controller result = (MaxLengthAttribute)actionDescriptor .ControllerDescriptor .GetCustomAttributes(typeof(MaxLengthAttribute), false) .SingleOrDefault(); return result; } } 

Y su atributo, que no debe contener ningún comportamiento, debería verse más o menos así:

 // This attribute should contain no behavior. No behavior, nothing needs to be injected. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] public class MaxLengthAttribute : Attribute { public MaxLengthAttribute(int maxLength) { this.MaxLength = maxLength; } public int MaxLength { get; private set; } } 

Tuve problemas con los proveedores de filtros de acción personalizados, sin lograr que funcionara para mis atributos de autenticación. También probaba varios enfoques con la inyección de propiedades y constructores, pero no encontré una solución que me pareciera agradable.

Finalmente terminé inyectando funciones en mis atributos. De esta forma, las pruebas unitarias pueden inyectar una función que devuelve un falso o simulacro, mientras que la aplicación puede inyectar una función que resuelva la dependencia con el contenedor IoC.

Acabo de escribir sobre este enfoque aquí: http://danielsaidi.com/blog/2015/09/11/asp-net-and-webapi-attributes-with-structuremap

Funciona muy bien en mi proyecto y resuelve todos los problemas que tuve con los otros enfoques.