SERVIZIO WEB ASPNET CORE PUBBLICATO IN AMBIENTE LINUX

(Visual studio code windows, Debian 10 Buster, Virtual Box, Nginx, Proxy inverso Kestrel, MariaDB, HeidiSQL)

1. Visual Studio Code

Installiamo visual studio code e le estensioni C# e C# Extensions

2. Creiamo un progetto vuoto

Apriamo una finestra dos (prompt dei comandi) o PowerShell, spostarsi nella directory in cui si vuole creare il progetto e inizializzarne uno vuoti con il comando:

dotnet new web -n WebApi01

per la lista completa dei template disponibili digitare dotnet new -–help

3. Impostiamo il progetto in Visual Studio Code

Apriamo Visual Studio Code e selezioniamo la cartella del progetto appena creato

Verrà visualizzato il progetto vuoto appena creato in VS Code

4. Creiamo un installazione di Linux Debian 10 in Virtual Box

Per l’installazione utilizzeremo un immagine iso minimale dato che useremo solo la console e non installeremo nessuna interfaccia grafica

L’immagine iso è prelevabile dal sito ufficiale

https://www.debian.org/CD/netinst/

5. Installiamo il web server nginx

apt update
apt install nginx

avviamo nginx con il comando

service nginx start

e verifichiamo che sia raggiungibile con il browser della macchina host puntando l’ip (ip a per visualizzare le informazioni della scheda di rete)

Per comodità si può configurare un indirizzo ip fisso modificando il file di configurazione /etc/network/interfaces

6. Configuriamo il proxy inverso kestrel

Editiamo e modifichiamo il file di configurazione /etc/nginx/sites-available/default

server {
    listen        80;
    server_name   example.com *.example.com;
    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

server {
    listen   80 default_server;
    # listen [::]:80 default_server deferred;
    return   444;
}

Verifichiamo la sintassi del file di configurazione con il comando

nginx -t

nel caso si riscontrino errori potrebbero essere indicati i numeri delle righe in cui sono presenti (se si utilizza l’editor nano si possono visualizzare, in fase di editing, i numeri delle righe digitando ALT+à)

se non sono presenti errori si può far rileggere il file di configurazione al server

nginx -s reload

7. Installiamo il runtime aspnet core

Aggiungere la chiave di firma del pacchetto Microsoft all’elenco delle chiavi attendibili:

wget https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb

installiamo il runtime 3.1

apt-get install -y aspnetcore-runtime-3.1
apt-get install -y dotnet-sdk-3.1

8. Installiamo e configuriamo MariaDB

Per installare il database server eseguiamo il comando:

apt install -y mariadb-server

eseguiamo il seguente script per disabilitare l’accesso da remoto per l’utente root

mysql_secure_installation

Change the root password - n
Remove anonymous users - y
Disallow root login remotely – y
Reload privilege tables now – y

Creiamo un nuovo utente con gli stessi privilegi dell’utente root accedendo alla shell del database

mysql

MariaDB [(none)]> GRANT ALL ON *.* TO 'admin'@'192.168.1.%' IDENTIFIED BY 'password' WITH GRANT OPTION;
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> exit;

9. Abilitiamo l’accesso remoto al database MariaDB

Di default MariaDB accetta connessioni solo dal localhost quindi controlliamo se è possibile accedere da remoto al database server, con il comando

netstat -an | grep 3306

Se il database risulta in ascolto nel localhost (127.0.0.1) dovremmo modificare il file di configurazione

/etc/mysql/mariadb.conf.d/50-server.cnf 

modificando il valore del bind-address da 127.0.0.1 a 0.0.0.0

Dopo aver effettuato la modifica riavviamo il servizio di MariaDB

systemctl restart mariadb

ed eseguendo nuovamente il comando

netstat -an | grep 3306

controlleremo l’avvenuta modifica

10. Installazione HeidiSQl e accesso al database server

Scarichiamo ed installiamo il client HeidiSQL per accedere al database dall’indirizzo

https://www.heidisql.com/

creiamo una nuova sessione compilando i dati per l’accesso al database server

Nel caso si stia tentando di accedere con l’utente admin e non si riesca ad accedere, bisogna verificare come è stato configurato l’utente.

Al punto 8 abbiamo creato l’utente admin specificando che fa parte della rete 192.168.1.% (% wildcard che indica tutti gli ip) quindi se si sta tentando di accedere da un client di una rete differente, bisogna aggiornare i dettagli dell’utente accedendo alla shell del database server:

mysql

visualizziamo i dati dell’utente admin:

MariaDB [(none)]>SELECT Host, User FROM mysql.user WHERE User=’admin’;

e nel caso aggiorniamo il dettaglio dell’host:

MariaDB [(none)]>UPDATE mysql.user SET Host=’%’ WHERE User=’admin’;

MariaDB [(none)]>FLUSH PRIVILEGES;

11. Creiamo un database di test

Accedendo alla sessione di HeidiSQL creata, clicchiamo con il tasto destro sul nome del server, selezioniamo nuovo database e impostando il nome TestDB

Allo stesso modo, cliccando con il tasto destro del mouse sul nome del database appena creato, creiamo una tabella chiamata Utenti

Cliccando ora con il tasto destro nell’area relativa alle colonne, possiamo creare le colonne:

Creiamo due colonne:

Id – int – not null
Descrizione – varchar – not null

Clicchiamo il tasto Salva per memorizzare le colonne create

12. Creiamo il servizio

Apriamo VS Code e il progetto vuoto precedentemente creato

Impostiamo la connessione al database nel file appsettings.json

Installiamo i pacchetti per utilizzare l’entity framework nel progetto.

Dal menù Terminal di vs code selezioniamo New Terminal e dalla finestra di terminale creata installiamo i seguenti pacchetti:

D:\...\WebApi01> dotnet add package MySql.Data.EntityFrameworkCore
D:\...\WebApi01> dotnet add package Microsoft.EntityFrameworkCore.Design

Utilizziamo il comando dotnet-ef (da netcore 3.0) per la creazione dei files Model del database

D:\...\WebApi01> dotnet-ef dbcontext scaffold "server=IP_SERVER;port=3306;user=admin;password=USER_PASSWORD;database=TestDB" MySql.Data.EntityFrameworkCore -o Models -f

nel progetto verrà creata la cartella Models e i files TestDBContext.cs e Utenti.cs

Nel file TestDBContext.cs viene creata la classe derivata da DbContext dove viene esposta la proprietà DbSet

Nel file Utenti.cs viene creata la classe che rappresenta gli oggetti presenti nel database

Nel file TestDBContext.cs c’è un warning relativo alla stringa di connessione che espone in chiaro la password dell’utente, per la connessione. Possiamo cancellare tutto il metodo OnConfiguring dato che la stringa di connessione al database l’abbiamo impostata nell’appsettings.json e configureremo la connessione nel file Startup.cs

File Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using WebApi01.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace WebApi01
{
    public class Startup
    {

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDbContext<TestDBContext>(options =>
            options.UseMySQL(Configuration.GetConnectionString("DefaultConnection")));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

File TestController.cs

using Microsoft.AspNetCore.Mvc;
using WebApi01.Models;
using System.Linq;

namespace WebApi01.Controllers
{

    [ApiController]
    [Route("api/test")]
    public class TestController : ControllerBase
    {

        private TestDBContext dBContext;

        // L'instanza di DbContext è passata via dependency injection
        public TestController(TestDBContext context)
        {
            this.dBContext = context;
        }

        [HttpGet]
        public IActionResult test()
        {
            return Ok(dBContext.Utenti.ToList());
        }

    }
}

File Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace WebApi01
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>()
                    .UseUrls(new[] {"http://0.0.0.0:5001"});
                });
    }
}

