Overview of SignalR - Pro ASP.NET SignalR: Real-Time Communication in .NET with SignalR 2.1 (2014)

Pro ASP.NET SignalR: Real-Time Communication in .NET with SignalR 2.1 (2014)

Chapter 2. Overview of SignalR

When learning a brand-new code library, you might initially think that it is great and you will use it for every project going forward. Although sometimes the new library will work in your latest project, it is probably not the best solution. This chapter discusses the technologies on which ASP.NET SignalR is built. These are the technologies that enable SignalR to support a wide range of server and client platforms. Because you’ll be eager to try SignalR, you will write your first SignalR application. After creating the first application, the focus is on when it is best to use SignalR.

To get even more out of SignalR, we go over what you can customize, extend, and scale to fit your project needs. But with all good things there are limitations, and we discuss a few of SignalR’s limitations, too.

In summary, the following topics are discussed in this chapter:

· Technologies behind SignalR

· Supported server and client platforms

· Getting started with SignalR

· When to use SignalR

· Extensibility of SignalR

· Limitations of SignalR

Technologies Behind SignalR

There are a few technologies that stand out in SignalR that give it the strength and flexibility that make real-time web applications easy. These technologies help applications be flexible so they are not dependent on a specific host. They also make it easy to connect to a wide number of clients without having to worry about the client-specific transports. As well, these technologies allow your application to be highly customizable and to scale.

Open Web Interface for .NET (OWIN)

Many developers know that most ASP.NET projects are built with a dependency on the System.Web assembly that was built primarily for Internet Information Services (IIS). The first break away from the dependency to System.Web came with the release of Web API in 2012. With theSystem.Web dependency removed, developers could self-host Web API much more easily, which created a new issue in which there could be multiple hosted implementations (Web API, static pages, MVC, and others), all running in their own process with separate hosts. To handle this problem, web developers came up with a common interface that enables decoupling of the web applications from web servers. This common interface is OWIN, which enforces a structure and process for the HTTP requests and responses. The OWIN interface is very simple (see Listing 2-1).

Listing 2-1. OWIN Interface

Func<IDictionary<string, object>, Task>

The latest OWIN specifications can be found at http://www.owin.org/spec/owin-1.0.0.html, but the interface is the important part. The interface is a function that takes an IDictionary object that contains the environment variables and returns a task once the function is finished.

The environment variables dictionary stores information about the request, the response, and any relevant server state. There is a set of variables that is required to be populated within the dictionary during an evaluation of the function. The server is required to provide body streams and header collections for the request and response. The OWIN application is responsible for populating the response variables in the dictionary for the response data. SignalR implements the OWIN interface, allowing it to be hosted as middleware by any host that implements the OWIN interface with compatible a framework.

Connection Transports

SignalR provides four connection transports: Web Sockets, Server Sent Events, Forever Frame, and long polling. These transports provide key technology elements that make real-time or near real-time connections possible. The most desirable transport is Web Sockets, which provides real-time communication through a full-duplex socket. The other transport technologies can provide only near real-time communication because of transport limitations. Of these transports Server Sent Events is the closest to real time, followed by Forever Frame and then long polling. All the transports are part of a fallback mechanism that automatically provides the best connection by starting with the most desirable connection and falling back until an appropriate one is located.

Dependency Resolver

In SignalR there is a dependency resolver that provides a great deal of customization and flexibility. At the root of the dependency resolver is an object container with specialized logic. The container implements Inversion of Control (IoC) by the container controlling the dependencies of any object requested. This means the container has control of what dependent objects are resolved for a class instead of the class controlling its own internal dependencies. To explain how the dependency resolver works by using an IoC container, we’ll go over an IoC example with dependency injection.

Inversion of Control

To understand the IoC, look at the code in Listing 2-2, which has the dependencies defined inside the class that we cannot change. This is a common pattern used by many developers that forces hard dependencies on internal classes.

Listing 2-2. Example of Code with Internal Dependencies on Other Classes

public class EmailAlertService
{
public void SendAlert()
{
//Send Alert
}
}

public class AlertSystem
{
private EmailAlertService _alertService;
public AlertSystem()
{
_alertService = new EmailAlertService();
}
public void ThresholdExceeded()
{
_alertService.SendAlert();
}
}

