SignalR’da Bağımlılık EklemeDependency Injection in SignalR

, Mike Ison, Patrick fletiby Mike Wasson, Patrick Fletcher

Warning

Bu belge, SignalR 'nin en son sürümü için değildir.This documentation isn't for the latest version of SignalR. ASP.NET Core SignalR'ye göz atın.Take a look at ASP.NET Core SignalR.

Bu konuda kullanılan yazılım sürümleriSoftware versions used in this topic

Bu konunun önceki sürümleriPrevious versions of this topic

SignalR 'nin önceki sürümleri hakkında daha fazla bilgi için bkz. SignalR daha eski sürümleri.For information about earlier versions of SignalR, see SignalR Older Versions.

Sorular ve açıklamalarQuestions and comments

Lütfen bu öğreticiyi nasıl beğentireceğiniz ve sayfanın en altındaki açıklamalarda İyileştiğimiz hakkında geri bildirimde bulunun.Please leave feedback on how you liked this tutorial and what we could improve in the comments at the bottom of the page. Öğreticiyle doğrudan ilgili olmayan sorularınız varsa, bunları ASP.NET SignalR forumuna veya StackOverflow.com'e gönderebilirsiniz.If you have questions that are not directly related to the tutorial, you can post them to the ASP.NET SignalR forum or StackOverflow.com.

Bağımlılık ekleme, nesneler arasında sabit kodlanmış bağımlılıkları kaldırmanın bir yoludur. bu sayede, bir nesnenin bağımlılıklarını test etmek için (sahte nesneleri kullanarak) veya çalışma zamanı davranışını değiştirmek kolaylaşır.Dependency injection is a way to remove hard-coded dependencies between objects, making it easier to replace an object's dependencies, either for testing (using mock objects) or to change run-time behavior. Bu öğreticide, SignalR hub 'larda bağımlılık ekleme işlemlerinin nasıl gerçekleştirileceği gösterilmektedir.This tutorial shows how to perform dependency injection on SignalR hubs. Ayrıca, SignalR ile IOC kapsayıcılarını nasıl kullanacağınızı gösterir.It also shows how to use IoC containers with SignalR. Bir IOC kapsayıcısı, bağımlılık ekleme için genel bir çerçevedir.An IoC container is a general framework for dependency injection.

Bağımlılık ekleme nedir?What is Dependency Injection?

Bağımlılık ekleme konusunda zaten bilgi sahibiyseniz bu bölümü atlayın.Skip this section if you are already familiar with dependency injection.

Bağımlılık ekleme (dı), nesnelerin kendi bağımlılıklarını oluşturmadan sorumlu olmayan bir modeldir.Dependency injection (DI) is a pattern where objects are not responsible for creating their own dependencies. İşte, bu, bir değer olarak motive etmek için basit bir örnektir.Here is a simple example to motivate DI. İletileri günlüğe kaydetmek için gereken bir nesneniz olduğunu varsayalım.Suppose you have an object that needs to log messages. Günlüğe kaydetme arabirimi tanımlayabilirsiniz:You might define a logging interface:

interface ILogger 
{
    void LogMessage(string message);
}

Nesneniz, iletileri günlüğe kaydetmek için bir ILogger oluşturabilirsiniz:In your object, you can create an ILogger to log messages:

// 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.This works, but it's not the best design. FileLogger başka bir ILogger uygulamasıyla değiştirmek istiyorsanız SomeComponentdeğiştirmeniz gerekir.If you want to replace FileLogger with another ILogger implementation, you will have to modify SomeComponent. Birçok farklı nesnenin FileLoggerkullanduymasını, bunların tümünü değiştirmeniz gerekir.Supposing that a lot of other objects use FileLogger, you will need to change all of them. Ya da tek bir FileLogger yapmaya karar verirseniz, uygulama genelinde de değişiklikler yapmanız gerekir.Or if you decide to make FileLogger a singleton, you'll also need to make changes throughout the application.

