Docker CLI and Compose Information Message

April 3, 2024

From the Docker CLI when using a Docker Context for Azure and you do

docker compose up

You get the following message

Docker Compose's integration for ECS and ACI will be retired in November 2023. Learn more: https://docs.docker.com/go/compose-ecs-eol/

What that means, I think, is that Docker have stopped supported Compose from the Docker CLI. I’m not sure if you can use/is-supported Docker Compose from the azure CLI

Docker Containers and Azure – An Introduction

April 3, 2024

The Problem

I wanted to start using Docker Containers on Azure as I had been using them locally for ages and found them very useful. I had progressed to using Docker Compose for multi-container apps and even for single-container apps as the compose-file contained all the information needed to run the container rather than remember port-mappings, volumes etc for the docker run command.

However….the documentation for containers on Azure is confusing. It’s simple to get a container up and running but it’s not so simple to understand what/where/why. Hence this article

more “Docker Containers and Azure – An Introduction”

Serilog in .Net Core 6

September 25, 2022

The Problem

I wanted to add Serilog to a WebAPI using .Net Core 6. The “startup” has changed from .Net Core 3 and the “wiring” up of services so it wasn’t clear to me how to get it all working.

The Solution

After some head scratching and searching I work out that you needed from this article and this article (thanks to the authers)

So I created a method

protected void ConfigureLogging(WebApplicationBuilder appBuilder)
{
appBuilder.Host.UseSerilog((ctx, lc) => lc
.ReadFrom.Configuration(ctx.Configuration)
.WriteTo.Console()
);

}

This method is called from the program.cs “startup” code.

Placed the following in appsetttings.json

{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"DRNJ": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Filter": [
{
"Name": "ByExcluding",
"Args": {
"expression": "@mt = 'An unhandled exception has occurred while executing the request.'"
}
}
],
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "c:\\temp\\log\\apilog-.txt",
"rollingInterval": "Day"
}
}
]
},
"AllowedHosts": "*"
}

 

And, voila, it all worked

 

Serilog And .Net Core 3.1

September 25, 2022

The Problem

I found configuring Serilog for .Net Core quite complex and poorly documented. All I wanted to do was to pipe log messages to a text file.

The Solution

After some searching I found this project.

The following code was added to Program.cs

public static void Main(string[] args)
{ 
CreateHostBuilderWithSimpleSerilog(args).Build().Run();
}