If you look at the AlertSystem class, you see that there is a dependency on the EmailAlertService class that is hidden from consumers of the AlertSystem class. In other words, the dependency is not exposed publicly, so there is a tight coupling between AlertSystem andEmailAlertService. It might not look bad at first, but AlertSystem is restricted to sending only email alerts and cannot be unit tested very easily.

So for the AlertSystem class, we want to implement the IoC technique so that the control of internal dependencies is inverted to the consuming class. There are a few ways to invert control, but the most popular is by using a dependency injection pattern.

In dependency injection, a class provides a mechanism for the consuming classes to provide the internal dependencies. There are three types of dependency injection: constructor injection, property injection, and interface injection. Constructor injection is the most common and is used in SignalR.

Constructor injection has several benefits. First, any consuming class knows exactly what dependencies are needed to create the instance. Second, once the instance is created, all the required dependencies have been provided. Listing 2-3 shows an example of the constructor injection for the AlertSystem class.

Listing 2-3. Example of Class that Allows Constructor Dependency Injection

public class AlertSystem
{
private IAlertService _alertService;
public AlertSystem(IAlertService alertService)
{
_alertService = alertService;
}
public void ThresholdExceeded()
{
_alertService.SendAlert();
}
}

Using IoC, we can expose internal dependencies to consuming classes. But now that we have access to internal dependencies, we need a way to control how they are used.

Inversion of Control Container

The Inversion of Control container (IoC container) controls registering, locating, and the lifetime of objects inside of it. Depending on the IoC container, it can also provide additional functionality.

To make the container aware of the objects that it is responsible for, you need to register the objects, which can be done by convention or configuration. One example of convention registration is registering any class that implements an interface with a similar name as the interface. For example, it could be a DBAlerter class that implements the IAlerter interface. Configuration registrations can generally be done in a configuration file or programmatically. An example of a programmatic registration is shown in Listing 2-4.

Listing 2-4. Configuration Registration Example

container.RegisterType<IAlerter,DBAlerter>();

Once an object has been registered in the container, there are various methods to retrieve that object. It can be retrieved by explicitly requesting (resolving) the object or by being an injected dependency of a requested object. In Listing 2-5, an object is being explicitly requested by having the container resolve that interface. Containers can also resolve objects automatically for you if they are part of a dependency chain, which typically occurs when an object is being requested that has dependencies in its constructor. If the container has those dependencies registered, it resolves them.

Listing 2-5. Resolving Objects from a Container

IAlerter alerter = container.Resolve<IAlerter>();

Every object in the container has a lifetime. The type of supported lifetimes varies depending on the container. For example, you may want the same object, such as a configuration class, to be returned on every request. To accomplish this, the object is registered as a Singleton. There are also times when you want a new instance of an object every time, such as a new Controller for a request. This type of lifetime and others can be registered on a per-object basis. When you register objects in the container, you define their lifetimes

Dependency Resolver Example

The dependency resolver allows for the replacement of most of the SignalR components. Some of the base classes such as PersistentConnection use the service locator pattern to resolve its dependencies.

SERVICE LOCATOR PATTERN

SignalR uses a weakly typed service locator pattern in a lot of the core classes. This pattern allows a locator object to be injected into a class via the constructor, which calls the Resolve method on the locator to acquire dependent objects.

As an example, in Listing 2-6 the dependency resolver is configured to use our implementation of IJsonSerializer for classes that derive from the PersistentConnection because the type is resolved from the dependency resolver.

Listing 2-6. Example of Overriding IJsonSerializer in the Dependency Resolver

public class MyJsonSerializer : IJsonSerializer
{
//logic removed for simplicity
}

protected void ConfigureTypes()
{
GlobalHost.DependencyResolver.Register(typeof(IJsonSerializer), () => new MyJsonSerializer());
}

This is a very powerful technology enables us to customize SignalR applications very specifically.

Task Parallel Library

The task parallel library is a set of APIs that are part of.NET Framework 4.0/4.5. These APIs help provide a simple but powerful way to add parallelism to programs, which is accomplished by abstracting the difficulties of concurrent threading into tasks controlled by a scheduler. In .NET 4.5, the Async and Await keywords were added to simplify the code even more for parallelism.

With the task parallel library, SignalR can utilize resources more efficiently. A lot of the code is written so that returns a Task; the scheduler can then optimize the code more efficiently for parallelism.

