Hier ist das Setup. Angenommen, ich habe einen Aktionsfilter, der eine Instanz eines Dienstes benötigt:
public interface IMyService
{
void DoSomething();
}
public class MyService : IMyService
{
public void DoSomething(){}
}
Ich habe dann einen ActionFilter, der eine Instanz dieses Dienstes benötigt:
public class MyActionFilter : ActionFilterAttribute
{
private IMyService _myService; // <--- How do we get this injected
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_myService.DoSomething();
base.OnActionExecuting(filterContext);
}
}
In MVC 1/2 war das Injizieren von Abhängigkeiten in Aktionsfilter ein bisschen nervig. Der gebräuchlichste Ansatz war die Verwendung eines benutzerdefinierten Aktionsaufrufers, wie hier zu sehen ist: http://www.jeremyskinner.co.uk/2008/11/08/dependency-injection-with-aspnet-mvc-action-filters/ The Hauptmotivation für diese Problemumgehung war, dass dieser folgende Ansatz als schlampige und enge Kopplung mit dem Container angesehen wurde:
public class MyActionFilter : ActionFilterAttribute
{
private IMyService _myService;
public MyActionFilter()
:this(MyStaticKernel.Get<IMyService>()) //using Ninject, but would apply to any container
{
}
public MyActionFilter(IMyService myService)
{
_myService = myService;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_myService.DoSomething();
base.OnActionExecuting(filterContext);
}
}
Hier verwenden wir die Konstruktorinjektion und überladen den Konstruktor, um den Container zu verwenden und den Service zu injizieren. Ich bin damit einverstanden, dass der Container eng mit dem ActionFilter gekoppelt ist.
Meine Frage lautet jedoch: Sind in ASP.NET MVC 3, wo wir eine Abstraktion des verwendeten Containers haben (über den DependencyResolver), all diese Rahmen noch erforderlich? Gestatten Sie mir zu demonstrieren:
public class MyActionFilter : ActionFilterAttribute
{
private IMyService _myService;
public MyActionFilter()
:this(DependencyResolver.Current.GetService(typeof(IMyService)) as IMyService)
{
}
public MyActionFilter(IMyService myService)
{
_myService = myService;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_myService.DoSomething();
base.OnActionExecuting(filterContext);
}
}
Jetzt weiß ich, dass einige Puristen sich darüber lustig machen könnten, aber im Ernst, was wäre der Nachteil? Es ist weiterhin testbar, da Sie den Konstruktor verwenden können, der zur Testzeit einen IMyService verwendet und auf diese Weise einen Mock-Service einfügt. Sie sind nicht an eine Implementierung eines DI-Containers gebunden, da Sie den DependencyResolver verwenden. Gibt es also Nachteile bei diesem Ansatz?
Im Übrigen ist hier ein weiterer guter Ansatz, um dies in MVC3 mithilfe der neuen IFilterProvider-Schnittstelle zu tun: http://www.thecodinghumanist.com/blog/archives/2011/1/27/structuremap-action-filters-and-dependency-injection-in -asp-net-mvc-3
Antworten:
Ich bin nicht positiv, aber ich glaube, Sie können einfach einen leeren Konstruktor (für den Attributteil ) verwenden und dann einen Konstruktor haben, der den Wert tatsächlich einfügt (für den Filterteil ). *Bearbeiten : Nach einigem Nachlesen scheint es, dass der akzeptierte Weg, dies zu tun, die Eigenschaftsinjektion ist:
public class MyActionFilter : ActionFilterAttribute { [Injected] public IMyService MyService {get;set;} public override void OnActionExecuting(ActionExecutingContext filterContext) { MyService.DoSomething(); base.OnActionExecuting(filterContext); } }
In Bezug auf die Frage, warum Sie nicht eine Service Locator- Frage verwenden sollten: Sie verringert meist nur die Flexibilität Ihrer Abhängigkeitsinjektion. Was wäre zum Beispiel, wenn Sie einen Protokollierungsdienst injizieren und dem Protokollierungsdienst automatisch den Namen der Klasse geben möchten, in die er injiziert wird? Wenn Sie Konstruktorinjektion verwenden, würde das großartig funktionieren. Wenn Sie einen Dependency Resolver / Service Locator verwenden, haben Sie kein Glück.
Aktualisieren
Da dies als Antwort akzeptiert wurde, möchte ich auf die Aufzeichnung eingehen und sagen, dass ich Mark Seemans Ansatz bevorzuge, da er die Verantwortung des Aktionsfilters vom Attribut trennt. Darüber hinaus bietet die MVC3-Erweiterung von Ninject einige sehr leistungsstarke Möglichkeiten, Aktionsfilter über Bindungen zu konfigurieren. Weitere Informationen finden Sie in den folgenden Referenzen:
Update 2
Wie @usr in den Kommentaren unten ausgeführt hat, werden
ActionFilterAttribute
s beim Laden der Klasse instanziiert und dauern die gesamte Lebensdauer der Anwendung. Wenn dieIMyService
Schnittstelle kein Singleton sein soll, handelt es sich letztendlich um eine Captive-Abhängigkeit . Wenn die Implementierung nicht threadsicher ist, kann dies zu erheblichen Schmerzen führen.Wenn Sie eine Abhängigkeit mit einer kürzeren Lebensdauer als der erwarteten Lebensdauer Ihrer Klasse haben, ist es ratsam, eine Fabrik zu injizieren, um diese Abhängigkeit bei Bedarf zu erzeugen, anstatt sie direkt zu injizieren.
quelle
DependencyResolver.GetService
hat die Bindungsmethode keine Ahnung, in welche Klasse diese Abhängigkeit eingefügt wird. Was wäre, wenn SieIMyService
für bestimmte Arten von Aktionsfiltern einen anderen erstellen möchten? Oder, wie ich in meiner Antwort sagte, was wäre, wenn Sie derMyService
Implementierung ein spezielles Argument liefern möchten, um ihr mitzuteilen, in welche Klasse sie injiziert wurde (was für Logger nützlich ist)?Ja, es gibt Nachteile, da es viele Probleme mit IDependencyResolver selbst gibt, und zu diesen können Sie die Verwendung eines Singleton Service Locator sowie Bastard Injection hinzufügen .
Eine bessere Option besteht darin, den Filter als normale Klasse zu implementieren, in die Sie die gewünschten Dienste einfügen können:
public class MyActionFilter : IActionFilter { private readonly IMyService myService; public MyActionFilter(IMyService myService) { this.myService = myService; } public void OnActionExecuting(ActionExecutingContext filterContext) { if(this.ApplyBehavior(filterContext)) this.myService.DoSomething(); } public void OnActionExecuted(ActionExecutedContext filterContext) { if(this.ApplyBehavior(filterContext)) this.myService.DoSomething(); } private bool ApplyBehavior(ActionExecutingContext filterContext) { // Look for a marker attribute in the filterContext or use some other rule // to determine whether or not to apply the behavior. } private bool ApplyBehavior(ActionExecutedContext filterContext) { // Same as above } }
Beachten Sie, wie der Filter den filterContext untersucht, um festzustellen, ob das Verhalten angewendet werden soll oder nicht.
Dies bedeutet, dass Sie weiterhin Attribute verwenden können, um zu steuern, ob der Filter angewendet werden soll oder nicht:
public class MyActionFilterAttribute : Attribute { }
Jetzt ist dieses Attribut jedoch vollständig inert.
Der Filter kann mit der erforderlichen Abhängigkeit erstellt und zu den globalen Filtern in global.asax hinzugefügt werden:
GlobalFilters.Filters.Add(new MyActionFilter(new MyService()));
Ein detaillierteres Beispiel für diese Technik finden Sie in diesem Artikel, obwohl sie auf die ASP.NET-Web-API anstelle von MVC angewendet wird: http://blog.ploeh.dk/2014/06/13/passive-attributes
quelle
Die von Mark Seemann vorgeschlagene Lösung erscheint elegant. Allerdings ziemlich komplex für ein einfaches Problem. Die Verwendung des Frameworks durch Implementierung von AuthorizeAttribute fühlt sich natürlicher an.
Meine Lösung bestand darin, ein AuthorizeAttribute mit einer statischen Delegate-Factory für einen in global.asax registrierten Dienst zu erstellen. Es funktioniert für jeden DI-Container und fühlt sich etwas besser an als ein Service Locator.
In global.asax:
Meine benutzerdefinierte Attributklasse:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class MyAuthorizeAttribute : AuthorizeAttribute { public static Func<IAuthorizeService> AuthorizeServiceFactory { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { return AuthorizeServiceFactory().AuthorizeCore(httpContext); } }
quelle