asp.net-mvc
Abhängigkeitsspritze
Suche…
Bemerkungen
Bei der Abhängigkeitsinjektion (DI) wird die Codekopplung reduziert. Stellen Sie sich jede Art von Interaktion vor, bei der etwas wie im Beispiel für "hart codierte Abhängigkeit" neu aufgebaut wird.
Ein großer Teil des Schreibens von Code ist die Möglichkeit, diesen zu testen. Jedes Mal, wenn wir eine neue Abhängigkeit neu aufbauen, ist es schwierig, unseren Code zu testen, da wir diese Abhängigkeit nicht kontrollieren können.
Wie würden Sie Code testen, der zum Beispiel von DataTime.Now abhängt? Es ändert sich immer, so dass Sie keine Referenz haben. Dies ist, wenn Sie einen stabilen Parameter als Ausgangspunkt eingeben. Sie können es steuern, Sie können Tests basierend auf verschiedenen Werten schreiben und sicherstellen, dass Sie immer das richtige Ergebnis erhalten.
Eine gute Option ist es daher, eine Schnittstelle oder eine abstrakte Klasse als Parameter im Konstruktor DI zu übergeben.
Eine Schnittstelle stellt einen gut definierten Vertrag dar. Sie können sich immer auf die vorhandenen Methoden verlassen und Sie können sich immer auf die Methodensignaturen verlassen.
Sobald Sie DI verwenden, werden andere Aspekte geöffnet. Selbst wenn Sie beispielsweise eine Schnittstelle übergeben, benötigen Sie eine echte Implementierung, um tatsächlich arbeiten zu können. Hier treten andere Konzepte auf. Wir können IOC (Inversion of Control) verwenden, um unsere Abhängigkeiten aufzulösen. Das bedeutet, dass wir unseren Code anweisen, für jeden Vertrag immer eine spezifische Implementierung zu verwenden. Natürlich gibt es auch andere Möglichkeiten. Wir könnten jeden Vertrag immer mit einer bestimmten Implementierung instanziieren, und ab diesem Zeitpunkt kann unser Code diesen Teil verwenden:
public ILogging Logging { get; set }
Irgendwann initialisieren wir es.
Logging = new FileLogging();
Dies funktioniert immer, solange unsere Klasse den erwarteten Auftrag erfüllt:
public class FileLogging : ILogging
Ab dem Initialisierungszeitpunkt verwenden wir immer das Logging-Objekt. Dies macht das Leben einfacher, denn wenn wir uns zum Beispiel dafür entscheiden, ein DatabaseLogging zu ändern und zu verwenden, müssen wir den Code nur an einer Stelle ändern. Genau hier initialisieren wir die Logging-Klasse.
Ist DI nur zum Testen geeignet? Nein, DI ist auch beim Schreiben von wartungsfähigem Code wichtig. Es ermöglicht die klare Trennung der Bedenken.
Wenn Sie Code schreiben, denken Sie ... ist er testbar? Kann ich einen Test schreiben? Dann ist das Einfügen eines DateTime-Werts anstelle der Verwendung von DateTime erforderlich. Jetzt ist es sinnvoll.
Ninject-Konfigurationen
Nach der Installation eines IoC-Containers (Inversion of Control) sind einige Optimierungen erforderlich, damit es funktioniert. In diesem Fall verwende ich Ninject. Ersetzen Sie in der NinjectWebCommon-Datei, die sich im Ordner App_Start befindet, die CreateKernel-Methode durch:
private static IKernel CreateKernel()
{
// Create the kernel with the interface to concrete bindings
var kernel = RegisterServices();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
Und die RegisterServices-Methode mit:
private static StandardKernel RegisterServices()
{
Container container = new Container();
// encapsulate the interface to concrete bindings in another class or even in another layer
StandardKernel kernel = container.GetServices();
return kernel;
}
Erstellen Sie eine neue Klasse für die Bindung, die in diesem Fall als Container bezeichnet wird:
public class Container
{
public StandardKernel GetServices()
{
// It is good practice to create a derived class of NinjectModule to organize the binding by concerns. In this case one for the repository, one for service and one for app service bindings
return new StandardKernel(new NinjectRepositoryModule(),
new NinjectServiceModule(),
new NinjectAppServiceModule());
}
}
Schließlich ändern Sie in jeder abgeleiteten NinjectModule-Klasse die Bindungen, die die Load-Methode überladen, wie folgt:
public class NinjectRepositoryModule: NinjectModule
{
public override void Load()
{
// When we need a generic IRepositoryBase<> to bind to a generic RepositoryBase<>
// The typeof keyword is used because the target method is generic
Bind(typeof (IRepositoryBase<>)).To(typeof (RepositoryBase<>));
// When we need a IUnitOfWorkbind to UnitOfWork concrete class that is a singleton
Bind<IUnitOfWork>().To<UnitOfWork>().InSingletonScope();
}
}
Ein weiteres Beispiel für abgeleitete NinjectModule:
public class NinjectServiceModule :NinjectModule
{
public override void Load()
{
// When we need a IBenefitService to BenefitService concrete class
Bind<IBenefitService>().To<BenefitService>();
// When we need a ICategoryService to CategoryService concrete class
Bind<ICategoryService>().To<CategoryService>();
// When we need a IConditionService to ConditionService concrete class
Bind<IConditionService>().To<ConditionService>();
}
}
Nutzung der Schnittstellen
Verwenden Sie in der konkreten Klasse, die den Dienst benötigt, die Schnittstelle, um auf den Dienst zuzugreifen, anstatt auf seine Implementierung wie folgt:
public class BenefitAppService
{
private readonly IBenefitService _service;
public BenefitAppService(IBenefitService service)
{
_service = service;
}
public void Update(Benefit benefit)
{
if (benefit == null) return
_service.Update(benefit);
_service.Complete();
}
}
Wenn Sie jetzt etwas in der konkreten Klasse benötigen, stört der obige Code nicht. Sie können die Dienstimplementierung für einen anderen völlig anderen Unterschied ändern. Solange die Schnittstelle die Schnittstelle erfüllt, können Sie damit zufrieden sein. Es macht es auch sehr einfach, es zu testen.
Abhängigkeit der Konstruktorabhängigkeit
Die Konstruktorabhängigkeitspritze erfordert Parameter im Konstruktor, um Abhängigkeiten einzufügen. Sie müssen also die Werte übergeben, wenn Sie ein neues Objekt erstellen.
public class Example
{
private readonly ILogging _logging;
public Example(ILogging logging)
{
this._logging = logging;
}
}
Fest codierte Abhängigkeit
public class Example
{
private FileLogging _logging;
public Example()
{
this._logging = new FileLogging();
}
}
Parameter DI
public DateTime SomeCalculation()
{
return DateTime.Now.AddDays(3);
}
vs
public DateTime SomeCalculation(DateTime inputDate)
{
return inputDate.AddDays(3);
}
Ninject-Abhängigkeitsinjektion
Abhängigkeitsauflöser wird verwendet, um eng gekoppelte Klassen zu vermeiden, die Flexibilität zu verbessern und das Testen zu vereinfachen. Sie können einen eigenen Abhängigkeitsinjektor erstellen (nicht empfohlen) oder einen der gut geschriebenen und getesteten Abhängigkeitsinjektoren verwenden. In diesem Beispiel werde ich Ninject verwenden .
Schritt 1: Erstellen Sie einen Abhängigkeitsauflöser.
Laden Sie zunächst Ninject von NuGet herunter. Erstellen Sie einen Ordner mit dem Namen Infrastruktur und fügen Sie eine Klasse mit dem Namen NinjectDependencyResolver hinzu :
using Ninject;
using System;
using System.Collections.Generic;
using System.Web.Mvc;
public class NinjectDependencyResolver
: IDependencyResolver
{
private IKernel kernel;
public NinjectDependencyResolver()
{
// Initialize kernel and add bindings
kernel = new StandardKernel();
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
// Bindings added here
}
}
Das MVC Framework ruft die GetService- und GetServices- Methoden auf, wenn eine Klasse für die Bearbeitung einer eingehenden Anforderung benötigt wird.
Schritt zwei: Abhängigkeitsauflöser registrieren.
Jetzt haben wir unseren benutzerdefinierten Abhängigkeitsauflöser, und wir müssen ihn registrieren, damit das MVC-Framework unseren Abhängigkeitsauflöser verwenden kann. Registrieren Sie den Abhängigkeitsauflöser in der Datei " Global.asax.cs ":
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
DependencyResolver.SetResolver(new NinjectDependencyResolver());
// .....
}
Schritt drei: Bindungen hinzufügen.
Stellen Sie sich vor, wir haben folgende Schnittstelle und Implementierung:
public interface ICustomCache
{
string Info { get; }
}
public class CustomCache : ICustomCache
{
public string Info
{
get
{
return "Hello from CustomCache.";
}
}
}
Wenn Sie CustomCache in unserem Controller verwenden möchten, ohne unseren Controller eng mit CustomCache zu koppeln, müssen Sie ICustomCache an CustomCache binden und mithilfe von Ninject einfügen. Zuerst binden Sie ICustomCache an CustomCache, indem Sie der AddBindings () - Methode von NinjectDependencyResolver folgenden Code hinzufügen :
private void AddBindings()
{
// Bindings added here
kernel.Bind<ICustomCache>().To<CustomCache>();
}
Bereiten Sie dann Ihren Controller für die Injektion wie folgt vor:
public class HomeController : Controller
{
private ICustomCache CustomCache { get; set; }
public HomeController(ICustomCache customCacheParam)
{
if (customCacheParam == null)
throw new ArgumentNullException(nameof(customCacheParam));
CustomCache = customCacheParam;
}
public ActionResult Index()
{
// cacheInfo: "Hello from CustomCache."
string cacheInfo = CustomCache.Info;
return View();
}
}
Dies ist ein Beispiel für eine Costructor-Injektion und es ist eine Form der Abhängigkeitseinspritzung . Wie Sie sehen, ist unser Home-Controller nicht von der CustomCache-Klasse itslef abhängig. Wenn Sie eine andere Implementierung von ICustomCache in unserer Anwendung verwenden möchten, müssen Sie nur ICustomCache an eine andere Implementierung binden. Dies ist der einzige Schritt, den wir tun müssen. Das, was hier passiert ist, ist, dass MVC Framework unseren registrierten Abhängigkeitsauflöser gebeten hat, eine Instanz der HomeController- Klasse über die GetService- Methode zu erstellen. Die GetService-Methode fragt den Ninject-Kernel, das angeforderte Objekt zu erstellen, und der Ninject-Kernel untersucht den Typ in seiner Laufzeit und stellt fest, dass der Konstruktor von HomeController einen ICustomCache erfordert, und für ICustomCache wurde bereits eine Bindung hinzugefügt. Ninject erstellt eine Instanz der gebundenen Klasse , erstellt daraus einen HomeController und gibt diesen MVC Framework zurück.
Abhängigkeitsketten.
Wenn Ninject versucht, einen Typ zu erstellen, werden andere Abhängigkeiten zwischen dem Typ und anderen Typen untersucht, und wenn Ninject versucht, diese auch zu erstellen. Wenn für unsere CustomCache-Klasse beispielsweise ICacheKeyProvider erforderlich ist und wenn für ICacheKeyProvider hinzugefügtes Bining hinzugefügt wird, kann Ninject es für unsere Klasse bereitstellen.
ICacheKeyProvider- Schnittstelle und SimpleCacheKeyProvider- Implementierung:
public interface ICacheKeyProvider
{
string GenerateKey(Type type);
}
public class SimpleCacheKeyProvider
: ICacheKeyProvider
{
public string GenerateKey(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
return string.Format("{0}CacheKey", type.Name);
}
}
Geänderte CustomCache- Klasse
public class CustomCache : ICustomCache
{
private ICacheKeyProvider CacheKeyProvider { get; set; }
public CustomCache(ICacheKeyProvider keyProviderParam)
{
if (keyProviderParam == null)
throw new ArgumentNullException(nameof(keyProviderParam));
CacheKeyProvider = keyProviderParam;
}
...........
}
Bindung für ICacheKeyProvider hinzufügen :
private void AddBindings()
{
// Bindings added here
kernel.Bind<ICustomCache>().To<CustomCache>();
kernel.Bind<ICacheKeyProvider>().To<SimpleCacheKeyProvider>();
}
Wenn wir jetzt zu HomeController navigieren, erstellt Ninject eine Instanz von SimpleCacheKeyProvider , um CustomCache zu erstellen, und verwendet CustomCache-Instanz, um HomeController zu erstellen.
Ninject verfügt über eine Reihe großartiger Funktionen, wie zum Beispiel eine verkettete Abhängigkeitsinjektion, und Sie sollten sie prüfen, wenn Sie Ninject verwenden möchten.