13 Aggiorniamo il database

dotnet-ef migrations add CreateIdentityModels
dotnet-ef database update

14. Pubblichiamo l’applicazione

dotnet publish -c Release -r linux-x64

Copiamo i files presenti nella cartella

D:\..\WebApi01\bin\Release\netcoreapp3.1\linux-x64\publish

nella directory del server

/var/www/webapi01

15. Creiamo il servizio Linux

nano /etc/systemd/system/kestrel-webapi01.service

[Unit]
Description=Example .NET Web API App running on Ubuntu

[Service]
WorkingDirectory=/var/www/helloapp
ExecStart=/usr/bin/dotnet /var/www/helloapp/helloapp.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

avviamo il servizio

systemctl start kestrel-helloapp.service

il servizio creato è raggiungibile all’indirizzo

http://192.168.1.184:5001/api/test

è possibile controllare lo stato del servizio con il comando

systemctl status kestrel-helloapp.service

RIFERIMENTI:

https://docs.microsoft.com/it-it/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-3.1

https://docs.microsoft.com/it-it/dotnet/core/install/linux-debian

https://www.digitalocean.com/community/tutorials/how-to-install-mariadb-on-debian-10

https://webdock.io/en/docs/how-guides/how-enable-remote-access-your-mariadbmysql-database

https://docs.microsoft.com/it-it/dotnet/core/deploying/

Byte[] To Image

public static System.Windows.Controls.Image ByteArrayToImage(byte[] bytesImg)
{
	System.Windows.Media.Imaging.BitmapImage bitmapImage = new System.Windows.Media.Imaging.BitmapImage();
	bitmapImage.BeginInit();
	bitmapImage.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad;
	bitmapImage.StreamSource = new MemoryStream(bytesImg);
	bitmapImage.EndInit();

	System.Windows.Controls.Image img = new System.Windows.Controls.Image();
	img.Source = bitmapImage;

	return img;
}

Image To Byte[]

public static byte[] BitmapImageToByteArray(System.Windows.Controls.Image img)
{
	byte[] ImgTemp;

	System.Windows.Media.Imaging.BitmapImage bitmapImage = new System.Windows.Media.Imaging.BitmapImage();
	bitmapImage = ((System.Windows.Media.Imaging.BitmapImage)img.Source);

	System.Windows.Media.Imaging.JpegBitmapEncoder encoder = new System.Windows.Media.Imaging.JpegBitmapEncoder();
	encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bitmapImage));
	using (MemoryStream ms = new MemoryStream())
	{
		encoder.Save(ms);
		ImgTemp = ms.ToArray();
	}

	return ImgTemp;
}

test code

Test snippet code

private ContextMenuStrip menuPanelTastoDx()
{
	ContextMenuStrip contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip();
	ToolStripMenuItem item;

	item = new ToolStripMenuItem();
	item.Text = "Cancella Immagine";
	item.ImageIndex = 0;
	contextMenuStrip1.Items.Add(item);

	item = new ToolStripMenuItem();
	item.Text = "Dettagli";
	item.ImageIndex = 1;
	contextMenuStrip1.Items.Add(item);

	contextMenuStrip1.Items.Add(new ToolStripSeparator());

	item = new ToolStripMenuItem();
	item.Text = "Cancella tutte le descrizioni";
	item.ImageIndex = 2;
	contextMenuStrip1.Items.Add(item);
	
	contextMenuStrip1.ItemClicked += contextMenuStrip1_ItemClicked;

	return contextMenuStrip1;
}