Aracılığıyla paylaş


SignalR 1.x Sürümünde Bağımlılık Ekleme

Patrick Fletcher tarafından

Uyarı

Bu belgeler SignalR'nin en son sürümüne yönelik değildir. SignalR ASP.NET Core göz atın.

Bağımlılık ekleme, nesneler arasındaki sabit kodlanmış bağımlılıkları kaldırmanın bir yoludur ve test etmek (sahte nesneler kullanarak) veya çalışma zamanı davranışını değiştirmek için bir nesnenin bağımlılıklarını değiştirmeyi kolaylaştırır. Bu öğreticide SignalR hub'larında bağımlılık ekleme işleminin nasıl gerçekleştirebileceğiniz gösterilmektedir. Ayrıca SignalR ile IoC kapsayıcılarının nasıl kullanılacağını da gösterir. IoC kapsayıcısı, bağımlılık eklemeye yönelik genel bir çerçevedir.

Bağımlılık Ekleme nedir?

Bağımlılık ekleme hakkında zaten bilginiz varsa bu bölümü atlayın.

Bağımlılık ekleme (DI), nesnelerin kendi bağımlılıklarını oluşturmakla sorumlu olmadığı bir desendir. AŞAĞıDA DI'yi motive etmek için basit bir örnek verilmiştir. İletileri günlüğe kaydetmesi gereken bir nesneniz olduğunu varsayalım. Bir günlük arabirimi tanımlayabilirsiniz:

interface ILogger 
{
    void LogMessage(string message);
}

Nesnenizde, iletileri günlüğe kaydetmek için bir ILogger oluşturabilirsiniz:

// Without dependency injection.
class SomeComponent
{
    ILogger _logger = new FileLogger(@"C:\logs\log.txt");

    public void DoSomething()
    {
        _logger.LogMessage("DoSomething");
    }
}

Bu işe yarar, ancak en iyi tasarım değildir. öğesini başka bir ILogger uygulamayla değiştirmek FileLogger istiyorsanız, öğesini değiştirmeniz SomeComponentgerekir. Birçok başka nesnenin kullandığını FileLoggervarsayarak, bunların tümünü değiştirmeniz gerekir. Ya da tekil yapmaya FileLogger karar verirseniz, uygulama genelinde de değişiklik yapmanız gerekir.

Daha iyi bir ILogger yaklaşım, örneğin bir oluşturucu bağımsız değişkeni kullanarak nesnesine "eklemek"tir:

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

Artık nesne, hangilerinin ILogger kullanılacağını seçmekle sorumlu değildir. Buna bağlı nesneleri değiştirmeden uygulamaları değiştirebilirsiniz ILogger .

var logger = new TraceLogger(@"C:\logs\log.etl");
var someComponent = new SomeComponent(logger);

Bu desen oluşturucu ekleme olarak adlandırılır. Başka bir desen, bağımlılığı bir ayarlayıcı yöntemi veya özelliği aracılığıyla ayarladığınız ayarlayıcı ekleme işlemidir.

SignalR'de Basit Bağımlılık Ekleme

SignalR ile Çalışmaya Başlama öğreticisinde yer alan Sohbet uygulamasını göz önünde bulundurun. Bu uygulamadan hub sınıfı aşağıdadır:

public class ChatHub : Hub
{
    public void Send(string name, string message)
    {
        Clients.All.addMessage(name, message);
    }
}

Sohbet iletilerini göndermeden önce sunucuda depolamak istediğinizi varsayalım. Bu işlevi soyutlayan bir arabirim tanımlayabilir ve arabirimi sınıfına eklemek ChatHub için DI kullanabilirsiniz.

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

Tek sorun bir SignalR uygulamasının hub'ları doğrudan oluşturmamasıdır; SignalR bunları sizin için oluşturur. SignalR varsayılan olarak bir hub sınıfının parametresiz oluşturucuya sahip olmasını bekler. Ancak, hub örnekleri oluşturmak için bir işlevi kolayca kaydedebilir ve bu işlevi KULLANARAK DI gerçekleştirebilirsiniz. GlobalHost.DependencyResolver.Register'ı çağırarak işlevi kaydedin.

protected void Application_Start()
{
    GlobalHost.DependencyResolver.Register(
        typeof(ChatHub), 
        () => new ChatHub(new ChatMessageRepository()));

    RouteTable.Routes.MapHubs();

    // ...
}

Şimdi SignalR, bir ChatHub örnek oluşturması gerektiğinde bu anonim işlevi çağırır.

IoC Kapsayıcıları

Önceki kod basit durumlar için uygundur. Ama yine de şunu yazmanız gerekiyordu:

... new ChatHub(new ChatMessageRepository()) ...

Birçok bağımlılığı olan karmaşık bir uygulamada, bu "kablolama" kodunun çoğunu yazmanız gerekebilir. Özellikle bağımlılıklar iç içe yerleştirilmişse bu kodun bakımı zor olabilir. Birim testi de zordur.

