Building ASP.NET Core app strengthened with AngularJS 2

Article Source

Introduction

In this tutorial I will show you how to create Master-Detail web application that also have a search option. We will use the data of the Cruising Company. It contains data about: ships, booking info, sale units etc.

Using the code

The application consist of two parts:
  • Backend: RESTful API written in .NET Core
  • Frontend: SPA written in Angular 2
Download and install following tools and libraries: The source code is located on Github. For simplicity reasons here I am going to highlight only the important parts of the code.

Start by creating new project: New -> Project. Select Web -> ASP.NET Core Angular 2 Starter.


Replace following files with the appropriate code. project.json
{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.1",
      "type": "platform"
    },
    "Microsoft.AspNetCore.AngularServices": "1.0.0-*",
    "Microsoft.AspNetCore.Diagnostics": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.1",
    "Microsoft.AspNetCore.Razor.Tools": {
      "version": "1.0.0-preview2-final",
      "type": "build"
    },
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0",
    "Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
    "Microsoft.Extensions.Logging": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.Extensions.Logging.Debug": "1.0.0",
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
  },

  "tools": {
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
    "Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final"
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "appsettings.json",
      "ClientApp/dist",
      "node_modules",
      "Views",
      "web.config",
      "wwwroot"
    ]
  },

  "scripts": {
    "prepublish": [
      "npm install",
      "node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod",
      "node node_modules/webpack/bin/webpack.js --env.prod"
    ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  },

  "tooling": {
    "defaultNamespace": "CruiseCompanyApp"
  }
}
package.json
{
  "name": "Angular2Spa",
  "version": "0.0.0",
  "dependencies": {
    "@angular/common": "2.0.0",
    "@angular/compiler": "2.0.0",
    "@angular/core": "2.0.0",
    "@angular/forms": "2.0.0",
    "@angular/http": "2.0.0",
    "@angular/platform-browser": "2.0.0",
    "@angular/platform-browser-dynamic": "2.0.0",
    "@angular/platform-server": "2.0.0",
    "@angular/router": "3.0.0",
    "@types/node": "^6.0.38",
    "angular2-platform-node": "~2.0.10",
    "angular2-universal": "~2.0.10",
    "angular2-universal-polyfills": "~2.0.10",
    "aspnet-prerendering": "^1.0.6",
    "aspnet-webpack": "^1.0.11",
    "bootstrap": "^3.3.7",
    "css": "^2.2.1",
    "css-loader": "^0.25.0",
    "es6-shim": "^0.35.1",
    "expose-loader": "^0.7.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^2.2.1",
    "preboot": "^4.5.2",
    "raw-loader": "^0.5.1",
    "rxjs": "5.0.0-beta.12",
    "style-loader": "^0.13.0",
    "to-string-loader": "^1.1.5",
    "ts-loader": "^0.8.2",
    "typescript": "^2.0.0",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.14",
    "webpack-externals-plugin": "^1.0.0",
    "webpack-hot-middleware": "^2.10.0",
    "webpack-merge": "^0.14.1",
    "zone.js": "^0.6.21"
  }
}

Now rebuild project so all dependencies are installed. Add Models folder in the root of your project and add those 2 files: JSONModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace A2SPA.Models {
  public class SalesUnit {
    [Key]
    public int id { get; set; }
    public string name { get; set; }
    public string country { get; set; }
    public string currency { get; set; }
    }

  public class Ship {
    [Key]
    public int id { get; set; }
    public int salesUnitId { get; set; }
    public string name { get; set; }
    }

  public class Booking {
    [Key]
    public int id { get; set; }
    public int shipId { get; set; }
    public string bookingDate { get; set; }
    public float price { get; set; }
    }

  public class JSONModel {
    public List salesUnits { get; set; }
    public List ships { get; set; }
    public List bookings { get; set; }
    }
  }

ViewModels.cs
using System.ComponentModel.DataAnnotations;

namespace A2SPA.Models {
  //List
  public class SearchView {
    [Key]
    public int BookingId { get; set; }
    public string ShipName { get; set; }
    public string BookingDate { get; set; }
    public float Price { get; set; }
    }

  //List
  public class SalesView {
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Country { get; set; }
    public float Total { get; set; }
    public string Currency { get; set; }
    }

  //Detail
  public class SalesDetailView {
    public int BookingId { get; set; }
    public string ShipName { get; set; }
    public float Price { get; set; }
    public string Currency { get; set; }
    }

  }

Add Api folder in the root of your project and place this file inside:
BookingsAPI.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Hosting;
using A2SPA.Models;

namespace CruiseCompanyApp.Controllers {
  [Produces("application/json")]
  [Route("api")]
  public class BookingsAPI : Controller {
    private IHostingEnvironment _env;
    public JSONModel jsonData;
    public BookingsAPI (IHostingEnvironment env) {
      _env = env;
      ReadJSON();
      }

