asp.net-mvc
Iniezione di dipendenza
Ricerca…
Osservazioni
L'intero punto di Dependency Injection (DI) è ridurre l'accoppiamento di codice. Immagina qualsiasi tipo di interazione che implica la creazione di qualcosa di simile nell '"Esempio di dipendenza hard codificato".
Una grande parte del codice di scrittura è la capacità di testarlo. Ogni volta che creiamo una nuova dipendenza, rendiamo il nostro codice difficile da testare perché non abbiamo alcun controllo su tale dipendenza.
Come testare il codice che dipende da DataTime.Ora per esempio? Cambia sempre in modo da non avere riferimenti. Questo è quando si inietta un parametro stabile come punto di partenza. Puoi controllarlo, puoi scrivere test basati su vari valori e assicurarti di ottenere sempre il risultato giusto.
Una buona opzione è quindi passare un'interfaccia o una classe astratta come parametro nel costruttore DI.
Un'interfaccia rappresenta un contratto ben definito, puoi sempre fare affidamento sui metodi per essere lì e puoi sempre fare affidamento sulle firme del metodo.
Quando inizierai a utilizzare DI si apriranno altri aspetti. Ad esempio, anche se si passa un'interfaccia a un certo punto, sarà necessaria un'implementazione reale per eseguire effettivamente qualsiasi lavoro. È qui che compaiono altri concetti. Possiamo usare IOC (Inversion of Control) per risolvere le nostre dipendenze. Ciò significa che istruiamo il nostro codice a utilizzare sempre un'implementazione specifica per qualsiasi contratto. Ovviamente ci sono altri modi per farlo. Potremmo sempre creare un'istanza di ogni contratto con un'implementazione specifica e da quel momento in poi il nostro codice può utilizzare quella parte:
public ILogging Logging { get; set }
ad un certo punto lo inizializziamo.
Logging = new FileLogging();
questo funzionerà sempre finché la nostra classe rispetterà il contratto previsto:
public class FileLogging : ILogging
dal momento iniziale in poi usiamo sempre l'oggetto Logging. Questo semplifica la vita perché se mai decidessimo di cambiare e utilizzare un DatabaseLogging, per esempio, dovremmo solo cambiare il codice in un posto e questo è esattamente il punto in cui inizializziamo la classe di registrazione.
DI è valido solo per i test? No, DI è importante anche quando si scrive codice gestibile. Permette di chiarire la separazione delle preoccupazioni.
Quando scrivi un codice, pensa ... è testabile, posso scrivere un test, cioè quando iniettare un valore DateTime invece di usare DateTime.Ora ha senso.
Ninject Configurations
Dopo l'installazione di un contenitore IoC (Inversion of Control), sono necessarie alcune modifiche per farlo funzionare. In questo caso, userò Ninject. Nel file NinjectWebCommon, che si trova nella cartella App_Start, sostituire il metodo CreateKernel con:
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;
}
}
E il metodo RegisterServices con:
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;
}
Crea una nuova classe per l'associazione che in questo caso si chiama Contenitore:
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());
}
}
Infine, in ciascuna classe derivata di NinjectModule, è possibile modificare i collegamenti che sovraccaricano il metodo Load come:
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();
}
}
Un altro esempio di NinjectModule derivato:
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>();
}
}
Utilizzo delle interfacce
Nella classe concreta che richiede il servizio, utilizzare l'interfaccia per accedere al servizio anziché alla sua implementazione come:
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();
}
}
Ora se hai bisogno di qualcosa nella classe concreta, non interferirà nel codice qui sopra. È possibile modificare l'implementazione del servizio per un'altra differenza completamente diversa e, a patto che soddisfi l'interfaccia, si è pronti. Inoltre rende molto facile testarlo.
Iniezione di dipendenza del costruttore
La Iniezione della dipendenza del costruttore richiede parametri nel costruttore per iniettare le dipendenze. Quindi devi passare i valori quando crei un nuovo oggetto.
public class Example
{
private readonly ILogging _logging;
public Example(ILogging logging)
{
this._logging = logging;
}
}
Dipendenza hard coded
public class Example
{
private FileLogging _logging;
public Example()
{
this._logging = new FileLogging();
}
}
parametro DI
public DateTime SomeCalculation()
{
return DateTime.Now.AddDays(3);
}
vs
public DateTime SomeCalculation(DateTime inputDate)
{
return inputDate.AddDays(3);
}
Iniezione delle dipendenze di Ninject
Il resolver delle dipendenze viene utilizzato per evitare classi strettamente accoppiate, migliorare la flessibilità e semplificare i test. È possibile creare il proprio iniettore di dipendenza (non consigliato) o utilizzare uno degli iniettori di dipendenza ben scritti e testati. In questo esempio userò Ninject .
Fase 1: creare il resolver di dipendenza.
Prima di tutto, scarica Ninject da NuGet. Creare una cartella denominata Infrastruttura e aggiungere una classe denominata NinjectDependencyResolver :
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
}
}
MVC Framework chiamerà i metodi GetService e GetServices quando ha bisogno dell'insediamento di una classe per soddisfare una richiesta in arrivo.
Fase 2: registrare il resolver di dipendenza.
Ora abbiamo il nostro resolver di dipendenza personalizzato e dobbiamo registrarlo per dire al framework MVC di usare il nostro risolutore di dipendenze. Registra il resolver di dipendenza nel file Global.asax.cs :
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
DependencyResolver.SetResolver(new NinjectDependencyResolver());
// .....
}
Fase tre: aggiungere associazioni.
Immagina di avere l'interfaccia e l'impianto seguenti:
public interface ICustomCache
{
string Info { get; }
}
public class CustomCache : ICustomCache
{
public string Info
{
get
{
return "Hello from CustomCache.";
}
}
}
Se vogliamo usare CustomCache nel nostro controller senza accoppiare saldamente il nostro controller con CustomCache, allora dobbiamo associare ICustomCache a CustomCache e iniettarlo usando Ninject. Per prima cosa, associare ICustomCache a CustomCache aggiungendo il seguente codice al metodo AddBindings () di NinjectDependencyResolver :
private void AddBindings()
{
// Bindings added here
kernel.Bind<ICustomCache>().To<CustomCache>();
}
Quindi preparare il controller per l'iniezione come di seguito:
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();
}
}
Questo è un esempio di iniezione del costruttore ed è una forma di iniezione di dipendenza . Come vedi, il nostro controller Home non dipende dalla classe di CustomCache. Se vogliamo utilizzare un'altra implementazione di ICustomCache nella nostra applicazione, l'unica cosa che dobbiamo cambiare è associare ICustomCache a un altro impianto e questo è l'unico passo che dobbiamo compiere. Che cosa è successo qui è, MVC Framework chiesto il nostro risolutore dipendenza registrato per creare un'istanza della classe HomeController tramite il metodo GetService. Il metodo GetService chiede al kernel di Ninject di creare l'oggetto richiesto e il kernel di Ninject esamina il tipo nel suo termine e scopre che il costruttore di HomeController requeires un ICustomCache e l'associazione è già stata aggiunta per ICustomCache . Ninject crea un'istanza della classe associata, la usa per creare HomeController e la restituisce MVC Framework.
Catene di dipendenza.
Quando Ninject cerca di creare il tipo, esamina altre depenenze tra il tipo e altri tipi e se c'è qualche tentativo di Ninject anche per crearle. Ad esempio, se la nostra classe CustomCache richiede ICacheKeyProvider e se bining aggiunto per ICacheKeyProvider Ninject può fornirlo per la nostra classe.
Interfaccia ICacheKeyProvider e implacazione SimpleCacheKeyProvider :
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);
}
}
Classe CustomCache modificata
public class CustomCache : ICustomCache
{
private ICacheKeyProvider CacheKeyProvider { get; set; }
public CustomCache(ICacheKeyProvider keyProviderParam)
{
if (keyProviderParam == null)
throw new ArgumentNullException(nameof(keyProviderParam));
CacheKeyProvider = keyProviderParam;
}
...........
}
Aggiungi associazione per ICacheKeyProvider :
private void AddBindings()
{
// Bindings added here
kernel.Bind<ICustomCache>().To<CustomCache>();
kernel.Bind<ICacheKeyProvider>().To<SimpleCacheKeyProvider>();
}
Ora, quando navighiamo su HomeController, Ninject crea un'istanza di SimpleCacheKeyProvider che lo usa per creare CustomCache e utilizza l'istanza di CustomCache per creare HomeController .
Ninject ha un numero di grandi caratteristiche come l'iniezione della dipendenza concatenata e dovresti esaminarle se vuoi usare Ninject.