Asp.net core 6

De Banane Atomic
Aller à la navigationAller à la recherche

Links

dotnet CLI

Bash.svg
# help
dotnet -h

# get versions of .NET SDK, Host, .NET runtimes installed
dotnet --info

# list available templates (webapi, blazor)
dotnet new
# create a new Web API project
dotnet new webapi -o [project-name] --no-https

# create a solution file and folder
dotnet new sln -o MySolution
# add a project to the solution
dotnet sln add ./MyProject/MyProject.csproj

# add a reference to another project
dotnet add reference ../otherproject/otherproject.csproj

# restore and build
dotnet build
# restore, build and run
dotnet run
# force url and port (default: http://127.0.0.1:5000)
ASPNETCORE_URLS="http://127.0.0.1:5123" dotnet run         # linux
dotnet run --ASPNETCORE_ENVIRONMENT Development            # powershell
$env:ASPNETCORE_URLS="http://127.0.0.1:5123" ; dotnet run  # powershell

# run all the unit tests
dotnet test

Log

By default, an app is logging into Console, Debug, EventSource, EventLog (only when running on Windows).

Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureLogging(logging =>
        {
            logging.ClearProviders();  // remove all the providers: no more logs
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
Cs.svg
public class MyClass
{
    private readonly ILogger logger;

    public MyClass(ILogger<MyClass> logger)
    {
        this.logger = logger;
    }

    public void MyMethod()
    {
        logger.LogDebug("Debug");
        logger.LogTrace("Trace");
        logger.LogInformation("Info");
        logger.LogWarning("Warn");
        logger.LogError("Error");
        logger.LogCritical("Fatal");
    }
}
config.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",  // log level par défaut
      "Microsoft": "Warning",    // pour les loggers commençant par Microsoft
      "MyClass": "Debug"         // pour les loggers commençant par MyClass
    }
  }

Built-in logging providers

Console
  • VS code "DEBUG CONSOLE" tab
  • In the console window when the app is run with dotnet run
Debug Debug window when debugging in Visual Studio
On Linux journal or /var/log/message or /var/log/syslog
EventSource Event Tracing for Windows (ETW)
EventLog Windows Event Log
TraceSource System.Diagnostics.TraceSource libraries and providers ()
Azure App Service text files in an Azure App Service app's file system and to blob storage in an Azure Storage account

journal

mar 19 17:53:10 hostname logtest[29321]: fail: LogTest.Controllers.ItemController[0]
mar 19 17:53:10 hostname logtest[29321]:       Log message

mar 19 17:58:27 hostname logtest[29321]: fail: Microsoft.AspNetCore.Server.Kestrel[13]
mar 19 17:58:27 hostname logtest[29321]:       Connection id "...", Request id "...": An unhandled exception was thrown by the application.
mar 19 17:58:27 hostname logtest[29321]: System.Exception: exception message
mar 19 17:58:27 hostname logtest[29321]:    at LogTest.Controllers.ItemController.ThrowException() in /dev-path/LogTest/Controllers/ItemController.cs:line 41

apache log

  • access.log is well filled by each query
  • error.log is never filled

Log dans un fichier - Serilog.Extensions.Logging.File

Ajoute Serilog.RollingFile comme provider, tous les log seront loggués dans un fichier.
NuGet → Serilog.Extensions.Logging.File

Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddFile(Configuration.GetSection("Logging"));

    loggerFactory.AddFile(
        "MyApplication-{Date}.log",
        LogLevel.Error,                       // default: LogLevel.Information
        fileSizeLimitBytes: 1024 * 1024 * 5,  // MaxSize, default 1GB (1024 * 1024 * 1024)
        retainedFileCountLimit: 2,            // MaxNbFiles, default: 32
        isJson: true);                        // default: false
config.json
{
  "Logging": {
    "PathFormat": "MyApplication-{Date}.log",
    "LogLevel": {
      "Default": "Error",
      "Microsoft": "Information"
    },
    "FileSizeLimitBytes": 5242880,
    "RetainedFileCountLimit": 2,
    "Json": true
  }
}
MyApplication-20180501.log
2018-05-01T14:45:26.9980829+02:00 0HLDFC5FFGHTQ:00000004 [INF] Info (859a4ace)
MyApplication-20180501.log
{
    "@t": "2018-05-01T12:51:41.3576555Z",
    "@m": "Info",
    "@i": "859a4ace",
    "SourceContext": "MyApp.Data.MyAppRepository",
    "ActionId": "7e10a1a8-7ad5-4c85-9e24-eca298b66616",
    "ActionName": "MyApp.Controllers.ListController.Add (MyApp)",
    "RequestId": "0HLDFC8VQARGR:00000008",
    "RequestPath": "/List/Add"
}