With the use of the Async and Await keywords, the SignalR code is more efficient by being able to provide more asynchronous methods. These asynchronous methods allow the scheduler to switch to other work while these methods are awaiting expensive operations such as network input/output (I/O).

These APIs provide a key functionality that allows SignalR to scale and work more efficiently.

Message Backplanes

As your application scales, you have two options: to scale up or scale out. Scaling up is when you improve or upgrade the components in a system that is running the application. This type of scaling provides only a little bit of improvement, so you also have to scale out. Scaling out is when the application is run on many computers. To scale out, the hosts need to communicate with each other, which is done with a message backplane.

A message backplane is a specialized application that is built specifically to transport messages between systems using a defined API interface. The message backplane provides a few characteristics that help applications excel in scaling out.

The first characteristic is a central routing channel for communication between all the applications. Without this central routing channel, as the number of application servers increases, the complexity of connecting all the application servers to each other increases at a quadratic rate. But this is all simplified for the application because it has to know only the address of the message backplane; the message backplane takes care of delivering the message.

The second characteristic of the message backplane is the asynchronicity of message sending. The originating application just has to send the message to the message backplane and can return back to its processing. Now that the message is in the message backplane, it is its responsibility to deliver the message to all the intended recipients.

Another characteristic of the message backplane is a common message schema. All backplane consumers have an agreed-on message schema that allows them to communicate without involving complex interface logic that is likely to change as new or updated software is used.

SignalR supports a number of backplane options and is explained in more detail later in this chapter and in Chapter 9.

Supported Server Platforms and Clients

As mentioned earlier, SignalR is very flexible, so there are numerous choices for the server and client platforms. Although the supported server platforms are generally Microsoft based, other operating systems such as Linux are available by using the Mono Framework. The clients range from current web browsers to .NET clients to iOS and Android. iOS and Android have a dependency on a customized version of the Mono Framework if using the Xamarin tools. There also is an SDK for Android that does not require the Mono Framework, but it has limited capabilities.

Server Platforms

The supported server operating systems are the following:

· Windows Server 2012 R2

· Windows Server 2012

· Windows Server 2008 R2

· Windows Server 2008

· Windows 8

· Windows 7

· Windows Azure

· Linux

Image Note Again, supporting Linux requires compiling the SignalR assemblies using Mono.

Client Platforms

The number of client platforms that SignalR supports is pretty vast, thanks to the browsers that support JavaScript. The following list shows the supported clients (there may be many more that are not officially supported):

· Web browsers

· Internet Explorer 8+

· Google Chrome

· Firefox

· Safari

· Opera

· .NET 4.5 and 4.0 clients

· Windows RT

· Windows Phone 8

· Silverlight 5

· Android

· iOS

Image Note Android and iOS clients are supported by using customized versions of Mono by Xamarin.

Getting Started with SignalR

Now that we have discussed the technologies on which SignalR is built, it is time to try it. To create the sample applications, you need to have Visual Studio 2012 or later installed and be able to run the server on a platform that supports .NET 4.5. The first thing we show you is NuGet, which is used to get the needed SignalR assemblies into your solution. Next, we focus on creating a sample PersistentConnection server and client application.

NuGet

When incorporating third-party dependencies in the past, the usual solution involved downloading the needed assemblies and copying them into the project in a dependency directory. This solution has problems when the assemblies aren’t checked in to source control properly, conflict with existing versions already on the local box, or won’t be deployed correctly because of bad build setups. To resolve these problems, Visual Studio 2012 and later versions include the NuGet Package Manager.

The NuGet Package Manager allows you to add/update/delete packages that are hosted on an official NuGet feed or other feeds. These packages are then controlled on a solution and project level. NuGet packages provide versioning and dependencies, and you can specify the version that you are downloading when you install. The tool automatically pulls down any dependencies that are needed.

Package Manager Dialog Box

The Package Manager dialog box (see Figure 2-1) is a graphical interface that can be used to search, install, manage, and update packages. Here you can search for the packages that you want to add to your solution/project. Once you find the package you want to install, you can select the projects you want to install to. The Package Manager also enables you to manage packages if there is an installed package that you want to install to more projects or uninstall from projects. Finally, it allows you to update the packages that you have installed to have the latest version that is available.

