Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
signalr / SignalR Programming in Microsoft ASP.NET.pdf
Скачиваний:
65
Добавлен:
25.05.2015
Размер:
19.23 Mб
Скачать

typeof(IJavaScriptProxyGenerator), () => generator.Value

);

The lists that we saw earlier with the calls made to the Dependency Resolver requesting components can serve as a guide to know which parts of the framework can be replaced simply by modifying the register in the way that we have already seen.

Another example: when we looked at hubs, we remarked that the minimization and compaction of the dynamically generated proxy did not come implemented out of the box, but it had been structurally provided for. This can also be seen in the list of calls to GetService() from the Dependency Resolver that we looked at earlier. There is a request for an IJavaScriptMinifier object, and a null value is returned, so SignalR assumes that the proxy will not be minimized.

Again, we can modify the register so that it will return an object in whose Minify() method it will receive a script in the form of a character string, and it must return the result of the minimization process. Thus, if we install the AjaxMin package with NuGet, we could automatically minimize the generated proxy as follows:

// Initialization code

var minifier = new Lazy<MyJavascriptMinifier>(

() => new MyJavascriptMinifier()

);

GlobalHost.DependencyResolver.Register(

typeof(IJavaScriptMinifier), () => minifier.Value

);

// Custom minifier

public class MyJavascriptMinifier : IJavaScriptMinifier

{

public string Minify(string source)

{

return new Minifier().MinifyJavaScript(source);

}

}

Dependency Injection

Dependency Injection (DI) is a design pattern that describes a set of techniques whose purpose is to achieve less cohesion between the components of our applications. Although it shares objectives with the Service Locator—the pattern implemented by the SignalR Dependency Resolver—it uses a different approach regarding the way in which each component obtains the dependencies—other compo- nents—that it needs to operate.

Thus, whereas the Service Locator advocates an active schema—that is, it is the component itself that uses the Dependency Resolver to request from it the required dependencies—with Dependency

Injection, these dependencies are satisfied externally, even achieving total decoupling from the

Dependency Resolver, while favoring the creation of classes with very explicit dependencies at code

196 Chapter 9Advanced topics

www.it-ebooks.info

level. In practice, this means that our classes will be cleaner, simpler, more independent, reusable, easily testable with unit testing, and highly maintainable.

Let’s look at it with a piece of code. As you can see, the following hub is strongly coupled to other components of the system:

public class CustomerHub: Hub

{

...

public void Create(Customer customer)

{

var repo = new CustomerRepository(); repo.Save(customer);

var mailer = new MailManager(); mailer.Send(customer.Email, "Welcome!");

}

}

The code suggests that, to operate, the CustomerHub class needs the services provided by another two classes, CustomerRepository and MailManager. That is, in the code that we observe, CustomerHub has these two dependencies and is strongly coupled to them. Because they are being instantiated directly, there would be no way to change the classes used without modifying this code and all the points in the system where the same thing is done.

Using interfaces and the Dependency Resolver would improve this situation, because instead of directly creating the instances, we could request them from this component:

public class CustomerHub: Hub

{

IDependencyResolver resolver = GlobalHost.DependencyResolver;

...

public void Create(Customer customer)

{

var repo = resolver.Resolve<ICustomerRepository>(); repo.Save(customer);

var mailer = resolver.Resolve<IMailManager>(); mailer.Send(customer.Email, "Welcome!");

}

}

In the body of the method, there would no longer be any reference to the specific classes on which we depend—only to the interfaces or contracts with which they must comply. Thus, the Dependency Resolver will be in charge of providing them.

However, there are still the following problems with this code:

■■First, we have introduced a dependency to the Service Locator used—in this case, the SignalR Dependency Resolver. This limits the possibilities for use and makes the classes less reusable.

■■Second, the code of Create(), whose main objective should be to store a Customer object in the repository and send it a welcome email, has too much noise due to the pipework—obtain- ing dependencies—that it is forced to do.

Advanced topicsChapter 9

197

www.it-ebooks.info

■■Third, there is an additional, more subtle problem that has been carried on from the start: the dependencies of the class are neither clear nor explicit. Although there are tools to show class dependencies, even graphically, the ideal situation would be that the simple quick view of a component would give us a clear idea of what other elements it needs to function. However, in this case, we would have to review the whole class to see at what points we are using the Dependency Resolver.

Dependency Injection can help with this last point. Although there are several ways to use this principle, the most popular one is to make our classes receive the components they need—their dependencies—as parameters of their constructor, and always using abstractions provided by interfaces.

Applying this concept to the preceding example, our portion of the hub would be as follows:

public class CustomerHub : Hub

{

private ICustomerRepository _customerRepository; private IMailManager _mailManager;

public CustomerHub(ICustomerRepository customerRepository, IMailManager mailManager)

{

_customerRepository = customerRepository; _mailManager = mailManager;

}

public void Create(Customer customer)

{

_customerRepository.Save(customer); _mailManager.Send(customer.Email, "Welcome!");

}

...

}

Note that the size of the Create() method has decreased significantly, because now it has to focus only on performing its principal task. In addition, we need only a quick look at the constructor method of the class to know that it depends on two components, and although we do not know exactly which ones they are, we do know what contracts they are required to fulfill.

Above all, we have completely decoupled our hub from the other components, with the advantages that that entails.

Manual dependency injection

If we tried to connect to a hub such as the preceding one, we would get at best a 500 error indicating that there is no constructor without parameters for the hub. This is only natural, because for all this to work, we need someone to supply the dependencies to the hub constructor. Who better than the Dependency Resolver to take care of that?

Remember that a few pages ago we were checking the calls that were internally made to the Dependency Resolver, and when we obtained the ones corresponding to the execution of a hub, we

198 Chapter 9Advanced topics

www.it-ebooks.info

could see that SignalR asked the Dependency Resolver for an instance of the hub class, but a null value was returned:

...

***Requested type IServerCommandHandler, provided: ServerCommandHandler

***Requested type IAssemblyLocator, provided: EnumerableOfAssemblyLocator

***Requested type ITransportHeartbeat, provided: TransportHeartbeat

***Requested type CustomerHub, provided: null

All that we would need would be to register in the Dependency Resolver an association between the data type requested (CustomerHub) and the action that would create the instance. This action is the point from which the constructor would be invoked, supplying it the required dependencies.

The code to enter in the application startup would be the following:

GlobalHost.DependencyResolver.Register(

typeof(CustomerHub), () => new CustomerHub(

new CustomerRepository(), new MailManager()

)

);

This way, every time SignalR needs a CustomerHub type object—which will happen on every call or client interaction—it will request it from the Dependency Resolver, which will execute the lambda function and return the created instance.

If we work with persistent connections instead of hubs, we can get exactly the same result by applying the same techniques. These objects, of types descending from PersistentConnection, are also requested from the Dependency Resolver, so we can capture that moment and supply an object with the dependencies already satisfied:

// Configuration: GlobalHost.DependencyResolver.Register(

typeof(MyConnection), () => new MyConnection(

new MyDependency()

)

);

// Persistent Connection

public class MyConnection: PersistentConnection

{

private IMyDependency _dependency;

public MyConnection(IMyDependency dependency)

{

_dependency = dependency;

}

...

}

However, the latter case is not quite recommended, and we must be very careful with its use, because the life of a PersistentConnection can be long (depending on the transport used, the

Advanced topicsChapter 9

199

www.it-ebooks.info

Соседние файлы в папке signalr