.NET 6 – dotnet new console

สร้างโปรเจ็กส์ด้วย Template แบบ Console Application

>  dotnet new console -o Console6

จะได้ไฟล์

  1. Console6\Console6.csproj
  2. Console6\Program.cs

ไฟล์ Console6.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

ไฟล์ Program.cs

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

X509Certificate2 กับ .NET 6 Console

ดูข้อมูลของ certificate

using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Xml;

namespace ConsoleApp6;

class Program
{
    public static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(Program));

    static void Main()
    {
        try
        {
            XmlDocument log4netConfig = new XmlDocument();
            log4netConfig.Load(File.OpenRead("log4net.config"));
            var repo = log4net.LogManager.CreateRepository(Assembly.GetEntryAssembly(),
                       typeof(log4net.Repository.Hierarchy.Hierarchy));
            log4net.Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);

            log.Info("Hello World!");

            //Create X509Certificate2 object from .p12 file.
            X509Certificate2 x509 = new X509Certificate2(@"C:\file.p12", "password",
                X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);

            //Create X509Certificate2 object from .cer file.
            //byte[] rawData = ReadFile(args[0]);
            //x509.Import(rawData);

            //Log information contained in the certificate.
            log.Info(string.Format("Subject: {0}", x509.Subject));
            log.Info(string.Format("Issuer: {0}", x509.Issuer));
            log.Info(string.Format("Version: {0}", x509.Version));
            log.Info(string.Format("Valid Date: {0}", x509.NotBefore));
            log.Info(string.Format("Expiry Date: {0}", x509.NotAfter));
            log.Info(string.Format("Thumbprint: {0}", x509.Thumbprint));
            log.Info(string.Format("Serial Number: {0}", x509.SerialNumber));
            log.Info(string.Format("Friendly Name: {0}", x509.PublicKey.Oid.FriendlyName));
            log.Info(string.Format("Public Key Format: {0}", x509.PublicKey.EncodedKeyValue.Format(true)));
            log.Info(string.Format("Raw Data Length: {0}", x509.RawData.Length));
            log.Info(string.Format("Certificate to string: {0}", x509.ToString(true)));
            log.Info(string.Format("Certificate to XML String: {0}", x509.PublicKey.Key.ToXmlString(false)));

            ////Add the certificate to a X509Store.
            //X509Store store = new X509Store();
            //store.Open(OpenFlags.MaxAllowed);
            //store.Add(x509);
            //store.Close();
        }
        catch (Exception ex)
        {
            log.Error(ex.Message);
            log.Error(ex.ToString());
        }
    }
}

Stamp CA ลงไฟล์ PDF

ติดตั้ง Package

PM> Install-Package iTextSharp -Version 5.5.13.2
PM> Install-Package System.Windows.Extensions -Version 6.0.0
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using System.Reflection;
using System.Runtime.Versioning;
using System.Security.Cryptography.X509Certificates;
using System.Xml;

namespace ConsoleApp6;

[SupportedOSPlatform("windows")]
class Program
{
    public static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(Program));

    static void Main()
    {
        try
        {
            XmlDocument log4netConfig = new XmlDocument();
            log4netConfig.Load(File.OpenRead("log4net.config"));
            var repo = log4net.LogManager.CreateRepository(Assembly.GetEntryAssembly(),
                       typeof(log4net.Repository.Hierarchy.Hierarchy));
            log4net.Config.XmlConfigurator.Configure(repo, log4netConfig["log4net"]);

            log.Info("Hello World!");

            //Create X509Certificate2 object from .p12 file.
            X509Certificate2 x509 = new X509Certificate2(@"C:\file.p12", "password",
                X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);

            //Create X509Certificate2 object from .cer file.
            //byte[] rawData = ReadFile(args[0]);
            //x509.Import(rawData);

            //Add the certificate to a X509Store.
            X509Store store = new X509Store();
            store.Open(OpenFlags.MaxAllowed);
            store.Add(x509);


            X509Certificate2Collection sel = X509Certificate2UI.SelectFromCollection(store.Certificates, null, null, X509SelectionFlag.SingleSelection);
            // X509Certificate2Collection sel = store.Certificates;

            X509Certificate2 cert = sel[0];

            Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
            Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] {
            cp.ReadCertificate(cert.RawData)};

            //IExternalSignature externalSignature = new X509Certificate2Signature(cert, "SHA-1");
            //IExternalSignature externalSignature = new X509Certificate2Signature(cert, DigestAlgorithms.SHA256);

            // var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(x509.PrivateKey).Private;
            var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(x509.GetRSAPrivateKey()).Private;
            IExternalSignature externalSignature = new PrivateKeySignature(pk, "SHA-256");

            string pathToBasePdf = @"c:\tmp\InputPDF.pdf";
            string pathToSignPdf = @"c:\tmp\OutputPDF.pdf";
            //string pathToSignatureImage = "";
            PdfReader pdfReader = new PdfReader(pathToBasePdf);

            var signedPdf = new FileStream(pathToSignPdf, FileMode.Create);

            PdfStamper pdfStamper = PdfStamper.CreateSignature(pdfReader, signedPdf, '\0');
            PdfSignatureAppearance signatureAppearance = pdfStamper.SignatureAppearance;

            // signatureAppearance.SignatureGraphic = Image.GetInstance(pathToSignatureImage);
            signatureAppearance.SetVisibleSignature(new Rectangle(100, 100, 250, 150), pdfReader.NumberOfPages, "Signature");
            //signatureAppearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;
            signatureAppearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;

            MakeSignature.SignDetached(signatureAppearance, externalSignature, chain, null, null, null, 0, CryptoStandard.CMS);

            store.Close();
        }
        catch (Exception ex)
        {
            log.Error(ex.Message);
            log.Error(ex.ToString());
        }
    }
}