Daha iyi bir yaklaşım, nesne içine bir ILogger eklemek — örneğin, bir Oluşturucu bağımsız değişkeni kullanarak:A better approach is to "inject" an ILogger into the object—for example, by using a constructor argument:

// 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, hangi ILogger kullanacağınızı seçmeden nesne sorumlu değildir.Now the object is not responsible for selecting which ILogger to use. Kendisine bağımlı nesneleri değiştirmeden ILogger uygulamaları geçebilirsiniz.You can switch ILogger implementations without changing the objects that depend on it.

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

Bu düzene Oluşturucu Eklemeadı verilir.This pattern is called constructor injection. Başka bir model, bir ayarlayıcı yöntemi veya özelliği aracılığıyla bağımlılığı ayarladığınız ayarlayıcı ekleme işlemi olur.Another pattern is setter injection, where you set the dependency through a setter method or property.

SignalR 'de basit bağımlılık eklemeSimple Dependency Injection in SignalR

SignalR Ile çalışmayabaşlama öğreticiden sohbet uygulamasını göz önünde bulundurun.Consider the Chat application from the tutorial Getting Started with SignalR. Bu uygulamadaki Merkez sınıfı aşağıda verilmiştir:Here is the hub class from that application:

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

Göndermeden önce sohbet iletilerini sunucuda depolamak istediğinizi varsayalım.Suppose that you want to store chat messages on the server before sending them. Bu işlevi soyutlayan bir arabirim tanımlayabilir ve arabirimi ChatHub sınıfına eklemek için DI kullanabilirsiniz.You might define an interface that abstracts this functionality, and use DI to inject the interface into the ChatHub class.

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 doğrudan hub oluşturmamalıdır; SignalR bunları sizin için oluşturur.The only problem is that a SignalR application does not directly create hubs; SignalR creates them for you. Varsayılan olarak, SignalR bir hub sınıfının parametresiz bir oluşturucuya sahip olmasını bekler.By default, SignalR expects a hub class to have a parameterless constructor. Ancak, hub örnekleri oluşturmak için bir işlevi kolayca kaydedebilir ve bu işlevi, DI işlemini gerçekleştirmek için kullanabilirsiniz.However, you can easily register a function to create hub instances, and use this function to perform DI. Globalhost. DependencyResolver. Registerçağırarak işlevi kaydedin.Register the function by calling GlobalHost.DependencyResolver.Register.

public void Configuration(IAppBuilder app)
{
    GlobalHost.DependencyResolver.Register(
        typeof(ChatHub), 
        () => new ChatHub(new ChatMessageRepository()));

    App.MapSignalR();

    // ...
}

Artık ChatHub bir örnek oluşturması gerektiğinde SignalR bu anonim işlevi çağıracaktır.Now SignalR will invoke this anonymous function whenever it needs to create a ChatHub instance.

IoC kapsayıcılarıIoC Containers

Önceki kod, basit durumlar için uygundur.The previous code is fine for simple cases. Ancak yine de şunu yazmanız gerekiyordu:But you still had to write this:

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

Birçok bağımlılığı olan karmaşık bir uygulamada, bu "kablolama" kodunun çoğunu yazmanız gerekebilir.In a complex application with many dependencies, you might need to write a lot of this "wiring" code. Özellikle bağımlılıklar iç içe ise, bu kod bakımını yapmak zor olabilir.This code can be hard to maintain, especially if dependencies are nested. Ayrıca birim testi de zordur.It is also hard to unit test.

Bir çözüm bir IOC kapsayıcısı kullanmaktır.One solution is to use an IoC container. Bir IOC kapsayıcısı, bağımlılıkları yönetmekten sorumlu bir yazılım bileşenidir. Türleri kapsayıcıya kaydeder ve sonra nesneleri oluşturmak için kapsayıcıyı kullanın.An IoC container is a software component that is responsible for managing dependencies.You register types with the container, and then use the container to create objects. Kapsayıcı, bağımlılık ilişkilerini otomatik olarak belirler.The container automatically figures out the dependency relations. Birçok IOC kapsayıcısı ayrıca nesne ömrü ve kapsamı gibi şeyleri denetlemenize olanak tanır.Many IoC containers also allow you to control things like object lifetime and scope.

