Attributi di WebApi dell’iniezione di dipendenza costruttore

Ho cercato l’opzione di non parametrizzazione per gli attributi WebApi.

La mia domanda è semplicemente se questo è effettivamente ansible utilizzando Structuremap?

Ho cercato su google, ma continuo a inventare l’iniezione di proprietà (che preferisco non usare) o supposte implementazioni di iniezione del costruttore che finora non sono stato in grado di replicare.

Il mio contenitore di scelta è Structuremap, tuttavia qualsiasi esempio di questo sarà sufficiente in quanto sono in grado di convertirlo.

Chiunque ha mai gestito questo?

Sì, è ansible. Tu (come la maggior parte delle persone) ti viene lanciata dalla commercializzazione di Microsoft di Action Filter Attributes, che sono convenientemente inseriti in una singola class, ma non del tutto compatibili con DI.

La soluzione è rompere l’attributo del filtro di azione in 2 parti come dimostrato in questo post :

  1. Un attributo che non contiene alcun comportamento per contrassegnare i controller e i metodi di azione con.
  2. Una class DI-friendly che implementa IActionFilter e contiene il comportamento desiderato.

L’approccio consiste nell’utilizzare IActionFilter per verificare la presenza dell’attributo e quindi eseguire il comportamento desiderato. Il filtro azione può essere fornito con tutte le dipendenze (tramite il costruttore) e quindi iniettato quando l’applicazione è composta.

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

NOTA: se si ha bisogno di una delle dipendenze del filtro per avere una durata inferiore al singleton, è necessario utilizzare un GlobalFilterProvider come in questa risposta .

Per collegarlo a StructureMap, è necessario restituire un’istanza del contenitore dal modulo di configurazione DI. Il metodo Application_Start fa ancora parte della composizione root, quindi puoi usare il contenitore ovunque all’interno di questo metodo e non è ancora considerato un pattern di localizzazione del servizio. Tieni presente che non mostro qui una configurazione WebApi completa, perché presumo che tu abbia già una configurazione DI DI funzionante con WebApi. Se ne hai bisogno, questa è un’altra domanda.

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

L’implementazione di MaxLengthActionFilter sarebbe simile a questa:

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

E il tuo attributo che non dovrebbe contenere alcun comportamento dovrebbe assomigliare a questo:

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

Ho faticato con i fornitori di filtri azione personalizzati, senza farlo funzionare per gli attributi auth. Ho anche provato vari approcci con il costruttore e l’iniezione di proprietà, ma non ho trovato una soluzione che fosse piacevole.

Alla fine ho finito per iniettare funzioni nei miei attributi. In questo modo, i test unitari possono iniettare una funzione che restituisce un falso o un mock, mentre l’applicazione può iniettare una funzione che risolve la dipendenza con il contenitore IoC.

Ho appena scritto questo approccio qui: http://danielsaidi.com/blog/2015/09/11/asp-net-and-webapi-attributes-with-structuremap

Funziona davvero bene nel mio progetto e risolve tutti i problemi che ho avuto con gli altri approcci.