By Joe Crick.
Article Contents
- Introduction
- Why DoneJS and ASP.NET
- A Brief Overview of SignalR
- Overview of the Server-side API
- Creating the Chat Client
- Compiling and Deploying the API Code
- Test the Application
Introduction
With the front-end world changing on a daily basis, figuring out what framework to use can be daunting. How do you know the framework you use today will be supported tomorrow? As a busy ASP.NET MVC developer, you need something you can understand quickly. You need something reliable, something with staying power.
DoneJS is a well-established, professionally maintained, Open Source JavaScript MVVM framework. This article will help you learn how to use DoneJS with an ASP.NET MVC Application. Together, we’ll build a real-time chat client, using: DoneJS, SignalR, and ASP.NET MVC Web API. The back end will be an ASP.NET MVC API deployed to Azure. We will consume that API with a DoneJS client app.
After reading this article, you’ll understand the fundamentals of how to create a DoneJS application, including:
- Using generators to set up a new application, and add components
- Configuring routing
- Integrating SignalR for managing real-time data
You’ll also learn how to integrate that application with an ASP.NET MVC back end.
If you want to jump right in, and read the code, you can do so here:
- DoneJS Client
- ASP.NET MVC Web API
Why DoneJS & ASP.NET?
- MVC/SignalR and DoneJS are both well suited to real-time applications.
- Just like ASP.NET MVC, DoneJS uses an MVVM architecture. If you already understand ASP.NET MVC, a lot of the concepts in this article will be very familiar.
- DoneJS takes a modular approach to building applications—again, just like ASP.NET MVC.
- As with Visual Studio, DoneJS makes the development process easy, generating much of your boilerplate code for you.
A Brief Overview of SignalR
SignalR is an Open Source library from Microsoft that manages two-way, real-time communication between server and client. SignalR supports WebSockets (a technology that facilitates persistent connections between client and server). It also supports techniques for older browsers. SignalR includes APIs for:
- Connection management,
- Grouping connections, and
- Authorization
SignalR has an ASP.NET server library and a JavaScript client library. Both are required when building a SignalR application. For greater detail on SignalR, see this site.
Overview of the Server-side API
This article is focused on how to create a front-end DoneJS SignalR client. We will not discuss how to create a SignalR Web API. There are several, excellent articles on this from Microsoft. For more information, follow this link. To test your SignalR client, you will need a SignalR server. The full code for an ASP.NET MVC 5 SignalR Web API is provided. You can get the code here. At the end of this article, we’ll walk through compiling the server-side code, and installing it on Azure.
How the Client and Server Work Together
SignalR creates and manages a connection between clients and the server (or Hub). One or more clients can connect to a single Hub. The Hub can dispatch client-side events on one, some, or all connected clients.
Figure 1: SignalR connecting a client and a server
In our chat application, when a client creates a new post, it calls a method on the Hub. The Hub then dispatches an event on the other connected clients that insert the new post.
Figure 2: How the chat application works
Creating the Chat Client
- Install the DoneJS command line utility globally:
npm i -g donejs
- Create a new DoneJS application called donejs-signalr-chat:
donejs add app donejs-signalr-chat
DoneJS will ask you a series of setup questions. Press Enter to accept the defaults. Once the add appscript has completed, change directories into the new application:
cd donejs-signalr-chat
- Test the installation. DoneJS comes with a development server. Start the dev server with this command:
donejs develop
If you go to localhost:8080, you should see a default Hello World page. If you see this, your setup was successful!
Figure 3: A successful test
Application Foundations
Bootstrap
To make things look nice, install Bootstrap:
npm i bootstrap --save
Then, add the following import statement to the src/index.html file, just below the body tag:
<body> <can-import from="bootstrap/less/bootstrap.less!" />
can-import allows you to import dependencies into your component from a view template. These dependencies can be resources, such as the less file imported above, helper modules, or other components.
MS SignalR Client
Install the MS SignalR Client library.
npm i ms-signalr-client --save
This library is required to create our client-side Hub proxy. Finally, install jquery, which is required by the ms-signalr-client.
npm i jquery --save
Overview of State & the Application View Model
State in a DoneJS application is divided between application-level (global) and component. The macro state of the application is managed in the Application ViewModel, or AppViewModel. By convention, the AppViewModel is contained in the app.js file that DoneJS generated when it created the app. Additionally, each component also has a ViewModel that maintains its state.
As the object containing the state of the application, the AppViewModel shares application-level data across components. It is also synchronized with the app’s URL.
We’ll learn more about Application and Component View Models as we work with them, below.
Using DoneJS Generators
DoneJS generators speed up your development time. They remove the grunt work by creating a lot of your boilerplate code for you. In fact, we already used one, donejs add app, when we created our client application. We’ll use another one, add component, to create our components, and their tests. The add component generator takes two arguments:
- file || folder (You can specify either a filename, or a folder name.)
- tag-name (The HTML tag name for the component.)
The file option creates a file component in the current folder. File components are self contained. All their styles, &c., are in one file. They are best suited for simple, small components. If you provide the generator’s first argument using this format name.component, it will assume you intend to create a single file component. For example:
donejs add component home.component chat-home
would create a component in the current directory with a filename of
home.component
and a tag name of
<chat-home>
Figure 4a: Checking the extensions, part 1
Figure 4b: Checking the extensions, part 2
Create Application Components
Our app requires two components. For the first, which is very simple, we will create a single file component:
donejs add component home.component chat-home
The second component will be more complex. We’ll create a multi-file, folder component.
donejs add component messages chat-messages.
Home Component
The Home Component will contain the code for our home page. We’ve included a Home Component mostly to showcase routing. All that will be on the Home Component is an image, and a link to the chat page. It will look like this:
Figure 5: The Home Component
Open the home.component file, and update the code to look like this:
<can-component tag="chat-home"> <style type="less"> display: block; </style> <template> <h1 class="page-header text-center"> <img src="http://donejs.com/static/img/ donejs-logo-white.svg" alt="DoneJS logo" style>="width: 100%; /> <br>Chat </h1> <a href="{{routeUrl page='chat'}}" class="btn btn-primary btn-block btn-lg"> Start chat </a> </template> </can-component>
Let’s look at some of the key elements of this code.
Component Tag
<can-component tag="chat-home">
This line of code defines the tag name that will be used for the component. You would use the component in a page or embedded in another component like this:
<chat-home/>
Styles
<style type="text/less">
All styles for single-page components should be inlined.
In-app Links
<a href="{{routeUrl page='chat'}}"
To take advantage of DoneJS’ unique and powerful routing, the preceding code uses the routeUrl helper to create in-app links. routeUrl populates the anchor’s href with a URL like “/chat”. When the anchor is clicked, the URL sets the page property on the application’s ViewModel to the value chat.
Messages Component
The Messages Component is the heart of the client application. It will look like this:
Figure 6: The Messages Component
Inside the messages folder, the DoneJS generator has created:
- messages.html: A file you can use to demo/develop your component in isolation.
- messages.js: The JS code for the ViewModel and Component.
- messages.less: The less file.
- messages.md: A Markdown file where you can document your component.
- messages.stache: The view.
- messages_test.js: Test scripts. This defaults to QUnit. However, you can use whatever test harness you want.
- test.html: The test runner page.
You can leave most of the files untouched. We will work with two files:
- messages.js
- messages.stache
messages.js
messages.js contains the JS code for the ViewModel and Component.
ViewModel & SignalR Proxy
Just as in ASP.NET MVC, the ViewModel provides data to the view. It serves as a junction among views, models, and external services.
Figure 7: The ViewModel
In DoneJS, a ViewModel is a can.Map. A can.Map is an observable object. It provides a way for you to listen for and keep track of changes to objects. DoneJS Observables are somewhat different from Rx Observables. Both Rx and DoneJS Observables have Subjects and Observers. However, Rx Observables (in other words, IObservables) are sequences that start and complete. Once an Rx Observable has completed, it’s “dead.” Nothing else will come from it. DoneJS observables are not run-once sequences. They are objects that adhere to the Observer pattern. Until they unsubscribe, Observers in DoneJS are notified of changes to the Subject.
We will define and instantiate the SignalR proxy in our ViewModel. The hub proxy will allow us to define methods on the client that a Hub can call from the server, and to invoke methods on a Hub at the server. Creating a SignalR hub proxy requires references to jQuery and the SignalR core lib. The jQuery version must be 1.6.4, or later. We also need a reference to the SignalR JavaScript file. We’ll import these files in messages.js, as below:
import $ from 'jquery'; import 'ms-signalr-client';
Now, let’s define the ViewModel. Our ViewModel will have four properties, and one method:
export const ViewModel = Map.extend({ define: { messages: { Type: List, value: [] }, loading: { type: 'boolean', value: true }, connection: { type: '*', value: () => $.hubConnection('http:// donechatserver20161101024824.azurewebsites.net') }, proxy: { type: '*', value: function() { const connection = this.attr('connection'); return connection.createHubProxy('DoneChatHub'); } } }, send(event) { event.preventDefault(); this.attr('proxy').invoke('sendChat', this.attr('name'), this.attr('body')).done(function() { console.log('message sent'); }); } });
Let’s look at some of the key elements of this code.
Map.extend
export const ViewModel = Map.extend({
The static extend method of can.Map creates a new constructor function. When the can.Component is created, it will create an instance of the ViewModel we have defined.
define
define: { messages: { Type: List, value: [] },
The define property of the can.Map allows you to control the behavior of attributes on a can.Map. To use it, you specify a define object. The define object’s properties specify property descriptors that will be added to instances of the can.Map.
In the previous code, we have created the following properties:
- Messages
- Loading
- Connection
- Proxy
The messages property has both a Type and a default value defined. The Type property is set to List, which refers to a can.List (in other words, an Observable Array). When the Type property is set, it provides a constructor function that converts any value passed into the property to a specific type or value. The Type property is instance specific. For more information on setting the Type property of a can.Map see: Types. The value property is set to an empty array, which will be converted to a blank can.List when the ViewModel is instantiated.
The connection, proxy, and loading properties have a type defined. Note the case difference. The type function is called no matter what, and expected to return the value that should be set on the map. Notice that the type of the proxy property is *. By default, when an Object is passed into a property declared in the define, it is converted into can.Map instance. Setting the type of the property to * prevents this behavior.
Finally, note that we have created our SignalR proxy. The proxy is composed of a connection and a proxy. Both of those are defined as properties on our ViewModel. When the ViewModel is created, the value functions associated with the properties will be called, and the objects instantiated as properties of the ViewModel.
Methods
send(event){ ... }
You can define methods on a can.Map the same way you would on an ES6 class. Simply supply the method’s name and parameters.
Component
The Component element defines the properties of the component you are creating:
- The tag name (in other words, the HTML tag name, such as: <chat-messages/> )
- The ViewModel
- The View template
- Events: Events provide declarative event binding. They allow you to listen to DOM events, as well as events on the ViewModel.
- Helpers: Allow you to extend the functionality of a view, without muddying up the view with logic. They are specifically intended for working directly with the DOM. For example, if you need a complex conditional statement to set a CSS class name, a helper may be a good choice.
- &c.
The can.Component created in the messages.js file by the DoneJS generator looks like this:
export default Component.extend({ tag: 'chat-messages', viewModel: ViewModel, template });
Starting the SignalR Hub Proxy
Next, we need to start the hub, and tell it what to listen for:
inserted: function inserted() { const viewModel = this.viewModel; const connection = viewModel.attr('connection'); /** * The proxy function called by the SignalR server * must be defined before you call connection.start(); * Here, we are receiving the broadcast update from SignalR * and updating our Chat list with the chat received. */ viewModel.attr('proxy').on('chatBroadcast', function (message) { viewModel.attr('messages').push({ name: message.Sender.Name, body: message.Message }); }); /* * Here is where we connect with the SignalR server. */ connection.start() .done(function () { viewModel.attr('loading', false); console.('Now connected, connection ID=' + connection.id); }) .fail(function () { console.log('Could not connect'); }); }
viewModel.attr(‘proxy’).on(‘chatBroadcast’…
As the comments indicate, this line of code listens for chat broadcasts—in other words, messages posted by other SignalR chat clients—from the server.
connection.start
This section of code connects the hub proxy to the SignalR Hub. It logs out the connection id to the console, if the connection succeeds, and an error message, if it fails.
The View
Our messages view will be constructed as follows:
<div class="menu-item"><a href="{{routeUrl page='home'}}">Home</a></div> {{#unless loading}} {{#each messages}} <div class="list-group-item"> <h4 ="list-group-item-heading">{{name}}</h4> <p class="list-group-item-text">{{body}}</p> </div> {{else}} <div class="list-group-item"> <h4 class="list-group-item-heading">No messages</h4> </div> {{/each}} <form class="row chat-form" ($submit)="send(%event)"> <div class="col-sm-3"> <input type="text" class="form-control" placeholder="Your name" {($value)}="name"/> </div> <div class="col-sm-6"> <input type="text" class="form-control" placeholder="Your message"{($value)}="body"/> </div> <div class="col-sm-3"> <input type="submit" class="btn btn-primary btn-block" value="Send"/> </div> </form> {{else}} <div class="loader"> <img src="https://raw.githubusercontent.com/joe-crick/ RoutingInCanJs/master/img/rolling.gif"/> </div> {{/unless}}
Binding Syntax
Binding facilitates the exchange of data between the ViewModel and the View. DoneJS’ stache templates provide several ways of binding data:
- (event)=”key()” for event binding.
- {prop}=”key” for one-way binding to a child.
- {^prop}=”key” for one-way binding to a parent.
- {(prop)}=”key” for two-way binding.
In our View, above, we use event binding, for example:
# This binds the submit action to the send method on the ViewModel <form class="row chat-form" ($submit)="send(%event)">
and two-way binding, for example:
# This binds the value of this input to the name property # of the ViewModel <input type="text" class="form-control" placeholder="Your name" {($value)}="name"/>
For more information on working with stache Views, see can.stache.
Setup Routing
In most frameworks, routing is a simple mapping between URLs and controllers. DoneJS works a little differently. In DoneJS, a route is a mapping between URL strings (like /user/1) and properties on our application’s view-model. Routes can reflect and set the application’s state. Let’s look at an example.
Route Mapping
Open up the app.js file from your src directory. Add the following code:
import route from "can/route/"; import 'can/route/pushstate/'; route('/:page', { page: 'home' });
What have we done? First, we imported two libraries:
- can-route: Provides default routing functionality.
- can-route-pushstate: Allows you to use the browser’s pushstate for more friendly, usable URLs.
Second, we set up a simple routing rule. Let’s break down the code to see what’s going on.
route('/:page...
This portion of the rule creates a mapping between the first section of the application URL and the page property on the ApplicationViewModel (AVM). This means that any time the page property is set on the AVM, the URL will be updated. Likewise, any time that the page section of the URL is updated, the page property on the AVM will be updated. The following example illustrates the mapping between route and URL for the route: /:page/:color
Figure 8: The mapping between route and URL
page: 'home'
This portion of the rule sets the default page to the home page (in other words, the default value of the AVM’s page property to “home”).
Mapping Application State to Content Display
The routing of content in the application is configured in our index.stache file. Here, the routing process should be a bit more familiar. You’ll be creating a mapping between states of properties in the AppViewModel, and components to load. Open up the index.stache file, and add the following:
<div class="container"> <div class="row"> <div class="col-sm-8 col-sm-offset-2"> {{#eq page 'chat'}} <can-import from="donejs-chat/messages/"> {{#if isPending}} Loading... {{else}} <chat-messages/> {{/if}} </can-import> {{else}} <can-import from="donejs-chat/home.component!"> {{#if isPending}} Loading... {{else}} <chat-home/> {{/if}} </can-import> {{/eq}} </div> </div> </div>
Above, we’ve created a simple two-page routing. The key pieces of code to note are:
{{#eq page 'chat'}} ... <chat-messages .../> {{else}} <chat-home/> ... {{/eq}}
If the page property of the AppViewModel is set to chat, the chat-messages component is displayed. Otherwise, chat-home is displayed. To learn more about routing, visit the CanJS guide on Application State and Routing.
Compiling and Deploying the API Code
Create a new ASP.NET API Application
Create a new project in Visual Studio, called DoneChatServeR, with the “empty template” and “eb API” options clicked.
Update Dependencies
Begin by making sure all your dependencies are up to date. We can get everything with NuGet. In Solution Explorer, right-click the project name and click “Manage NuGet Packages”. Update all of the packages already installed to their newest versions. On the left, click “Updates->nuget.org” and you will see all of the packages that need to be updated. Click “Update All.” When that is done, close the window. Open the Package Manager Console:
Tools > NuGet Package Manager > Package Manager Console
Run the following command:
Install-Package Microsoft.AspNet SignalR
Compile and Deploy to Azure (1)
- Open the code you downloaded from GitHub, as an existing project in Visual Studio.
- Build the application.
- Right-click the project in the project viewer, and select Publish.
Figure 9: Selecting Publish
- On the Connection tab of the Publish Web wizard, enter in the details for your Azure account, and click Next.
Figure 10: Entering account details
- On the Settings tab, click Next.
Figure 11: Clicking Next
- On the Preview tab, click Publish.
Figure 12: Publishing the document
- Upon successful deployment, the default browser automatically opens to the URL of the deployed Web app, and the application that you created is now running in the cloud. You will likely see something like this:
Figure 13: Success
Don’t worry. Your application is most likely running.
Final Steps
We’re almost there…
Update the DoneJS App with the Azure Server URL
Open up your messages.js file, and update this line:
const connection = $.hubConnection('INSERT-AZURE-DESTINATION-URL-HERE');
Test the Application
To really test the functionality of the chat application, you’ll either need two computers, or two separate browsers (for example, Firefox and Chrome). DoneJS comes with a built-in development server. You can start it by typing:
donejs develop
By default, the server listens on port 8080. Open up a browser, and go to http://localhost:8080. Navigate to the chat page. Open up a separate browser, and repeat these steps—opening up the chat page. Post a message to the chat board. You should see the message immediately appear on the browser you did not post the message in.
Figure 14: Your chat form is ready
In Summary
Figure 15: The process
In this article, you learned how to use DoneJS in an ASP.NET MVC Application. As you have seen, ASP.NET MVC and DoneJS work quite well together. Furthermore, they are conceptually similar. Both use an MVVM architecture. This shared architectural approach is one of several reasons why DoneJS is a great choice for MVC applications. If you’d like to learn more about DoneJS, you can read more at http://donejs.com.
About the Author
Joe Crick is a developer at a Bitovi, where they develop OS tools, including DoneJS.
Reference
Images in this section taken from: https://azure.microsoft.com/en-us/documentation/articles/web-sites-dotnet-get-started/.