Serilog and Dependency Injection

Utiliser plutôt Serilog.Extensions.Logging.File avec la DI par défaut fournit par Microsoft.Extensions.Logging
Program.cs
static void Main(string[] args)
{
    Log.Logger = new LoggerConfiguration()
        .WriteTo.Console(outputTemplate: customTemplate)
        .CreateLogger();

    var services = new ServiceCollection();
    services.AddTransient<IMyClass, MyClass>()
            .AddSingleton(Log.Logger);

    var provider = services.BuildServiceProvider();

    var myObject = provider.GetService<IMyClass>();
    myObject.MyMethod();
MyClass.cs
public class MyClass : IMyClass
{
    private readonly ILogger logger;

    public MyClass(ILogger logger)
    {
        this.logger = logger.ForContext<MyClass>();
    }

    public void MyMethod()
    {
        logger.Debug("message");

Nuget packages

Bash.svg
# ajouter un package NuGet
dotnet add package <package_name>
dotnet add package <package_name> --version 2.0.0

# lists the NuGet packages
dotnet list package
# lists packages that have newer versions available
dotnet list package --outdated

# supprimer un package
dotnet remove package <package_name>

Directory.Build.targets

Allows you to manage the versions of Nuget packages in a single centralized file.

Directory.Build.targets
<!-- Directory.Build.targets file has to be at the solution root -->
<Project>
  <ItemGroup>
    <PackageReference Update="AutoMapper" Version="10.1.1" />
MyProject/MyProject.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <!-- remove the version -->
    <PackageReference Include="AutoMapper" />

Configuration

Sources des settings par défaut dans l'ordre de lecture:

  1. appsettings.json
  2. appsettings.<EnvironmentName>.json
  3. UserSecrets in Development environment
  4. Environment variables
  5. Command-line arguments
Les settings de la source suivante écrasent ceux déjà chargés.
Startup.cs
using Microsoft.Extensions.Configuration;  // Microsoft.Extensions.Configuration.Abstractions.dll

public IConfiguration Configuration { get; }

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

// recherche Key, si Key n'a pas été trouvé affecte default value à la variable value
var value = Configuration.GetValue<string>("Key", "default value");
var value = Configuration["Key"];

// pour les connection strings
Configuration.GetConnectionString("SqlServer1")

// dotnet add package System.Data.SqlClient
var builder = new SqlConnectionStringBuilder(Configuration.GetConnectionString("SqlServer4")); // from appsettings.json
builder.Password = Configuration["DbPassword"]; // from secrets storage in Dev and appsettings.json in Prod 
var connectionString = builder.ConnectionString;

appsettings.json

appsettings.json
{
  "Key": "Value",
  "ConnectionStrings": {
    "SqlServer1": "Server=(localdb)\\MSSQLLocalDB;Database=MyDb;Integrated Security=True;MultipleActiveResultSets=True",
    "SqlServer2": "Server=(localdb)\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;",
    "SqlServer3": "Server=localhost;Database=MyDb;User=sa;Password=pwd;",
    "SqlServer4": "server=localhost;database=MyDb;user=sa;",
    "MySql": "Server=localhost;Database=MyDb;User=root;Password=pwd;",
    "Sqlite": "Data Source=MyDb.db"
  },
  "DbPassword": "****"
}

File Configuration Provider

Useful for using a file other than appsettings.config or for Console projects.

Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureHostConfiguration(configHost =>
        {
            configHost.SetBasePath(Directory.GetCurrentDirectory());
            configHost.AddJsonFile("passwords.json", optional: true);
        })
        .UseStartup<Startup>();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
           .ConfigureAppConfiguration((hostingContext, config) =>
           {
               config.AddJsonFile("passwords.json", optional: false, reloadOnChange: false);
               config.AddXmlFile("appsettings.xml", optional: true, reloadOnChange: true);
           })
           .UseStartup<Startup>();
}

Safe storage of app secrets in development in ASP.NET Core

Useful in dev environment to not store password in source code.

Bash.svg
# aller dans le dossier contenant le *.csproj
cd MyProject

# adds a UserSecretsId element within a PropertyGroup of the .csproj file
dotnet user-secrets init

# set a secret
# put a space as first character so the command line is not saved in the history
 dotnet user-secrets set "key" "secret"
# the whole connection string
 dotnet user-secrets set "ConnectionStrings:SqlServer" "server=localhost;database=test;user=test;password=****"
# only the connection string password
 dotnet user-secrets set "DbPassword" "****"

# the secrets are stored in a JSON configuration file in a system-protected user profile folder on the local machine
# Windows: %APPDATA%\microsoft\UserSecrets\<userSecretsId>\secrets.json
# Linux:   ~/.microsoft/usersecrets/<userSecretsId>/secrets.json


# list all secrets of the current project
dotnet user-secrets list

# remove a secret in the current project
dotnet user-secrets remove "key"

# remove all secrets of the current project
dotnet user-secrets clear
WebApi/Program.cs
// The user secrets configuration source is automatically added in development mode
// when the project calls CreateDefaultBuilder to initialize a new instance of the host with preconfigured defaults.
// CreateDefaultBuilder calls AddUserSecrets when the EnvironmentName is Development
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
Console/Program.cs
public static IConfigurationRoot Configuration { get; set; }

private static void Main()
{
    var builder = new ConfigurationBuilder();
    // dotnet add package Microsoft.Extensions.Configuration.UserSecrets
    builder.AddUserSecrets<Program>();
    Configuration = builder.Build();

Environment variables

Command-line

Bash.svg
dotnet MyApp MyKey="value"

Environnement

L’environnement utilisé est contenu dans la variable environnement ASPNETCORE_ENVIRONMENT 3 valeurs sont supportées par le framework:

  • Development
  • Staging
  • Production (valeur par défaut)
Bash.svg
# lancer le service avec l'environnement Development
ASPNETCORE_ENVIRONMENT=Development dotnet run
dotnet run --ASPNETCORE_ENVIRONMENT Development

Configuration pour la ligne de commande:

Properties/launchSettings.json
{
  "profiles": {
    "my_project": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Configuration pour vs code:

.vscode/launch.json
"configurations": [
    {
        "name": ".NET Core Launch (web)",
        "env": {
            "ASPNETCORE_ENVIRONMENT": "Development"
        }
    }
]

Configuration pour vs:
VS → Projet Properties → Debug → Environment variables → ASPNETCORE_ENVIRONMENT = Development

Deployment Scripts

MyProject.csproj
<Target Name="MyPublishScripts" AfterTargets="BeforePublish">
  <Exec Command="npm install" />
  <Exec Command="gulp minify" />
  <Exec Command="ng build" />
</Target>

Publish

Applications can be published in two different modes: self-contained or runtime-dependent.

  • self-contained includes the .NET Core runtime and libraries, and your application and its dependencies. Users of the application can run it on a machine that doesn't have the .NET Core runtime installed.
  • runtime-dependent includes only your application itself and its dependencies. Users of the application have to separately install the .NET Core runtime.
Bash.svg
# publier dans le dossier bin\Debug\netcoreapp2.0\publish
dotnet publish -c Release -r linux-arm --self-contained false
# -c : configuration : Debug (default), Release
# -r <RUNTIME_IDENTIFIER> : publishes the application for a given runtime
# --self-contained [true|false] : publishes the .NET Core runtime with the application (default true if a runtime identifier is specified and the project is an executable project)
# -o /path/folder : output directory (default: bin/[configuration]/[framework]/publish or bin/[configuration]/[framework]/[runtime]/publish )

# rendre l'exécutable exécutable pour tous
chmod +x bin/Release/netcoreapp2.1/ubuntu.16.04-x64/publish/myproject
MyProject.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
    <RuntimeIdentifier>win10-x64,ubuntu-x64,linux-x64</RuntimeIdentifier>
  </PropertyGroup>
Par défaut la publication se fait en mode runtime-dependent: le dossier contient seulement les dll du projet.
En spécifiant le runtime la publication se fait en mode self-contained: le dossier contient toutes les dll nécessaire ainsi qu'un exécutable adapté au runtime.

RID Catalog (list): ubuntu-18.04-arm64, arch-x64, win10-x64

Define the port on deployed application

By defaut the following urls are used: http://localhost:5000 https://localhost:5001

appsettings.json
{
  "Kestrel": {
    "EndPoints": {
      "Http": {
        "Url": "http://localhost:6000"

It can also be configured in the service file.

Linux

Service file

/etc/systemd/system/kestrel-myproject.service
[Unit]
Description=My Project description

[Service]
WorkingDirectory=/srv/myproject
ExecStart=/srv/myproject/myproject
ExecStart=/usr/bin/dotnet /srv/myproject/myproject.dll
Restart=always
RestartSec=10  # Restart service after 10 seconds if dotnet service crashes
KillSignal=SIGINT
SyslogIdentifier=myproject
User=http
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=ASPNETCORE_URLS=http://localhost:5001
Environment=ConnectionStrings__MyConnectionString=server\x3dlocalhost\x3bdatabase\x3dMyDb\x3buser\x3dMyUser\x3bpassword\x3dMyPwd

[Install]
WantedBy=multi-user.target
Bash.svg
# escape value as connection string
systemd-escape "<value-to-escape>"

Version

MyProject.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AssemblyVersion>1.1.1.1</AssemblyVersion>
    <FileVersion>2.2.2.2</FileVersion>
    <Version>3.3.3.3-xyz</Version>
  </PropertyGroup>
</Project>
Cs.svg
typeof(Startup).Assembly.GetName().Version;  // 1.1.1.1
Assembly.GetEntryAssembly().GetName().Version;  // 1.1.1.1
Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version;  // 2.2.2.2
Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;  // 3.3.3.3-xyz

Git

.gitignore
# Build results
bin/
obj/

# .NET Core
Properties/launchSettings.json

# Log files
*.log

ARM

Bash.svg
# créé un dossier /bin/Debug/netcoreapp2.0/linux-arm/publish/ contenant l’exécutable et ses dépendances pour ARM
dotnet publish -r linux-arm

# copier le dossier linux-arm sur le serveur ARM
cd linux-arm/publish
./MyApp
# [1]    8952 segmentation fault (core dumped)  ./MyApp
# systemd-coredump[8962]: Failed to parse PID "0": Numerical result out of range

# Unhandled Exception: [1]    9060 segmentation fault (core dumped)  ./MyApp
# systemd-coredump[9083]: Failed to parse PID "0": Numerical result out of range

Description

Réécriture de ASP.NET:

  • moderne, plus besoin de conserver la compatibilité
  • léger, l'application ne charge que ce dont elle a besoin (packages MVC, Logging, Identity)
  • performant
  • cross platform et open source
  • MVC et Web API, plus de WebForm

ASP.NET Core.jpg

Migrate from ASP.NET Core 5.0 to 6.0

Update the target framework in every project file

MyProject.csproj
<PropertyGroup>
  <TargetFramework>net6.0</TargetFramework>

Update package references

Directory.Build.targets
<Project>
  <ItemGroup>
    <PackageReference Update="Microsoft.AspNetCore.*" Version="6.0.1" />
    <PackageReference Update="Microsoft.Extensions.*" Version="6.0.1" />
    <PackageReference Update="Microsoft.EntityFrameworkCore*" Version="6.0.1" />

New hosting model

Apps migrating to 6.0 don't need to use the new minimal hosting model.
Using Startup and the Generic Host used by the ASP.NET Core 3.1 and 5.0 templates is fully supported.

Remove Startup.cs.

Program.cs
public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    // IServiceCollection services → builder.Services
    // IConfiguration Configuration → builder.Configuration

    var app = builder.Build();
    // IWebHostEnvironment env → app.Environment
    // IConfiguration Configuration → app.Configuration
    using (var scope = app.Services.CreateScope())
    {
        var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
        context.Database.Migrate();
    }

    app.Run();

Update the debug launcher

.vscode/launch.json
"configurations": [
    {
        // If you have changed target frameworks, make sure to update the program path.
        "program": "${workspaceFolder}/MyProject/bin/Debug/net6.0/MyProject.dll",

Update the settings

appsettings.json
"Logging": {
  "LogLevel": {
    // "Microsoft": "Warning",
    // "Microsoft.Hosting.Lifetime": "Information"
    "Microsoft.AspNetCore": "Warning"

Installation

Archlinux

Bash.svg
pacman -S aspnet-runtime dotnet-sdk
# installe dotnet-host dotnet-runtime aspnet-runtime dotnet-sdk
Ajouter ~/.dotnet/tools à PATH pour que les dotnet tools fonctionnent depuis le shell.
Les assembly sont installées dans /usr/share/dotnet/shared/Microsoft.NETCore.App/x.y.z
/etc/environment
# disable .net core telemetry
DOTNET_CLI_TELEMETRY_OPTOUT=1

Windows

  • Visual Studio Installer → Workloads → ASP.NET and web development
Powershell.svg
# version de .net core installée
dotnet --version

Ubuntu ARM