Inserimento delle dipendenze in SignalR
Avviso
Questa documentazione non è per la versione più recente di SignalR. Esaminare ASP.NET Core SignalR.
Versioni software usate in questo argomento
- Visual Studio 2013
- .NET 4.5
- SignalR versione 2
Versioni precedenti di questo argomento
Per informazioni sulle versioni precedenti di SignalR, vedere Versioni precedenti di SignalR.
Domande e commenti
Lasciare commenti e suggerimenti su come è piaciuta questa esercitazione e cosa è possibile migliorare nei commenti nella parte inferiore della pagina. Se si hanno domande che non sono direttamente correlate all'esercitazione, è possibile pubblicarle nel forum di ASP.NET SignalR o StackOverflow.com.
L'inserimento delle dipendenze è un modo per rimuovere le dipendenze hardcoded tra gli oggetti, semplificando la sostituzione delle dipendenze di un oggetto, per il test (usando oggetti fittizi) o per modificare il comportamento di runtime. Questa esercitazione illustra come eseguire l'inserimento delle dipendenze sugli hub SignalR. Illustra anche come usare i contenitori IoC con SignalR. Un contenitore IoC è un framework generale per l'inserimento delle dipendenze.
Che cos'è l'inserimento delle dipendenze?
Ignorare questa sezione se si ha già familiarità con l'inserimento delle dipendenze.
L'inserimento delle dipendenze è un modello in cui gli oggetti non sono responsabili della creazione delle proprie dipendenze. Ecco un semplice esempio per motivare l'inserimento delle dipendenze. Si supponga di avere un oggetto che deve registrare i messaggi. È possibile definire un'interfaccia di registrazione:
interface ILogger
{
void LogMessage(string message);
}
Nell'oggetto è possibile creare un oggetto ILogger
per registrare i messaggi:
// Without dependency injection.
class SomeComponent
{
ILogger _logger = new FileLogger(@"C:\logs\log.txt");
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
Questo funziona, ma non è il design migliore. Se si vuole sostituire FileLogger
con un'altra ILogger
implementazione, sarà necessario modificare SomeComponent
. Supponendo che molti altri oggetti FileLogger
usino , sarà necessario modificarli tutti. In alternativa, se si decide di apportare FileLogger
un singleton, sarà necessario apportare modifiche in tutta l'applicazione.
Un approccio migliore consiste nell'inserire un oggetto ILogger
nell'oggetto, ad esempio usando un argomento del costruttore:
// With dependency injection.
class SomeComponent
{
ILogger _logger;
// Inject ILogger into the object.
public SomeComponent(ILogger logger)
{
if (logger == null)
{
throw new NullReferenceException("logger");
}
_logger = logger;
}
public void DoSomething()
{
_logger.LogMessage("DoSomething");
}
}
Ora l'oggetto non è responsabile della selezione di quale ILogger
usare. È possibile cambiare ILogger
implementazioni senza modificare gli oggetti che dipendono da esso.
var logger = new TraceLogger(@"C:\logs\log.etl");
var someComponent = new SomeComponent(logger);
Questo modello è denominato injection del costruttore. Un altro modello è l'inserimento di setter, in cui si imposta la dipendenza tramite un metodo o una proprietà setter.
Inserimento di dipendenze semplice in SignalR
Prendere in considerazione l'applicazione Chat dall'esercitazione Introduzione con SignalR. Ecco la classe hub dell'applicazione:
public class ChatHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
}
Si supponga di voler archiviare i messaggi di chat nel server prima di inviarli. È possibile definire un'interfaccia che astrae questa funzionalità e usare l'inserimento dell'inserimento dell'interfaccia nella ChatHub
classe .
public interface IChatRepository
{
void Add(string name, string message);
// Other methods not shown.
}
public class ChatHub : Hub
{
private IChatRepository _repository;
public ChatHub(IChatRepository repository)
{
_repository = repository;
}
public void Send(string name, string message)
{
_repository.Add(name, message);
Clients.All.addMessage(name, message);
}
L'unico problema è che un'applicazione SignalR non crea direttamente hub; SignalR li crea automaticamente. Per impostazione predefinita, SignalR prevede che una classe hub abbia un costruttore senza parametri. Tuttavia, è possibile registrare facilmente una funzione per creare istanze hub e usare questa funzione per eseguire l'inserimento delle dipendenze. Registrare la funzione chiamando GlobalHost.DependencyResolver.Register.
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver.Register(
typeof(ChatHub),
() => new ChatHub(new ChatMessageRepository()));
App.MapSignalR();
// ...
}
SignalR richiamerà ora questa funzione anonima ogni volta che deve creare un'istanza ChatHub
.
Contenitori IoC
Il codice precedente va bene per i casi semplici. Ma hai ancora dovuto scrivere questo:
... new ChatHub(new ChatMessageRepository()) ...
In un'applicazione complessa con molte dipendenze, potrebbe essere necessario scrivere un sacco di codice "cablaggio". Questo codice può essere difficile da gestire, soprattutto se le dipendenze sono annidate. È anche difficile eseguire unit test.
Una soluzione consiste nell'usare un contenitore IoC. Un contenitore IoC è un componente software responsabile della gestione delle dipendenze. Si registrano i tipi con il contenitore e quindi si usa il contenitore per creare oggetti. Il contenitore individua automaticamente le relazioni di dipendenza. Molti contenitori IoC consentono anche di controllare elementi come la durata e l'ambito degli oggetti.
Nota
"IoC" è l'acronimo di "inversione del controllo", che è un modello generale in cui un framework chiama nel codice dell'applicazione. Un contenitore IoC costruisce automaticamente gli oggetti, che "inverte" il normale flusso di controllo.
Uso di contenitori IoC in SignalR
L'applicazione Chat è probabilmente troppo semplice per trarre vantaggio da un contenitore IoC. Si esamini invece l'esempio StockTicker .
L'esempio StockTicker definisce due classi principali:
StockTickerHub
: classe hub, che gestisce le connessioni client.StockTicker
: singleton che contiene i prezzi azionari e li aggiorna periodicamente.
StockTickerHub
contiene un riferimento al StockTicker
singleton, mentre StockTicker
contiene un riferimento a IHubConnectionContext per .StockTickerHub
Usa questa interfaccia per comunicare con StockTickerHub
le istanze. Per altre informazioni, vedere Trasmissione server con ASP.NET SignalR.
È possibile usare un contenitore IoC per separare queste dipendenze un po'. In primo luogo, è necessario semplificare le StockTickerHub
classi e StockTicker
. Nel codice seguente ho commentato le parti che non abbiamo bisogno.
Rimuovere il costruttore senza parametri da StockTickerHub
. Si userà invece l'inserimento delle dipendenze per creare l'hub.
[HubName("stockTicker")]
public class StockTickerHub : Hub
{
private readonly StockTicker _stockTicker;
//public StockTickerHub() : this(StockTicker.Instance) { }
public StockTickerHub(StockTicker stockTicker)
{
if (stockTicker == null)
{
throw new ArgumentNullException("stockTicker");
}
_stockTicker = stockTicker;
}
// ...
Per StockTicker rimuovere l'istanza singleton. Successivamente si userà il contenitore IoC per controllare la durata di StockTicker. Rendere anche pubblico il costruttore.
public class StockTicker
{
//private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
// () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
// Important! Make this constructor public.
public StockTicker(IHubConnectionContext<dynamic> clients)
{
if (clients == null)
{
throw new ArgumentNullException("clients");
}
Clients = clients;
LoadDefaultStocks();
}
//public static StockTicker Instance
//{
// get
// {
// return _instance.Value;
// }
//}
È quindi possibile effettuare il refactoring del codice creando un'interfaccia per StockTicker
. Questa interfaccia verrà usata per separare la StockTickerHub
classe dalla StockTicker
classe .
Visual Studio semplifica questo tipo di refactoring. Aprire il file StockTicker.cs, fare clic con il pulsante destro del mouse sulla dichiarazione di StockTicker
classe e scegliere Refactoring ... Estrai interfaccia.
Nella finestra di dialogo Estrai interfaccia fare clic su Seleziona tutto. Lasciare invariate le altre impostazioni predefinite. Fare clic su OK.
Visual Studio crea una nuova interfaccia denominata IStockTicker
e modifica StockTicker
anche per derivare da IStockTicker
.
Aprire il file IStockTicker.cs e modificare l'interfaccia in pubblico.
public interface IStockTicker
{
void CloseMarket();
IEnumerable<Stock> GetAllStocks();
MarketState MarketState { get; }
void OpenMarket();
void Reset();
}
StockTickerHub
Nella classe modificare le due istanze di StockTicker
in IStockTicker
:
[HubName("stockTicker")]
public class StockTickerHub : Hub
{
private readonly IStockTicker _stockTicker;
public StockTickerHub(IStockTicker stockTicker)
{
if (stockTicker == null)
{
throw new ArgumentNullException("stockTicker");
}
_stockTicker = stockTicker;
}
La creazione di un'interfaccia IStockTicker
non è strettamente necessaria, ma volevo mostrare in che modo l'inserimento delle dipendenze può contribuire a ridurre l'accoppiamento tra i componenti nell'applicazione.
Aggiungere la libreria Ninject
Esistono molti contenitori IoC open source per .NET. Per questa esercitazione userò Ninject. Altre librerie popolari includono Castle Windsor, Spring.Net, Autofac, Unity e StructureMap.
Usare Gestione pacchetti NuGet per installare la libreria Ninject. In Visual Studio scegliere Console diGestione>pacchetti NuGetdal menu Strumenti. Nella finestra Console di gestione pacchetti immettere il comando seguente:
Install-Package Ninject -Version 3.0.1.10
Sostituire il resolver di dipendenze SignalR
Per usare Ninject all'interno di SignalR, creare una classe che deriva da DefaultDependencyResolver.
internal class NinjectSignalRDependencyResolver : DefaultDependencyResolver
{
private readonly IKernel _kernel;
public NinjectSignalRDependencyResolver(IKernel kernel)
{
_kernel = kernel;
}
public override object GetService(Type serviceType)
{
return _kernel.TryGet(serviceType) ?? base.GetService(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
return _kernel.GetAll(serviceType).Concat(base.GetServices(serviceType));
}
}
Questa classe esegue l'override dei metodi GetService e GetServices di DefaultDependencyResolver. SignalR chiama questi metodi per creare vari oggetti in fase di esecuzione, incluse le istanze dell'hub, nonché vari servizi usati internamente da SignalR.
- Il metodo GetService crea una singola istanza di un tipo. Eseguire l'override di questo metodo per chiamare il metodo TryGet del kernel Ninject . Se il metodo restituisce Null, eseguire il fallback al resolver predefinito.
- Il metodo GetServices crea una raccolta di oggetti di un tipo specificato. Eseguire l'override di questo metodo per concatenare i risultati di Ninject con i risultati del sistema di risoluzione predefinito.
Configurare le associazioni Ninject
A questo punto si userà Ninject per dichiarare associazioni di tipo.
Aprire la classe Startup.cs dell'applicazione (creata manualmente in base alle istruzioni del pacchetto in readme.txt
o creata aggiungendo l'autenticazione al progetto). Startup.Configuration
Nel metodo creare il contenitore Ninject, che Ninject chiama il kernel.
var kernel = new StandardKernel();
Creare un'istanza del sistema di risoluzione delle dipendenze personalizzato:
var resolver = new NinjectSignalRDependencyResolver(kernel);
Creare un'associazione per IStockTicker
come indicato di seguito:
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>() // Bind to StockTicker.
.InSingletonScope(); // Make it a singleton object.
Questo codice sta dicendo due cose. Prima di tutto, ogni volta che l'applicazione richiede un IStockTicker
, il kernel deve creare un'istanza di StockTicker
. In secondo luogo, la StockTicker
classe deve essere creata come oggetto singleton. Ninject creerà un'istanza dell'oggetto e restituirà la stessa istanza per ogni richiesta.
Creare un'associazione per IHubConnectionContext come indicato di seguito:
kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
Questo codice crea una funzione anonima che restituisce un IHubConnection. Il metodo WhenInjectedInto indica a Ninject di usare questa funzione solo durante la creazione di IStockTicker
istanze. Il motivo è che SignalR crea internamente istanze IHubConnectionContext e non si vuole eseguire l'override del modo in cui SignalR le crea. Questa funzione si applica solo alla StockTicker
classe .
Passare il resolver di dipendenze al metodo MapSignalR aggiungendo una configurazione hub:
var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
Aggiornare il metodo Startup.ConfigureSignalR nella classe Startup dell'esempio con il nuovo parametro:
public static void ConfigureSignalR(IAppBuilder app, HubConfiguration config)
{
app.MapSignalR(config);
}
SignalR userà ora il resolver specificato in MapSignalR, anziché il resolver predefinito.
Di seguito è riportato il listato di codice completo per Startup.Configuration
.
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
var kernel = new StandardKernel();
var resolver = new NinjectSignalRDependencyResolver(kernel);
kernel.Bind<IStockTicker>()
.To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>() // Bind to StockTicker.
.InSingletonScope(); // Make it a singleton object.
kernel.Bind(typeof(IHubConnectionContext<dynamic>)).ToMethod(context =>
resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();
var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);
}
}
Per eseguire l'applicazione StockTicker in Visual Studio, premere F5. Nella finestra del browser passare a http://localhost:*port*/SignalR.Sample/StockTicker.html
.
L'applicazione ha esattamente la stessa funzionalità di prima. Per una descrizione, vedere Trasmissione server con ASP.NET SignalR. Il comportamento non è stato modificato; ha reso il codice più semplice da testare, gestire ed evolvere.