แปลงไฟล์ PDF เป็น Base64String กับ .NET 6 WebApi

สร้างโปรเจ็กส์แบบ ASP.NET Core Web API

เพิ่ม API Controller ชื่อ Values (Controllers/ValuesController.cs)

using Microsoft.AspNetCore.Mvc;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace WebApi1.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly ILogger<ValuesController> _logger;
        private IWebHostEnvironment _env;

        public ValuesController(ILogger<ValuesController> logger
            , IWebHostEnvironment env)
        {
            _logger = logger;
            _env = env;
        }

        // GET api/<ValuesController>/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            string rootPath;
            if (!string.IsNullOrEmpty(_env.WebRootPath))
                rootPath = _env.WebRootPath;
            else
                rootPath = _env.ContentRootPath;
            _logger.LogInformation($"rootPath  = {rootPath}");


            #region Convert byte[] to Base64String
            string pdfPathIP = System.IO.Path.Combine(rootPath, "resource/testIP.pdf");
            _logger.LogInformation($"pdfPathIP = {pdfPathIP}");
            byte[] bytesIP = System.IO.File.ReadAllBytes(pdfPathIP);
            string pdfBase64 = Convert.ToBase64String(bytesIP);
            #endregion


            #region Convert Base64String to byte[]
            string pdfPathOP = System.IO.Path.Combine(rootPath, "resource/testOP.pdf");
            _logger.LogInformation($"pdfPathOP = {pdfPathOP}");
            byte[] bytesOP = Convert.FromBase64String(pdfBase64);
            System.IO.File.WriteAllBytes(pdfPathOP, bytesOP);
            #endregion

            return pdfBase64;
        }
    }
}

วางไฟล์ทดสอบไว้ที่ resource/testIP.pdf

ทดลองเรียก https://localhost:7034/values/5

จะได้ไฟล์ resource/testOP.pdf

JWT Authentication ด้วย .NET 6 WebApi

สร้างโปรเจ็กส์แบบ ASP.NET Core Web API

ติดตั้ง Package Microsoft.IdentityModel.Tokens 6.15.0

PM> Install-Package Microsoft.IdentityModel.Tokens -Version 6.15.0

ติดตั้ง Package System.IdentityModel.Tokens.Jwt 6.15.0

PM> Install-Package System.IdentityModel.Tokens.Jwt -Version 6.15.0

สร้างคลาส Entities\User.cs

using System.Text.Json.Serialization;

namespace WebApi1.Entities
{
    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Username { get; set; }

        [JsonIgnore]
        public string Password { get; set; }
    }
}

สร้างคลาส Models\AuthenticateRequest.cs

using System.ComponentModel.DataAnnotations;

namespace WebApi1.Models
{
    public class AuthenticateRequest
    {
        [Required]
        public string Username { get; set; }

        [Required]
        public string Password { get; set; }
    }
}

สร้างคลาส Models\AuthenticateResponse.cs

using WebApi1.Entities;

namespace WebApi1.Models
{
    public class AuthenticateResponse
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Username { get; set; }
        public string Token { get; set; }