Çözümlerden biri bir IoC kapsayıcısı kullanmaktır. IoC kapsayıcısı, bağımlılıkları yönetmekle sorumlu olan bir yazılım bileşenidir. Türleri kapsayıcıya kaydeder ve sonra nesneleri oluşturmak için kapsayıcıyı kullanırsınız. Kapsayıcı, bağımlılık ilişkilerini otomatik olarak anlar. Birçok IoC kapsayıcısı, nesne ömrü ve kapsam gibi öğeleri denetlemenize de olanak sağlar.

Not

"IoC", bir çerçevenin uygulama koduna çağırdığı genel bir desen olan "denetimin ters çevrilmesi" anlamına gelir. IoC kapsayıcısı nesnelerinizi sizin için oluşturur ve bu da normal denetim akışını "tersine çevirir".

SignalR'da IoC Kapsayıcılarını Kullanma

Sohbet uygulaması büyük olasılıkla IoC kapsayıcısından yararlanamayacak kadar basittir. Bunun yerine StockTicker örneğine göz atalım.

StockTicker örneği iki ana sınıfı tanımlar:

  • StockTickerHub: İstemci bağlantılarını yöneten hub sınıfı.
  • StockTicker: Hisse senedi fiyatlarını tutan ve bunları düzenli aralıklarla güncelleştiren bir tekil.

StockTickerHub tekil StockTicker için bir başvuru tutarken StockTicker , için IHubConnectionContext'e bir başvuru tutar StockTickerHub. Örneklerle StockTickerHub iletişim kurmak için bu arabirimi kullanır. (Daha fazla bilgi için bkz. ASP.NET SignalR ile Sunucu Yayını.)

Bu bağımlılıkları biraz çözmek için bir IoC kapsayıcısı kullanabiliriz. İlk olarak ve StockTicker sınıflarını StockTickerHub basitleştirelim. Aşağıdaki kodda ihtiyacımız olmayan bölümleri açıklama satırı yaptım.

parametresiz oluşturucuyu içinden StockTickerkaldırın. Bunun yerine hub'ı oluşturmak için her zaman DI kullanacağız.

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

    // ...

StockTicker için tekil örneği kaldırın. Daha sonra StockTicker ömrünü denetlemek için IoC kapsayıcısını kullanacağız. Ayrıca oluşturucuyu genel yapın.

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 clients)
    {
        if (clients == null)
        {
            throw new ArgumentNullException("clients");
        }

        Clients = clients;
        LoadDefaultStocks();
    }

    //public static StockTicker Instance
    //{
    //    get
    //    {
    //        return _instance.Value;
    //    }
    //}

Ardından için bir arabirim StockTickeroluşturarak kodu yeniden düzenleyebiliriz. sınıfını StockTicker birbirinden StockTickerHub ayırmak için bu arabirimi kullanacağız.

Visual Studio bu tür yeniden düzenlemeyi kolaylaştırır. StockTicker.cs dosyasını açın, sınıf bildirimine StockTicker sağ tıklayın ve Yeniden düzenle ... öğesini seçin. Arabirimi Ayıkla...

Visual Studio Code üzerinde görüntülenen ve Arabirimi Yeniden Düzenle ve Ayıkla seçeneklerinin vurgulandığı sağ tıklama açılan menüsünün ekran görüntüsü.

Arabirimi Ayıkla iletişim kutusunda Tümünü Seç'e tıklayın. Diğer varsayılan değerleri bırakın. Tamam'a tıklayın.

Tümünü Seç seçeneğinin vurgulandığı ve O K seçeneğinin görüntülendiği Arabirimi Ayıkla iletişim kutusunun ekran görüntüsü.

Visual Studio adlı IStockTickeryeni bir arabirim oluşturur ve ayrıca'dan IStockTickertüretilen şekilde değişirStockTicker.

IStockTicker.cs dosyasını açın ve arabirimini public olarak değiştirin.

public interface IStockTicker
{
    void CloseMarket();
    IEnumerable<Stock> GetAllStocks();
    MarketState MarketState { get; }
    void OpenMarket();
    void Reset();
}

sınıfında öğesinin StockTickerHub iki örneğini StockTicker olarak IStockTickerdeğiştirin:

[HubName("stockTicker")]
public class StockTickerHub : Hub
{
    private readonly IStockTicker _stockTicker;

    public StockTickerHub(IStockTicker stockTicker)
    {
        if (stockTicker == null)
        {
            throw new ArgumentNullException("stockTicker");
        }
        _stockTicker = stockTicker;
    }

IStockTicker Arabirim oluşturmak kesinlikle gerekli değildir, ancak DI'nin uygulamanızdaki bileşenler arasındaki bağlantının azaltılmasına nasıl yardımcı olabileceğini göstermek istedim.

Ninject Kitaplığı'nı ekleme

.NET için birçok açık kaynak IoC kapsayıcısı vardır. Bu öğreticide Ninject'i kullanacağım. (Diğer popüler kitaplıklar Castle Windsor, Spring.Net, Autofac, Unity ve StructureMap'tir.)