Note

"IoC", bir çerçevenin uygulama koduna çağırdığı genel bir model olan "denetimin INVERSION" anlamına gelir."IoC" stands for "inversion of control", which is a general pattern where a framework calls into application code. Bir IOC kapsayıcısı, nesnelerinizi sizin için oluşturur, bu da normal denetim akışını "tersine çevirir".An IoC container constructs your objects for you, which "inverts" the usual flow of control.

SignalR 'de IOC kapsayıcıları kullanmaUsing IoC Containers in SignalR

Sohbet uygulaması, bir IOC kapsayıcısından faydalanmak için büyük olasılıkla çok basittir.The Chat application is probably too simple to benefit from an IoC container. Bunun yerine StockTicker örneğine göz atalım.Instead, let's look at the StockTicker sample.

StockTicker örneği iki ana sınıfı tanımlar:The StockTicker sample defines two main classes:

  • StockTickerHub: istemci bağlantılarını yöneten hub sınıfı.StockTickerHub: The hub class, which manages client connections.
  • StockTicker: stok fiyatlarını tutan ve düzenli aralıklarla güncelleştiren bir tek.StockTicker: A singleton that holds stock prices and periodically updates them.

StockTickerHub, StockTicker tekil öğesine bir başvuru tutar StockTicker ancak StockTickerHubiçin IHV Ubconnectioncontext 'e bir başvuru barındırır.StockTickerHub holds a reference to the StockTicker singleton, while StockTicker holds a reference to the IHubConnectionContext for the StockTickerHub. StockTickerHub örneklerle iletişim kurmak için bu arabirimi kullanır.It uses this interface to communicate with StockTickerHub instances. (Daha fazla bilgi için bkz. ASP.NET SignalR Ile sunucu yayını.)(For more information, see Server Broadcast with ASP.NET SignalR.)

Bu bağımlılıklara bir bit eklemek için bir IOC kapsayıcısı kullanabiliriz.We can use an IoC container to untangle these dependencies a bit. İlk olarak, StockTickerHub ve StockTicker sınıflarını basitleşelim.First, let's simplify the StockTickerHub and StockTicker classes. Aşağıdaki kodda, ihtiyaç duyduğumuz parçalar hakkında yorum yaptı.In the following code, I've commented out the parts that we don't need.

Parametresiz oluşturucuyu StockTickerHubkaldırın.Remove the parameterless constructor from StockTickerHub. Bunun yerine, her zaman, hub 'ı oluşturmak için DI 'yi kullanacağız.Instead, we will always use DI to create the 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;
    }

    // ...

StockTicker için Singleton örneğini kaldırın.For StockTicker, remove the singleton instance. Daha sonra, StockTicker ömrünü denetlemek için IoC kapsayıcısını kullanacağız.Later, we'll use the IoC container to control the StockTicker lifetime. Ayrıca, oluşturucuyu genel hale getirin.Also, make the constructor public.

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

Daha sonra, StockTickeriçin bir arabirim oluşturarak kodu yeniden düzenleyebilirsiniz.Next, we can refactor the code by creating an interface for StockTicker. Bu arabirimi, StockTickerHub StockTicker sınıfından ayırmak için kullanacağız.We'll use this interface to decouple the StockTickerHub from the StockTicker class.

Visual Studio bu tür yeniden düzenleme kolaylaştırır.Visual Studio makes this kind of refactoring easy. StockTicker.cs dosyasını açın, StockTicker sınıfı bildirimine sağ tıklayın ve yeniden Düzenle ... seçeneğini belirleyin. Arabirimi Ayıkla.Open the file StockTicker.cs, right-click on the StockTicker class declaration, and select Refactor ... Extract Interface.

