Atilla Tanrikulu

I am an experienced software engineer and architect living in Germany. I’m passionate about distributed scalable enterprise web-based microservices/applications and delivering great user experiences. I have created some amazing enterprise-level applications that many people have used and hopefully enjoyed.

Articles

Java Quick Reference Apache Kafka Tutorial Guvenli Kod Gelistirme Making an Enterprise Scale Angular Project Step by Step Nightly SQL Server Database Backup with command line batch file and windows scheduler AOP Framework without proxy pattern IdentityServer Nedir Middleware Pattern With Csharp And Javascript Docker most used commands Online Proje Dokumantasyonu, Docker, Nginx, mdwiki How to use Github Pages for static websites Inheritance with JavaScript, EC6 (ECMAScript 6, ECMAScript 2015) Object oriented javascript and Inheritance Singleton Pattern with Javascript Factory Pattern with Javascript Open terminal here mac os x service IdentityServer4-Angular-6-integration JMater notlari, kurulum ve kullanim Learn Jekyll in 12 Steps Make Mac Application with Automater from sh script Make spotlight index markdown or code files OAuth 2.0 Nedir (RFC6749) Using Custom CSS and Custom JavaScript to an Angular Project Cross Platform Desktop Application With .Net Core 2x and Angular 6x front-end projects with nodejs gulp bower yeoman and angularjs Host Asp.Net Core on Linux with Apache Redis kurulumu ve ayarlari Useful Mac OS Apps Choosing internet connection on multiple interface windows Name Server Kurulumu How to define domain name for your dynamic IP SQL table data compare, and prepare insert satements Useful Git Commands TFS ile Otomatik deployment yapmak Spring Boot Tutorial Sql server icin maliyetli sorgularin tespit edilmesi Arama Motoru Optimizasyonu (SEO) My installed mac apps

Cross Platform Desktop Application With .Net Core 2x and Angular 6x

We will use 4 framework for our desktop application which are;

  • Net Core: Open source cross platform framework, developed by Microsoft and the community
  • Angular CLI: Angular CLI is a command line interface to scaffold and build angular apps using nodejs
  • Electron: Electron enables you to create cross platform desktop applications with pure JavaScript by providing a runtime (NodeJS)
  • NodeJs: JavaScript runtime environment, on various platforms (Windows, Linux, Unix, Mac OS X, etc.)

1. Install NodeJS

If you want to create Angular project, you must install nodejs first. Because Angular and Angular CLI uses nodejs development environment.

After the installation, you will have npm (Node Package Manager) on your terminal/command line

2. Create Project Directories, Install Electron


// create project directory
mkdir desktopapp

// create src directory
mkdir src

// create project directories
cd src

mkdir angular, netcore, electron

// Create NodeJS Project
npm init
// After above command, it will ask some questions
// response apropriate information for npm init questions

// install electron libraries with nodejs
cd electron
npm i -D electron@latest

In this example it created initial package.json then I modified like this

{
  "name": "cross-platform-desktop-application-with-netcore2x-and-angular6x",
  "version": "1.0.0",
  "description": "Cross Platform Desktop Application with .Net Core 2 and Angular 6",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "build": "build"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/atillatan/cross-platform-desktop-application-with-netcore2x-and-angular6x.git"
  },
  "keywords": [
    "Cross",
    "Platform",
    "Desktop",
    "Application",
    "with",
    ".Net",
    "Core",
    "2",
    "and",
    "Angular",
    "6"
  ],
  "author": "atillatan",
  "license": "MIT",
  "dependencies": {},
  "devDependencies": {
    "electron": "^2.0.3",
    "electron-builder": "^20.15.1",
    "electron-packager": "12.1.0"
  },
  "bugs": {
    "url": "https://github.com/atillatan/cross-platform-desktop-application-with-netcore2x-and-angular6x/issues"
  },
  "homepage": "https://github.com/atillatan/cross-platform-desktop-application-with-netcore2x-and-angular6x#readme",
  "build": {
    "appId": "Cross-Platform-Desktop-Application-with-Net-Core-Angular",
    "directories": {
      "buildResources": "../../assets",
      "output": "../../dist/electron"
    },
    "extraResources": {
      "from": "../../dist/netcore/",
      "to": "dist/netcore",
      "filter": [
        "**/*"
      ]
    },
    "mac": {
      "category": "Cross Platform Desktop Application with .Net Core 2 and Angular 6"
    },
    "win": {
      "target": [
        "nsis"
      ]
    }
  }
}