public static IHostBuilder CreateHostBuilderWithSimpleSerilog(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((context, builder) =>
{
//----------------------------------------------------------------------
// Simple SeriLog |
// Using https://github.com/serilog/serilog-extensions-logging-file |
//----------------------------------------------------------------------
builder.AddFile(context.Configuration.GetLoggerConfiguration());
})
.ConfigureWebHostDefaults(webBuilder =>
{
if (bool.Parse(Environment.GetEnvironmentVariable("USE_HTTP_SYS") ?? "false"))
{
webBuilder.UseHttpSys(options =>
{
options.Authentication.Schemes = AuthenticationSchemes.None;
options.Authentication.AllowAnonymous = true;
});
}
webBuilder.UseStartup<Startup>();
});

Along with

 public static class StartupConfiguration
    {

        public static IConfigurationSection GetLoggerConfiguration(this IConfiguration config)
        {
            return config.GetSection("Logging");
        }

        public static T Get<T>(this IConfiguration config, string name)
        {
            return config
                 .GetSection(name)
                 .Get<T>();
        }
    }

And then the Appsettings.json section for logging

 

"Logging": {
"OutputTemplate": "{Timestamp:o} {RequestId,13} [{Level:u3}] {Message} Properties: {Properties:j} ({EventId:x8}){NewLine}{Exception}",
"PathFormat": "Logs/MyLog-{Date}.log",
"MinimumLevel": "Information",
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
}

 

And Voila. Logging to file works and Dependency Injection of Serilog is wired up too.

Raspberry Pi 4 and Ubuntu and SSD

November 16, 2021

The Problem

I made the mistake of trying to install Ubuntu on an SSD on an already working Raspberry Pi 4. The device as Raspian on it and was working normally for over six months. However, I was playing with docker and wanted some features not available in Raspbian. Rather than flash an SD Card I thought that I would use a Samsung 840 Pro 500GB SSD that I had lying around.

I followed the instructions from Ubuntu I tried to install Ubuntu Server 21 64Bit. The SSD was written correctly but when I booted the Pi 4 the boot took ages and I got many messages of the type

EXT4-fs error (device nvme0n1p7): ext4_find_entry:1463: inode #525023

on the console. I tried Ubuntu 20 instead, this would not boot. I tried workstation 21 and it showed the same issues as server. So I reverted to Ubuntu 21 server.

Investigation

I searched for P i4 Ubuntu SSD issues and found this article. So at least I was not alone in my problems.

After more searching I found an article that suggested adding

pcie_aspm=force

or

nvme_core.default_ps_max_latency_us=5500

To the grub configuration.

Great…apart from it seems that Ubuntu on a Pi 4 doesn’t seem to use grub (perhaps I am wrong) so I couldn’t update any grub boot configuration.

A bit of searching at turns out that on the Pi you can use “quirks” mode for the USB controller. All you need is the device ID….

Following this article I found out how to get this ID for my USB enclosure.

The Solution

I flashed the SSD with a clean install of Ubuntu Server 21 (on my Windows 10 desktop). The SSD has a boot device which is readble from WIndows. I edited cmdline.txt and added

usb-storage.quirks=152d:0578:u

to the end of the single line in that file

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=LABEL=writable rootfstype=ext4 elevator=deadline rootwait fixrtc quiet splash usb-storage.quirks=152d:0578:u

The 152d:0578 is the ID of my USB controller.

Plug the SSD into the Pi 4 and, voila, it boots and works

 

 

NHIbernate, Sybase and .NET Core

June 28, 2021

The Problem

I was working on a system utilising .NET 4.x and Sybase. This combination is not supported by Entity Framework so NHibernate was used.

It was planned to migrate the project to .NET Core, however a number of issues were encountered ustilising NHibernate.

Much investigation was performed, espcially using Resharper to disassemble the Sybase driver

The solution is shown below. Voila, you can use .NET Core and NHibernate and Sybase.

Solution Attempt Number 1

Code

  • Create WebAPI project on DotNet Core 3.1
  • Add references to NHibernate 5.2.7

Errors

Discussion

It appears that the NHibernate code cannot find the the Sybase ASE driver Sybase.ADONet2.AseClient installed as part of the nuget package

 

Solution Attempt Number 2

Code

As per Verrsion 1 but with

  • NHibernate 5.2.7 Source code downloaded and .csproj added to solution and references instead of NHibernate nuget package (so can step through code)
  • Reference added to Sysbase DLLs

Errors

Discussion

An exception is thrown in the Sybase.AdoNet2.ASeClient.dll in the constructor which calls LoadLibraries():

NB Disassembly of the driver done by ReSharper

The error is thrown at the line 252 on the Directory.GetAccessControl method – this is not supported in .Net Core:

https://stackoverflow.com/questions/41584932/support-for-file-security-in-net-core

The “workaround” suggested does not appear to work within the context of the Driver (not known why but driver might run in a restricted execution context). This article also discusses the driver error and that some System.IO methods have been removed in dotnetcore.

The Real Solution

Code

  • Add AdoNetCore.AseClient nuget package – this is a 3rd party Sybase ASE driver
  • Reference NHibernate nuget version 5.2.7
  • Add Driver classes as below

These files  are “driver” classes customised to use the AdoNetCore.AseClient driver. These are “copies” of the equivalent files in the NHibernate source code with the following modifications

The above specification for connection.driver_class is necessary as “full assembly specification” giving assembly name and class name otherwise NHibernate doesn’t know in which DLL to “looK” for the class.

The CustomDialect class was changed to reference these files:

 

Discussion

he  connection.driver_class specification in SybaseASECoreDialect was the only “tricky” thing to “get right”.  In the NHibernate “virgin” SysbaseASE15Dialect only specifies the “Driver” class name for the connection.driver_class

 

The NHibernate can “find” Nhibernate.Driver.SybaseAseClientDriver as it is contained within the NHibernate DLL. Using the custom classes it is necessary to specify the “full name” as the code is in separate assembly

API Versioning and Swagger

June 28, 2021

The Problem

API Versioning was added to a Microsoft C# Web API project .NET Core.

[ApiVersion("2.1")]
public class MyController: ApiController
{
}

The Swagger page for the API then did not list the API endpoints

The Solution

Simply add the [ApiController] attribute to the class

[ApiVersion("2.1")]
[ApiController]
public class MyController: ApiController
{
}

 

 

Android Srongswan Configuration I

March 14, 2021

The Problem

I was trying to configure a Strongswan IPSec client on my Android phone to tunnel to StrongsWan/FreesWan on a linux server. It almnost worked but I got an error:

No trusted public RSA key found for XXXX

Where XXX was my X.509 certficate details.

Strange – as my client and server and CA certificates were all self generated and worked with Windows IPSec client talking to the same IPSec server.

The Solution

I searched and found this article

What it was suggesting was that the Android Strongswan client:

that the configured server address/hostname is contained
in the certificate as subjectAltName. 

If that's not the case you have to configure the server 
identity manually in the VPN profile, either to a 
subjectAltName that's actually contained in the certificate 
(if the server finds a config with that identity) 
or to the full subject DN of the server certificate

What does this mean in reality? (NB Thanks to the person who answered the question)

FIrst, get the Certifcate details via

 openssl x509 -noout -in certificate.pem -subject

The result will be something of the form

subject=C = GB, ST = YY, L = XX, O = ABCD Certificates, OU = ABCD CA, CN = ZZZ, emailAddress = info@dumphuc.com

edit this and remove spaces to

C=GB,ST=YY,L=XX,O=ABCD Certificates,OU=ABCDCA,CN=ZZZ,emailAddress=info@dumphuc.com

On the Android phone enter this into the “Server Identity” line of the VPN profile

 

Configure the other VPN settings in the Android client and, voila, the VPN will work

.Net Core WebAPI Request and Response Logging

July 6, 2020

The Problem

In .NET 4.x it was straightforward to read the request body of an incoming HTTP request, log the information and also log the outgoing response back to the caller. I thought it would be just as easy in .Net Core, how wrong a I was as Microsoft do their utmost to hide the Request body from you.

The Solution

Accessing the Request and Response bodies must be done in HTTP Middleware, and, after reading much on StackOverflow I came up with the following solution (and my thanks to all the original authors and people who helped me). I cannot take credit for this code, I just adapted it from ideas on StackOverflow

Remember 

For .Net Core 2.1  use  context.HttpContext.Request.EnableRewind();
For .Net Core 3.1  context.HttpContext.Request.EnableBuffering();

 

/// <summary>
/// Want to be able to read the Request.Body to get the incoming JSON
/// In .net 4 this was more or less simple. In Core it has changed
/// Need to be able to "rewind" of Request.Body - have to do that
/// Thanks to
/// https://stackoverflow.com/questions/40494913/how-to-read-request-body-in-a-asp-net-core-webapi-controller
/// in request pipeline middleware
/// 
/// For ASP.NET 2.1
/// context.HttpContext.Request.EnableRewind();
/// For ASP.NET 3.1
/// context.HttpContext.Request.EnableBuffering();
///
/// See also https://stackoverflow.com/questions/43403941/how-to-read-asp-net-core-response-body/43404745
/// </summary>
public class LogRequestResponseMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;

private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
private const int ReadChunkBufferLength = 4096;

private bool LogRequest;
private bool logResponse;

public LogRequestResponseMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, bool requestLogging, bool responseLogging)
{
//--------------------------------------------------------------------
// Config |
//--------------------------------------------------------------------

this.LogRequest = requestLogging;
this.logResponse = responseLogging;

_next = next;
_logger = loggerFactory.CreateLogger<LogRequestResponseMiddleware>();

//--------------------------------------------------------------------
// Create stream to use in place of HttpContext.Response.Body stream |
// which is write-only |
//--------------------------------------------------------------------

_recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
}


