Using Gulp in an ASP.NET 4.5 application

The use of a task runner in an ASP.NET application provide a series of advantages about concatenating and minifying CSS and Javascript application files and offers a real and valid alternative to the ASP.NET optimization framework.

One of the most recent task runner is Gulp, that using code over configuration for more readable syntax and node streams for better performances at build time.

Web application

If the referenced project is an ASP.NET 4.5 application, the first steps are adding two files in the project; the first is package.json:

npm

And the second is gulpfile.js:

gulp

Node.js installation

The primary step is to install Node.js, the server-side javascript runtime which will be used by Gulp.

Node.js installation is very simple and offer a GUI to do that; once installed, the next step is the Gulp installation.

Gulp

Npm is the package manager of Node.js and it can be used to install Gulp; a package could be installed locally (in the application folder under node_modules) or globally (the folder specified in the NODE_PATH environment variable); with this second option the package could be used later for other applications.

Therefore, Gulp should be installed globally; opening a command prompt you can perform the installation with the option g (global):

npm install gulp -g

If the project is shared with other developers, is recommended to add gulp as a development dependency of the project; in order to do that, run this command:

npm install gulp –save-dev

Once installed the package.json file will be changed with the new reference of Gulp.

{
"version": "1.0.0",
"name": "ANGULAR-SIGNALR",
"devDependencies": {
"gulp": "^3.9.1"
}
}

Inject

With Gulp inject plugin is possible to manage the js/css references in the application main page.

For example, you can use this plugin to inject javascript files of an angular application in the main page.

In order to do that, you need to first install the plugin:

npm install –save-dev gulp-inject

Than you need to include the plugin in the gulpfile.js and write the task:

'use strict';

var gulp = require('gulp'),
inject = require('gulp-inject')

var config = {
js: 'App/**/*.js',
dist: './dist',
indexDev: 'index.dev.html'
}

//Inject
gulp.task('inject', function (cb) {
var target = gulp.src(config.indexDev);
var sources = gulp.src([config.js]);

return target.pipe(inject(sources))
.pipe(gulp.dest('.'));
});
// End inject

And in the index.dev.html body add the injection placeholders:

<body ng-app="angularSignalR">
<!-- inject:js -->
<script src="/App/App.js"></script>
  <script src="/App/Commons.js"></script>
<script src="/App/Controllers/ModalsController.js"></script>
  <script src="/App/Controllers/OrdersController.js"></script>
<script src="/App/Directives/DecimalNumberValidationDirective.js"></script>
  <script src="/App/Services/NotificationsService.js"></script>
<script src="/App/Services/OrdersService.js"></script>
  <script src="/App/Models/Order.js"></script>
<!-- endinject -->
</body>

Once inject task will be executed, it’ll retrive all javascript file from config.js path and inject them into the html page, between the corrispondent placeholders.

Minification

This is the process to minify vendor css, javascript vendor libraries and javascript application files.

You need to install some plugins:

npm install gulp-clean-css –save-dev

npm install gulp-useref –save-dev

npm install gulp-if –save-dev

npm install gulp-rename –save-dev

Add the new task to the gulpfile.js:

//Useref
gulp.task('useref', ['inject'], function () {
var source = gulp.src('index.dev.html');

return source
.pipe(useref())
.pipe(gulpif('*.css', cleanCss()))
.pipe(gulpif('*.js', uglify()))
.pipe(gulp.dest(config.dist));
});
//End Useref

Add the useref build placeholders in the index.dev.html body:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Angular SignalR Application</title>

<!-- build:css Content/lib.min.css -->
	<link href="/Content/bootstrap.css" rel="stylesheet" />
	<link href="/Content/ui-bootstrap-csp.css" rel="stylesheet" />
	<link href="/Content/loading-bar.css" rel="stylesheet" />
	<link href="/Content/toaster.css" rel="stylesheet" />
<!-- endbuild -->

<!-- build:js Scripts/lib.min.js -->
<script src="/Scripts/jquery-2.2.1.js"></script>
  <script src="/Scripts/jquery.signalR-2.2.0.js"></script>
<script src="/Scripts/bootstrap.js"></script>
  <script src="/Scripts/q.js"></script>
<script src="/Scripts/angular.js"></script>
  <script src="/Scripts/angular-resource.js"></script>
<script src="/Scripts/angular-animate.js"></script>
  <script src="/Scripts/angular-ui-router.js"></script>
<script src="/Scripts/angular-ui/ui-bootstrap.js"></script>
  <script src="/Scripts/angular-ui/ui-bootstrap-tpls.js"></script>
<script src="/Scripts/loading-bar.js"></script>
  <script src="/Scripts/toaster.js"></script>
<!-- endbuild -->
</head>
<body ng-app="angularSignalR">
<toaster-container></toaster-container>