Ninject kitaplığını yüklemek için NuGet Paket Yöneticisi'ni kullanın. Visual Studio'da Araçlar menüsünde NuGet Paket Yöneticisi Paket Yöneticisi>Konsolu'nu seçin. Paket Yöneticisi Konsolu penceresinde aşağıdaki komutu girin:

Install-Package Ninject -Version 3.0.1.10

SignalR Bağımlılık Çözümleyicisi'ni değiştirme

SignalR içinde Ninject kullanmak için DefaultDependencyResolver'dan türetilen bir sınıf oluşturun.

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

Bu sınıf, DefaultDependencyResolver'ınGetService ve GetServices yöntemlerini geçersiz kılar. SignalR, hub örnekleri ve SignalR tarafından dahili olarak kullanılan çeşitli hizmetler dahil olmak üzere çalışma zamanında çeşitli nesneler oluşturmak için bu yöntemleri çağırır.

  • GetService yöntemi, bir türün tek bir örneğini oluşturur. Ninject çekirdeğinin TryGet yöntemini çağırmak için bu yöntemi geçersiz kılın. Bu yöntem null döndürürse varsayılan çözümleyiciye geri dönün.
  • GetServices yöntemi, belirtilen türde bir nesne koleksiyonu oluşturur. Ninject'ten gelen sonuçları varsayılan çözümleyicinin sonuçlarıyla birleştirmek için bu yöntemi geçersiz kılın.

Ninject Bağlamalarını Yapılandırma

Şimdi tür bağlamalarını bildirmek için Ninject kullanacağız.

RegisterHubs.cs dosyasını açın. yönteminde RegisterHubs.Start Ninject'in çekirdeği çağırdığı Ninject kapsayıcısını oluşturun.

var kernel = new StandardKernel();

Özel bağımlılık çözümleyicimizin bir örneğini oluşturun:

var resolver = new NinjectSignalRDependencyResolver(kernel);

için IStockTicker aşağıdaki gibi bir bağlama oluşturun:

kernel.Bind<IStockTicker>()
    .To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()  // Bind to StockTicker.
    .InSingletonScope();  // Make it a singleton object.

Bu kod iki şey söylüyor. İlk olarak, uygulamanın her IStockTickerihtiyacı olduğunda, çekirdeğin bir örneği oluşturması StockTickergerekir. İkinci olarak StockTicker , sınıfı tekil bir nesne olarak oluşturulmalıdır. Ninject nesnenin bir örneğini oluşturur ve her istek için aynı örneği döndürür.

IHubConnectionContext için aşağıdaki gibi bir bağlama oluşturun:

kernel.Bind<IHubConnectionContext>().ToMethod(context =>
    resolver.Resolve<IConnectionManager>().GetHubContext<StockTickerHub>().Clients
).WhenInjectedInto<IStockTicker>();

Bu kod, IHubConnection döndüren anonim bir işlev oluşturur. WhenInjectedInto yöntemi Ninject'e bu işlevi yalnızca örnek oluştururken IStockTicker kullanmasını söyler. Bunun nedeni SignalR'nin IHubConnectionContext örneklerini dahili olarak oluşturması ve SignalR'nin bunları oluşturma biçimini geçersiz kılmak istememizdir. Bu işlev yalnızca sınıfımız StockTicker için geçerlidir.

Bağımlılık çözümleyicisini MapHubs yöntemine geçirin:

RouteTable.Routes.MapHubs(config);

Artık SignalR, varsayılan çözümleyici yerine MapHubs'da belirtilen çözümleyiciyi kullanacaktır.

için tam kod listesi aşağıdadır RegisterHubs.Start.

public static class RegisterHubs
{
    public static void Start()
    {
        var kernel = new StandardKernel();
        var resolver = new NinjectSignalRDependencyResolver(kernel);

        kernel.Bind<IStockTicker>()
            .To<Microsoft.AspNet.SignalR.StockTicker.StockTicker>()
            .InSingletonScope();

        kernel.Bind<IHubConnectionContext>().ToMethod(context =>
                resolver.Resolve<IConnectionManager>().
                    GetHubContext<StockTickerHub>().Clients
            ).WhenInjectedInto<IStockTicker>();

        var config = new HubConfiguration()
        {
            Resolver = resolver
        };

        // Register the default hubs route: ~/signalr/hubs
        RouteTable.Routes.MapHubs(config);
    }
}

StockTicker uygulamasını Visual Studio'da çalıştırmak için F5 tuşuna basın. Tarayıcı penceresinde adresine http://localhost:*port*/SignalR.Sample/StockTicker.htmlgidin.

Internet Explorer tarayıcı penceresinde görüntülenen A S P nokta NET Signal R Hisse Senedi DeğerLeyicisi Örneği ekranının ekran görüntüsü.

Uygulama, öncekiyle tam olarak aynı işlevselliğe sahiptir. (Açıklama için bkz. ASP.NET SignalR ile Sunucu Yayını.) Davranışı değiştirmedik; yalnızca kodu test etme, koruma ve geliştirmeyi kolaylaştırdı.