public virtual async Task Invoke(HttpContext context)
{
//-----------------------------------------------------
// Request |
//-----------------------------------------------------

if (this.LogRequest)
{
//-------------------------------------------------
// Turn on Buffering/Rewind |
//-------------------------------------------------
context.Request.EnableBuffering();

//-----------------------------------------------------
// Log Request |
//-----------------------------------------------------
await this.LogRequestAsync(context);
}


//-----------------------------------------------------
// Response |
//-----------------------------------------------------
if (this.logResponse)
{
//-----------------------------------------------------
// Log Response |
//-----------------------------------------------------
await this.LogResponseAsync(context);
}
else
{
//-----------------------------------------------------
// Fire next in chain |
//-----------------------------------------------------
await _next.Invoke(context);
}

}


/// <summary>
/// Log the incoming request
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task LogRequestAsync(HttpContext context)
{
using (var requestStream = _recyclableMemoryStreamManager.GetStream())
{
var origPosition = context.Request.Body.Position;
context.Request.Body.Position = 0;
await context.Request.Body.CopyToAsync(requestStream);
context.Request.Body.Position = origPosition;

_logger.LogInformation($"Http Request Information:{Environment.NewLine}" +
$"Schema:{context.Request.Scheme} " +
$"Host: {context.Request.Host} " +
$"Path: {context.Request.Path} " +
$"QueryString: {context.Request.QueryString} " +
$"Request Body: {ReadStreamInChunks(requestStream)}");
}
}


