
Introduction
Ember.js is a powerful JavaScript MVC framework to create complex web applications
that eliminates boilerplate and provides a standard application architecture, it
supports UI Bindings, Composed Views, Web Presentation Layers, and plays nicely with
others. In order to create a real-time interaction web application, I add a SignalR hub
and REST service with ASP.NET MVC.
MVC Basics
The purpose of MVC pattern is to separate concerns of view, model, and controller.
The model is where data is kept, the view describes the presentation of application,
and the controller acts as the link between the model and the view.

This is a Ember.js's MVC implementation, client side part

There is another important concept in Ember.js, that is Router or StateManager,
it works mostly like ASP.NET MVC Route, but in client side. In this article, a router
do his responsibility as connecting the main controller to the main application view.
Ember.js uses Handlebars integrated templates, for easy creating and maintaining
views.
Set up project
At first, we need to create an empty ASP.NET MVC project, I use ASP.NET MVC4 with
Razor view engine, this is the _Layout.cshtml.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
<script src="../../Scripts/libs/jquery-1.7.2.min.js" type="text/javascript"></script>
<script src="../../Scripts/libs/json3.min.js" type="text/javascript"></script>
<script src="../../Scripts/libs/handlebars.min.js" type="text/javascript"></script>
<script src="../../Scripts/libs/ember-1.0.0-pre.2.min.js" type="text/javascript"></script>
</head>
<body>
@RenderBody()
</body>
</html>
And Index.cshtml:
@{
ViewBag.Title = "Ember.n.SignalR";
}
<div id="app"></div>
<script src="../../Scripts/u.js" type="text/javascript"></script>
<script src="../../Scripts/app.js" type="text/javascript"></script>
- u.js contains two methods to random a string and a number in JavaScript
- app.js contains all JavaScript code for the application.
The Model in server side
We create simple customer DTO (in this example we use DTO as model) with basic information and simple result for REST
method.
Customer.cs:
[Serializable]
public class Customer
{
public Guid? Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public bool Active { get; set; }
}
Result.cs
public class Result
{
public int ErrorCode { get; set; }
public object ErrorMessage { get; set; }
public object Data { get; set; }
}
The REST service
We already defined DTO, now we need to create a simple customer REST service transfer
data in JSON format CustomerController.cs host at /customer/. To conform
naming conventions between C# and JavaScript JSON object,
I use Newtonsoft.Json.Serialization with CamelCasePropertyNamesContractResolver.
public class CustomerController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public string Read(Guid? id)
{
}
[AcceptVerbs(HttpVerbs.Delete)]
public string Delete(Guid id)
{
}
[AcceptVerbs(HttpVerbs.Put)]
public string Update(Customer customer)
{
}
[AcceptVerbs(HttpVerbs.Post)]
public string Create(Customer customer)
{
}
}
About the data repository for customers, I create a simple implementation CustomerDS.cs, storing data in the ~/App_Data/Customers.em physical file in binary format.
Now all server side codes needed was done. We focus on Ember application architecture
in next step.
The Ember Application
At a glance, we create an application object
var app = Ember.Application.create({
name: "ManageCustomerApp",
rootElement: '#app',
ApplicationController: Ember.Controller.extend(),
ready: function () {
this.initialize();
},
getView: function (name) {
var template = '';
$.ajax(
{
url: '/Templates/' + name + '.htm',
async: false,
success: function (text) {
template = text;
}
});
return Ember.Handlebars.compile(template);
}
});
The rootElement defines application outlet is the id of main div in Index.cshtml - We extend
ApplicationController to inherit outlet support
ready: event,
this is optional, we define it to put additional code when neededgetView: method,
I define this method to synchronously get a template via Ajax, then use Handlebars
compile it to a view.
The Ember Model
We need to create a data store object at client side to interact with REST service
/customer/
app.Store = Ember.Object.extend({
update: function (customer) {
var message = null;
var xhr = $.ajax(
{
url: '/customer/update/',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(customer),
type: 'PUT',
async: false,
success: function (data) {
message = data;
}
});
if (xhr.status != 200) { message = { errorCode: xhr.status, errorMessage: xhr.statusText };
}
return message;
},
read: function (id) {
var message = null;
var xhr = $.ajax(
{
url: '/customer/read/',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({ 'id': id }),
type: 'POST',
async: false,
success: function (data) {
message = data;
}
});
if (xhr.status != 200) { message = { errorCode: xhr.status, errorMessage: xhr.statusText };
}
return message;
},
remove: function (id) {
var message = null;
var xhr = $.ajax(
{
url: '/customer/delete/',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({ 'id': id }),
type: 'DELETE',
async: false,
success: function (data) {
message = data;
}
});
if (xhr.status != 200) { message = { errorCode: xhr.status, errorMessage: xhr.statusText };
}
return message;
},
create: function (customer) {
var message = null;
var xhr = $.ajax(
{
url: '/customer/create/',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(customer),
type: 'POST',
async: false,
success: function (data) {
message = data;
}
});
if (xhr.status != 200) { message = { errorCode: xhr.status, errorMessage: xhr.statusText };
}
return message;
}
});
In previous part we already defined model in server side and the REST service just
return object in JSON format, in order binding it to views, we need to define it
as Ember model.
app.CustomerModel = Ember.Object.extend({
id: null,
firstName: null,
lastName: null,
email: null,
phone: null,
active: false,
quiet: false,
random: function () {
this.setProperties({ firstName: String.random(), lastName: String.random(),
email: String.random().toLowerCase() + '@gmail.com', phone: '(097) ' + Number.random(3) + '-' + Number.random(4) });
return this;
},
plain: function () {
return this.getProperties("id", "firstName", "lastName", "email", "phone", "active");
}
});
app.ResultModel = Ember.Object.extend({
errorCode: 0,
errorMessage: null
});
random: create a random customer by using two random methods in
u.js.
plain: get back JSON object before send to REST service to improve performance (no need
for additional properties of Ember.Object).
The Ember Controller
app.CustomerController = Ember.Controller.extend({
store: app.Store.create(),
currentResult: null,
currentCustomer: null,
random: function () {
var customer = app.CustomerModel.create().random();
if (this.get('currentCustomer')) {
this.get('currentCustomer')
.set('active', false)
.setProperties(this.get('currentResult').data);
}
this.set('currentCustomer', customer);
},
create: function (customer) {
this.set('currentResult', this.get('store').create(customer.plain()));
if (!this.currentResult.errorCode) {
this.set('currentCustomer', app.CustomerModel.create());
var newCustomer = app.CustomerModel.create(this.get('currentResult').data);
this.get('customers').pushObject(newCustomer);
}
},
remove: function (id) {
var customer = this.get('customers').findProperty('id', id);
if (!customer) return;
this.set('currentResult', this.store.remove(customer.id));
if (!this.currentResult.errorCode) {
if (this.get('currentCustomer').id === id) {
this.set('currentCustomer', app.CustomerModel.create());
}
this.get('customers').removeObject(customer);
}
},
read: function (id) {
this.set('currentResult', this.store.read(id));
if (!this.currentResult.errorCode) {
if (Ember.isArray(this.currentResult.data)) { var array = Ember.ArrayController.create({ content: [] });
this.currentResult.data.forEach(function (item, index) {
array.pushObject(app.CustomerModel.create(item));
});
return array;
}
else { var customer = this.get('customers').findProperty('id', this.currentResult.data.id)
customer && customer.setProperties(this.currentResult.data);
return customer;
}
}
else { return id ? null : Ember.ArrayController.create({ content: [] });
}
},
update: function (customer) {
this.set('currentResult', this.store.update(customer.plain()));
if (!this.currentResult.errorCode) {
}
},
save: function (customer) {
var customer = this.get('currentCustomer');
if (!customer.id) { this.create(customer);
}
else { this.update(customer);
}
},
edit: function (id) {
if (this.get('currentCustomer').id != id) { this.get('currentCustomer')
.setProperties({ active: false })
.setProperties(this.get('currentResult').data);
}
else {
return;
}
var customer = this.read(id);
this.set('currentCustomer', customer.set('active', true));
this.set('currentResult',
app.ResultModel.create({
errorMessage: 'Click Submit to save current customer.',
data: customer.getProperties("firstName", "lastName", "email", "phone") }));
},
customers: Ember.ArrayController.create({ content: [] }),
initialize: function () {
var array = this.read();
this.set('customers', array);
this.random();
this.set('currentResult', app.ResultModel.create({ errorMessage: 'Click Submit to create new customer.' }));
}
});
app.customerController = app.CustomerController.create();
Our customerController controls logic when create, update or delete a customer, it stores
result of each action in currentResult property and stores
editing/creating
customer in currentCustomer property. The customers array model is our customers
storage, it always be synchronized to server via app.Store and it is used to bind
to our views. We call this.read() to retrieve all customers from server when our
controller is initialized. Do you know why we must use get or set method here? That
is JavaScript way to deal with handling property changed. Actually in C#, the get/set
properties compiled to get/set methods in CLR.
The Ember View
We saw the connecting between controller and model, now we dig into views. The view
displays/binds values from / to model via controller anchor. I define view templates
in separated htm files, load it via AJAX and use Ember.Handlebars to compile the
response text, it is easier to modify than putting it in script tag, we could use any
html editor such as MS Visual Studio, Notepad++...to edit view templates. Let see
create_edit_customer template, it is defined at create_edit_customer.htm.
<div id="mainForm">
<div>
<label for="firstName">
First name</label><br />
{{view Ember.TextField valueBinding="content.currentCustomer.firstName"}}
<a href="#" {{action "random"
on="click" target="this" }}>Random new customer</a>
</div>
<div>
<label for="lastName">
Last name</label><br />
{{view Ember.TextField valueBinding="content.currentCustomer.lastName"}}
</div>
<div>
<label for="email">
Email</label><br />
{{view Ember.TextField valueBinding="content.currentCustomer.email"}}
</div>
<div>
<label for="phone">
Phone</label><br />
{{view Ember.TextField valueBinding="content.currentCustomer.phone"}}
</div>
<p>
<button id="submit" {{action "save"
on="click" target="this" }} >Submit</button>
</p>
</div>
The CreateEditCustomerView
app.CreateEditCustomerView = Ember.View.extend({
template: app.getView('create_edit_customer'),
contentBinding: 'controller.namespace.customerController',
name: "create_edit_customer",
save: function (event) {
this.get('content').save();
},
random: function () {
this.get('content').random();
}
});
As you see in the template, there are some Handlebars syntax, The first name text
box is defined as {{view Ember.TextField valueBinding="content.currentCustomer.firstName"}}.
Root binding object - content is our customer controller, bound via binding syntax
of Ember "contentBinding". At this stage, you may ask "How it was bound?" That is
Ember magic syntax, we use content + "Binding" to tell Ember know that
controller.namespace.customerController
is bound to content property of CreateEditCustomerView. There are 2 actions in this
view, first is random a new customer and second is save a customer, in the template,
you easily found two lines {{action "random" on="click" target="this" }} and {{action
"save" on="click" target="this" }}. Both of those actions are click event.
To display a list of customers, we need a template
<div id="customerListHeader">
List of customers
</div>
<div id="customerListContent">
<table>
{{#unless view.customers.length}}
<tr>
<th> First name </th>
<th> Last name </th>
<th> Email </th>
<th> Phone </th>
<th> #Action </th>
</tr>
<tr>
<td colspan="4" align="center">
There is no customer yet
</td>
</tr>
{{else}}
<tr>
<th> First name </th>
<th> Last name </th>
<th> Email </th>
<th> Phone </th>
<th> #Action </th>
</tr>
{{#each view.customers}}
<tr {{bindAttr class="this.active:active:normal"}} {{bindAttr id="this.id"}}>
<td>
{{this.firstName}}
</td>
<td>
{{this.lastName}}
</td>
<td>
{{this.email}}
</td>
<td>
{{this.phone}}
</td>
<td align="center">
<a href="#" class="edit" {{action "edit"
on="click" target="this" }} {{bindAttr value="this.id"}}>Edit</a>
|
<a href="#" class="delete" {{action "remove"
on="click" target="this" }} {{bindAttr value="this.id"}}>Delete</a>
</td>
</tr>
{{/each}}
{{/unless}}
</table>
</div>
The CustomerListView
app.CustomerListView = Ember.View.extend({
contentBinding: 'controller.namespace.customerController',
customersBinding: 'controller.namespace.customerController.customers',
template: app.getView('customer_list'),
name: "customer_list",
edit: function (event) {
var id = $(event.target).attr('value');
var controller = this.get('content').edit(id);
},
remove: function (event) {
var id = $(event.target).attr('value');
var controller = this.get('content');
this.animateItem(id, function () {
controller.remove(id);
}, controller);
},
animateItem: function (id, callback, target) {
$('#' + id).animate({ opacity: 0 }, 200, "linear", function () {
$(this).animate({ opacity: 1 }, 200);
if (typeof callback == 'function') {
target = target | null;
callback.call(target);
}
});
}
});
Each action create/edit/delete customer, we need to display message result, in
MessageView, this is message template.
{{#unless content.currentResult.errorCode}}
<div id='message'>
{{content.currentResult.errorMessage}}
</div>
{{else}}
<div id='error'>
{{content.currentResult.errorMessage}}
</div>
{{/unless}}
This view takes no action
app.MessageView = Ember.View.extend({
template: app.getView('message'),
name: "message"
});
CreateEditCustomerView, CustomerListView, and
MessageView are composed to ApplicationView, template defined in main.htm.
<h3>{{view.Title}}</h3>
<div>
<div id="message">
{{view App.MessageView}}
</div>
<div id="createEditCustomer">
{{view App.CreateEditCustomerView}}
</div>
<div id="customerList">
{{view App.CustomerListView}}
</div>
</div>
<div id="footer">
</div>
app.ApplicationView = Ember.View.extend({
Title: "Example of Ember.js application",
template: app.getView('main'),
name: "ApplicationView"
});
The Ember Route
We already created application, controllers, views, models but there is one more thing to make our application work, a route.
Route will connect application controller to application view, initialize application controller and in sequential order,
application view is bound/rendered to screen. Now user can see, touch views and get right response from controllers.
app.Router = Ember.Router.extend({
root: Ember.Route.extend({
defaults: Ember.Route.transitionTo('index'),
index: Ember.Route.extend({
route: '/',
connectOutlets: function (router) {
var controller = router.get('applicationController');
var context = app.customerController;
context.initialize();
controller.connectOutlet('application', context); }
})
})
});
Embedding SignalR
With Visual Studio, we can add SignalR from nuget.
Install-Package Microsoft.AspNet.SignalR -pre
RegisterHubs.cs was automatically added in the folder App_Start.
using System.Web;
using System.Web.Routing;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hosting.AspNet;
[assembly: PreApplicationStartMethod(typeof(Ember.n.SignalR.RegisterHubs), "Start")]
namespace Ember.n.SignalR
{
public static class RegisterHubs
{
public static void Start()
{
RouteTable.Routes.MapHubs();
}
}
}
We use Hub instead of PersistentConnection to easily create a communication from server with all clients
namespace Ember.n.SignalR.Hubs
{
using Ember.n.SignalR.DTOs;
using Microsoft.AspNet.SignalR.Hubs;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Microsoft.AspNet.SignalR;
public class CustomerHub : Hub
{
public static IHubContext Instance
{
get{
return GlobalHost.ConnectionManager.GetHubContext<customerhub>();
}
}
JsonSerializerSettings _settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
public void Add(Customer customer)
{
Clients.All.add(JsonConvert.SerializeObject(customer, _settings));
}
public void Update(Customer customer)
{
Clients.All.update(JsonConvert.SerializeObject(customer, _settings));
}
public void Remove(Customer customer)
{
Clients.All.remove(JsonConvert.SerializeObject(customer, _settings));
}
}
}
In order clients could communicate with server through SignalR pipeline, we need to include
the hub script and implement customerHub in client side
<script src="/signalr/hubs " type="text/javascript"></script>
(function (app) {
var hub = $.connection.customerHub;
function findCustomer(id) {
var c = app.customerController.get('customers').findProperty('id', id);
return c;
}
hub.client.add = function (message) {
var customer = JSON.parse(message);
var c = findCustomer(customer.id);
!c && app.customerController.get('customers').pushObject(app.CustomerModel.create(customer));
}
hub.client.update = function (message) {
var customer = JSON.parse(message);
var c = findCustomer(customer.id);
c && c.set('quiet', true) && c.setProperties(customer) && c.set('quiet', false);
}
hub.client.remove = function (message) {
var customer = JSON.parse(message);
var c = findCustomer(customer.id);
if (c) {
if (c.id === app.customerController.get('currentCustomer').id) {
app.customerController.set('currentCustomer', null);
app.customerController.random();
}
app.customerController.get('customers').removeObject(c);
}
}
$.connection.hub.start();
app.hub = hub;
})(window.App);
We simply manipulate models via customerController, Ember.js binding will take
care the rest and ensure UI
reflects the modified models right.
The Ember Observers
In previous section, after inject SignalR, each client already get real-time updating what's changed on the server . For more interesting, we use Ember observable to observe property changed when user typing and notify server via customerHub, after that server will broadcast the change to all clients. Let's add above lines of code to CustomerModel
propertyChanged: function () {
try {
if (!this.get('quiet') && this.get('id')) {
app.hub.server.update(this.plain());
}
}
catch (e) {
}
} .observes('firstName', 'lastName', 'email', 'phone', 'active'), We just notify server when firstName, lastName, email, phone or active property of existing customer changed. The quiet checking prevents loop notifying from client to server and server to client, when an object is getting changing from server, it will not notify server again that changing.
Enjoy our work
Now let build and run the web application, random a new customer and click submit. As you see on the screen, the UI changing works well without any DOM manipulation. Click edit link to edit and modify anything on first name, last name...text box, the edited row below reflects what we have just typed immediately.
To see how Ember.js interactw with SignalR, do copy the URL on the browser and paste to another browser window, arrange both windows near together, then create / edit / delete customer on the one, the other also got changed customer like a mirror. That is really cool right.
Conclusion
Controllers, views, models and routes in Ember.js work very similar with ASP.NET MVC, so who already worked with ASP.NET MVC
can easily understand and get benefit from Ember.js. This article just is a introduction about Ember.js, to make a complex
and real-time web application, we must dig into Ember objects more and more. SignalR in this example is a bridge, it makes sure
models in client side synchronized with server.
References