        public AuthenticateResponse(User user, string token)
        {
            Id = user.Id;
            FirstName = user.FirstName;
            LastName = user.LastName;
            Username = user.Username;
            Token = token;
        }
    }
}

สร้างคลาส Helpers\AppSettings.cs

namespace WebApi1.Helpers
{
    public class AppSettings
    {
        public string Secret { get; set; }
    }
}

สร้างคลาส Helpers\AuthorizeAttribute.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApi1.Entities;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]

public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = (User)context.HttpContext.Items["User"];
        if (user == null)
        {
            // not logged in
            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
        }
    }
}

สร้างคลาส Helpers\JwtMiddleware.cs

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using WebApi1.Services;

namespace WebApi1.Helpers
{
    public class JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly AppSettings _appSettings;

        public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
        {
            _next = next;
            _appSettings = appSettings.Value;
        }

        public async Task Invoke(HttpContext context, IUserService userService)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

            if (token != null)
                attachUserToContext(context, userService, token);

            await _next(context);
        }

        private void attachUserToContext(HttpContext context, IUserService userService, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
                    ClockSkew = TimeSpan.Zero
                }, out SecurityToken validatedToken);

                var jwtToken = (JwtSecurityToken)validatedToken;
                var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);

                // attach user to context on successful jwt validation
                context.Items["User"] = userService.GetById(userId);
            }
            catch
            {
                // do nothing if jwt validation fails
                // user is not attached to context so request won't have access to secure routes
            }
        }
    }
}

สร้างคลาส Services\UserService.cs

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WebApi1.Entities;
using WebApi1.Helpers;
using WebApi1.Models;

namespace WebApi1.Services
{
    public interface IUserService
    {
        AuthenticateResponse Authenticate(AuthenticateRequest model);
        IEnumerable<User> GetAll();
        User GetById(int id);
    }

    public class UserService : IUserService
    {
        // users hardcoded for simplicity, store in a db with hashed passwords in production applications
        private List<User> _users = new List<User>
        {
            new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" }
        };

        private readonly AppSettings _appSettings;

        public UserService(IOptions<AppSettings> appSettings)
        {
            _appSettings = appSettings.Value;
        }

        public AuthenticateResponse Authenticate(AuthenticateRequest model)
        {
            var user = _users.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);

            // return null if user not found
            if (user == null) return null;

            // authentication successful so generate jwt token
            var token = generateJwtToken(user);

            return new AuthenticateResponse(user, token);
        }

        public IEnumerable<User> GetAll()
        {
            return _users;
        }

        public User GetById(int id)
        {
            return _users.FirstOrDefault(x => x.Id == id);
        }

        // helper methods

        private string generateJwtToken(User user)
        {
            // generate token that is valid for 7 days
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
                //Expires = DateTime.UtcNow.AddDays(7),
                Expires = DateTime.UtcNow.AddMinutes(1),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }
}

แก้ไขไฟล์ appsettings.json

{
  "AppSettings": {
    "Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

แก้ไขไฟล์ Program.cs

using WebApi1.Helpers;
using WebApi1.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

ConfigurationManager configuration = builder.Configuration;

// configure strongly typed settings object
builder.Services.Configure<AppSettings>(configuration.GetSection("AppSettings"));

// configure DI for application services
builder.Services.AddScoped<IUserService, UserService>();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.UseMiddleware<JwtMiddleware>();

app.MapControllers();

app.Run();

สร้าง API Controller Controllers\UsersController.cs

using Microsoft.AspNetCore.Mvc;
using WebApi1.Models;
using WebApi1.Services;

namespace WebApi1.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private IUserService _userService;

        public UsersController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpPost("authenticate")]
        public IActionResult Authenticate(AuthenticateRequest model)
        {
            var response = _userService.Authenticate(model);

            if (response == null)
                return BadRequest(new { message = "Username or password is incorrect" });

            return Ok(response);
        }

        [Authorize]
        [HttpGet]
        public IActionResult GetAll()
        {
            var users = _userService.GetAll();
            return Ok(users);
        }
    }
}

ทดลองเรียกใช้งาน https://localhost:7078/Users จะได้

{
    "message": "Unauthorized"
}

ต้องไปเรียก https://localhost:7078/Users/authenticate เพื่อขอ token มาก่อน

Request

{
    "username": "test",
    "password": "test"
}

Response