image

Figure 2-1. Package Manager dialog box

Although the Package Manager dialog box is great for simple package management, sometimes more control over packages is needed. You can use the Package Manager Console in these situations.

Package Manager Console

The Package Manager Console (see Figure 2-2) allows you to install/update/remove packages using PowerShell scripts. The level of functionality is very similar to the Package Manager dialog box, but you can be more specific with the PowerShell commands. Specific examples of using the console to gain more functionality include requesting a specific version of a package, requesting prereleased packages, and passing arguments to the package (such as the project name).

image

Figure 2-2. Package Manager Console

Important SignalR NuGet Packages

The following list includes some of the main packages for SignalR. The package description is listed, followed by the command to install the package using the NuGet Package Manager Console.

· Server and client package for hosting with IIS and ASP.NET and JavaScript client

Install-Package Microsoft.AspNet.SignalR

· Server package for hosting SignalR endpoints

Install-Package Microsoft.AspNet.SignalR.Core

· Client package for .NET SignalR applications

Install-Package Microsoft.AspNet.SignalR.Client

· Client package for JavaScript SignalR applications

Install-Package Microsoft.AspNet.SignalR.JS

First Sample Application

Now that you are ready to create the sample application, start Visual Studio 2012. To create a new SignalR application, follow these steps:

1. Choose File image New image Project, as shown in Figure 2-3.

image

Figure 2-3. Creating a new application project

2. Under Installed image Templates image Visual C#, select ASP.NET Web Application in the New Project dialog box (see Figure 2-4).

image

Figure 2-4. New Project dialog box

3. Name the application Chapter 2 - First Sample Application.

4. Select OK.

5. Select the MVC template shown in Figure 2-5.

image

Figure 2-5. Selecting a project template

6. Start the Package Manager Console, which is found under Tools image Library Package Manager image Package Manager Console.

7. Inside the Package Manager Console, run the following command to add the SignalR assemblies to the project (see Figure 2-6):

Install-Package Microsoft.AspNet.SignalR

image

Figure 2-6. Install of SignalR package

You see NuGet resolving the dependencies, displaying the licenses, and adding the assemblies to the project in Figures 2-7, 2-8, and 2-9, respectively.

image

Figure 2-7. SignalR package dependencies

image

Figure 2-8. SignalR license agreements

image

Figure 2-9. SignalR project reference adds

After the assemblies are installed, you have to create an endpoint.

8. Right-click on the Chapter 2 - First Sample Application project to add a folder, as shown in Figure 2-10.

image

Figure 2-10. Adding a new folder

9. Name the new folder PersistentConnections.

10.Right-click the PersistentConnections folder and add a class, as shown in Figure 2-11.

image

Figure 2-11. Adding a class

11.Name this class SamplePersistentConnection, as shown in Figure 2-12.

image

Figure 2-12. SamplePersistentConnection class name dialog box

12.Update the SamplePersistentConnection class to what is shown in Listing 2-7. Resolve any missing using statements.

Listing 2-7. SamplePersistentConnection Class

public class SamplePersistentConnection : PersistentConnection
{
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
return Connection.Broadcast(data);
}
}

13.Update the StartUp.cs class to what is shown in Listing 2-8.

Listing 2-8. StartUp.cs Class

public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR<SamplePersistentConnection>("SamplePC");
ConfigureAuth(app);
}
}

14.Right-click on the Chapter 2 - First Sample Application project and add an HTML page, as shown in Figure 2-13.

image

Figure 2-13. Adding a static HTML file

15.Name the HTML file Index (see Figure 2-14).

image

Figure 2-14. Specifying a name

16.Add the scripts in Listing 2-9 to the head section of Index.html with the correct versions in your project.

Listing 2-9. JavaScript for SignalR Client Application

<script src="/scripts/jquery-1.8.2.js" type="text/javascript"></script>
<script src="/scripts/jquery.signalR-1.1.2.js" type="text/javascript"></script>

<script>
$(function () {
var connection = $.connection('/SamplePC');

connection.received(function (data) {
$('#messages').append('<li>' + data + '</li>');
});

connection.start().done(function () {
$("#send").click(function () {
connection.send($('#name').val() + ': ' +$('#message').val());
});
});

});
</script>

17.Add the HTML content in Listing 2-10 to the body section of Index.html.

