In this blog post we are going to setup a basic invoice system. It uses the ASP.NET Core identity system. Every step for creating the app is described and at the end you should have a working Invoicing system (being it a bit simple one).
For convenience install the sqlitebrowser
sudo apt install sqlitebrowser
If you did not already installed the dotnet-aspnet-codegenerator already….
Scaffold the Identity pages you want to change later on, for now we are going to use a SqLite database and override the Register, Login and Logout pages.
Before we are going to add the migrations we change the name (and location) of the database to dbs/identity.db (we will have separate databases for users and data).
Start Visual Code in the root of the WebApp directory.
code .
Wait a few seconds for the window below to appear and answer Yes. If the window below does not appear press F1 and type “.NET”, then select “.NET: Generate assets for build and debug”.
Open the file appsettings.json in the root of the project and change WebApp.db to dbs/identity.db. Also create the folder dbsin the root of WebApp.
Now we are going to create the Migrations for the initial Identity database and update the database with this migration.
Create the initial migration for the identity system
dotnet ef migrations add InitialCreateIdentity
Create the database
dotnet ef database update
Check the databastructure with SQLite browser
Because we did not use the --auth parameter on initial create of the project our Startup.cs is not prepared to use authentication. Add the line below right after app.UseCookiePolicy
app.UseAuthentication();
We also have to add the _LoginPartial to _Layout.cshtml because of this. Add the partial _LogingPartial to /Pages/Shared/_Layout.cshtml right before the ul which contains the Home link. Add the line below:
<partial name="_LoginPartial" />
To test authorization place the [Authorize] attribute on the PrivacyModel class and add the using Microsoft.AspNetCore.Authorisation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspnetCore.Authorisation;
namespace WebApp.Pages
{
[Authorize]
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}
}
It is possible to configure password options in /Areas/Identity/IdentityHostingStartUp.cs. For example: do not require an uppercase character in the password:
Now we are going to add our first CRUD pages. We are going to store Invoices with our application. First create a directory Models and place a file Invoice.cs in it with the following code in it:
namespace WebApp.Models
{
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Invoice
{
public int ID { get; set; }
public string Product { get; set; }
[DataType(DataType.Date)]
public DateTime InvoiceDate { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal DiscountPercentage { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Amount { get; set; }
}
}
Scaffold model CRUD pages
Execute the command below:
The following files will be generated or adjusted:
In /Startup.cs the InvoiceDbContext is added to the services configuration A directory /Pages/Invoice is created and all files in there are also generated A directory /Data is created in which a file InvoiceDbContext.cs is stored with the data context for the invoices The file appsettings.json is modified. A connection string is added for the InvoiceDbContext (we will change this below)
In case you are on Linux. Default the DbContext is using SqlServer, that is not supported on the Linux platform. Goto the file Startup.cs and replace SqlServer with SqLite for the InvoiceDbContext.
Retrieve your name and url with the command: nuget sources
When you get the error “Scheme already exists: Identity.Application” you probably generated the Identity pages with a different context then ApplicationDbContext.
Build the solution (Ctrl-Shift-B, Enter) en start debugging F5. Navigate to the Privacy page and verify that you have to login before you can continue to this page.
In case you get error “PlatformNotSupportedException: LocalDB is not supported on this platform.” you probably generated the Identity pages with a different context then ApplicationDbContext.
With help of the Sequelize ORM you can manage your database models and queries. Below is an example of how to setup a many to many relationship with Nodejs and the Sequelize ORM. At the end of this article you can find a link to the source code.
First of all our scenario:
We have Invoices and Products in our database. As you can imagine you can receive a invoice for multiple products. Also a product can occur on multiple invoices. The relation between Invoices and Products is many to many.
1 Invoice can have 1 or more products and 1 product can have one or more invoices.
The way to model this in a database is with a so called join table. This table has a pointer to an Invoice and a pointer to a Product. Lets say we want to send two invoices.
Invoice number 2019001 with a SSD and a Laptop on it and Invoice number 2019002 with a harddisk and monitor on it. We have not yet sold any Desktop’s.
Our Invoice and Product tables look like this:
INVOICE PRODUCTS
ID NR ID DESC
1 2019001 1 Laptop
2 2019002 2 SSD
3 Harddisk
4 Monitor
5 Desktop
Then we have our join table, I have called it ProductInvoices after sending the invoices to our customer it will have the contents as shown below
Ok, now lets switch over to Nodejs and Sequelize. How are we going to model this in Sequelize? Below are all the steps to create a working program.
Create a new directory and setup your node environment, also initialize a new Sequelize setup with the Sequelize init command (you need to install the sequelize-cli package for this to work).
After this you will have the following directory structure (I use Visual Code for my development work as you can see).
Now we are going to create the Invoice model. Start up visual code in the m_to_m directory (execute code . in this directory). Right click on the models folder and choose new file. Name it invoice.js. Place the code shown below in it.
module.exports = (sequelize, DataTypes) => {
var Invoice = sequelize.define('Invoices', {
nr : {
type : DataTypes.INTEGER,
allowNull : false,
unique : {msg : 'Invoice nr should be unique'}
}
});
Invoice.associate = (models) => {
Invoice.belongsToMany(models.Products, {
through: 'ProductInvoices',
as: 'Products',
foreignKey: 'invoiceId'
});
};
return Invoice;
}
Next create a file product.js also in the models folder with the following content:
module.exports = (sequelize, DataTypes) => {
var Product = sequelize.define('Products', {
name : {
type : DataTypes.STRING,
allowNull : false,
unique : {msg : 'Product name should be unique'}
}
});
Product.associate = (models) => {
Product.belongsToMany(models.Invoices, {
through: 'ProductInvoices',
as: 'Invoices',
foreignKey: 'productId'
});
};
return Product;
}
The next model is optional. When you do not specify it Sequelize will generate one for you. Specifying it yourself has the advantage that you can also query this model if needed and add some attributes to the relation. For this example I have added a ‘remark’ attribute to this join table. We can add a remark to the product for this invoice.
So for now lets code it, but remember, it is optional if there is no need to query the join table or you do not have any relation attributes.
Now our model is complete and we can start to program against it. I love the Sequelize ORM, don’t you, no?, you will in a minute…..
We have to do some configuration for the database connection before we are able to create the database. In this example I will be using a SQLite database. Remove the file generate by Sequelize at ~/m_to_m/config/config.json. Create a new file at this location but with the extension js. Place the contens shown below in this config.js file
There is one last thing to do before our database setup is complete. Edit the generated index.js file at ~/m_to_m/models and change config.json to config.js (remove the “on” at the end of the filename extension).
Now the database setup is complete, lets start to write some code. Create a new file in the root of your project and name it app.js, place the code shown below in it.
When you execute this node program the database database_dev.sqlite3 will be created (see the config.js file in the config directory). The SQL that is executed against the SQLite driver is also shown:
~/m_to_m$: node app.js
Executing (default): CREATE TABLE IF NOT EXISTS
`Invoices` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `nr` INTEGER NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
Executing (default): PRAGMA INDEX_LIST(`Invoices`)
Executing (default): PRAGMA INDEX_INFO(`sqlite_autoindex_Invoices_1`)
Executing (default): CREATE TABLE IF NOT EXISTS
`Products` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
Executing (default): PRAGMA INDEX_LIST(`Products`)
Executing (default): PRAGMA INDEX_INFO(`sqlite_autoindex_Products_1`)
Executing (default): CREATE TABLE IF NOT EXISTS `ProductInvoices` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `productId` INTEGER NOT NULL REFERENCES `Products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, `invoiceId` INTEGER NOT NULL REFERENCES `Invoices` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, `remark` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, UNIQUE (`productId`, `invoiceId`));
Executing (default): PRAGMA INDEX_LIST(`ProductInvoices`)
Executing (default): PRAGMA INDEX_INFO(`sqlite_autoindex_ProductInvoices_1`)
~/m_to_m$:DB Created
To inspect the contents of this database you can for example use “DB Browser for SQLite” or DBeaver . Install “DB Browser for SQLite” with
sudo apt install sqlitebrowser
DB Browser for SQLite in action
Now we are going to add some records to our database and query the database. We will be adding five products and two invoices as described above. After that we will generate an overview of the data in the database.
Here is the code to store and query our Invoices and products.
var models = require('./models');
(async () => {
let db = await models.sequelize.sync();
console.log(`DB ${db.options.storage} created.`);
await models.Invoices.destroy({ where : {}, truncate : true});
await models.Products.destroy({ where : {}, truncate : true});
let laptop = await models.Products.create( { name : 'Laptop' })
let ssd = await models.Products.create( { name : 'SSD' });
let harddisk = await models.Products.create( { name : 'Harddisk' });
let monitor = await models.Products.create( { name : 'Monitor' });
let desktop = await models.Products.create( { name : 'Desktop' });
invoice = await models.Invoices.create({ nr : 2019001 });
await invoice.addProducts([ssd], {through: { remark: '10% discount on SSD' }});
await invoice.addProducts([harddisk, laptop]);
invoice = await models.Invoices.create({ nr : 2019002 });
await invoice.addProducts([harddisk, monitor]);
invoices = await models.Invoices.findAll(
{
include : [ {model: models.Products, through: 'ProductInvoices', as: 'Products'}]
}
);
invoices.forEach(invoice => {
console.log(`INVOICE: ${invoice.id} ${invoice.nr}`);
invoice.Products.forEach(product => {
console.log(`\tPRODUCT: ${product.id} ${product.name}`);
})
console.log();
});
products = await models.Products.findAll(
{
include : [ {model: models.Invoices, through: 'ProductInvoices', as: 'Invoices'}]
}
);
products.forEach(product => {
console.log(`PRODUCT ${product.id} ${product.name}`);
product.Invoices.forEach(invoice => {
console.log(`\tINVOICE: ${invoice.id} ${invoice.nr}`);
})
console.log();
});
/*
* Weird but possible adding records via jointable.....
*/
pi = await models.ProductInvoices.create(
{
invoiceId : (await models.Invoices.create({ nr : 2019009})).id,
productId : (await models.Products.findOne({ where : { name : 'Desktop' } })).id
});
productInvoices = await models.ProductInvoices.findAll(
{
include : [
{model: models.Products, as : 'Product'},
{model: models.Invoices, as : 'Invoice'}
]
}
);
productInvoices.forEach(pi => {
console.log(`${pi.id} ${pi.Invoice.id} ${pi.Invoice.nr} ${pi.Product.id} ${pi.Product.name}`);
});
})();
After running this code you will have two Invoices and five Products in your database. The image below shows the console output of running this Nodejs program. I have switched of Sequelize logging (add an attribute “logging” : false to the appropriate entry in ~/config/config.json) so the output is a bit more readable.
You can find the complete working example here and a typescript variant can be found here.
Got to elaborate on this, see https://www.npmjs.com/package/sequelize-typescript
Create a new directory and run the command below in it
npm init -y
Next install nodemon with
sudo npm install -D nodemon
Add a script tag to your package.json to execute the nodemon command. This works because npm looks under node_modules/.bin for an executable. The package.json looks then like this
In this article I will show you how to use express sessions. Default express-sessions are stored in memory. With help of the package ‘session-file-store’ you can persist sessions to your filesystem.
Use memory store for sessions (default)
First setup your nodejs app to use express and express-essions:
# Create new package.json file
npm init -y
# Add express and express-ession to node module
npm install express express-ession --save
Now add an app.js file to the current folder with the following contents:
and navigate to ‘http://localhost:3000/’. A webpage shows up with the text ‘New client’. Now hit F5 and see the text ‘Returning client (2 times)’ appearing. The session is created on first request with a ‘views’ variable in it. Every next visit of the site this ‘views’ variable is incremented with 1.
Use a FileStore for session data
Now if you want to use persistent session you will have to install the session-file-store with:
npm install session-file-store --save
Uncomment the two lines of code in app.js and you are ready to go. Sessions are stored on the filesystem in a sub folder called ‘sessions’ below the location of your app.js.
If you are using nodemon to monitor changes in your nodejs code be sure to exclude monitoring of the ‘sessions’ folder as it will change on every request of the browser. Start nodemon with:
nodemon --ignore sessions/ app,js
Custom session id’s
In case you want to generate custom session id’s you will have to provide a genid callback to the session initialized. First add the uuid package with
npm install uuid
Add the require statement to the top of your app.js file:
const uuidv1 = require('uuid/v1')
And add the genid callback to the session initialization:
app.use(session({
genid: (req) => {
return 'app_' + uuidv1() // use UUIDs for session IDs
},
secret: 'keyboard cat',
resave : false,
saveUninitialized : false,
store: new FileStore()
})
)
In this article I’m going to create a minimalistic CRUD application with nodejs, express and mongodb. First I will show you the pug files and finally the nodejs code for creating our application.
To use mongodb you have to install it on your (ubuntu) box with:
sudo apt install mongodb
Then we have to add the node module to our project (and package.json) with:
npm install mongodb --save
Now on to the pug files. First of all the ‘index.pug’ file (remember pug files are stored in the views folder (default).
html
head
body
h2 Add email address
a(href='/all') All
form(method='POST' action='/add')
div
p Your email address
input#name.form-control(type='text', placeholder='Email address' required name='email')
p
button.btn.btn-primary(type='submit') Sign up
Then we have the ‘all.pug’ file which gives us an overview of entries in the database together with a link to delete or edit the entry
html
head
body
h2 List of email addresses
a(href='/') Add
table
each email in email_addresses
tr#email_list_item
td #{email.email}
td #{email.i}
td
a(href='delete?id=' + email._id) x
td
a(href='edit?id=' + email._id) e
We have an ‘edit.pug’ file to edit our document entries
html
head
body
h2 Update email address
a(href='/all') All
form(method='POST' action='/edit')
input#email.form-control(type='hidden', name='id' value=id)
div
p Edit email address
input#email.form-control(type='text', placeholder='Email address' required name='email' value=email_address)
p
button.btn.btn-primary(type='submit') Update
p
And finally we have our app.js nodejs application.
Remember that browsers will complain about an invalid certificate. For most browsrs you can add a security exception for the certificate.
Now we have to tell nodejs to make use of this certificate when starting the https server. We have to create an option object with two properties: ‘key’ and ‘cert’. When we create the https server we pass in this option object:
In this article i will talk about asynchronous nodejs functions. I use express as the routing middleware and pug as the template engine. The code below shows the setup for the nodejs snippets used later on in this post:
/*
* Setup:
* - use 'express' for routing
* - use 'fs' for reading file from filesystem
* - use 'pug' as the templating engine
*/
const express = require('express')
const fs = require('fs')
const app = express()
const port = 3000
app.use(express.json());
app.use(express.urlencoded({ extended: true }))
app.set('view engine', 'pug')
We have setup the nodejs app with the code above. Now we are going to add the routes that we want to publish in our example app, see the code below:
// render input form which posts to /read route
app.get('/', (req, res) => {
res.render('index')
}
)
// read the file
app.post('/read', async (req, res, next) => {
fs.readFile('./test.txt', (err, data) =>{
if (err) {
next(err)
}
next(data)
})
});
app.listen(port, () => console.log(`Listening on port ${port}`))
When we navigate to ‘http://localhost:3000’ the route ‘/’ is processed. This will render our index.pug file which has the following contents:
html
head
body
form(method='POST' action='/read')
div
p Your email address
input#name.form-control(type='text', placeholder='Email address' name='email')
p
button.btn.btn-primary(type='submit') Sign up
Pressing the ‘Sign up’ button will take us to the ‘http://localhost:3000/read’ route. As you can see this route will try to read the ‘./test.txt’ file, The fs.readFile is an asynchronous function. When the readFile operation is complete it will execute the callback that is provided as the second parameter.
When an error occurs fs.readFile will execute the callback with an error object (the data parameter will be ‘undefined’). The error object contains, as you expect, a description of the error:
Error: ENOENT: no such file or directory, open './does_not_exist.txt'
If you would like to have a complete stacktrace in your error object you should change the line ‘next(err)’ to next(new Error(err)’, the resulting error after this change:
Error: Error: ENOENT: no such file or directory, open './does_not_exist.txt'
at ReadFileContext.fs.readFile [as callback] (/home/uname/node/app.js:21:18)
at FSReqWrap.readFileAfterOpen [as oncomplete] (fs.js:420:13)
The code above makes use of the callback function of the readFile method on the fs object. An alternative way of accomplishing the same result is to use promises. Lets have a look at the code below:
app.post('/read', (req, res, next) => {
new Promise((resolve,reject) => {
fs.readFile('./test.txt', 'utf-8', (err, data) => {
if (err) {
// in the case of error, reject the promise, executing the catch code block
reject(err);
}
else {
// in the case of success, resolve the promise, exectuing the then code block
resolve(data);
}
});
})
.then((data) => {
res.send(data)
})
.catch((err) => {
res.send(err)
})
});
As you can see we wrap our fs.readFile in a Promise object. A promise will be rejected on error (the file could not be found for example) and will be resolved on success. When a promise is rejected the catch code block will be executed. If the promise is resolved the then code block will be executed.
The advantage of using a promise is that we can wait on the result if we want to. The code below will wait for the readFile function to complete and then continues the /read handle after the wait promise line
app.post('/read', async (req, res, next) => {
let fileContent = null
let promise = new Promise((resolve,reject) => {
fs.readFile('./test.txt', 'utf-8', (err, data) => {
if (err) {
// in the case of error, reject the promise, executing the catch code block
reject(err);
}
else {
// in the case of success, resolve the promise, exectuing the then code block
resolve(data);
}
});
})
.then((data) => {
fileContent = data
})
.catch((err) => {
res.send(err)
})
await promise
if (fileContent) {
// the catch of the promise did not send(..) any text
res.send(fileContent)
}
});
First of all install VirtualBox guest additions (goto “Devices” -> “Insert Guest Additions CD Image…”) and reboot your machine. In VirtualBox goto “Devices” -> “Shared Folders” -> “Shared folder settings..”. Press the “plus” button to the right of the (empty) folder list. Choose your path and type a folder name (select auto mount and make permanent for convenience). Press OK
Now the shared folder is created but not active yet in your guest. Reboot your guest machine and go to the folder /media/sf_Sharename (virtualbox creates a folder in /media with the name of your share with a sf_ prefix.
When you cd into this directory you probable get an access denied error. To solve this add your account to the vboxsf group with this command: sudo usermod -aG vboxsf [username]
To mount the shared folder with specific gid (change gid to your desired value):
sudo mount -t vboxsf Downloads /mnt/shared -o umask=0022,gid=999
When you get the infamous “protocol error” double check your shared folder name. Be sure to use the uid and gid assigned to your account (check /etc/passwd and /etc/groups for your uid and gid). If you do not use uid and gid the shared folder may have the wrong access rights.