👋🏽 Code Cards
Background or Worker Services in .NET
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging( (context, builder) => builder.AddConsole())
.ConfigureServices(services =>
{
services.Configure<HostOptions>(options =>
{
// gracefull shut down time
options.ShutdownTimeout = TimeSpan.FromSeconds(30);
});
services.AddHostedService<Worker>();
})
.Build();
await host.RunAsync();
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
public Worker(ILogger<Worker> logger, IHostApplicationLifetime hostApplicationLifetime)
{
_logger = logger;
_hostApplicationLifetime = hostApplicationLifetime;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
try
{
// callback methods when host is gracefully shutting down the service
_hostApplicationLifetime.ApplicationStarted.Register(() => _logger.LogInformation("started"));
_hostApplicationLifetime.ApplicationStopping.Register(() => _logger.LogInformation("stopping"));
_hostApplicationLifetime.ApplicationStopped.Register(() => _logger.LogInformation("stopped"));
return base.StartAsync(cancellationToken);
}
catch (Exception e)
{
_logger.LogError(e.Message);
return Task.CompletedTask;
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
var stopWatch = Stopwatch.StartNew();
await base.StopAsync(cancellationToken);
// it will print 30 seconds if stopped with ctrl + c
_logger.LogInformation($"Worker Service Stopped in : {stopWatch.ElapsedMilliseconds}");
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
stoppingToken.Register(() => _logger.LogInformation($"Worker service token is canceled"));
while (!stoppingToken.IsCancellationRequested)
{
try
{
await DoWork();
}
catch (Exception e)
{
_logger.LogCritical(e, "I can not work anymore!");
/* Trigger graceful shutdown of service
This will respect time set on host.
services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds(30);
});
*/
}
finally
{
// if you can not recover, stop it.
// All hosted services in host will be stopped in reverse order of registration
_hostApplicationLifetime.StopApplication();
}
}
}
private async Task<bool> DoWork()
{
_logger.LogInformation("I started doing work!");
// press ctrl + c - after above message - ctrl + c is equal to StopService from Windows Host
// defualt graceful shutdown is 6 seconds
// work will never complete
await Task.Delay(50000);
_logger.LogInformation("I am done with work.");
return true;
}
}
Template Method Pattern by using C# Inheritance
public interface IAwsService{};
public interface IFileReader{};
public record CloudFileInfo(string SourcePath, string DestinationPath, string CloudTier);
public abstract class CloudFileMigration<T> //Its abstract thus can not be used directly, thus must be inherited.
{
private readonly IAwsService _awsService;
private readonly IFileReader _fileReader;
// All subclasses will have to provide these services or dependencies
protected CloudFileMigration(IAwsService awsService, IFileReader fileReader)
{
_awsService = awsService;
_fileReader = fileReader;
}
// A Template Method or Invariant Trait of the Pattern
public async Task Migrate()
{
var items = await GetItems();
await Task.WhenAll(items.Select(MigrateItem));
async Task MigrateItem(T item)
{
var cloudFileInfo = await GetCloudFileInfo(item);
await UploadToAws3(cloudFileInfo);
var hasUpdateItemSucceeded = await UpdateItem(item, cloudFileInfo);
if (!hasUpdateItemSucceeded)
//handle failure
await DeleteFromAws3(cloudFileInfo);
}
}
private Task UploadToAws3(CloudFileInfo cloudFileInfo) => Task.CompletedTask; //Do the work
private Task DeleteFromAws3(CloudFileInfo cloudFileInfo) => Task.CompletedTask; // Do the work
/*
* All abstract methods below will have different behavior in sub classes.
* This is Variant Trait of the Pattern
*/
protected abstract Task<IEnumerable<T>> GetItems();
protected abstract Task<bool> UpdateItem(T item, CloudFileInfo cloudFileInfo);
protected abstract Task<CloudFileInfo> GetCloudFileInfo(T item);
}
public record AssetFile(int PrimaryKey, string LocalPath, DateOnly CreatedDate);
public class AssetFileMigration : CloudFileMigration<AssetFile>
{
public AssetFileMigration(IAwsService awsService, IFileReader fileReader) : base(awsService, fileReader){}
protected override Task<IEnumerable<AssetFile>> GetItems() =>
Task.FromResult(Enumerable.Empty<AssetFile>()); // Fetch it from source, say DB
protected override Task<bool> UpdateItem(AssetFile item, CloudFileInfo cloudFileInfo) =>
Task.FromResult(true); //Update DB & log etc
// 1- Store in bucket A, Cloud Tier, And Local Path To Read from
protected override Task<CloudFileInfo> GetCloudFileInfo(AssetFile item) =>
Task.FromResult(new CloudFileInfo("", "", ""));
}
public record StatementFile(int PrimaryKey, string LocalPath, DateOnly LastUpdatedDate);
public class StatementFileMigration : CloudFileMigration<StatementFile>
{
public StatementFileMigration(IAwsService awsService, IFileReader fileReader) : base(awsService, fileReader){}
protected override Task<IEnumerable<StatementFile>> GetItems() =>
Task.FromResult(Enumerable.Empty<StatementFile>()); // Fetch it DB
protected override Task<bool> UpdateItem(StatementFile item, CloudFileInfo cloudFileInfo) =>
Task.FromResult(true); //Update DB
// 1- Store in bucket B, Cloud Tier based on LastUpdateDate, And Local Path To Read from
protected override Task<CloudFileInfo> GetCloudFileInfo(StatementFile item) =>
Task.FromResult(new CloudFileInfo("", "", ""));
}
Template Method Pattern by using C# Delegates
public interface IAwsService{};
public interface IFileReader{};
public record CloudFileInfo(string SourcePath, string DestinationPath, string CloudTier);
public record AssetFile(int PrimaryKey, string LocalPath, DateOnly CreatedDate);
public record StatementFile(int PrimaryKey, string LocalPath, DateOnly LastUpdatedDate);
public class CloudFileMigrationSimple
{
private readonly IAwsService _awsService;
private readonly IFileReader _fileReader;
public CloudFileMigrationSimple(IAwsService awsService, IFileReader fileReader)
{
_awsService = awsService;
_fileReader = fileReader;
}
// A Template Method or Invariant Trait of the Pattern
// Steps getItems, UploadToAws3, updateItem are executed in same order, and handling of failure
// While Func<Task<IEnumerable<T>>> getItems, Func<T, Task<bool>> updateItem behavior depends upon caller
public async Task Migrate<T>(Func<Task<IEnumerable<T>>> getItems, CloudFileInfo cloudFileInfo,
Func<T, Task<bool>> updateItem)
{
var items = await getItems();
await Task.WhenAll(items.Select(MigrateItem));
async Task MigrateItem(T item)
{
await UploadToAws3(cloudFileInfo);
var hasUpdateItemSucceeded = await updateItem(item);
if (!hasUpdateItemSucceeded)
//handle failure
await DeleteFromAws3(cloudFileInfo);
}
}
private Task UploadToAws3(CloudFileInfo cloudFileInfo) => Task.CompletedTask; //Do the work
private Task DeleteFromAws3(CloudFileInfo cloudFileInfo) => Task.CompletedTask; // Do the work
}
public static class Client
{
public static async Task Run()
{
CloudFileMigrationSimple migrationSimple = new(default, default);
await migrationSimple.Migrate(() => Task.FromResult(Enumerable.Empty<StatementFile>()), default,
(item) => Task.FromResult(true));
await migrationSimple.Migrate(() => Task.FromResult(Enumerable.Empty<AssetFile>()), default, UpdateAssetFile);
Task<bool> UpdateAssetFile(AssetFile item)
{
return Task.FromResult<bool>(true);
}
}
}
Tuples Equality
var adnan = (Name: "Adnan", Age: 40);
var anotherAdnan = (Name: "Adnan", Age: 40);
var areTheySame = adnan == anotherAdnan ? "Yes" : "No";
Console.WriteLine($"Are they same? {areTheySame}");
//output: Are they same? Yes
var someoneElse = (FirstName: "Adnan", Age: 40);
areTheySame = adnan == someoneElse ? "Yes" : "No";
Console.WriteLine($"Are they same? {areTheySame}");
//output: Are they same? Yes
/*
But why?
Because Named Tuples are syntax sugar. It makes code more readable.
Tuples equality is done on Tuple properties, Item1, Item2
*/
C# 9 Records
AnsiConsole.MarkupLine("[bold green]Hi, My name is record, I was born in C# 9.[/]");
var size = 10;
var names = new Name[size];
for (var i = 0; i < size; i++) names[i] = new Name(Faker.Name.Last(), Faker.Name.First());
try
{
//to sort implement IComparable - seen below
foreach (var name in names.OrderByDescending(x => x))
{
//create copied with with
var anotherName = name with { };
AnsiConsole.WriteLine(anotherName.ToString());//prints same value as name
AnsiConsole.WriteLine(name.ToString());
};
//deconstruction
foreach (var (lastName, firstName) in names)
{
AnsiConsole.WriteLine($"{firstName}, {lastName}");
}
}
catch (Exception e)
{
AnsiConsole.WriteLine("I fail to sort when IComparable is not implemented.");
AnsiConsole.WriteException(e);
}
record Name(string LastName, string FirstName) : IComparable<Name>
{
public int CompareTo(Name? other)
{
return other is null ? 1 : string.Compare(ToString(), other.ToString(), StringComparison.Ordinal);
}
public override string ToString()
{
return $"{LastName}, {FirstName}";
}
}