install node packages, that is defined in package.json

npm install

3. Install Angular CLI

// go to angular directory
cd ../angular

// install angular
npm install -g @angular/cli

Create dist directory

// go to root directory
cd ../

// create dist, build directories
mkdir dist, build

Editor I will use Visual Studio Code for editing the project open project with Visual Studio Code

code .

In the electron project directory, Create main.js and paste below code electron application is a nodejs aplication, it’s has only single js file main.js

const {
    app,
    BrowserWindow,
    Menu
} = require('electron');

const path = require('path');
const url = require('url');
// process.env.NODE_ENV = 'production';

let mainWindow;
const os = require('os');
var apiProcess = null;

// #region Events
app.on('ready', init);

app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') {
        app.quit()
    }
});

app.on('activate', function () {
    if (mainWindow === null) {
        createMainWindow()
    }
});

process.on('exit', function () {
    console.log('Exit electron application..');
    apiProcess.kill();
});
// #endregion

function init() {
    startNetCoreApi();
    createMainWindow();
}

function createMainWindow() {
    console.log('start');
    //create new window
    mainWindow = new BrowserWindow({
        width: 920,
        height: 600,
        frame: true,
        resizable: true
    });

    mainWindow.loadURL('http://localhost:5000/index.html');
    // Quit app when closed
    mainWindow.on('close', function (e) {
        mainWindow = null;
    })
    // Create menu  
    const mainMenuTemplate = [{
        label: 'File',
        submenu: [{
            label: 'Quit',
            accelerator: process.platform == 'darwin' ? 'Command+Q' : 'Ctrl+q',
            click() {
                app.quit();
            }
        }]
    }];

    // if mac, add empty object to menu
    if (process.platform == 'darwin') {
        mainMenuTemplate.unshift({});
    }

    // Add developer tools item if not in production
    if (process.env.NODE_ENV !== 'production') {
        mainMenuTemplate.push({
            label: 'Developer Tools',
            submenu: [{
                    label: 'Toggle Devtools',
                    accelerator: process.platform == 'darwin' ? 'Command+i' : 'Ctrl+i',
                    click(item, focusedWindow) {
                        focusedWindow.toggleDevTools();
                    }
                },
                {
                    role: 'reload'
                }
            ]
        })
    }

    // Build menu from temmplate 
    const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
    // Insert menu
    Menu.setApplicationMenu(mainMenu);
}


function startNetCoreApi() {
    var spawn = require('child_process').spawn;

    var wokingDirectory = path.join(__dirname, '../../dist/netcore');

    if(process.env.NODE_ENV === 'production'){
        wokingDirectory = path.join(__dirname, '../dist/netcore');
    }

    var apiPath = path.join(wokingDirectory, '/netcore.exe');

    if (os.platform() === 'darwin') {
        apiPath = path.join(wokingDirectory, '//netcore');
    }

    console.log(apiPath);

    apiProcess = spawn(apiPath, {
        cwd: wokingDirectory
    });

    apiProcess.stdout.on('data', (data) => {
        console.log(`stdout: ${data}`);
        if (mainWindow == null) {
            console.log('createMainWindow');
            createMainWindow();
        }
    });
};

4. Create .Net Core Project

  • Install .Net Core

Download latest version of .net core framework from https://www.microsoft.com/net/download/windows and install it. After the installation you will have dotnet command environment in you terminal/command prompt

  • Open your terminal or, windows PowerShell, then execute following commands
// Go to netcore project
cd src

dotnet new webapi -n netcore
cd netcore
dotnet restore
dotnet build
dotnet run

Add netcore/Controllers/SpaController.cs like this

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace netcore.Controllers
{
    [Route("[controller]/[action]")]
    public class SpaController : Controller
    {
        // GET spa/getusers
        [HttpGet]
        public IEnumerable<dynamic> GetUsers()
        {
            return new List<dynamic> {
                new { Name = "Bob", FamilyName = "Smith", Age = 32, email = "test1" },
                new { Name = "Alice", FamilyName = "Smith", Age = 33, email = "test2" },
                new { Name = "Amy", FamilyName = "Smith", Age = 32, email = "test3" },
                new { Name = "Adam", FamilyName = "Smith", Age = 32, email = "test4" }
             };
        }
    }
}

Add netcore/Controllers/DefaultController.cs like this

using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;

namespace netcore.Controllers
{
    public class DefaultController : Controller
    {
        public IActionResult Index()
        {
            return File("~/index.html", "text/html");
        }
    }
}

Change startup.cs like this

using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;

namespace netcore
{
    public class Startup
    {
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration) => Configuration = configuration;

        public void ConfigureServices(IServiceCollection services)
        {
            services
            .AddCors()
            .AddMvc()
            .AddJsonOptions(options =>
             {
                 options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
                 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
                 options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
             });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseCors(policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

            app.UseStaticFiles();

            app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "index.html" } });

            app.UseMvc(routes =>
           {
               routes.MapRoute(
                 name: "default",
                 template: "{controller}/{action}/{id?}");

               // Catch all Route - catches anything not caught be other routes
               routes.MapRoute(
                   name: "catch-all",
                   template: "{*url}",
                   defaults: new { controller = "Default", action = "Index" }
               );
           });

        }
    }
}

and test it http://localhost:5000/api/values

5. Create Angular Project

  • Open your terminal or windows PowerShell, then execute following commands
// go to `src` directory
cd src
ng new angular --skip-tests --routing
// --routing : add routing functionality to project
// --skip-test: skip test functionality

cd angular
ng serve --open

Using the –open (or just -o) option will automatically open your browser on http://localhost:4200/.

You can also read the following tutorial https://angular.io/guide/quickstart for Angular installation.

Add HtmlClientModule to app.module.ts like this

//  angular/src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Add Materialize to index.html like this

// angular/src/index.html
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Cross Platform Desktop Application</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
</head>

<body>
  <app-root>Loading...</app-root>
</body>

</html>

Change src/app/app.component.ts like this

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  head = 'Cross Platform Desktop Application';
  users = null;

  constructor(private http: HttpClient) {
    this.listUsers();
  }

  listUsers(): void {
    this.http.get<any>(`http://localhost:5000/spa/getusers`).subscribe(data => {
      this.users = data;
    });
  }

}

Change src/app/app.component.html like this

<nav>
  <div class="nav-wrapper">
    <a class="brand-logo center"></a>
  </div>
</nav>

<table class="striped"> 
  <thead>
    <tr>
      <th>Name</th>
      <th>Family Name</th>
      <th>Age</th>
      <th>Email</th>
    </tr>
  </thead>

  <tbody>
    <tr *ngFor="let user of users">
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
</table>

Change output directory from angular.json

{
    "projects": {
        "angular": {
            "architect": {
                "build": {
                    ...
                    "options": {
                        ...
                        "outputPath": "../../dist/netcore/win/wwwroot",
                        ...
                    }
                    ...
                }
            }
    }
}

6. Integrate with Electron

// run.cmd
@echo off

:: publish netcore project
cd src/netcore
dotnet restore
dotnet build
dotnet publish -r win10-x64 --self-contained --output ../../dist/netcore

:: publish angular project
cd ../angular
:: npm install

cmd /c ng build --base-href ./


:: publish electron project
cd ../electron
::npm install

cmd /c npm start

// run.sh
#!/bin/sh

# publish netcore project
cd src/netcore
dotnet restore
dotnet build
dotnet publish -r osx.10.11-x64 --self-contained --output ../../dist/netcore

# publish angular project
cd ../angular
npm install

ng build --base-href=./ 

# publish electron project
cd ../electron
npm install

npm start

project source code: https://github.com/atillatan/cross-platform-desktop-application-with-netcore2x-and-angular6x

/assets/img/interface.PNG

Date: 2018-05-25 10:20:00 +0000