Click here to Skip to main content
13,193,441 members (23,578 online)
Click here to Skip to main content
Add your own
alternative version

Stats

21.6K views
606 downloads
37 bookmarked
Posted 21 Sep 2016

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

, 21 Sep 2016
Rate this:
Please Sign up or sign in to vote.
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.

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 a 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 https://visualstudiogallery.msdn.microsoft.com/32f1fa1b-cdd5-4bd3-8f51-cd8f099f46bc.

From Visual C#/Web, select ASP.NET core Web Application (.NET framework).

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.

Select the "Empty" template and untick "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 that 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 pakcage 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 provider 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, ecipe) 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 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();
        }

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

Now the URL is working, and a JSON result got returned.

Grunt

1) Add Grunt

Add a new folder "scripts" which this 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-clicking on the project, going to Add - New Item, and selecting 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 need Microsoft to fix. For the time being, we have to open bowe.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 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 lined 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 java scripts 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', [
        // 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.

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

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

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 Part2, 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 looks better.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Fred Song (Melbourne)
Software Developer (Senior)
Australia Australia
Fred Song is a senior software developer who lives in Melbourne, Australia. In 1993, he started Programming using Visual C++, Visual Basic, and Oracle Developer Tools. From 2003, He started with .Net using C#.

He is often working with software projects in different business domains based on different Microsoft Technologies like SQL-Server, C#, VC++, ASP.NET, WCF,WPF and Silverlight, although he also did some development works on IBM AS400.

You may also be interested in...

Comments and Discussions

 
QuestionCould you use Angular 2? Pin
mason0027-Sep-16 20:35
membermason0027-Sep-16 20:35 
AnswerRe: Could you use Angular 2? Pin
Fred Song (Melbourne)23-Jan-17 11:05
memberFred Song (Melbourne)23-Jan-17 11:05 
GeneralRe: Could you use Angular 2? Pin
mason0023-Jan-17 15:38
membermason0023-Jan-17 15:38 
QuestionA great example Pin
Member 1135844524-Sep-16 5:11
memberMember 1135844524-Sep-16 5:11 
QuestionUmmmm ... what? Pin
Ryan Criddle21-Sep-16 12:48
memberRyan Criddle21-Sep-16 12:48 
AnswerRe: Ummmm ... what? Pin
Fred Song (Melbourne)21-Sep-16 13:54
memberFred Song (Melbourne)21-Sep-16 13:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.171018.2 | Last Updated 21 Sep 2016
Article Copyright 2016 by Fred Song (Melbourne)
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid