Category: .NET Core

Embed files for a Nuget ASP.NET Core package

For NetCoreBlockly I have the need to embed some files in the NUGET package (https://www.nuget.org/packages/NetCore2Blockly ) to display a GUI.

After unsuccessful trying to deploy, I  have read https://weblog.west-wind.com/posts/2018/Jan/29/Distributing-Content-and-Showing-a-ReadMe-file-in-a-NET-Core-Nuget-Package .

So I must learn from the experts: e.g. , swagger : https://github.com/domaindrivendev/Swashbuckle.AspNetCore it is displaying the HTML UI.  How it does ? By embedding into the .csproj the index.html : https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/Swashbuckle.AspNetCore.SwaggerUI.csproj

But NetCoreBlockly have more than 1 file – more js, more css … and I want to be done automatically.

So I solved this in 3 steps:

 

  1. I Embed all files from a folder in the nuget csproj

<ItemGroup>
<EmbeddedResource Include=”blocklyFiles\**\*”>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>

2. In the NuGet I obtain the files as embedded

var manifestEmbeddedProvider =
new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly());

3. I generate ASP.NET Core endpoints for each file from the Embed, according to the tree :

private static void mapFile(string dirName, IFileProvider provider, IApplicationBuilder appBuilder)
{
var folder = provider.GetDirectoryContents(dirName);
foreach (var item in folder)
{
    if (item.IsDirectory)
    {
        mapFile(dirName + "/" + item.Name, provider, appBuilder);
        continue;
    }
    string map = (dirName + "/" + item.Name).Substring("blocklyFiles".Length);
    appBuilder.Map(map, app =>
    {
        var f = item;

        app.Run(async cnt =>
        {
            //TODO: find from extension
            //cnt.Response.ContentType = "text/html";
            using var stream = new MemoryStream();
            using var cs = f.CreateReadStream();
            byte[] buffer = new byte[2048]; // read in chunks of 2KB
            int bytesRead;
            while ((bytesRead = cs.Read(buffer, 0, buffer.Length)) > 0)
            {
                stream.Write(buffer, 0, bytesRead);
            }
            byte[] result = stream.ToArray();
            var m = new Memory<byte>(result);
            await cnt.Response.BodyWriter.WriteAsync(m);
        });
    });
}

}        

Optional:

4. Because I need to test , copy the files in the CI from a real ASP.NET Core website to the .csproj with the NUGET package

5. You can replace some tags with your values – or just use a .js file that generates options ( see BlocklyUIOptions  below)

 

If you want to see in Action, browse to https://netcoreblockly.herokuapp.com/blockly.html

If you want to see the code, goto https://github.com/ignatandrei/NETCoreBlockly/blob/master/src/NetCore2Blockly/NetCore2Blockly/ExtensionMethods/CLIExtension.cs and see public static void UseBlocklyUI(this IApplicationBuilder appBuilder, BlocklyUIOptions options =null)

Passing a WebAPI application from .NET Core 2 to .NET Core 3 in 5 +1 (EF) steps

Simple steps:

1. Modiffy in the .csproj  <TargetFramework>netcoreapp2.0</TargetFramework> to <TargetFramework>netcoreapp3.1</TargetFramework> 

2.Delete <PackageReference Include=”Microsoft.AspNetCore.All” Version=”2.0.8″ />

3. Modify

public static IWebHost BuildWebHost(string[] args) =>
public static IHostBuilder CreateHostBuilder(string[] args) =>

WebHost.CreateDefaultBuilder(args)
Host.CreateDefaultBuilder(args)

