This article shows you how to integrate ASP.NET Core MVC, Fluent NHibernate and AngularJS to build a simple web application. It walks you through the elements involved in building a Single Page Application. It covers Fluent NHibernate, its installation, adding data model classes, mapping classes, and repository class, adding and configuring Grunt, and installing Angular JS, adding index.html, Angular JS app module, service factory, and controller.
Introduction
ASP.NET 5 is dead, and is being renamed to ASP.NET Core 1.0. However, though naming the new, a completely written from scratch ASP.NET framework "ASP.NET 5" was a bad idea for one major reason: 5 makes it seem like ASP.NET 5 is bigger, better, and replaces ASP.NET 4.6. Not so. So ASP.NET 5 is now ASP.NET Core 1.0, and NET Core 5 is now .NET Core 1.0. Why 1.0? Because these are new. The whole .NET Core concept is new. The .NET Core 1.0 CLI is very new. Not only that, but .NET Core isn't as complete as the full .NET Framework 4.6.
One of the big new features of working with ASP.NET Core is cross-platform compatibility. As of this version, we can both develop and run ASP.NET Core on Windows, which has always been the case, but also on Mac OS X and Linux operating systems.
ASP.NET Core and MVC are the server side of things, but as we touched on earlier, in a single-page application, there is also a client-side component. Angular is actually one of the more popular frameworks. It's built on top of the web technologies – HTML, CSS, and JavaScript – and follows a model view whatever pattern, which basically allows us to create apps that have a decoupling between the presentation and the business logic.
NHibernate is an object-relational mapping (ORM) solution for the Microsoft .NET platform. It provides a framework for mapping an object-oriented domain model to a traditional relational database. Fluent NHibernate offers an alternative to NHibernate's standard XML mapping files. Rather than writing XML documents (.hbm.xml files), Fluent NHibernate allows you to write mappings in strongly typed C# code. This allows for easy refactoring, improved readability and more concise code.
In this article, I want to show how to build a Single Page Application – MasterChef with ASP.NET Core MVC, Fluent Hibernate, and Angular JS.
Master Chef Recipe Data Model UML
In your local SQL Express, just create a database "MasterChef
". Then run schema.sql under sql folder.
Create MasterChef Application in Visual Studio 2015 Update 3
In order to use ASP.NET Core, you need update ASP.NET web tools. The latest version is 2.0.2, download from this link.
From Visual C#/Web, select ASP.NET core Web Application (.NET Framework).
ASP.NET Core has two kinds of applications:
- ASP.NET Core .NET Framework Application is an application running on Windows using the .NET Framework.
- ASP.NET Core .NET Core Application is a cross-platform application running on Windows, Linux, and OS X using .NET Core.
Select the "Empty" template and uncheck "Host in cloud".
Have a look at the ASP.NET Core Web solution structure. It creates a "src" folder and the actual project is under "src" folder. Within this src folder is a special folder here - wwwroot, which is a folder that's going to hold all of our live web files. So any HTML, our eventual angularapp.js, any other minified scripts, image assets, or things like that which are going to be served up on the live site go in here. But all of our source code – the code that actually runs this ASP.NET 5 MVC 6 Angular application – none of that is ever going to go in the wwwroot folder.
Add Fluent NHibernate Data Models
1) Install Fluent NHibernate
Add Fluent NHibernate package from Nuget Package manager.
Open project.json and add "FluentNHibernate": "2.0.3"
2) Add Data Model Classes
In our solution, create "Models" folder. Add Recipe
, RecipeStep
and RecipeItem
model classes to Models folder.
public class Recipe
{
public virtual Guid RecipeId { get; set; }
public virtual string Name { get; set; }
public virtual string Comments { get; set; }
public virtual DateTime ModifyDate { get; set; }
public virtual IList<RecipeStep> Steps{ get; set; }
}
public class RecipeStep
{
public virtual Guid RecipeStepId { get; set; }
public virtual int StepNo { get; set; }
public virtual string Instructions { get; set; }
public virtual IList<RecipeStep> RecipeItems { get; set; }
}
public class RecipeItem
{
public virtual Guid ItemId { get; set; }
public virtual string Name { get; set; }
public virtual float Quantity { get; set; }
public virtual string MeasurementUnit { get; set; }
}
3) Add Fluent NHibernate Mapping Classes
As mentioned before, NHibernate Mapping XML (.hbm) is replaced by mapping class in Fluent Hibernate. So we need to provide the mapping class for each data model class.
public class RecipeMap : ClassMap<Recipe>
{
public RecipeMap()
{
Id(x => x.RecipeId);
Map(x => x.Name);
Map(x => x.Comments);
Map(x => x.ModifyDate);
HasMany(x => x.Steps).KeyColumn("RecipeId").Inverse().OrderBy("StepNo Asc");
Table("Recipes");
}
}
How to map a list? In the RecipeMap
class, use HasMany(x=>x.Steps). KeyCoumn("RecipeId")
is the reference key in RecipeSteps
table. Also, recipe steps need to be sorted by StepNo
, which uses OrderBy
. The same mapping happens in the RecipeStepMap
class.
public class RecipeStepMap : ClassMap<RecipeStep>
{
public RecipeStepMap()
{
Id(x => x.RecipeStepId);
Map(x => x.StepNo);
Map(x => x.Instructions);
HasMany(x => x.RecipeItems).KeyColumn("RecipeStepId").Inverse();
Table("RecipeSteps");
}
}
public class RecipeItemMap : ClassMap<RecipeItem>
{
public RecipeItemMap()
{
Id(x => x.ItemId);
Map(x => x.Name);
Map(x => x.Quantity);
Map(x => x.MeasurementUnit);
Table("RecipeItems");
}
}
4) Add Repository Class
We use repository pattern to separate the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. The business logic should be agnostic to the type of data that comprises the data source layer.
The repository mediates between the data source layer and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source. A repository separates the business logic from the interactions with the underlying data source.
In the Repository
class, we need to configure Fluent NHibernate session.
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012
.ConnectionString("Server=.\\sqlexpress;
Database=MasterChef; Integrated Security=SSPI;"))
.Mappings(m => m
.FluentMappings.AddFromAssemblyOf<Repository>())
.BuildSessionFactory();
_session = _sessionFactory.OpenSession();
You can add a list of mapping class by .FluentMappings.Add(…)
. Also, you can add all mapping classes in an assembly by .FluentMappings.AddFromAssembly(…)
Add Web API Controller
1) Install ASP.NetCore.Mvc
We add the Asp.NetCore.Mvc
package on Nuget Package manager, project.json.
2) Add API RecipesController
Create an "api" folder, then right click the API folder to add a new item. In ASP.NET, select Web API Controller Class template. We name our class to RecipesController.cs.
In the RecipesController
class, we set up functions to deal with basic CRUD requests. We're getting a GET
request here requesting all recipes. We have another function here, Get
, that takes an id so a user can request a specific recipe that we return. And we also have some more functions here like POST
which allows a user to create a new recipe. And also PUT
, where we can update an existing recipe. And finally, DELETE
, where a specific recipe can be deleted. So all of this is coming as boilerplate from the Web API controller class, and we will add to this to create our actual application.
In RecipesController
class, we add _repository
member to handle the database stuff.
HttpGet
to get all recipes.
[HttpGet]
public IEnumerable<Recipe> Get()
{
return _repository.GetAllRecipes();
}
HttpGet(id)
to get a specific recipe.
[HttpGet("{id}")]
public IActionResult Get(Guid id)
{
var recipe = _repository.GetRecipe(id);
if (recipe != null)
return new ObjectResult(recipe);
else
return new NotFoundResult();
}
HttpPost(Recipe)
to add or update a recipe. If the input recipe id is empty, we add a new recipe; otherwise, we update the existing recipe.
[HttpPost]
public IActionResult Post([FromBody]Recipe recipe)
{
if (recipe.RecipeId == Guid.Empty)
{
return new ObjectResult(_repository.AddRecipe(recipe));
}
else
{
var existingOne = _repository.GetRecipe(recipe.RecipeId);
existingOne.Name = recipe.Name;
existingOne.Comments = recipe.Comments;
_repository.UpdateRecipe(existingOne);
return new ObjectResult(existingOne);
}
}
HttpPut(id, recipe)
to update a recipe.
[HttpPut("{id}")]
public IActionResult Put(Guid id, [FromBody]Recipe recipe)
{
var existingOne = _repository.GetRecipe(recipe.RecipeId);
existingOne.Name = recipe.Name;
existingOne.Comments = recipe.Comments;
_repository.UpdateRecipe(recipe);
return new ObjectResult(existingOne);
}
HTTPDelete
to delete a recipe.
[HttpDelete("{id}")]
public IActionResult Delete(Guid id)
{
_repository.DeleteRecipe(id);
return new StatusCodeResult(200);
}
3) Configure MVC
After we add recipes controller, we suppose http://localhost/api/recipes returns all recipes. Let’s try.
It’s not working. Why? That because we haven’t configured MVC yet.
Go to Startup.cs.
Add services.AddMvc()
in ConfigureServices(…)
, and add app.UseMvc in Configure(…)
.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure
(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
After adding these, let’s try again, click "IIS Express" in the menu bar.
Now the URL is working, and a JSON result got returned.
Grunt
1) Add Grunt
Add a new folder "scripts" which is going to hold all of our script files. As we mentioned before, all flying scripts and htmls are under the wwwroot folder. So now, we need install Grunt to help us automate install all scripts under the "scripts" folder to the wwwroot folder. Eventually, we want to have Grunt help us watch this folder and combine and minify all of the scripts in it before moving that result over to our live wwwroot folder.
To install Grunt, we're going to be using the built-in support for that NPM package manager that ASP.NET Core brings. That's in addition to the support for the Bower package manager as well as the NuGet package manager.
Right-click on my project to add a New Item. I'm going to go and select Client-side and scroll here until I see an NPM configuration file option, package.json is an appropriate name.
Open package.json, add grunt to dependencies.
{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"devDependencies": {
"grunt": "1.0.1",
"grunt-contrib-uglify": "2.0.0",
"grunt-contrib-watch": "1.0.0",
"bower": "1.7.9"
}
}
2) Configure Grunt
The next thing is actually configuring Grunt. Right-click on the project, go to Add - New Item, and select Grunt Configuration file from the Client-side section. Leave the name as a gruntfile.js and Add that file.
We configure the uglify plugin and the watch plugin separately. For the uglify plugin (which helps us take all of the JavaScript files in our scripts folder to be minified into another file – app.js) that's going to be in the wwwroot folder. The second configuration is setting to watch scripts folder. And any time there is a change to any JavaScript files in that folder, it automatically runs uglify ensuring that we have the latest version of our scripts in app.js in our wwwroot folder. The last thing in this Grunt configuration file is registering tasks for running.
Open Gruntfile.js:
module.exports = function (grunt) {
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.initConfig({
uglify: {
my_target: {
files: {
'wwwroot/app.js': ['scripts/app.js', 'scripts/**/*.js']
}
}
},
watch: {
scripts: {
files: ['scripts/**/*.js'],
tasks: ['uglify']
}
}
});
grunt.registerTask('default', ['uglify', 'watch']);
};
Now, we should be able to actually run this file. Go to the View menu - Other Windows. Select the Task Runner Explorer that's going to show up at the bottom. Refresh it to see saved tasks. So get running the default task. Now you can see the output. And right now, the result is that nothing has been written to our wwwroot/app.js. That is what we expect because we don't have any scripts in our script files to minify and then create that app.js out of.
Angular JS
1) Install Angular JS
We use bower package manager to grab our client-side Angular JavaScript files. So the first thing to do is right-click on our project to Add a New Item. On the Client-side to select a Bower Configuration File, "bower.json".
After it’s done, bower.json is not showing on our solution. What’s happening? I don’t know. It should be a bug of Visual Studio, which needs Microsoft to fix. For the time being, we have to open bower.json from file system.
In bower.json, add "jquery
", "bootstrap
", "angular
", "angular-route
", and "angular-resource
" in dependencies
section.
{
"name": "asp.net",
"private": true,
"dependencies": {
"jquery": "3.1.0",
"bootstrap": "3.1.0",
"angular": "1.5.8",
"angular-route": "1.5.8",
"angular-resource": "1.5.8"
}
After saving it, Visual Studio begins to restore all these packages automatically.
After restoring is finished, Visual Studio installs these packages under the wwwroot\lib folder.
2) Add index.html
Now we need to add an index.html under wwwroot folder as our home page.
We just create a very simple index.html, make sure it gets used by ASP.NET Core.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<h1>Master Chef Recipes</h1>
</body>
</html>
Start our web app by clicking "IIS Express".
It appears to not be working. That is because we haven’t told ASP.NET core to use static files.
Go to startup.cs.
Add the below two lines in Configure(…)
method:
app.UseDefaultFiles();
app.UseStaticFiles();
Then start our web app again.
Now it’s working.
3) Add Angular JS App Module
Now what we want to create is an Angular module that's going to be our application. Remember we create all our JavaScripts under the "scripts" folder. So right click the "scripts" folder in our project. Add a New Item. In the Client-side template section, select AngularJs Module. The default name of app.js.
In app.js, change the app name to "masterChefApp
".
(function () {
'use strict';
angular.module('masterChefApp', [
]);
})();
4) Add Service Factory
From Angular JS, we need call the server web API somehow. That can be done by service. So we create a service factory to make an http call.
First, we create a "service" folder under scripts. Then right click "service" to add a new item. In Client Side, select AngularJs Factory template. Change the name to recipesFactory.js.
In recipesFactory.cs, change app to masterChefApp
, then in getData()
call $http.get(‘api/recipes/’)
.
(function () {
'use strict';
angular
.module('masterChefApp')
.factory('recipesFactory', recipesFactory);
recipesFactory.$inject = ['$http'];
function recipesFactory($http) {
var service = {
getData: getData
};
return service;
function getData() {
return $http.get('/api/recipes');
}
}
})();
5) Add Angular JS Controller
Now we want to create a client-side controller that can display recipes in the browser.
First, we create a controller folder under scripts. Then, right-clicking controller, add a new item. In Client-Side, select AngularJs controller using $scope
template. Change name to recipesController.js.
Within the body of controller function, setting up one scope variable that calling recipes. And we are setting it by using recipesFactory
and its getData
function.
function () {
'use strict';
angular
.module('masterChefApp')
.controller('recipesController', recipesController);
recipesController.$inject = ['$scope', 'recipesFactory'];
function recipesController($scope, recipesFactory) {
$scope.recipes = [];
recipesFactory.getData().success(function (data) {
$scope.recipes = data;
}).error(function (error) {
});
}
})();
After saving it, you can have a look at the wwwroot folder, a minified app.js has been created automatically, which includes all scripts in app.js, recipesFactory.js and recipesController.js.
6) Change index.html to Use Angular Module
The first thing is to use the ng-app
directive to activate our Angular application. So we have to be sure to use that same name we defined in app.js, which is masterChefApp
, when we use the ng-app
directive. Also, we need to import some script files that we need. Finally, referencing app.js – JavaScript file all of which you can see is in my wwwroot folder. Moving on, on my <body>
tag, use another directive. And this is the ng-cloak
directive. And this is going to keep the body hidden until Angular has completely loaded the data and rendered my template. Inside <body>
, define a div
. Use the ng-controller
directive to indicate that for this div
.
<!DOCTYPE html>
<html ng-app="masterChefApp">
<head>
<base href="/">
<meta charset="utf-8" />
<title>Master Chef Recipes</title>
<script src="lib/angular/angular.min.js"></script>
<script src="lib/angular-resource/angular-resource.min.js"></script>
<script src="lib/angular-route/angular-route.min.js"></script>
<script src="app.js"></script>
<link href="lib/bootstrap/dist/css/bootstrap.min.css"
rel="stylesheet" media="screen">
</head>
<body ng-cloak>
<div ng-controller="recipesController">
<h1>Master Chef Recipes</h1>
<ul>
<li ng-repeat="recipe in recipes">
<p> {{recipe.name}} - {{recipe.comments}}</p>
<ul>
<li ng-repeat="step in recipe.steps">
<p> step {{step.stepNo}} : {{step.instructions}}</p>
<ul>
<li ng-repeat="item in step.recipeItems">
<p> {{item.name}}
{{item.quantity}} {{item.measurementUnit}}</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</body>
</html>
Ok. Now let’s run it by clicking IIS Express.
There are two master chef recipes showing on the browser. One is Honey Chicken, and the other is Mongolian Lamb. Very easy to learn, just like Angular JS.
Angular Resource Makes Master Chef Better
In recipesFactory
, we call $http.get
explicitly. It’s working. But we can make it easier and better by ngResource
. The ngResource
module provides interaction support with RESTful services via the $resource
service. In $resource
service all http get, post, put and delete action has been built in. You just need pass Url, and don’t need to call them explicitly.
First in app.js, we register a custom module, recipesService
.
(function () {
'use strict';
angular.module('masterChefApp', [
'recipesService'
]);
})();
Then add another AngularJS Factory
class, we name it recipesService.cs.
In recipesService.cs, we inject ngresource
and pass "/api/recipes/:id
".
(function () {
'use strict';
var recipesService = angular.module('recipesService', ['ngResource']);
recipesService.factory('Recipe', ['$resource', function ($resource) {
return $resource('/api/recipes/:id');
}]);
})();
In recipesController.cs, we can use Recipe.query()
directly.
(function () {
'use strict';
angular
.module('masterChefApp')
.controller('recipesController', recipesController);
recipesController.$inject = ['$scope', 'Recipe'];
function recipesController($scope, Recipe) {
$scope.recipes = Recipe.query();
}
})();
Now we don’t need recipesFactory.cs anymore. We delete it. Then run our master chef web app again.
It works like a charm.
Conclusion
In this article, I show you how to integrate ASP.NET Core MVC, Fluent NHibernate and AngularJS to build a simple web application. In Master Chef Part 2, I’ll talk about Angular route and using Angular route build a SPA CRUD (list, add, edit, delete) application. Also, I’ll show you how to use bootstrap styles to make your web app look better.
History
- 6th February, 2020: Initial version
Fred is a senior software developer who lives in Melbourne, Australia. In 1993, he started Programming using Visual C++, Visual Basic, Java, and Oracle Developer Tools. From 2003, He started with .Net using C#, and then expertise .Net development.
Fred is often working with software projects in different business domains based on different Microsoft Technologies like SQL-Server, C#, VC++, ASP.NET, ASP.Net MVC, WCF,WPF, Silverlight, .Net Core and Angular, although he also did some development works on IBM AS400.