Dotnet Core Hosted Service

Problem Statement: Develop a hosted service that runs in a dotnet core app. This service needs to support dependency injection and logging configuration from the app.

Frameworks:
Asp.Net core 3.0 (This works in 2.2 as well)

Step 1:
In Startup.cs add this hosted service to Service Collection

services.AddHostedService();

Step 2:
I just want to highlight of couple of key areas from snippet below.

  • Hosted services are singleton, so you cannot inject scoped or transient services.
  • Service Provider can be used to create scope from which you can get all required services from dependency container.
  • Cancellation token is the key service that can be used to handle graceful shutdown and also keep the service running in the background.
public class EmailBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<EmailBackgroundService> _logger;
public EmailBackgroundService(IServiceProvider serviceProvider, ILogger<EmailBackgroundService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await RunSomeprocess();
await Task.Delay(5000, stoppingToken);
}
}
private async Task RunSomeprocess()
{
using var providerScope = _serviceProvider.CreateScope();
var userService = providerScope.ServiceProvider.GetService<IUserService>();
_logger.Log( LogLevel.Information , $"{userService.GetUserId()}");
}
}

Have fun!

Entity Framework Core Override Conventions

Problem Statement: In our project, we use code first approach to generate migrations. I see one feature which I couldn’t control through Fluent API.

EF core is generating indexes on foreign keys. I see this as default behavior but I always like to have control in creating indexes.

Current Frameworks:

Asp.Net Core 2.2 API with EF Core.

Solution 1:

I can go and delete unwanted indexes after every migration is generated but this manual step might be missed one day, so I started looking for automating this.

Solution 2:

I found EF core configuration has given the option to replace one of its core services, so I tried to remove the Foreign Key Index convention.

public static IServiceCollection InitDatabaseContext(this IServiceCollection services, string connectionString)
{
services.AddDbContext<AppDbContext>(opts =>
{
opts.UseSqlServer(connectionString);
opts.ReplaceService<IConventionSetBuilder, CustomSetBuilder>();
});
return services;
}
public class CustomSetBuilder : SqlServerConventionSetBuilder
{
public CustomSetBuilder(RelationalConventionSetBuilderDependencies dependencies, ISqlGenerationHelper sqlGenerationHelper) : base(dependencies, sqlGenerationHelper)
{
}
public override ConventionSet AddConventions(ConventionSet conventionSet)
{
var et = conventionSet.ForeignKeyAddedConventions.FirstOrDefault(f => f is ForeignKeyIndexConvention);
if (et != null)
conventionSet.ForeignKeyAddedConventions.Remove(et);
return base.AddConventions(conventionSet);
}
}

Testing:
I did generate a migration for my project with default conventions and then did generate migration after removing this convention. I am 100% convinced that this convention affected only default index creation for all foreign keys.

Note: Please test thoroughly before you add this to your project.

Have fun!

.Net Core Projects Code Coverage

Code coverage features are only supported
Visual Studio Ultimate.
Resharper

I tried this approach to generate a report in local and also in Azure DevOps build pipeline.

Step 1:
Add this code coverage library (Go To Package) NuGet package for all test package.

Step 2:
I created a javascript file that does the entire heavy lifting of running unit tests with code coverage and convert to report and open the report.

var commandExists = require('command-exists');
const execSync = require('child_process').execSync;
const rimraf = require('rimraf');
function runCoverageReport() {
console.log('started code coverage');
rimraf.sync('TestResults');
let result = execSync('dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=./TestResults/');
console.log(result.toString());
console.log('finished code coverage');
}
function generateReport() {
console.log('started report generation');
let result = execSync('reportgenerator "-reports:./**/TestResults/coverage.cobertura.xml" "-targetdir:./TestResults/CoverageReport/"');
console.log(result.toString());
console.log('finished report generation');
}
function openReport() {
const osPlatform = process.platform;
console.log(`opening report for ${osPlatform}`);
if (osPlatform === 'darwin') {
execSync('open ./TestResults/CoverageReport/index.htm')
}
else if (osPlatform === 'win64' || osPlatform === 'win32') {
execSync('start ./TestResults/CoverageReport/index.htm')
}
}
async function main() {
try {
await commandExists('coverlet');
console.log('coverlet command found and running coverage report');
} catch (e) {
console.log('installing coverlet.....');
execSync('dotnet tool install --global coverlet.console');
}
runCoverageReport();
try {
await commandExists('reportgenerator');
console.log('reportgenerator command found and running report');
} catch (e) {
console.log('installing report generator.....');
execSync('dotnet tool install --global dotnet-reportgenerator-globaltool');
}
generateReport();
openReport();
};
main();

Step 3:
You have a couple of options to run this. I always create prefer to create a npm script command in package.json


"scripts" : {
"code:coverage" : "node code-coverage.js"
},
"devDependencies": {
"command-exists": "^1.2.8"
}

Finally
I have tested this both on windows and mac.

One Last Thing

I also integrated this into Azure DevOps build pipeline

For test runner task

Add publish code coverage task and then set the below settings

Happy Coding!