Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Master Chef (Part 1) ASP.NET Core MVC with Fluent NHibernate and AngularJS

0.00/5 (No votes)
6 Feb 2020 2  
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.
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

Image 1

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).

Image 2

ASP.NET Core has two kinds of applications:

  1. ASP.NET Core .NET Framework Application is an application running on Windows using the .NET Framework.
  2. ASP.NET Core .NET Core Application is a cross-platform application running on Windows, Linux, and OS X using .NET Core.

Image 3

Select the "Empty" template and uncheck "Host in cloud".

Image 4

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"

Image 5

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.

Image 6

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.

Image 7

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.

Image 8

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();
        }

        // This method gets called by the runtime. 
        // Use this method to configure the HTTP request pipeline.
        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.

Image 9

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.

Image 10

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.

Image 11

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.

Image 12

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".

Image 13

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.

Image 14

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.

Image 15

After restoring is finished, Visual Studio installs these packages under the wwwroot\lib folder.

Image 16

2) Add index.html

Now we need to add an index.html under wwwroot folder as our home page.

Image 17

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".

Image 18

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.

Image 19

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.

Image 20

In app.js, change the app name to "masterChefApp".

(function () {
    'use strict';

    angular.module('masterChefApp', [
        // Angular modules 
        //'ngRoute'

        // Custom modules 

        // 3rd Party Modules
        
    ]);
})();

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.

Image 21

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.

Image 22

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) {
                //log errors
        });
    }
})();

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.

Image 23

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', [
        // Angular modules 
        //'ngRoute'

        // Custom modules 
        'recipesService'
        // 3rd Party Modules
        
    ]);
})();

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.

Image 24

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here