Listing 2-10. HTML for SignalR client Application

<label>Name: </label>
<input id="name" /><br />
<input id="message" />
<button id="send" >Send</button> <ul id="messages"></ul>

18.Press F5 to start the server.

Image Note If you get a missing reference to Microsoft.Owin or Microsoft.Owin.Security when you run the project, your MVC template may be out of date. You can correct these errors by running Install-Package Microsoft.Owin andInstall-Package Microsoft.Owin.Security, respectively, in the Package Manager Console.

19.Go to http://localhost:####/Index.html, where #### is the assigned port name.

20.Open up a second browser and enter the URL from step 19.

21.Type a message; it should appear on both browsers (see Figure 2-15).

image

Figure 2-15. Two sample client applications communicating

That’s all that you have to do to have a fully functioning SignalR application. The next section shows you when SignalR should be used.

When to Use SignalR

Although we can build pages in a web server, these pages are static, and the user has to manually refresh the page to see whether there is new content. This experience can be improved by refreshing at set intervals or updating page content using Ajax calls in response to user actions.

But even this improved experience still feels disconnected, especially when actions or content delivery is delayed by the nature of the implementation. SignalR provides an enhanced experience that allows content to be pushed from the server when it is available and actions to be executed immediately on the server. This functionality is provided by having a connection persisted between the server and client.

Understanding the User Experience

To understand the experience, start by looking at a personal blog. The blog content is generally authored by one person and it is usually updated about once per day (if the user is a frequent blogger). Even the visible comments are not updated frequently because they are not visible until approved by a moderator. So it is common for the user to have to manually refresh the web site to check for updated content.

A more interactive experience involves an e-commerce site. When using this type of site, it is expected that interactions such as adding items to the cart occur on the current page without the need to refresh the page or post back. The content has the potential to change during a user’s session so there is usually a timer or an expiration on the pages to ensure that the latest content is displayed.

For a fully interactive site, many examples that are used every day include live news stories, chat rooms, games, and stock tickers. These sites demand that the latest content is pushed and the actions are performed as soon as possible. Using games as an example, you want to know your opponent’s position as soon as it is available so that you can react and quickly take action.

General Categories of SignalR Applications

Even though the same technology is used by SignalR applications, they provide different experiences. These experiences generally fall into the following categories.

· Real-time notification

· Peer-to-peer collaboration

· Real-time content delivery

The real-time notification category contains applications that receive a notification to the application that an event from the server has occurred. The notification is pushed from the server in real time to the client. The notification can provide information or notify the user or client that an action is available.

Here is an example of a real-time notification: you are on an e-mail client, and the inbox count increases in response to a command from the server. The command from the server does not provide any detail of the new inbox items, just the information that the count increased.

The peer-to-peer collaboration category is for applications that allow two or more clients to communicate interactively. These applications work on a shared set of data that is delivered and acted on in real time. Google Docs is a great example of how shared content is updated among two or more authors in real time.

With real-time content delivery, applications are fed content in real time. This content can be displayed to the user or consumed by the application. In a breaking news feed site, the content displays directly to the user as it is pushed from the server. In a game, however, the content delivery can be the current game state with no information displayed to the user.

When Not to Use SignalR

As great as SignalR is, there are times when you should not use it. In general, if the content is updated very infrequently or the client connection is unreliable, SignalR should not be used.

The previous example of a personal blog can be used to describe why SignalR should not be used with content that is infrequently updated. In this scenario, a persistent connection is made every time a user visits the site. Because the odds are that the content is not being updated while a visitor is visiting the site, no content is delivered using the persistent connection that was created. As with many personal blogs, there are very few interactions that a visitor is allowed to do, and the impact of the action does not need to be in real time. The benefits that SignalR provides are not used, so overhead is created for every visitor by creating a persistent connection.

It is also not a good idea to use SignalR in your applications when the client connection is known to be unreliable. Even if it would be great to use SignalR to provide real-time functionality to clients, there is an overhead to keep the persistent connection alive, and SignalR doesn’t provide a robust delivery mechanism. If the unreliable connection is a cell phone, the application has to use more battery power to keep up the persistent connection. The other issue with unreliable connections is that the delivery mechanisms were not built to be robust. So if the connection were to fail in the middle of message, logic would have to be added to both the client and the server to provide fail-proof mechanisms.