.UseStartup<Startup>()
.ConfigureWebHostDefaults(webBuilder =>

.Build();

to

public static IHostBuilder CreateHostBuilder(string[] args) =>

WebHost.CreateDefaultBuilder(args)
Host.CreateDefaultBuilder(args)

.UseStartup<Startup>()
.ConfigureWebHostDefaults(webBuilder =>

.Build();
{

webBuilder.UseStartup<Startup>();

});

3.  Modify BuildWebHost(args).Run();  to CreateHostBuilder(args).Build().Run();

4. Modify services.AddMvc();   to

services.AddControllers();

services.AddRouting();

5. Modify  app.UseMvc();  to

app.UseRouting();

app.UseEndpoints(endpoints =>

{

endpoints.MapControllers();

});

6. Optional : modify EF from

<PackageReference Include=”Microsoft.EntityFrameworkCore.SqlServer” Version=”2.1.1″ />
<PackageReference Include=”Microsoft.EntityFrameworkCore.SqlServer” Version=”3.1.3″ />

<PackageReference Include=”Microsoft.EntityFrameworkCore.Tools” Version=”2.1.1″ />
<PackageReference Include=”Microsoft.EntityFrameworkCore.Tools” Version=”3.1.3″>

<PackageReference Include=”Microsoft.VisualStudio.Web.CodeGeneration.Design” Version=”2.1.1″ />

to

<PackageReference Include=”Microsoft.EntityFrameworkCore.SqlServer” Version=”3.1.3″ />

<PackageReference Include=”Microsoft.EntityFrameworkCore.Tools” Version=”2.1.1″ />
<PackageReference Include=”Microsoft.EntityFrameworkCore.Tools” Version=”3.1.3″>

<PackageReference Include=”Microsoft.VisualStudio.Web.CodeGeneration.Design” Version=”2.1.1″ />
<PrivateAssets>all</PrivateAssets>

<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

</PackageReference>

<PackageReference Include=”Microsoft.VisualStudio.Web.CodeGeneration.Design” Version=”3.1.2″

Zip the whole ASP.NET Core application

This is a part of the series where about how I made the WebAPI2CLI - Execute ASP.NET Core WebAPI from Command Line
Source code on https://github.com/ignatandrei/webAPI2CLI/
1WebAPI2CLI - Description
2WebAPI2CLI- Organization
3WebAPI2CLI - implementing
4WebAPI2CLI - tests
5WebAPI2CLI - Devops and CI/CD
6WebAPI2CLI - documentation
7WebAPI2CLI - Conclusions
8WebAPI2CLI - Zip application

I realized that I should have somehow let the user play with the application from his PC. How can I do that , if the application is on some site  ? Answer: download the whole site as a zip file.

It should be relatively easy  for the user– so an endpoint routing should be used – something like “/zip”

It should be relatively easy  for the programmer – so I figured

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();

endpoints.MapZip(app); // IApplicationBuilder app
});

The code is also relativeley simple: enumerating recursive file and folders , creating the zip archive in memory, sending to the user

public static void MapZip(this IEndpointRouteBuilder endpoints, IApplicationBuilder app)
{
//see more at
//https://andrewlock.net/converting-a-terminal-middleware-to-endpoint-routing-in-aspnetcore-3/
endpoints.Map(“/zip”, async context =>
{
var response = context.Response;
var name = Assembly.GetEntryAssembly().GetName().Name + “.zip”;
response.ContentType = “application/octet-stream”;
var b = GetZip(app.ApplicationServices.GetService<IWebHostEnvironment>());
//https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/Infrastructure/FileResultExecutorBase.cs
var contentDisposition = new Microsoft.Net.Http.Headers.ContentDispositionHeaderValue(“attachment”);
contentDisposition.SetHttpFileName(name);
response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
await response.Body.WriteAsync(b);

});

}

private static Memory<byte> GetZip(IWebHostEnvironment env)
{
//var b = new Memory<byte>(Encoding.ASCII.GetBytes($”{env.ContentRootPath}”));
var firstDir = new DirectoryInfo(env.ContentRootPath);
var nameLength = firstDir.FullName.Length + 1;
using var memoryStream = new MemoryStream();
using var zipToOpen = new ZipArchive(memoryStream, ZipArchiveMode.Create, true);

 

foreach (FileInfo file in firstDir.RecursiveFilesAndFolders().Where(o => o is FileInfo).Cast<FileInfo>())
{
var relPath = file.FullName.Substring(nameLength);
var readmeEntry = zipToOpen.CreateEntryFromFile(file.FullName, relPath);
}
zipToOpen.Dispose();
var b = new Memory<byte>(memoryStream.ToArray());
return b;

}