<section class="container">
<div ui-view=""></div>
</section>

<!-- build:js Scripts/app.min.js -->
<!-- inject:js -->
<script src="/App/App.js"></script>
  <script src="/App/Commons.js"></script>
<script src="/App/Controllers/ModalsController.js"></script>
  <script src="/App/Controllers/OrdersController.js"></script>
<script src="/App/Directives/DecimalNumberValidationDirective.js"></script>
  <script src="/App/Services/NotificationsService.js"></script>
<script src="/App/Services/OrdersService.js"></script>
  <script src="/App/Models/Order.js"></script>
<!-- endinject -->
<!-- endbuild -->
</body>
</html>

As you can see, useref is a very powerful plugin; combining that with clean-css and uglify (plugin for minification) permit to generate minified files with few lines of code.

The final index.html page will be generated in dist subdirectory:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Angular SignalR Application</title>

	<link rel="stylesheet" href="Content/lib.min.css">

<script src="Scripts/lib.min.js"></script>
</head>
<body ng-app="angularSignalR">
  <toaster-container></toaster-container>

  <section class="container">

<div ui-view=""></div>

  </section>

  <script src="Scripts/app.min.js"></script>
</body>
</html>

The useref palceholders has been replaced with minified files.

Move result files

The index.html page generated in the dist directory have to be moved in the root directory and the css/js file in the Content/Scripts folders.

You need to install two plugins:

npm install gulp-rename –save-dev

npm install gulp del –save-dev

In order to moving the files, implementation of different tasks is needed:

//Move/Rename
gulp.task('renameIndexDist', ['inject'], function (cb) {
return gulp.src(config.dist + '/' + config.indexDev)
.pipe(rename({
basename: 'index'
}))
.pipe(gulp.dest(config.dist));
});

gulp.task('removeIndexDist', ['renameIndexDist'], function (cb) {
return del([config.dist + '/' + config.indexDev], { force: true });
});

gulp.task('copyDist', ['removeIndexDist'], function () {
return gulp.src(config.dist + '/**/*.*')
.pipe(gulp.dest('.'));
});

gulp.task('moveDist', ['copyDist'], function () {
return del([config.dist], { force: true });
});

gulp.task('renameIndexDev', ['inject'], function () {
return gulp.src('./' + config.indexDev)
.pipe(rename({
basename: 'index'
}))
.pipe(gulp.dest('.'));
});
//End Move/Rename

The task is composed of different subtasks; the first is renameIndexDist, that rename dist/index.dev.html file to dist/index.html; removeIndexDist remove the old index file, than copyDist move the files and modeDist delete dist directory as final step.

RenameIndexDev is a task that will be called in debug mode, when dist directory is not generated and index.dev.html is not minified.

Build configuration

The minification of javascript files is essentially in a release configuration, but not in debug mode, where unminify files are more useful for debugging.

To do that, it’s necessary to distiguish the configuration in the gulpfile.js and execute the correct tasks.

By open the project settings in Visual Studio, it’s possible to define a post-build event command:

build

In the gulpfile.js, add the environment parameter check:

gulp.task('scripts', function () {
if (process.env.NODE_ENV == 'Debug') {
gulp.start('inject', 'renameIndexDev');
}
else {
gulp.start('inject', 'useref', 'moveDist');
}
});

You can find the project here.

Using Gulp in an ASP.NET 4.5 application

Scaling out a SignalR application

There are several ways to scale a SignalR web application, such as Redis, SQL Server and Azure Service Bus.
To configure an Azure Service Bus on an existing application, two configurations are required in the Azure Management Portal, an Azure Cloud Service (if not exist) and an Azure Service Bus.

Cloud Service

The Cloud Service configuration require a DNS name, the reference to the subscription, the resource group and the location.

cloud-service_small

Later this service will be used to deploy the existing application.

Azure Service Bus

There are different types of Azure Service Bus and the Topic is the tipology that support a publish/subscribe messaging communication model.

service-bus_small

Once created it should be retrieve the connection string, that will be used in the web.config file of the application.

service-bus2_small

Web.config file

In the web.config file you need to bring back the topic connection string.

&lt;connectionStrings&gt;
&lt;add name=&quot;ServiceBus&quot; connectionString=&quot;Endpoint=sb://angular-signalr.servicebus.windows.net/;SharedAccessKeyName=...;SharedAccessKey=...&quot;/&gt;
&lt;/connectionStrings&gt;

Application update

In this example the application is a AngularJS application that uses SignalR.
In order to enable the application to comunicate with Service Bus, the installation of the NuGet package Microsoft.AspNet.SignalR.ServiceBus is required.
Once installed, in the Startup.cs file we need to pass the ServiceBus connection string to the SignalR GlobalHost static class.