{
    "id": 1,
    "firstName": "Test",
    "lastName": "User",
    "username": "test",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYmYiOjE2Mzk1OTM0OTYsImV4cCI6MTYzOTU5MzU1NiwiaWF0IjoxNjM5NTkzNDk2fQ.ingwuhAl4TB7n7lOMILt-hWpN07ggjwQu4VJF6Lky2U"
}

ถ้า username หรือ password ผิดจะได้

{
    "message": "Username or password is incorrect"
}

ทีนี้เรียก https://localhost:7078/Users พร้อมให้ค่า Bearer Token จะเรียกได้ละ (token มีอายุ 1 นาที ถ้าเกิน 1 นาทีก็จะ Unauthorized)

[
    {
        "id": 1,
        "firstName": "Test",
        "lastName": "User",
        "username": "test"
    }
]

Apache log4net™ กับ .NET 6 WebApi

สร้างโปรเจ็กส์แบบ ASP.NET Core Web API

ที่ไฟล์ Controllers/WeatherForecastController.cs เพิ่มโค๊ด _logger.* ไว้ดูผลการ log

using Microsoft.AspNetCore.Mvc;

namespace WebApi1.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {
            _logger.LogInformation("This is an INFORMATION message.");
            _logger.LogWarning("This is a WARNING message.");
            _logger.LogError("This is an ERROR message.");

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

เปิดหน้าต่าง Output โดยไปที่เมนู View | Output

ทดลองเรียก WeatherForecast เช่น https://localhost:7244/WeatherForecast แล้วดูที่หน้าต่าง Output

WebApi1.Controllers.WeatherForecastController: Information: This is an INFORMATION message.
WebApi1.Controllers.WeatherForecastController: Warning: This is a WARNING message.
WebApi1.Controllers.WeatherForecastController: Error: This is an ERROR message.

ติดตั้ง Package log4net และ Microsoft.Extensions.Logging.Log4Net.AspNetCore

PM> Install-Package log4net -Version 2.0.13
PM> Install-Package Microsoft.Extensions.Logging.Log4Net.AspNetCore -Version 6.0.0

สร้างไฟล์ log4net.config

<log4net>
	<root>
		<level value="ALL" />
		<appender-ref ref="file" />
	</root>
	<appender name="file" type="log4net.Appender.RollingFileAppender">
		<file value="net6demo.log" />
		<appendToFile value="true" />
		<rollingStyle value="Size" />
		<maxSizeRollBackups value="5" />
		<maximumFileSize value="10MB" />
		<staticLogFileName value="true" />
		<layout type="log4net.Layout.PatternLayout">
			<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
		</layout>
	</appender>
</log4net>

หรือ

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
    <file value="log/" />
    <datePattern value="yyMMdd'Jack.log'" />
    <staticLogFileName value="false"/>
    <appendToFile value="true"/>
    <rollingStyle value="Date"/>
    <maxSizeRollBackups value="100"/>
    <maximumFileSize value="15MB"/>
    <encoding value="UTF-8"/>
    <layout type="log4net.Layout.PatternLayout">
      <param name="ConversionPattern" value="%-5p%d{ HH:mm:ss} li:%line - [%method] %m  %n" />
    </layout>
  </appender>
  <root>
    <level value="ALL"/>
    <appender-ref ref="RollingLogFileAppender"/>
  </root>
</log4net>

.NET 5 แก้ไขไฟล์ Start.cs

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "EWBranchAPI", Version = "v1" });
            });

            ILoggerFactory _loggerFactory = (ILoggerFactory)new LoggerFactory();
            _loggerFactory.AddLog4Net();
            services.AddSingleton(_loggerFactory);
        }

.NET 6 แก้ไขไฟล์ Program.cs

var builder = WebApplication.CreateBuilder(args);

ILoggerFactory _loggerFactory = (ILoggerFactory)new LoggerFactory();
_loggerFactory.AddLog4Net();
builder.Services.AddSingleton(_loggerFactory);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

ทดลองเรียก WeatherForecast อีกครั้งจะได้ไฟล์ .log

2021-12-15 21:21:44,620 [.NET ThreadPool Worker] INFO WebApi1.Controllers.WeatherForecastController - This is an INFORMATION message.
2021-12-15 21:21:44,641 [.NET ThreadPool Worker] WARN WebApi1.Controllers.WeatherForecastController - This is a WARNING message.
2021-12-15 21:21:44,643 [.NET ThreadPool Worker] ERROR WebApi1.Controllers.WeatherForecastController - This is an ERROR message.