Why FileSystemWatcher doesn't work in Linux container watching Windows volume - c#

Given the program:
using System;
using System.IO;
namespace fsw_bug_poc
{
class Program
{
private static FileSystemWatcher _fileSystemWatcher;
static void Main(string[] args)
{
_fileSystemWatcher = new FileSystemWatcher("Watched", "*.*");
_fileSystemWatcher.Changed += Notify;
_fileSystemWatcher.Created += Notify;
_fileSystemWatcher.Deleted += Notify;
_fileSystemWatcher.Renamed += Notify;
_fileSystemWatcher.IncludeSubdirectories = true;
_fileSystemWatcher.EnableRaisingEvents = true;
Console.ReadKey(false);
}
private static void Notify(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"{e.FullPath} {e.ChangeType}");
}
}
}
The Dockerfile:
FROM mcr.microsoft.com/dotnet/core/runtime:2.2-stretch-slim AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
WORKDIR /src
COPY ["fsw-bug-poc.csproj", ""]
RUN dotnet restore "fsw-bug-poc.csproj"
COPY . .
WORKDIR "/src/"
RUN dotnet build "fsw-bug-poc.csproj" -c Release -o /app
FROM build AS publish
RUN dotnet publish "fsw-bug-poc.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENV DOTNET_USE_POLLING_FILE_WATCHER=true
RUN mkdir -p /app/Watched
VOLUME /app/Watched
ENTRYPOINT ["dotnet", "fsw-bug-poc.dll"]
According to this link adding ENV DOTNET_USE_POLLING_FILE_WATCHER=true to the Dockerfile fixes the FileSystemWatcher not working inside the container.
Even with this fix, FileSystemWatcher will not work when running a Linux container on Windows and mounting a shared driver to a volume:
docker build -t fsw-bug-poc .
docker run -it --rm -v C:\Shared:/app/Watched fsw-bug-poc
Modifying a file inside the container:
Modifying files in the shared volume folder:
Nothing happens!!
Can someone explain what is going on? The FileSystemWatcher is using a polling strategy, so it should work the same way, shouldn't it?

Switching to PhysicalFileProvider did the job. It seems to be a more portable implementation for file system watching strategies.
The current implementation of PhysicalFileProvider supports the DOTNET_USE_POLLING_FILE_WATCHER environment variable. I couldn't find any reference of it in FileSystemWatcher implementation.
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;
namespace fsw_bug_poc
{
class Program
{
private static PhysicalFileProvider _fileProvider;
private static IChangeToken _fileChangeToken;
static void Main(string[] args)
{
_fileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "."));
WatchForFileChanges();
Console.ReadKey(false);
}
private static void WatchForFileChanges()
{
_fileChangeToken = _fileProvider.Watch("*.*");
_fileChangeToken.RegisterChangeCallback(Notify, default);
}
private static void Notify(object state)
{
Console.WriteLine("File change detected");
WatchForFileChanges();
}
}
}

Related

How can I get the GitVersion version into my binary?