Arabirimi Ayıkla Iletişim kutusunda Tümünü Seç' e tıklayın.In the Extract Interface dialog, click Select All. Diğer varsayılan değerleri bırakın.Leave the other defaults. Tamam’a tıklayın.Click OK.

Visual Studio IStockTickeradlı yeni bir arabirim oluşturur ve ayrıca IStockTickertüretmeye StockTicker değişir.Visual Studio creates a new interface named IStockTicker, and also changes StockTicker to derive from IStockTicker.

IStockTicker.cs dosyasını açın ve arabirimi genel' e değiştirin.Open the file IStockTicker.cs and change the interface to public.

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

StockTickerHub sınıfında, iki StockTicker örneğini IStockTickerolarak değiştirin:In the StockTickerHub class, change the two instances of StockTicker to IStockTicker:

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

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

IStockTicker arabirimi oluşturmak kesinlikle gerekli değildir, ancak uygulamanızın uygulamanızdaki bileşenler arasında Eşleneni azaltmaya nasıl yardımcı olduğunu göstermek istiyorum.Creating an IStockTicker interface isn't strictly necessary, but I wanted to show how DI can help to reduce coupling between components in your application.

Nekleme kitaplığını eklemeAdd the Ninject Library

.NET için çok sayıda açık kaynaklı IFC kapsayıcısı vardır.There are many open-source IoC containers for .NET. Bu öğretici için Nekleme'yi kullanacağız.For this tutorial, I'll use Ninject. (Diğer popüler kitaplıklar, role Spring.net, Autofac, Unityve StructureMapiçerir.)(Other popular libraries include Castle Windsor, Spring.Net, Autofac, Unity, and StructureMap.)

Nekleme kitaplığınıyüklemek Için NuGet Paket Yöneticisi 'ni kullanın.Use NuGet Package Manager to install the Ninject library. Visual Studio 'da, Araçlar menüsünden NuGet Paket Yöneticisi > Paket Yöneticisi konsolu' nu seçin.In Visual Studio, from the Tools menu select NuGet Package Manager > Package Manager Console. Paket Yöneticisi konsolu penceresinde, aşağıdaki komutu girin:In the Package Manager Console window, enter the following command:

Install-Package Ninject -Version 3.0.1.10

SignalR bağımlılık çözümleyicisini değiştirmeReplace the SignalR Dependency Resolver

SignalR içinde Nekleme kullanmak için, Defaultdependencyresolver'dan türeyen bir sınıf oluşturun.To use Ninject within SignalR, create a class that derives from 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));
    }
}

Bu sınıf, Defaultdependencyresolver'ın GetService ve GetServices yöntemlerini geçersiz kılar.This class overrides the GetService and GetServices methods of DefaultDependencyResolver. SignalR, hub örnekleri ve ayrıca 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.SignalR calls these methods to create various objects at runtime, including hub instances, as well as various services used internally by SignalR.

  • GetService yöntemi bir türün tek bir örneğini oluşturur.The GetService method creates a single instance of a type. Nekleme çekirdeğinin TryGet yöntemini çağırmak için bu yöntemi geçersiz kılın.Override this method to call the Ninject kernel's TryGet method. Bu yöntem null döndürürse, varsayılan çözümleyiciye geri dönün.If that method returns null, fall back to the default resolver.
  • GetServices yöntemi, belirtilen türde nesnelerin bir koleksiyonunu oluşturur.The GetServices method creates a collection of objects of a specified type. Neklemesine ait sonuçları varsayılan çözümleyiciyle sonuçlarla birleştirmek için bu yöntemi geçersiz kılın.Override this method to concatenate the results from Ninject with the results from the default resolver.

Nekleme bağlamalarını yapılandırmaConfigure Ninject Bindings

Şimdi tür bağlamaları bildirmek için Nekleme kullanacağız.Now we'll use Ninject to declare type bindings.