private static IEnumerable<FileSystemInfo> RecursiveFilesAndFolders(this DirectoryInfo dir)
{
foreach (var f in dir.GetFiles())
yield return f;
foreach (var d in dir.GetDirectories())
{
yield return d;
foreach (var o in RecursiveFilesAndFolders(d))
yield return o;
}
}

 

Some points to be noted:

1. It will work only if the site and user have the same operating system( assuming you have did self contained the .NET Core application)

2. ContentDispositionHeaderValue – exists in 2 libraries

3. I have shameless copy code from StackOverflow and github aspnet

4. I did not put in a separate library, nor I do automatic tests. Sorry, too tired

5. It works only if you put

endpoints.MapZip(app); // IApplicationBuilder app

If not, it does not zip!

6. IEndpointRouteBuilder – it is defined only you have the library .net core  3, not .net standard 2

<TargetFramework>netcoreapp3.1</TargetFramework>

<ItemGroup>
<FrameworkReference Include=”Microsoft.AspNetCore.App” />
</ItemGroup>

Retarder–Fixed and Random strategy and Tests-part 4

Now I want to make a second strategy : the random strategy between a min and a max value. This is only to minimize the impact on the extension that uses the Random – it is good to be hidden inside. For Random Strategy it is better to re-use the Fixed Strategy – and this is the code:

using System;
using System.Threading.Tasks;

namespace NetCoreRetarderCore
{
    public class StrategyAwaitRandom : IStrategyAwait
    {
        StrategyAwaitFixed sf;
        public StrategyAwaitRandom(int min,int max)
        {
            if (min > max)
            {
                var x = min;
                min = max;
                max = min;
            }
            var random = new Random();
            var awaitTime = random.Next(min, max);
            sf = new StrategyAwaitFixed(awaitTime);
        }
        public Task<IStrategyAwait> AwaitDelayAfter()
        {
            return sf.AwaitDelayAfter();
        }

        public Task<IStrategyAwait> AwaitDelayBefore()
        {
            return sf.AwaitDelayBefore();
        }
    }
}

 

Also, I want to make some tests to verify the awaits. So I choose https://github.com/LightBDD/LightBDD and my tests looks like this:

using LightBDD.Framework;
using LightBDD.Framework.Scenarios;
using LightBDD.XUnit2;
using System;
using System.Threading.Tasks;
using Xunit;
[assembly: LightBddScope]

namespace NetCoreRetarderTest
{
    //https://github.com/LightBDD/LightBDD/wiki/Formatting-Parameterized-Steps