private async Task LogResponseAsync(HttpContext context)
{
//--------------------------------------------------------
// Keep reference to original body |
//--------------------------------------------------------
var originalBody = context.Response.Body;

using (var responseStream = _recyclableMemoryStreamManager.GetStream())
{
//-----------------------------------------------------
// Replace existing with read/write stream |
//-----------------------------------------------------
context.Response.Body = responseStream;

//-----------------------------------------------------
// Fire next in chain |
//-----------------------------------------------------
await _next.Invoke(context);

//-----------------------------------------------------
// Get Result Back |
//-----------------------------------------------------
string resp = ReadStreamInChunks(responseStream);

//------------------------------------------------------
// Write it back to original Response Body |
//------------------------------------------------------

byte[] response = Encoding.ASCII.GetBytes(resp);
await originalBody.WriteAsync(response, 0, response.Length);

//------------------------------------------------------
// Log It |
//------------------------------------------------------

_logger.LogInformation($"Http Response Information:{Environment.NewLine}" +
$"Schema:{context.Request.Scheme} " +
$"Host: {context.Request.Host} " +
$"Path: {context.Request.Path} " +
$"QueryString: {context.Request.QueryString} " +
$"Response Body: {resp}");
}

//-----------------------------------------------------------
// Restore |
//-----------------------------------------------------------
context.Response.Body = originalBody;
}


private static string ReadStreamInChunks(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
string result;
using (var textWriter = new StringWriter())
using (var reader = new StreamReader(stream))
{
var readChunk = new char[ReadChunkBufferLength];
int readChunkLength;
//do while: is useful for the last iteration in case readChunkLength < chunkLength
do
{
readChunkLength = reader.ReadBlock(readChunk, 0, ReadChunkBufferLength);
textWriter.Write(readChunk, 0, readChunkLength);
} while (readChunkLength > 0);

result = textWriter.ToString();
}

return result;
}

}

.NET Core WebAPI CORS

July 6, 2020

The Problem

How hard can it be to configure CORS in a .NET Core 3.1 WebAPI? The answer…quite hard.

The Microsoft documentation shows how to configure CORS and it should be straightforward. However, my experience and the exeperience of many others on StackOverflow have shown me that all is far from simple….Although the solution, when finally found was remarkably simple.

Code

CORS can be configured on the ConfigureServices method in Startup.cs with more or less:

services.AddCors(options =>
{
options.AddPolicy(name: "myCORSPolicy",
builder =>
{
    builder.WithOrigins(this.ApiConfiguration.CorsOrigins.ToArray());

     builder.AllowAnyMethod();
     builder.AllowAnyHeader();
     builder.AllowCredentials();
});

});

and in the Configure method

 app.UseCors("myCORSPolicy");

First Problem

So..I’m on an internal development network and I want to allow all origins (i.e. “*”) and AllowCredentials (as I want to use Active Directory).

Wrong!

The combination of .WithOrigins(“*”) and AllowCredentials is expressly forbidden and will generate a run-time exception.

Second Problem – Trailing Slashes

The .WithOrigins takes a “list” of origins i.e. URLs which can access your API. These must not have trailing slashes, e.g.

http://mydomain.com  - works

http://mydomain.com/ - CORS will not allow access from this origin

Microsoft do mention this in their documentation. however, it is far from clear and easily overlooked.