    //Main list
    [Route("bookings/{quarter}")]
    public List Bookings (int quarter) {
      var quarterBookings = BookingsByQuarter(quarter);

      var subQuery1 = from qb in quarterBookings
                      group qb by qb.shipId into shipBookings
                      select new {
                        ShipKey = shipBookings.Key,
                        Sum = shipBookings.Sum(p => p.price)
                        };

      var shipSum = from emp in jsonData.ships
                    join s in subQuery1 on emp.id equals s.ShipKey
                    join pd in jsonData.salesUnits on emp.salesUnitId equals pd.id
                    select new SalesView {
                      Id = pd.id,
                      Name = pd.name,
                      Country = pd.country,
                      Total = s.Sum,
                      Currency = pd.currency
                      };

      var subQuery2 = from s in shipSum
                      group s by s.Country
                        into res
                      select new SalesView {
                        Id = res.FirstOrDefault().Id,
                        Name = res.FirstOrDefault().Name,
                        Country = res.FirstOrDefault().Country,
                        Total = (float)Math.Round(res.Sum(r => r.Total)),
                        Currency = res.FirstOrDefault().Currency
                        };

      var salesViewList = subQuery2.OrderByDescending(c => c.Total).ToList();
      return salesViewList;
      }

    //Details
    [Route("bookings/{saleUnit}/{quarter}")]
    public List BookingsBySaleUnit (int saleUnit, int quarter) {
      string currency = jsonData.salesUnits.Where(u => u.id == saleUnit).FirstOrDefault().currency;
      var quarterBookings = BookingsByQuarter(quarter);
      var shipsBySaleUnit = from s in jsonData.ships
                            where s.salesUnitId == saleUnit
                            select s;

      var shipSum = (from b in quarterBookings
                     join s in shipsBySaleUnit on b.shipId equals s.id
                     select new SalesDetailView {
                       BookingId = b.id,
                       ShipName = s.name,
                       Price = b.price,
                       Currency = currency
                       }).ToList();

      return shipSum;
      }

    // Filter by bookingId or  shipname 
    [Route("searchbookings/{searchValue}")]
    public List SearchBookings (string searchValue) {
      IEnumerable searchResults = null;
      IEnumerable bookings = null;
      int id;
      bool isNumeric = int.TryParse(searchValue, out id);


      if (isNumeric)
        bookings = jsonData.bookings.Where(b => b.id == id);

      else {
        var getShipIds = jsonData.ships.Where(s => s.name.ToLower().StartsWith(searchValue.ToLower()));
        bookings = from booking in jsonData.bookings where getShipIds.Select(s => s.id).Contains(booking.shipId) select booking;
        }
      searchResults = from ship in jsonData.ships
                      join b in bookings on ship.id equals b.shipId
                      select new SearchView {
                        BookingId = b.id,
                        ShipName = ship.name,
                        BookingDate = DateTime.Parse(b.bookingDate).ToString("yyyy-MM-dd"),
                        Price = b.price
                        };
      return searchResults.ToList();
      }

    // Filter bookisng by quarter (0 for all)
    public List BookingsByQuarter (int quarter) {
      //  ReadJSON();
      int fromMonth = quarter * 3 - 3;
      int toMonth = quarter * 3;
      List res = null;

      if (quarter == 0)
        res = jsonData.bookings.ToList();
      else {
        res = (from b in jsonData.bookings
               where
                         Convert.ToDateTime(b.bookingDate).Year == 2016 &&
                         Convert.ToDateTime(b.bookingDate).Month > fromMonth &&
                         Convert.ToDateTime(b.bookingDate).Month <= toMonth
               select b).ToList();
        }
      return res;
      }

    private void ReadJSON () {
      if (jsonData != null) return;
      var jsonFile = _env.ContentRootPath + Path.DirectorySeparatorChar.ToString() + "Data" +
                                                               Path.DirectorySeparatorChar.ToString() + "TrialDayData.json";
      string fileContent;
      using (var stream = new FileStream(jsonFile, FileMode.Open))
      using (StreamReader sr = new StreamReader(stream)) {
        fileContent = sr.ReadToEnd();
        }
      jsonData = JsonConvert.DeserializeObject(fileContent);
      }

    }
  }

Add Controllers folder in the root of your project and place this file inside:
HomeController
using Microsoft.AspNetCore.Mvc;

namespace CruiseCompanyApp.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}

Data folder should contain file TrialDayData.json which you will find on Github

Now we should be able to test the application API call. Hit Ctrl-F5 and navigate to http://localhost:64880/api/bookings/1
[{"id":8,"name":"soyoulun.com","country":"China","total":961880.0,"currency":"¥"},
{"id":1,"name":"dreamlines.de","country":"Germany","total":906369.0,"currency":"€"},
{"id":3,"name":"dreamlines.it","country":"Italy","total":831574.0,"currency":"€"},
{"id":7,"name":"dreamlines.nl","country":"Netherlands","total":793538.0,"currency":"€"},
{"id":5,"name":"dreamlines.com.au","country":"Australia","total":778521.0,"currency":"AU$"},
{"id":2,"name":"dreamlines.com.br","country":"Brazil","total":753164.0,"currency":"R$"},
{"id":6,"name":"dreamlines.ru","country":"Russia","total":632055.0,"currency":"RUB"},
{"id":4,"name":"dreamlines.fr","country":"France","total":469810.0,"currency":"€"}]

Now it is still left to do the front-end part. For this task I suggest you to copy folders from GitHub and place it in your solution. In the end, your solution tree should look like this

Source code available at Github
and
Live demo available here



Points of Interest


ASP.NET Core is a powerful platform, though it has not yet been completed as ASP.NET MVC. Yet combined with the AngularJS framework, it gives excellent results.


Comments

Popular posts from this blog

Building NodeJS REST service and consuming it from within Android application

Thing Translator