    [FeatureDescription(

   @"This will test just how 

will await a determined number of time
")]
    public partial class TestStrategyAwait
    {
        
        [Scenario]

        [Label("wait times fixed")]

        [Trait("Category", "Wait")]
        [InlineData(3)]
        [InlineData(1)]
        [InlineData(0)]
        [InlineData(-1)]

        public async Task WaitTime(int nrSeconds)

        {
           
            await Runner.RunScenarioAsync(

                    _ => When_Choosing_Fixed_Await_For_NRSECONDS_Seconds(nrSeconds),

                    _=> And_Await(),

                    _=> Then_Time_Ellapsed_Will_Be_NRSECONDS_Seconds(Math.Max(nrSeconds, 0))
                    )

                ;

        }
        [Scenario]

        [Label("wait times random")]

        [ScenarioCategory("Wait")]
        [InlineData(3,1)]
        [InlineData(1,2)]
        [InlineData(0,1)]
        [InlineData(-1,5)]

        public async Task WaitTimeRandom(int min,int max)

        {

            await Runner.RunScenarioAsync(

                    _ => When_Choosing_Random_Await_Between_MIN_And_MAX_Seconds(min,max),

                    _ => And_Await(),

                    _ => Then_Time_Ellapsed_Will_Be_Between_MIN_And_MAX_Seconds(Math.Max(min, 0),Math.Max(max,0))
                    )

                ;

        }
    }
}

 
You can find the code at https://github.com/ignatandrei/NetCoreRetarder/commits/version4_tests

Retarder – Making a strategy to wait–part 3

Now I want to make the await configurable. To remind , first is just a random delay ( or static delay). The others are

  1. Delay execution of some endpoints, based on how frequent are their uses

  2. Delay execution based on headers / query string /  routes
  3. Delay execution based on client IP
  4. Delay execution based on the response

The best design pattern for this is Strategy  https://en.wikipedia.org/wiki/Strategy_pattern . Also, because I want to apply multiple await types ( maybe in order, maybe to compose) , a simple Command Pattern will be enough.

So, let’s see how it looks now:

using System.Threading.Tasks;

namespace NetCoreRetarderCore
{
    public interface IStrategyAwait
    {
        Task<IStrategyAwait> AwaitDelay();
    }
}

So now let’s see the implementation of a StrategyAwaitFixed

using System;
using System.Threading.Tasks;

namespace NetCoreRetarderCore
{
    public class StrategyAwaitFixed : IStrategyAwait
    {
        private readonly int milliseconds;

        public StrategyAwaitFixed(int milliseconds)
        {
            this.milliseconds = milliseconds;
        }

        public async Task<IStrategyAwait> AwaitDelay()
        {
            Console.WriteLine($"waiting {milliseconds}");
            
            if (milliseconds < 1)
                return null;

            await Task.Delay(milliseconds);
            
            Console.WriteLine($"finished waiting {milliseconds}");
            
            return null;
        }
    }
}

Now the Retarder Middleware should apply the awaiter one by one

using Microsoft.AspNetCore.Http;
using System;
using System.Threading.Tasks;

namespace NetCoreRetarderCore
{
    public class RetarderMiddleware : IMiddleware
    {
        private IStrategyAwait strategyAwait;

        public RetarderMiddleware(IStrategyAwait strategyAwait)
        {
            this.strategyAwait = strategyAwait;
        }

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            while (strategyAwait != null)
                strategyAwait = await strategyAwait?.AwaitDelay();
            
            await next(context);
            
        }
    }
}

The code from middleware extension is modified as generating a new random each time:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;

namespace NetCoreRetarderCore
{
    public static class RetarderExtensions
    {
        public static void AddRetarder(this IServiceCollection serviceCollection)
        {
            serviceCollection
                .AddTransient<IStrategyAwait, StrategyAwaitFixed>(
                    it =>
                    {
                        var random = new Random();
                        var awaitMilliseconds = random.Next(1, 1000);
                        return new StrategyAwaitFixed(awaitMilliseconds);
                    }
                );
            serviceCollection.AddTransient<RetarderMiddleware>();
        }
        public static void UseRetarder(this IApplicationBuilder app)
        {
            app.UseMiddleware<RetarderMiddleware>();
        }

    }
}

Full Code Source at https://github.com/ignatandrei/NetCoreRetarder/commits/version3_Making_a_strategy_to_wait

Retarder- reorganizing the project to easy use- part 2

Now it is the moment to start reorganizing the project to be easy to use by other programmers. I want, instead of registering the services manually, to can use .AddRetarder and .UseRetarder. So I create a new project, NetCoreRetarderCore.csproj , and move there the RetarderMiddleware . The only new thing is the extension class RetarderExtensions

 

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;

namespace NetCoreRetarderCore
{
    public static class RetarderExtensions
    {
        public static void AddRetarder(this IServiceCollection serviceCollection)
        {
            serviceCollection.AddTransient<RetarderMiddleware>();
        }
        public static void UseRetarder(this IApplicationBuilder app)
        {
            app.UseMiddleware<RetarderMiddleware>();
        }

    }
}

 

This let’s me reorganize the Startup as

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NetCoreRetarderCore;

namespace NetCoreRetarder
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddRetarder();
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();
            app.UseRetarder();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

You can find code at https://github.com/ignatandrei/NetCoreRetarder/commits/version2_reorg

Retarder- idea and POC–part 1

I was thinking about a new project in .NET Core  – Retarder ( Delayer  seems to be better , but I digress ). What about a middleware in .NET Core, that delays execution of any request with 1 second ( or something between 1 millisecond and 1 second )? That way , if someone wants to improve the project , just remove the Retarder !

Other ( better ) uses are :