public class Startup
{
public void Configuration(IAppBuilder app)
{
string connectionString = ConfigurationManager.ConnectionStrings[&quot;ServiceBus&quot;].ConnectionString;
GlobalHost.DependencyResolver.UseServiceBus(connectionString, &quot;NotificationsHub&quot;);
app.MapSignalR();
}
}

Publishing

Right click on the web application project and convert it to an Azure Cloud Service Project.

azure-conversion_small

A new Cloud Project will be added to the solution, than right click on the new project and Publish.

azure-publish_small

The publish process require a storage account, which can be selected from an existing or created a new one.

azure-publish2

In the next step, you need to select the Cloud Service created above.

azure-publish3

Once the publication process is terminated, the application is available from the cloud service address and the role instances communicate through the Service Bus.
A message dashboard is available by accessing the topic Service Bus created above.

service-bus-result_small

You can find the project here.

Scaling out a SignalR application

ASP.NET SignalR and TypeScript in an AngularJS SPA

SignalR is a javascript library for helping developers to add real-time functionalities to web applications.

This example is a Angular SPA developed using Typescript,  to take advantage of Object Oriented features in the client-side code development.

This application has a simple orders list, that every user can manage; every time a user make a change to an order or create a new one, every user window will be automatically refreshed with the new changes.

The application using SQL database, Entity Framework Code First, ASP.NET Web API, Angular 1.5, TypeScript and Bootstrap as front-end framework.

The first steps are server side implementations:

  • Entity Framework entity
  • Web API for CRUD operations
  • SignalR Hub

The EF Order table entity:

[Table("Orders")]
public class Order
{
public Guid Id { get; set; }
public string Article { get; set; }
public decimal Amount { get; set; }
public string Customer { get; set; }
public DateTime CreationDate { get; set; }
public string Notes { get; set; }
}

After creating the context class and performed the migrations commands, you can proceed with the implementation of the Web API Controller; ASP.NET scaffolding is very useful for a standard implementation:

public class OrdersController : ApiController
{
private Context db = new Context();

// GET: api/Orders
public IQueryable<Order> GetOrders()
{
return db.Orders.OrderByDescending(o => o.CreationDate);
}

// GET: api/Orders/5
[ResponseType(typeof(Order))]
public async Task<IHttpActionResult> GetOrder(Guid id)
{
Order order = await db.Orders.FindAsync(id);
if (order == null)
{
return NotFound();
}

return Ok(order);
}

// PUT: api/Orders/5
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutOrder(Guid id, Order order)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (id != order.Id)
{
return BadRequest();
}

db.Entry(order).State = EntityState.Modified;

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!OrderExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return StatusCode(HttpStatusCode.NoContent);
}

// POST: api/Orders
[ResponseType(typeof(Order))]
public async Task<IHttpActionResult> PostOrder(Order order)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

order.Id = Guid.NewGuid();
db.Orders.Add(order);

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (OrderExists(order.Id))
{
return Conflict();
}
else
{
throw;
}
}

return CreatedAtRoute("DefaultApi", new { id = order.Id }, order);
}

// DELETE: api/Orders/5
[ResponseType(typeof(Order))]
public async Task<IHttpActionResult> DeleteOrder(Guid id)
{
Order order = await db.Orders.FindAsync(id);
if (order == null)
{
return NotFound();
}

db.Orders.Remove(order);
await db.SaveChangesAsync();

return Ok(order);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}

private bool OrderExists(Guid id)
{
return db.Orders.Count(e => e.Id == id) > 0;
}
}

Then, the implementation relating the SignalR Hub; you have to add the Startup.cs file if not present and register the SignalR middleware:

using Owin;
using Microsoft.Owin;

[assembly: OwinStartup(typeof(AngularSignalR.Startup))]
namespace AngularSignalR
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}

Then, the SignalR Hub:

public class NotificationsHub : Hub
{
public async Task OrderChanges()
{
await Clients.Others.NotifyOrderChanges();
}
}

The OrderChanges method is called from every client making a change to an order; NotifyOrderChanges is the event fired to any other client connected.

Now it’s the time to starting with the AngularJS application; the key points are:

  • The factory that interact with Web API service
  • The factory that manage SignalR notifications
  • The main controller

One useful service in AngularJS framework is $resource, a factory that allows you to interact with RESTful service easily.

The dependency of the ngResource module should be declarated in the main app module, and then is possibile to use $resource service; this is the implementation of the OrdersService factory:

