As far as backend technologies are concerned, I really like ASP.NET and Web API. It is really easy to set up, and basically anything can talk HTTP. For front end development, I have grown to really really enjoy AngularJS. As far as an IDE is concerned, I really like Visual Studio. The problem that I’ve found is that these technologies don’t always play nice with one another. I still want to use MVC and Web API because I am comfortable and familiar with them, and they provide a really easy way to integrate server-side security (something I know is necessary but I don’t really like to think about). I couldn’t really find a definitive guide for how to integrate all of these technologies together, so I decided to make this one.
The entire project can be grabbed here from GitHub.
I’m using Visual Studio 2015 for this guide. Visual Studio 2013 can work, too, but you will need to install the Task Runner Explorer Visual Studio extension. This extension will allow Visual Studio to run all kinds of Node.js stuff, including NPM and gulp.
First, create a new web application project. There are a lot of different templates you can use, but I opt for an empty one so I don’t have to run around cleaning up all kinds of things that I don’t really care about. I still had it include MVC and Web API capability so that I wouldn’t have to manually install NuGet packages.
Setting up the folder structure
There are a lot of ways to structure your AngularJS application. John Papa has an excellent guide on GitHub on this. The point is, you can set up your folders however you like, but this is how I like to do it:
It’s not completely necessary to create all of the folders. Some of them will only have stuff put into them by gulp tasks, so you won’t be accessing them directly. Still, sometimes it is nice to be able to see the structure you’re working with as you’re developing.
NPM
I decided to use NPM for fetching AngularJS and other javascript libraries. NPM runs on top of Node.js, which will also be used to run Gulp, a streaming build service, so it makes sense to use NPM since it too runs on Node. After installing Node.JS, all you need to do is open a command prompt to the root directory of your repository and initialize NPM.
[code]
C:Reposaspnetmvc> npm init
[/code]
NPM Init will walk you through a setup process creating a package.json file. This file identifies which NPM packages should be installed. Add the package.json file to the root of your solution so that it will be visible in your solution explorer… this just makes it easier to find it later.
Once NPM is installed, we need to grab AngularJS, ng-animate, and ui-router. I use ui-router because it gives a lot more flexibility when determining the routes your website uses, and so it just makes sense to start with it. To add these packages, just open your package.json file. Add the following to the bottom (right before the last curly brace):
[code]
"dependencies": {
"angular": "^1.4.3",
"angular-animate": "^1.4.3",
"angular-ui-router": "^0.2.15",
}
[/code]
If you type these in (instead of copy/pasting), you will noticed that the version numbers are automatically populated when you type the colon. Task Runner Explorer can look up NPM package versions for you, and as soon as you save your package.json file, the npm init command will be executed, downloading the packages in your dependencies list into a node_modules folder.
Gulp
Gulp is a streaming build service that you can use to do all of your javascript/css processing. You can use it to minify, concatenate, and move all of your javascript files to one location. After all, having a single javascript file and a single css file is more efficient for browsers
To install Gulp and have it run inside Task Runner Explorer, you have to globally install it. To do this, open up a command prompt at your solution directory and type the following command:
[code]
C:Reposaspnetmvc> npm install gulp –g
[/code]
To add it to your package.json file, you can either edit the file manually (as before), or type this command in the command prompt:
[code]
C:Reposaspnetmvc> npm install gulp –save
[/code]
However, gulp by itself doesn’t really do much. You need to add some more gulp-specific NPM modules in order for gulp to be useful. Here are a list of the ones I use regularly, and what they do.
[code]
"gulp-if": "^1.2.5",
"gulp-concat": "^2.6.0",
"gulp-uglify": "^1.2.0",
"gulp-rename": "^1.2.2",
"gulp-watch": "^4.3.4",
"run-sequence": "^1.1.2"
[/code]
Module | Description |
---|---|
gulp-if | Provides conditionals for gulp. Useful if you want to have something like a “development” configuration and a “production” configuration. |
gulp-concat | Used to combine multiple files into a single file. |
gulp-uglify | Used to minify (uglify) javascript files. This makes them smaller, which is a good thing! |
gulp-rename | Renames the output of a gulp operation |
gulp-watch | A gulp task that will perpetually watch a directory for file changes. This allows the task runner to run a specific task when a file in a directory changes. That way, for instance, when you save a javascript file it can be automatically concatenated and minified and put where it belongs. |
run-sequence | Allows for a list of gulp tasks to be run in sequence. |
gulpfile.js
Create a file called gulpfile.js in the root of your project. This file will be where you define gulp tasks. Task Runner Explorer can read this file and give you a nice GUI for running these tasks. Inside of the gulpfile.js file, you define your gulp tasks. Here is my default one just to get you started.
[code language=”javascript”]
// Include gulp
var gulp = require(‘gulp’);
// Include plugins
var gulpif = require(‘gulp-if’);
var concat = require(‘gulp-concat’);
var uglify = require(‘gulp-uglify’);
var rename = require(‘gulp-rename’);
var watch = require(‘gulp-watch’);
var runSequence = require(‘run-sequence’);
// Switching variables. Set these on the build tasks to enable/disable certain actions
var argv = {
debug: false,
development: false,
production: false
};
// For Builds
gulp.task(‘Debug’, function () {
argv.debug = true;
runSequence(‘jsLib’, ‘jsCombine-home’);
});
gulp.task(‘Development’, function () {
argv.development = true;
runSequence(‘jsLib’, ‘jsCombine-home’);
});
gulp.task(‘Production’, function () {
argv.production = true;
runSequence(‘jsLib’, ‘jsCombine-home’);
});
// Watch for changes and call gulp tasks accordingly
gulp.task(‘watch’, function () {
gulp.watch([‘app/**/*.js’], [‘jsCombine-home’]);
});
gulp.task(‘jsLib’, function () {
return gulp.src([
‘../node_modules/angular/angular.js’,
‘../node_modules/angular-animate/angular-animate.js’,
‘../node_modules/angular-ui-router/release/angular-ui-router.js’,
])
.pipe(gulp.dest(‘app/assets/lib’));
});
var combine = function (name, additionalScriptArray) {
var sources = [
// Angular
‘app/assets/lib/angular.js’,
‘app/assets/lib/angular-ui-router.js’,
‘app/assets/lib/angular-animate.js’,
// Other Third Party Libs
// First Party Libs
(argv.production ? ‘app/common/config/prod.js’ : ‘app/common/config/dev.js’)
];
console.log("Additional scripts:");
console.log(additionalScriptArray);
if (additionalScriptArray && additionalScriptArray.length) {
sources = sources.concat(additionalScriptArray);
}
console.log(sources);
return gulp.src(sources)
.pipe(concat(name + ‘.js’))
.pipe(gulpif(argv.production, uglify()))
.pipe(gulp.dest(‘app/assets/scripts/’ + name));
}
gulp.task(‘jsCombine-home’, function () {
return combine(‘home’, [
‘app/home/homeController.js’,
‘app/home/*.js!(homeController.js)’
]);
});
[/code]
Now, in Visual Studio, if you open Task Runner Explorer you should be able to see your tasks.
The jsCombine-home task will combine all of the various script files into one called “home.js,” put into the app/assets/scripts/home folder.
You will also notice that I have a couple of different tasks specifically for debug, development, and production. In debug and development, the javascript files are concatenated together, but they are not minified. It is somewhat annoying (for debugging purposes) to have all of your files jammed into one javascript file, but if they were minified it would be downright impossible. By doing things this way, you need only invoke the production gulp task on your build server (or before you’re ready to publish) in order to get the minified versions.
Binding a Gulp Task
There are certain tasks (like watch, in particular) that you would want to start up as soon as you open your solution. Other tasks might be useful to be run after you compile. For that, Task Runner Explorer can bind tasks to certain IDE events. To do this, just right click on the task that you want and select which binding you want.
Combining AngularJS and an MVC Controller
We will use an MVC controller to “host” the AngularJS application. This will give us the ability to hook into ASP.NET security to restrict access or whatever else you might need to do.
Start by creating a controller and an empty view. This will also generate some CSS files, a _Layout.cshtml file, put some Javascript files (like jQuery) into the Scripts folder, and some other stuff. You can clean this up as you like. I removed the script files since we are using AngularJS, but for now I pretty much left everything else alone.
I modified the _Layout.cshtml file to add a @RenderSection(“Scripts”) Scripts section. This will make sure that when we add scripts, they go at the bottom of the HTML file (recommended practice by several people who are smarter than me).
In my cshtml view, I just add a script tag to what will eventually be my concatenated javascript file. This single file will include AngularJS, UI-Router, and my own script files (homeController.js and homeRouter.js). Then I add a simple div that identifies the angular app and the ui-view.
[code language=”html”]
@section Scripts {
<script src="~/app/assets/scripts/home/home.js"></script>
}
<div ng-app="app">
<div ui-view>
</div>
</div>
[/code]
Now we can make an AngularJS controller, router, and simple view. If you want a really simple way to add AngularJS components, I highly recommend Side Waffle. It is a visual studio extension made by John Papa, Mads Kristensen, and other really smart people. Once it is installed, a bunch of AngularJS templates will show up. These templates scaffold out different AngularJS components with all of the recommended bits and pieces.
At any rate, here is my homeController.js:
[code language=”javascript”]
(function () {
‘use strict’;
angular
.module(‘app’, [‘ui.router’])
.controller(‘homeController’, homeController);
homeController.$inject = [];
function homeController() {
/* jshint validthis:true */
var vm = this;
vm.title = ‘Home’;
activate();
function activate() { }
}
})();
[/code]
And here is my homeRouter.js:
[code language=”javascript”]
(function () {
var app = angular.module(‘app’);
app.config(homeRouter);
homeRouter.$inject = [‘$stateProvider’, ‘$urlRouterProvider’];
function homeRouter($stateProvider, $urlRouterProvider) {
// Set up default route
$urlRouterProvider.otherwise("/");
// Set up the states
$stateProvider
.state(‘home’, {
url: "/",
templateUrl: "../app/home/home.html"
});
}
})();
[/code]
And finally, an (extremely) simple view, home.html, just to prove that everything is working:
[code language=”html”]
<div ng-controller="homeController as vm">
{{vm.title}}
<div>
See, angular is working: {{2 + 2}} == 4!
</div>
</div>
[/code]
Next Steps
From here, you can edit the script files, and once you save them the watch gulp task will automatically concatenate your script files back into home.js, meaning all you need to do is refresh your browser and your changes will show up.
Again, all of this code is available on GitHub.