  1. Delay execution of some endpoints, based on how frequent are their uses
  2. Delay execution based on headers / query string /  routes
  3. Delay execution based on client IP
  4. Delay execution based on the response

 

But for the moment just stick with the static delay of all requests.

Seems to be a good idea to play with middleware. I started a new .NET Core 3.1 project and started coding .

You can find the first version at tag : https://github.com/ignatandrei/NetCoreRetarder/commits/version_1_just_data

The code for Retarder middleware is

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NetCoreRetarder
{
    public class RetarderMiddleware : IMiddleware
    {

        public RetarderMiddleware()
        {
        }

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            var random = new Random();
            //var awaitMilliseconds =random.Next(10000, 100000);
            var awaitMilliseconds = random.Next(1, 1000);
            Console.WriteLine($"awaiting {awaitMilliseconds}");
            await Task.Delay(awaitMilliseconds);
            Console.WriteLine($"***awaited {awaitMilliseconds}");

            await next(context);
        }
    }
}


 

The code for using Retarder is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace NetCoreRetarder
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //what if we comment this ?
            //services.AddSingleton<RetarderMiddleware>();
            services.AddTransient<RetarderMiddleware>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();
            //app.UseMiddleware(typeof(RetarderMiddleware));
            app.UseMiddleware<RetarderMiddleware>();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Some questions :

1.  What is the difference if we have services.AddSingleton  vs services.AddTransient for the middleware ?

2. What if we comment both ?

3. What if we do call later ( or before ) app.UseMiddleware ?

Documentation–part 6

This is a part of the series where about how I made the WebAPI2CLI - Execute ASP.NET Core WebAPI from Command Line
Source code on https://github.com/ignatandrei/webAPI2CLI/
1WebAPI2CLI - Description
2WebAPI2CLI- Organization
3WebAPI2CLI - implementing
4WebAPI2CLI - tests
5WebAPI2CLI - Devops and CI/CD
6WebAPI2CLI - documentation
7WebAPI2CLI - Conclusions
8WebAPI2CLI - Zip application

I need 2 types of documentation:

1. For let people know how to use

2. For the programmers – to understand what the classes are doing

 

For the first point, documentation to use

a. Github has already the readme.md file that shows details for the repository . What I need is something more- i.e. detailed instruction, FAQ, Author.

b. Github has made easy to make an online site  -by having the docs folder public with his own address. For example, from  https://github.com/ignatandrei/webAPI2CLI/ I can have https://ignatandrei.github.io/WebAPI2CLI/ . Now , the problem is how to concatenate those and put back to site

c. GitHub has also the concept of Actions –i.e.  DevOps integrated.

 

So –what I have done is to use pandoc , compile all the .md files into html ( and pdf ) and push modifications back to the site.

See https://github.com/ignatandrei/WebAPI2CLI/blob/master/.github/workflows/main.yml

– uses: actions/checkout@v2

– run: |

echo generate help file

mkdir output

git pull

– name: generate html

uses: docker://pandoc/latex:2.9

with: # needs a README in your repo root!

#args: “–standalone –output=output/README.html README.md”

args: “README.md docs/Demo.md docs/FAQ.md docs/Author.md –standalone -f gfm -t html  –toc -o output/output.html –metadata title=WebAPI2CLI”

– name: generate pdf

uses: docker://pandoc/latex:2.9

with: # needs a README in your repo root!

#args: “–standalone –output=output/README.html README.md”

args: “README.md docs/Demo.md docs/FAQ.md docs/Author.md –standalone -f gfm -t pdf  –toc -o output/output.pdf –metadata title=WebAPI2CLI”