module AngularSignalRApp.Services {

import ngr = ng.resource;

export interface IOrdersResourceClass extends ngr.IResourceClass<ngr.IResource<AngularSignalRApp.Models.IOrder>> {
create(order: AngularSignalRApp.Models.IOrder);
}

export class OrdersService {
private resource: IOrdersResourceClass;

constructor($resource: ngr.IResourceService) {
this.resource = <IOrdersResourceClass> $resource('/api/orders/:id', { id: '@Id' }, {
get: { method: "GET" },
create: { method: "POST" },
save: { method: "PUT" },
query: { method: "GET", isArray: true },
delete: { method: "DELETE" }
});
}

public create(order: AngularSignalRApp.Models.IOrder) {
return this.resource.create(order);
}

public save(order: AngularSignalRApp.Models.IOrder) {
if (order.Id == GuidEmpty) {
return this.resource.create(order);
}
else {
return this.resource.save(order);
}
}

public delete(order: AngularSignalRApp.Models.IOrder) {
return this.resource.remove(order);
}

public getAll() {
return this.resource.query();
}

static factory() {
return (r) => new OrdersService(r);
}
}

AngularSignalR.module.factory('OrdersService', ['$resource', OrdersService.factory()]);
}

Another factory is NotificationService, that expose the methods to interact with SignalR Hub:

module AngularSignalRApp.Services {

export class NotificationsService {

private connection: HubConnection;
private proxy: HubProxy;
private callback;

constructor() {
this.connection = $.hubConnection();
this.proxy = this.connection.createHubProxy('NotificationsHub');

this.proxy.on('NotifyOrderChanges', () => {
this.callback();
});

this.connection.start();
}

public NotifyOrderChanges() {
this.proxy.invoke('OrderChanges');
}

public OnOrderChanges(callback) {
if (callback) {
this.callback = callback;
}
}

static factory() {
return () => new NotificationsService();
}
}

AngularSignalR.module.factory('NotificationsService', [NotificationsService.factory()]);
}

The final step is the main controller, that uses OrdersService to manage CRUD operations on the orders and NotificationsService to tell with the SignalR Hub:

module AngularSignalRApp.Controllers {

import ngr = ng.resource;

export class OrdersController {
orders: ngr.IResourceArray<ngr.IResource<AngularSignalRApp.Models.IOrder>>;
order: AngularSignalRApp.Models.IOrder;
title: string;
toaster: ngtoaster.IToasterService;

private filter: ng.IFilterService;
private modalService: ng.ui.bootstrap.IModalService;
private ordersService: AngularSignalRApp.Services.OrdersService;
private notificationsService: AngularSignalRApp.Services.NotificationsService;

public static $inject = ['$filter', '$uibModal', 'toaster', 'OrdersService', 'NotificationsService'];

constructor(filter: ng.IFilterService, modalService: ng.ui.bootstrap.IModalService, toaster: ngtoaster.IToasterService,
ordersService: AngularSignalRApp.Services.OrdersService, notificationsService: AngularSignalRApp.Services.NotificationsService) {
this.filter = filter;
this.modalService = modalService;
this.ordersService = ordersService;
this.notificationsService = notificationsService;

this.notificationsService.OnOrderChanges(() => {
this.Load();
});

this.toaster = toaster;
this.title = "Orders";

this.Load();
}

public New() {
this.order = {
Id: GuidEmpty,
Article: "",
Amount: 0,
CreationDate: new Date(),
Customer: "",
Notes: ""
};
}

public Edit(order) {
this.order = order;
}

public Delete() {

var vm = this;
var modalInstance = vm.modalService.open({
animation: true,
templateUrl: "/App/Views/Shared/ConfirmDeleteModal.html",
controller: "ModalsController as vm",
size: "modal-sm"
});

modalInstance.result.then(function () {
vm.ordersService.delete(vm.order).$promise.then((data) => {
var orderToDelete = vm.filter('filter')(vm.orders, { Id: vm.order.Id })[0];
var index = vm.orders.indexOf(orderToDelete);
vm.orders.splice(index, 1);
vm.order = null;

vm.notificationsService.NotifyOrderChanges();
vm.toaster.success("Order deleted successfully.");
}, (error) => {
vm.toaster.error("Error deleting order.", error.data.message);
});
});
}

public Save() {
this.ordersService.save(this.order).$promise.then((data) => {
if (this.order.Id == GuidEmpty) {
this.order = data;
this.orders.push(data);
}

this.notificationsService.NotifyOrderChanges();
this.toaster.success("Order saved successfully.");

}, (error) => {
this.toaster.error("Error saving order", error.data.message);
});
}

public Close() {
this.order = null;
}

private Load() {
this.ordersService.getAll().$promise.then((data) => {
this.orders = data;
this.toaster.success("Orders loaded successfully.");
return;
}, (error) => {
this.toaster.error("Error loading orders", error.data.message);
});
}
}

AngularSignalR.module.controller('OrdersController', OrdersController);
}

The comunication with the SignalR Hub occur in the New, Save and Delete methods,
and when the NotifyOrderChanges event is fired.

You can find the project here.

 

 

ASP.NET SignalR and TypeScript in an AngularJS SPA