I am using GitVersion to version my C#.NET application. My application also has a -V option, to show the current version of the binary.
How can I get data from GitVersion into my application, so that it is updated each time I build?
I got it using a combination of a PowerShell script and a pre-build event:
The script is as follows (saved as gitversion.ps1 in the project dir:
$gitVersionJson = dotnet gitversion /output json
$By = [System.Text.Encoding]::Unicode.GetBytes($gitVersionJson)
$output =[Convert]::ToBase64String($By)
"using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
class GitVersion {
private static Dictionary<string, object> _values;
private static Dictionary<string, object> Values {
get {
if (_values == null) {
byte[] data = Convert.FromBase64String(""$output"");
string decodedString = Encoding.Unicode.GetString(data);
_values = JsonSerializer.Deserialize<Dictionary<string, object>>(decodedString);
}
return _values;
}
}
public static object MajorMinorPatch {
get {
return Values[""MajorMinorPatch""];
}
}
}
" | Out-File GitVersion.cs
"Generated GitVersion.cs" | Write-Output
Then in as a pre-build event, I added this in the Build settings:
powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File "$(ProjectDir)gitversion.ps1"
Or in myproject.csproj:
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File "$(ProjectDir)gitversion.ps1" " />
</Target>
This will create a GitVersion class, you can use in your code:
Console.WriteLine(GitVersion.MajorMinorPatch);

Starting a c# service with sc

Im writing a small selfcontained service for windows and macos using worker service template in c# visual studio.
Its using the same Codebase hence the check in the Program.cs
I've written the service, and it works on windows, when started from within visual studio.
I've published it using
dotnet publish .\WorkerServiceTest2\ -c Release -r win-x64 -- self-contained true /p:PublishSingleFile=true /p:PublishedTrimmed=true
and tried to install it using
runas /user:MYUSERNAME "sc.exe create WorkerServiceTest2 c:\Users\MYYUSERNAME\Documents\bla\bla\bla\WorkerServiceTest2.exe"
But it does not show up in the services list, and
sc.exe start WorkerServiceTest2
says this service is not installed.
Is there anywhere i can see how the sc.exe create worked out ?
Or perhaps someone can see what I'm doing wrong ?
Sincerely Thankyou
My Service Program.cs looks like this
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Runtime.InteropServices;
namespace WorkerServiceTest2
{
public class Program
{
public static void Main(string[] args)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){
Console.WriteLine("WinOS");
CreateHostBuilderWin(args).Build().Run();
} else
{
Console.WriteLine("MacOS");
CreateHostBuilderMac(args).Build().Run();
}
}
private static void configureServices(HostBuilderContext context, IServiceCollection services)
{
services.AddHostedService<Worker>();
}
public static IHostBuilder CreateHostBuilderWin(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
public static IHostBuilder CreateHostBuilderMac(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(configureServices);
}
}
My Worker.cs looks like this
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
using WorkerServiceTest2.SocketService;
namespace WorkerServiceTest2
{
public class Worker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//Her skal business logic være.
SocketServer socketServer = new SocketServer();
await socketServer.start();
}
}
}
}
This is a script that you can use. It will check if the service is installed or not. If it already exists, it will uninstall it and install the new one. Save it as MyScript.ps1 (or your own preference) and run like:
.\MyScript.ps1 -serviceName name_of_service -serviceUsername some_username -servicePassword some_password -binaryPath "C:\yourProgram.exe"
Script:
# Sample: howto run ps-script from power-shell:
#.\Install-WindowsService_v3.ps1 -serviceName aTestservice -serviceUsername some_username -servicePassword some_password -binaryPath "C:\yourProgram.exe"
param
(
[string]$serviceName,
[string]$serviceUsername,
[string]$servicePassword,
[string]$binaryPath,
[string]$startupType='Automatic',
[string]$dependsOn
)
$secpasswd = ConvertTo-SecureString $servicePassword -AsPlainText -Force
Write-Output "########################################"
Write-Output "Starting installation of windows service."
Write-Output "[serviceName] = $serviceName"
Write-Output "[serviceUsername] = $serviceUsername" -verbose
Write-Output "[binaryPath] = $binaryPath"
#Check Parameters
if (!$binaryPath) { throw "[binaryPath] parameter missing" }
if ((Test-Path $binaryPath)-eq $false)
{
Write-Output "Path doesn't exist: $binaryPath"
Write-Output "Service will not be installed."
throw [System.IO.FileNotFoundException] "$binaryPath doesn't exist."
}
# verify if the service already exists, and if yes remove it first
if (Get-Service $serviceName -ErrorAction SilentlyContinue)
{
Stop-Service -Name $serviceName
# using WMI to remove Windows service because PowerShell does not have CmdLet for this
$serviceToRemove = Get-WmiObject -Class Win32_Service -Filter "name='$serviceName'"
$serviceToRemove.delete()
Write-Output "Service $serviceName was stopped and uninstalled."
}
else
{
Write-Output "Service didn't exist on the server"
}
if ($startupType -eq "AutomaticDelayedStart" )
{
$startupType = "Automatic"
$enableDelayed = "true"
}
Write-Output "Installing service"
# creating credentials which can be used to run my windows service
$mycreds = New-Object System.Management.Automation.PSCredential ($serviceUsername, $secpasswd)
# creating windows service using all provided parameters
New-Service -name $serviceName -binaryPathName $binaryPath -displayName $serviceName -startupType $startupType -credential $mycreds -DependsOn $dependsOn
# Set "automatic delayed" after service was installed, since it is not a valid argument when using "New-Service"
if ($enableDelayed -eq "true" )
{
$command = "sc.exe config $serviceName start= delayed-auto"
$Output = Invoke-Expression -Command $Command -ErrorAction Stop
if($LASTEXITCODE -ne 0){
Write-Host "$Computer : Failed to set $serviceName to delayed start.
More details: $Output" -foregroundcolor red
$failedcomputers +=$ComputerName
} else {
Write-Host "$Computer : Successfully changed $serviceName
to delayed start" -foregroundcolor green
$successcomputers +=$ComputerName
}
}
# verify if the service exists after installation
if (Get-Service $serviceName -ErrorAction SilentlyContinue)
{
Write-Output "Installation complete."
}
else
{
throw "Installation failed."
}
Write-Output "########################################"
Also, in all my application I start them up like so:
static async Task Main(string[] args)
{
isService = !(Debugger.IsAttached || args.Contains("--console"));
IWebHost host = CreateWebHostBuilder(args).Build();
if (isService)
{
var hostService = new MyCustomWebService(host);
ServiceBase.Run(hostService);
}
else
{
await host.RunAsync();
}
}
public class MyCustomWebService: WebHostService
{
private ILogger<MyCustomWebService> logger;
public MyCustomWebService(IWebHost host) : base(host)
{
var loggerFactory = host.Services.GetService<ILoggerFactory>();
logger = loggerFactory.CreateLogger<MyCustomWebService>();
logger.LogInformation("Starting...");
}
protected override void OnStopped()
{
logger.LogInformation("Will stop now.");
base.OnStopped();
}
}
It requires Microsoft.AspNetCore.Hosting.WindowsServices
Further recommended reading:
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/windows-service?view=aspnetcore-5.0&tabs=visual-studio
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.windowsservices?view=aspnetcore-5.0