– run: |

cp ./output/output.html ./docs/index.html

cp ./output/output.pdf ./docs/Web2CLI.pdf

rm -rf ./output

 

You can see the results at https://ignatandrei.github.io/WebAPI2CLI/ and the sources at https://github.com/ignatandrei/WebAPI2CLI/tree/master/docs

For the second point, generating documentation from XML Comments in C#

I need some free .NET tool to do this. I did not found. I have hesitated also between https://github.com/EWSoftware/SHFB ( an old friend, but cumbersome ) and https://github.com/dotnet/docfx/ ( a new friend, but still cumbersome)

What I have done is to download docfx, add to the site, follow instructions , modify some of the templates , resist temptation to put everything into docfx ( he kinda want to be all inclusive)

docs_csharp:

# The type of runner that the job will run on

runs-on: windows-latest

steps:

# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it

– uses: actions/checkout@v2

– name: run documentation

run: |

DocumentationSources.bat

rem git config –local user.email “action@github.com”

rem git config –local user.name “GitHub Action”

rem git commit -m “generate documentation sources” -a –allow-empty

shell: cmd

– name: Push changes

uses: ad-m/github-push-action@master

with:

github_token: ${{ secrets.GITHUB_TOKEN }}

You can see the results at https://ignatandrei.github.io/WebAPI2CLI/sitedocs/api/index.html  and sources at https://github.com/ignatandrei/WebAPI2CLI/tree/master/docs/sitedocs

Devops + CI/CD-part 5

This is a part of the series where about how I made the WebAPI2CLI - Execute ASP.NET Core WebAPI from Command Line
Source code on https://github.com/ignatandrei/webAPI2CLI/
1WebAPI2CLI - Description
2WebAPI2CLI- Organization
3WebAPI2CLI - implementing
4WebAPI2CLI - tests
5WebAPI2CLI - Devops and CI/CD
6WebAPI2CLI - documentation
7WebAPI2CLI - Conclusions
8WebAPI2CLI - Zip application

What I need for the devops:

1. Building the solution

2. Running tests

3. Deploying packages to Nuget

Being part of the Microsoft stack, it is normal that I have choose for CI / CD the Azure Devops.

You can see the AzureDevops at https://dev.azure.com/ignatandrei0674/WebAPI2CLI/_build?definitionId=7&_a=summary and how it is done by reading https://github.com/ignatandrei/WebAPI2CLI/blob/master/azure-pipelines.yml

Seeing code coverage in AzureDevops

I need not only to know that tests have runned with success, but also to see the code coverage

For this I use the .NET local tools, coverlet and report generator

dotnet coverlet bin\$(buildConfiguration)\netcoreapp3.1\CLITests.dll –target “dotnet” –targetargs “test –no-build –configuration $(buildConfiguration)” –exclude ‘[*Test*]*’ –format opencover  –output $(Build.ArtifactStagingDirectory)\testResults\coverlet.xml

dotnet reportgenerator “-reports:$(Build.ArtifactStagingDirectory)\testResults\coverlet.xml” “-targetdir:$(Build.ArtifactStagingDirectory)\testResults” “-reporttypes:Cobertura;HtmlSummary;Badges;HtmlInline_AzurePipelines”

– task: PublishTestResults@2

inputs:

testResultsFormat: ‘VSTest’

testResultsFiles: ‘**/*.trx’

searchFolder: ‘$(Build.ArtifactStagingDirectory)\trx’

displayName: publish tests

– task: PublishCodeCoverageResults@1

displayName: ‘Publish code coverage’

inputs:

codeCoverageTool: Cobertura

summaryFileLocation: ‘$(Build.ArtifactStagingDirectory)\testResults\Cobertura.xml’

reportDirectory: ‘$(Build.ArtifactStagingDirectory)\testResults’

In this way , you can see the

Modify version 

For a good CD I need also a way to modify the version automatically.

 

The .Net local Tools provides a way to install some tools on the local system to be run within dotnet.

So I install pwsh  -and run a setversion.ps1

 

$TimeNow = Get-Date

$d = $TimeNow.ToUniversalTime()

$year = $TimeNow.Year

$startOfYear = Get-Date -Year $year -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0

$diff = NEW-TIMESPAN -Start $startOfYear -End $TimeNow

#$diff.TotalSeconds -as [int]

$assemblyVersion=$d.ToString(“1.yyyy.1MMdd.1HHmm”)

dotnet-property “**/*.csproj” AssemblyVersion:”$assemblyVersion”