Uygulamanızın Startup.cs sınıfını açın (readme.txt' deki paket yönergelerine göre el ile oluşturduğunuz veya projenize kimlik doğrulaması eklenerek oluşturulan).Open your application's Startup.cs class (that you either created manually as per the package instructions in readme.txt, or that was created by adding authentication to your project). Startup.Configuration yönteminde Nekleme kapsayıcısını oluşturun ve Nekleme, çekirdeğiçağırır.In the Startup.Configuration method, create the Ninject container, which Ninject calls the kernel.

var kernel = new StandardKernel();

Özel bağımlılık çözümleyicimizin bir örneğini oluşturun:Create an instance of our custom dependency resolver:

var resolver = new NinjectSignalRDependencyResolver(kernel);

IStockTicker için aşağıdaki şekilde bir bağlama oluşturun:Create a binding for IStockTicker as follows:

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

Bu kod iki şeyi söyleyerek.This code is saying two things. İlk olarak, uygulama bir IStockTickerihtiyaç duyduğunda, çekirdek bir StockTickerörneği oluşturacaktır.First, whenever the application needs an IStockTicker, the kernel should create an instance of StockTicker. İkincisi, StockTicker sınıfı tek bir nesne olarak oluşturulmuş olmalıdır.Second, the StockTicker class should be a created as a singleton object. Nekleme nesnenin bir örneğini oluşturur ve her istek için aynı örneği döndürür.Ninject will create one instance of the object, and return the same instance for each request.

Iubconnectioncontext için aşağıdaki şekilde bir bağlama oluşturun:Create a binding for IHubConnectionContext as follows:

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

Bu kod, bir Ihubconnectiondöndüren anonim bir işlev oluşturur.This code creates an anonymous function that returns an IHubConnection. Whenınjectedinto yöntemi, neklemesine bu işlevi yalnızca IStockTicker örnekleri oluştururken kullanmasını söyler.The WhenInjectedInto method tells Ninject to use this function only when creating IStockTicker instances. Bunun nedeni, SignalR 'nin, dahili olarak IHV Ubconnectioncontext örnekleri oluşturduğunu ve SignalR 'nin bunları nasıl oluşturduğunu geçersiz kılmak istemedik.The reason is that SignalR creates IHubConnectionContext instances internally, and we don't want to override how SignalR creates them. Bu işlev yalnızca StockTicker sınıfımız için geçerlidir.This function only applies to our StockTicker class.

Bir hub yapılandırması ekleyerek bağımlılık çözümleyicisini Mapsignalr yöntemine geçirin:Pass the dependency resolver into the MapSignalR method by adding a hub configuration:

var config = new HubConfiguration();
config.Resolver = resolver;
Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);

Örnek başlangıç sınıfındaki Startup. ConfigureSignalR yöntemini yeni parametresiyle güncelleştirin:Update the Startup.ConfigureSignalR method in the sample's Startup class with the new parameter:

public static void ConfigureSignalR(IAppBuilder app, HubConfiguration config)
{
    app.MapSignalR(config);
}

Şimdi SignalR, varsayılan çözümleyici yerine Mapsignalriçinde belirtilen çözümleyici 'yi kullanacaktır.Now SignalR will use the resolver specified in MapSignalR, instead of the default resolver.

Startup.Configurationiçin kod listesinin tamamı aşağıda verilmiştir.Here is the complete code listing for 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);
    }
}

Visual Studio 'da StockTicker uygulamasını çalıştırmak için F5 'e basın.To run the StockTicker application in Visual Studio, press F5. Tarayıcı penceresinde http://localhost:*port*/SignalR.Sample/StockTicker.html' a gidin.In the browser window, navigate to http://localhost:*port*/SignalR.Sample/StockTicker.html.

Uygulama daha önce olduğu gibi tamamen aynı işlevselliğe sahiptir.The application has exactly the same functionality as before. (Bir açıklama için bkz. ASP.NET SignalR Ile sunucu yayını.) Davranışı değiştirmedik; kodu test etmek, sürdürmek ve geliştirmek için daha kolay hale getirilir.(For a description, see Server Broadcast with ASP.NET SignalR.) We haven't changed the behavior; just made the code easier to test, maintain, and evolve.