Docker compose does not work for Selenium CSharp as expected

I have Docker for Windows on my machine and I want to start with one HUB 2 nodes at a ubuntu client, one for FireFox and one for Chrome, and both are running in parallel. There for I have A CSharp code, It runs Perfectly Whaen I run the : HUB, node-chrome-debug, and node-firefox-debug seperatelly, one by one like this from Windows POWERSHELL: :
docker run -d -p 4446:4444 --name selenium-hub -P selenium/hub
docker run -d -P --link selenium-hub:hub selenium/node-chrome-debug
docker run -d -P --link selenium-hub:hub selenium/node-firefox-debug
But If I want do the SAME with Docker compose file. I come in problems:
The Question is : What is wrong with this Docker compose file:
version: '3'
services:
seleniumhub:
image: selenium/hub
ports:
- 4444:4444
firefoxnode:
image: selenium/node-firefox-debug
ports:
- 4577
links:
- seleniumhub:hub
chromenode:
image: selenium/node-chrome-debug
ports:
- 4578
links:
- seleniumhub:hub
I'm Running The following CSharp code:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;
using OpenQA.Selenium;
using System;
using Assert = NUnit.Framework.Assert;
namespace SeleniumParalelTest
{
[TestFixture]
[Parallelizable]
public class FirefoxTesting : Hooks
{
public FirefoxTesting() : base(BrowserType.Firefox)
{
}
[Test]
public void FirefoxGoogleTest()
{
Driver.Navigate().GoToUrl("https://www.selenium.dev/");
Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);
Driver.FindElement(By.XPath("//*[#id='banner-blm']/h2/strong"));
System.Threading.Thread.Sleep(20000);
}
}
[TestFixture]
[Parallelizable]
public class ChromeTesting : Hooks
{
public ChromeTesting() : base(BrowserType.Chrome)
{
}
[Test]
public void ChromeGoogleTest()
{
Driver.Navigate().GoToUrl("https://www.selenium.dev/");
Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);
Driver.FindElement(By.XPath("//*[#id='banner-blm']/h2/strong"));
System.Threading.Thread.Sleep(20000);
}
}
}

How to setup Jenkins with ASP .NET Core

I am trying to create a Pipeline on Jenkins, to automate my build, test and deploy process.
pipeline {
agent any
environment {
myVersion = '0.9'
}
tools {
msbuild '.NET Core 2.0.0'
}
stages {
stage('checkout') {
steps {
checkout([$class: 'GitSCM', ...])
}
}
stage('restore') {
steps {
bat 'dotnet restore --configfile NuGet.Config'
}
}
stage('build') {
steps {
bat 'dotnet build'
}
}
stage('publish') {
steps {
...
}
}
}
}
When trying to run the build, I get this error message from Jenkins:
'dotnet' is not recognized as an internal or external command,
operable program or batch file.
What do I have to change to make this environment work?
I added my .NET CORE path etc. to the Jenkins Settings for MSBuild.
What am I missing?
Solved it like this:
environment {
myVersion = '0.9'
dotnet = 'path\to\dotnet.exe'
}
and than replaced my command with the %dotnet% variable.

Passing parameters to my docker container

Here is what I have done:
Create a new standard .Net C# console project
static void Main(string[] args)
{
foreach (var arg in args)
Console.WriteLine(arg);
}
Rightclick on the project and select Add->Docker Support
Edit the project's dockerfile and change its ENTRYPOINT:
ENTRYPOINT ["C:\app\ConsoleApp1.exe", "Hello", "World"]
Set a breakpoint and Run
args is empty.
docker-compose did emit my entrypoint during the build:
1>Step 5/5 : ENTRYPOINT ["C:\app\ConsoleApp1.exe", "Hello", "World"]
What am I missing?
You can get the command line arguments this way:
Environment.GetCommandLineArgs();
This works also in a Docker container.

Categories

Resources