dotnet dotnet-property “**/*.csproj” AssemblyVersion:”$assemblyVersion”

$version=$d.ToString(“1.0.yyyy.”) + ($diff.TotalSeconds -as [int]).ToString()

dotnet-property “**/*.csproj” Version:”$version”

dotnet dotnet-property “**/*.csproj” Version:”$version”

$releaseNotes = “BuildNumber $env:BUILD_BUILDNUMBER”

$releaseNotes += “;author $env:BUILD_SOURCEVERSIONAUTHOR”

$releaseNotes += “;message $env:BUILD_SOURCEVERSIONMESSAGE”

$releaseNotes +=”;source for this release github.com/ignatandrei/webAPI2CLI/commit/$env:BUILD_SOURCEVERSION”

$releaseNotes

dotnet-property “**/*.csproj” PackageReleaseNotes:”$releaseNotes”

dotnet dotnet-property “**/*.csproj” PackageReleaseNotes:”$releaseNotes”

I think that it is self explanatory.

Be a good nuget citizen –do not deploy every time a modification is made

For this I use devops variable :  deployNuget: ‘0’

– task: NuGetCommand@2

condition: and(succeeded(), eq(variables[‘deployNuget’], ‘1’))

inputs:

command: push

nuGetFeedType: external

packagesToPush: ‘$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg’

publishFeedCredentials: ‘nugetAndrei’

displayName: ‘dotnet nuget push’

Be a good AzureDevops citizen – do not run automation every time

I have some documentation on .md files – no need to rebuild everything when just the documentation is done

This is done in AzureDevOps by the trigger:

trigger:

branches:

include:

– master

paths:

exclude:

– docs/*

– README.md

Test your application

I also can test in AzureDevops what I have done by runnning

TestWebAPISite.exe  –CLI_ENABLED=1 –CLI_Commands=”GetMathId_Http,MathPOST”

It is just nice – maybe I can find a way to do something practical …

Create and running tests- part 4

This is a part of the series where about how I made the WebAPI2CLI - Execute ASP.NET Core WebAPI from Command Line
Source code on https://github.com/ignatandrei/webAPI2CLI/
1WebAPI2CLI - Description
2WebAPI2CLI- Organization
3WebAPI2CLI - implementing
4WebAPI2CLI - tests
5WebAPI2CLI - Devops and CI/CD
6WebAPI2CLI - documentation
7WebAPI2CLI - Conclusions
8WebAPI2CLI - Zip application

I have made a TestWebSite , in .NET Core 3.1, I was also thinking that WebApplicationFactory exists and tutorial like https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1   it will be easy to test the site.

However, I have found the hard way that is not a real server : it does not have the adresses / ports open. Read https://github.com/dotnet/aspnetcore/issues/4892 .

So I have made a hack – see public class LocalServerFactory in the source code.

Also, WithWebHostBuilder helps create a new WebApplicationFactory  -used to test with / or without /  CLI_ENABLED command line

 

Second point was testing methods like

configuration.GetValue<int?>(“CLI_HELP”, null);

Because it is an extension function, I cannot mock. So I refactored the code to

public bool ShouldShowHelp()
{
//deleted for easy of automating testing
//var showHelp = configuration.GetValue<int?>(“CLI_HELP”, null);
var cliHelp = configuration[“CLI_HELP”];
var showHelp = int.TryParse(cliHelp, out var val) && val == 1;
return showHelp;
}

Also, I have resorted to XBehave, to have tests written in easy form. And to Moq to mock various properties.

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.