Extensibility of SignalR

SignalR is very extensible, thanks to the technologies behind it. The extensibility is possible because of the forethought to make it support OWIN, IoC containers, and message backplanes. Summaries of these technologies are provided in the following paragraphs.

OWIN Components

With the support of OWIN, SignalR allows you to host your application on any operating system in which the assemblies are executable and the OWIN middleware interface is provided. This means that you can run your applications on a current version Windows server that is running IIS or on a Linux box that is running Mono. It also enables your application to run with other OWIN middleware such as Web API.

IoC Containers

As discussed previously, it is very easy to customize SignalR by using IoC containers. With SignalR, you can register types with the default implementation or provide your own IoC container. There are many examples of people successfully using containers such as Unity, Structure Map, and Ninject.

Scaling Out with Message Backplanes

You learned earlier in the chapter that message backplanes can be used to scale out SignalR. The SignalR assemblies provide message backplane support for SQL Server, Windows Azure Service Bus, and Redis. (We go into more detail about message backplanes in Chapter 9 and 10.)

SQL Server

The message backplane can be used with SQL Server, with Service Broker enabled or disabled for that database server. The messages are persisted into tables that are managed by SignalR. The SQL Server implementation of message backplane performance is much better with Service Broker enabled. Service Broker is the database’s internal queuing mechanism. This NuGet package can be installed with the Install-Package Microsoft.AspNet.SignalR.SqlServer command in the Package Manager Console.

Windows Azure Service Bus

The Windows Azure Service Bus allows you to create a message backplane that is managed in Windows Azure by Microsoft. This implementation of the service bus uses topics to send messages. The cost for using this cloud service is not very expensive; at the time of this writing, sending 1,000,000 messages and having 744 relay hours (about a month) costs less than $2.00. (This NuGet package can be installed with the Install-Package Microsoft.AspNet.SignalR.ServiceBus command in the Package Manager Console.) The Windows Azure Service Bus implementation is fairly easy to set up and provides good throughput, but it is hosted in Azure, which might add significant overhead and delay for on-premise applications.

Redis

Redis is an open source, advanced key-value store that is stored in memory. It supports a publisher/subscriber model used by SignalR to send the messages. (This NuGet package can be installed with the Install-Package Microsoft.AspNet.SignalR.Redis command in the Package Manager Console.) This implementation can be scaled to have great throughput, but it is the most complex message backplane.

Limitations of SignalR

When developing with SignalR, you might be affected by SignalR’s limitations. The power and extensibility of SignalR can be reduced, depending on the operating system and host on which you deploy it. Clients can have limitations as well. Depending on the type of application, the scale of an application can also be affected. Finally, there are limitations that are outside of your controllable environment that can limit your applications.

Server Platform Limitations

The server platform must support the .NET 4.5 runtime because the server code uses the Async and Await keywords extensively. When IIS is run on client OSs such as Windows 7 or Windows 8, there is a limitation of up to ten concurrent connections, whereas the server OSs are generally limited only by the amount of server resources.

Use of the Web Sockets protocol is limited by a few factors. The first factor is that in IIS-hosted applications, the Web Sockets protocol is supported only on IIS 8, IIS 8 express, or later versions of IIS that require the use of Windows Server 2012 or later.

Client Platform Limitations

Clients that are web browser–based may see limitations on how many connections can be made to the server from a web browser. These are internal rules that are set to keep the web browser stable.

Message Backplane Limitations

In scenarios in which the number of messages grows proportionally to the number of users or when there are high-frequency real-time collaborations, the message backplane can be a limiting factor. As the number of messages going through the message backplane increases, a bottleneck can occur.

External Limitations

Web Sockets are limited by the fact that every hop from server to client and back must support Web Sockets; otherwise, parts of the connection might be downgraded, or the protocol might not be supported at all.

Summary

This chapter provided an overview of SignalR. We started by discussing its core technologies: OWIN, connection transports, dependency resolvers, the task parallel library, and message backplanes. You saw that SignalR is supported on a wide range of servers and clients from Windows to iOS. We showed you how very little is needed to get started with SignalR development. You learned how SignalR can be customized, scaled, and extended through the core technologies. Finally, you saw that even great frameworks can have limitations, depending on the type of application.