Category: .NET Core

Db2Code–part 5 – testing

For testing I should find some databases to test to ( and do not forget that generating SELECT  from PK supports just 1 PK, not multiple).

Microsoft already show some sample database – you can download from https://github.com/microsoft/sql-server-samples/tree/master/samples/databases . However , those must exists in the moment of running tests – so I have as options:

  1. Have an Sql Server ( by docker or on PC) and run the sql every time at the beginning of every start run test
  2. Have an Sqlite database file with all data and copy it .

( SqlServer in memory will be  a good options, but it do not work with Sql insert/update….)

A specific test looks like this

static void CreateDb()
{

//the restore operation just replace the sqlite database
     DatabaseOperations.Restore(context, “Pubs”);

}

[Theory]
[InlineData(SearchCriteria.Equal, ediscountsColumns.stor_id, “8042”, 1)]
[InlineData(SearchCriteria.Different, ediscountsColumns.stor_id, “8042”, 2)]
[InlineData(SearchCriteria.Equal, ediscountsColumns.stor_id, null, 2)]
[InlineData(SearchCriteria.Different, ediscountsColumns.stor_id, null, 1)]
//[InlineData(SearchCriteria.Equal, ediscountsColumns.discount, “5”, 1)]
[InlineData(SearchCriteria.Equal, ediscountsColumns.lowqty, “100”, 1)]
public async Task SearchAdvanced(SearchCriteria sc, ediscountsColumns col, string val, int nrRecs)
{
     CreateDb();
     var data = await context.discountsSimpleSearch(sc, col, val).ToArrayAsync();
     Assert.Equal(nrRecs, data.Length);
}

One consequence will be that the tests cannot run in parallel .And  , after all, it is just a work of coding data and results. Tiresome work, however pays in the long term.

Db2Code- part 4- Architectural Changes for FrontEnd

Now comes the frontend . There are several options – Blazor, Angular, React , …. . The main problem is – how to generate JavaScript / TypeScript file where there is not .csproj available ? ( Yes, it is .esproj for javascript integration – however, you cannot add NuGet packages to it )

So the solution is to have another project that , once compiled, generates JS/ TS files . But  – why this should not be available for all other projects ? So , instead each project compile itself and then generate the .cs files, let’s have a project that compiles and generates files for all.  But this can be applied for each –so we have a generator for each type of classes from database ( table  model, context, search, controllers)

Now actual work is done by a powershell file that replaces templates in a .csproj, then runs dotnet ef scaffold for each template. This is the diagram of how it works for you

If you want to try it, download from

https://marketplace.visualstudio.com/items?itemName=ignatandrei.databasetocode  and start a new project with DB2Code project

Db2Code-part 3- deploy

Now how to let other developers to modify the connection string from a template? Easy said than done  – create a template for this. This is easier to speak about than to do .  So any project can be transformed into template  – but what about a solution ?

Anyway – the starting point is https://learn.microsoft.com/en-us/visualstudio/extensibility/getting-started-with-the-vsix-project-template?view=vs-2022 and https://learn.microsoft.com/en-us/visualstudio/ide/template-parameters?view=vs-2022 

Now it is another challenges:

  1. How to put into the template all the files that a project has, WITHOUT putting also the bin ?
  2. How to generate the correct usings with the name that the programmer gives to the project ? e.g. $ext_safeprojectname$
  3. How to generate the zip template file ?

So , for all those questions , a powershell is the answer ( OK. could be also a C# – but it is easier to modify and execute)

The organization is the following:

For each project I will put a template folder in the https://github.com/ignatandrei/QueryViewer/tree/main/src/NET7/DB2GUI/DB2GUITemplate/ProjectTemplates . Also an entry will be added for https://github.com/ignatandrei/QueryViewer/blob/main/src/NET7/DB2GUI/DB2GUITemplate/GeneratorAll.vstemplate .After this the powershell https://github.com/ignatandrei/QueryViewer/blob/main/src/NET7/DB2GUI/DB2GUITemplate/createAll.ps1 will run and update the folder and create the zip with the extension.

The project can be found at https://github.com/ignatandrei/QueryViewer/tree/main/src/NET7/DB2GUI/DB2GUITemplate and the powershell I will put here:

function AddFiles{
param($folder)
push-location
cd GeneratorFromDBTemp
cd $folder
$xml = ( Get-Content “MyTemplate.vstemplate”)

#$node= $xml.VSTemplate.TemplateContent.Project
#$node.ParentNode.RemoveChild($node)
#$node= $xml.SelectSingleNode(“//Project”)

$node= $xml.VSTemplate.TemplateContent.Project
gci *.* -r -Exclude *.*sproj,*.vstemplate, __TemplateIcon.ico | % {
     $rel = Resolve-Path -Relative $_
     $rel = $rel.replace( “.\”,””)
     $newelement = $xml.CreateElement(“ProjectItem”)
     $newelement.SetAttribute(“ReplaceParameters”, “true”)
     # $newelement.SetAttribute(“TargetFileName”, $rel)
     $newelement.InnerText =$rel
     $node.AppendChild($newelement)

}
$output = $xml.OuterXml -replace ‘xmlns=””‘, ”
$output | Out-File “MyTemplate.vstemplate”
pop-location

}

cls
$dt = $date = Get-Date # Get the current date and time
$v = $date.ToString(“yyyy.MM.dd.HHmmss”)

Write-Host ‘starting’
Remove-Item -Path .\a.zip -Force -Recurse -ErrorAction SilentlyContinue
Remove-Item -Path .\GeneratorFromDBTemp -Force -Recurse -ErrorAction SilentlyContinue
$FileLocation = “source.extension.vsixmanifest”
$xmlDoc =  Get-Content “source.extension.vsixmanifest”
Write-Host ‘reading vsixmanifest’
$xmlDoc | Format-List *
$node=$xmlDoc.PackageManifest.Metadata.Identity

$node=$node.SetAttribute(“Version”, $v)

$xmlDoc.OuterXml | Out-File $FileLocation
# $xmlDoc.Save(source.extension.vsixmanifest1)
New-item –ItemType “directory” GeneratorFromDBTemp
Copy-Item -Path GeneratorFromDB\* -Destination GeneratorFromDBTemp\ -Force -Recurse
push-location
cd GeneratorFromDBTemp
$FileLocation = “GeneratorAll.vstemplate”
$xml = ( Get-Content $FileLocation)
Write-Host ‘reading template’
$xml | Format-List *
$node = $xml.VSTemplate.TemplateData
Write-Host ‘this is’ $node   
$node.Name  =  “DB2Code”
$node.Description = “GeneratorFromDB” + ” ” + $v + ” See  See https://github.com/ignatandrei/queryViewer/”
$xml.OuterXml | Out-File $FileLocation

pop-location

push-location
cd ..
dotnet clean
pop-location

Write-Host “copying files”
copy-item -Path ..\ExampleModels\*             -Destination GeneratorFromDBTemp\ExampleModels\                             -Force -Recurse
copy-item -Path ..\ExampleControllers\*     -Destination GeneratorFromDBTemp\ExampleControllers\                         -Force -Recurse
copy-item -Path ..\ExampleContext\*             -Destination GeneratorFromDBTemp\ExampleContext\                             -Force -Recurse
copy-item -Path ..\ExampleWebAPI\*             -Destination GeneratorFromDBTemp\ExampleWebAPI\                             -Force -Recurse
copy-item -Path ..\GeneratorPowershell\*     -Destination GeneratorFromDBTemp\GeneratorPowershell\                         -Force -Recurse
copy-item -Path ..\GeneratorFromDB\*         -Destination GeneratorFromDBTemp\GeneratorFromDB\                             -Force -Recurse
copy-item -Path ..\examplecrats\*             -Destination GeneratorFromDBTemp\examplecrats\     -Exclude node_modules        -Force -Recurse
copy-item -Path ..\GeneratorCRA\*             -Destination GeneratorFromDBTemp\GeneratorCRA\                                 -Force -Recurse

push-location
cd GeneratorFromDBTemp

Write-Host “delete remaining folders”
Get-ChildItem  -Directory -Recurse -Filter “bin” | Remove-Item -Recurse
Get-ChildItem  -Directory -Recurse -Filter “obj” | Remove-Item -Recurse
Get-ChildItem  -Directory -Recurse -Filter “node_modules” | Remove-Item -Recurse
Get-ChildItem  -Directory -Recurse -Filter “.vscode” | Remove-Item -Recurse
Get-ChildItem  -Directory -Recurse -Filter “.config” | Remove-Item -Recurse

Write-Host “modify .cs files”
gci *.cs -r | % {
     $content  = Get-Content $_.FullName
     $newContent = $content -replace ‘Example’,’$safeprojectname$’
     if ($content -ne $newContent) {
         Set-Content -Path  $_.FullName -Value $newContent
         # Write-Host ‘replacing ‘ $_.FullName
        
     }
        
}

Write-Host “modify connection details”
gci connectionDetails.txt -r | % {
     $content  = Get-Content $_.FullName
     $newContent = $content
     $newContent = $newContent -replace ‘Example’,’$ext_safeprojectname$.’
     $newContent = $newContent -replace ‘GeneratorCRA’,’$ext_safeprojectname$.GeneratorCRA’
     if ($content -ne $newContent) {
         Set-Content -Path  $_.FullName -Value $newContent
         # Write-Host ‘replacing ‘ $_.FullName
        
     }
        
}

Write-Host “modify create.ps1”
gci create.ps1 -r | % {
     $content  = Get-Content $_.FullName
     $newContent = $content
     $newContent = $newContent -replace ‘examplecrats’,’$ext_safeprojectname$.examplecrats’   
     if ($content -ne $newContent) {
         Set-Content -Path  $_.FullName -Value $newContent
         # Write-Host ‘replacing ‘ $_.FullName
        
     }
        
}

Write-Host “modify .*sproj files”
gci *.*sproj -r | % {
     $content  = Get-Content $_.FullName
     $newContent = $content
     $newContent = $newContent -replace ‘Example’,’$ext_safeprojectname$.’
     $newContent = $newContent -replace ‘example’,’$ext_safeprojectname$.’
     $newContent = $newContent -replace “..\\GeneratorFromDB\\GeneratorFromDB.csproj”,’..\$ext_specifiedsolutionname$.GeneratorFromDB\$ext_specifiedsolutionname$.GeneratorFromDB.csproj’
     if ($content -ne $newContent) {
         Set-Content -Path  $_.FullName -Value $newContent
         # Write-Host ‘replacing ‘ $_.FullName
        
     }
        
}

pop-location

AddFiles “ExampleModels”
AddFiles “ExampleControllers”
AddFiles “ExampleContext”    
AddFiles “ExampleWebAPI”
AddFiles “GeneratorPowershell”
AddFiles “GeneratorFromDB”
AddFiles “examplecrats”
AddFiles “GeneratorCRA”

Compress-Archive -DestinationPath .\a -Path GeneratorFromDBTemp\*
Remove-Item .\GeneratorFromDB.zip
Remove-Item .\ProjectTemplates\GeneratorFromDB.zip
Copy-Item .\a.zip .\ProjectTemplates\GeneratorFromDB.zip
Move-Item .\a.zip .\GeneratorFromDB.zip

Db2Code–part 2- architecture

What we will build you can see here :

Each class will have it is own CodeTemplates\EFCore  templates from which will generate the code.

Let’s start with ExampleModels : Here will be the class definitions . From the table defintion , the DB2Code will generate

1. A definition of a interface with table columns as properties

public interface I_Department_Table 
{
 long IDDepartment { get; set; }
 string Name { get; set; }
}

2. A class with relationships

public partial class Department
{
    [Key]
    public long IDDepartment { get; set; }

    [StringLength(50)]
    [Unicode(false)]
    public string Name { get; set; } = null!;

    [InverseProperty("IDDepartmentNavigation")]
    public virtual ICollection<Employee> Employee { get; } = new List<Employee>();
}

3. class without relationship

public class Department_Table : I_Department_Table
{
 public long IDDepartment { get; set; }
 public string Name { get; set; }
 }

4. Explicit operator to convert 2 to 3

public static explicit operator Department_Table?(Department obj) { 
if(obj == null)
return null;
//System.Diagnostics.Debugger.Break();
var ret= new Department_Table();
ret.CopyFrom(obj as I_Department_Table );
return ret;
}
public static explicit operator Department?(Department_Table obj) { 
if(obj == null)
return null;
//System.Diagnostics.Debugger.Break();
var ret= new Department();
ret.CopyFrom(obj as I_Department_Table) ;
return ret;
}

5. Public method CopyFrom( interface)


public void CopyFrom(I_Department_Table other)  {
 this.IDDepartment = other.IDDepartment;
 this.Name = other.Name;
}

5. Enum with name of the columns

public enum eDepartmentColumns {
None = 0
,IDDepartment 
,Name 
}

6. Metadata with name of the tables and the columns

public static MetaTable metaData = new("Department");
static Department_Table (){
 MetaColumn mc=null;
 mc=new ("IDDepartment","long",false);
 metaData.AddColumn(mc);
 mc=new ("Name","string",false);
 metaData.AddColumn(mc);
}

 

Now it comes ExampleContext . Here will the database context and various search definitions that you need (e.g. for a column of type int, search will generate  = , > , < , between , in array  ) . More , it will generate metadata for the tables that are part of the context.

public  IAsyncEnumerable&lt;Department&gt; DepartmentSimpleSearch(GeneratorFromDB.SearchCriteria sc, eDepartmentColumns colToSearch, string value){}
public  IAsyncEnumerable&lt;Department&gt; DepartmentGetAll(){}
public async Task&lt;Department[]&gt; DepartmentFind_Array( SearchDepartment? search){}
public Task&lt;long&gt; DepartmentCount( SearchDepartment search)

 

Now it comes ExampleControllers  . This will generate controllers for REST API for the table and also Search Controllers for any kind of search that you may want.

//this is the REST controller
[ApiController]
[Route("[controller]")]    
public partial class RESTDepartmentController : Controller
{
    private ApplicationDBContext _context;
    public RESTDepartmentController(ApplicationDBContext context)
	{
        _context=context;
	}
    [HttpGet]
    public async Task&lt;Department_Table[]&gt; Get(){
        var data= await _context.Department.ToArrayAsync();
        var ret = data.Select(it =&gt; (Department_Table)it!).ToArray();
        return ret;

        
    }
    
        [HttpGet("{id}")]
    public async Task&lt;ActionResult&lt;Department_Table&gt;&gt; GetDepartment(long id)
    {
        if (_context.Department == null)
        {
            return NotFound();
        }
        var item = await _context.Department.FirstOrDefaultAsync(e =&gt; e.IDDepartment==id);

        if (item == null)
        {
            return NotFound();
        }

        return (Department_Table)item!;
    }


    [HttpPatch("{id}")]
        public async Task&lt;IActionResult&gt; PutDepartment(long id, Department value)
        {
            if (id != value.IDDepartment)
            {
                return BadRequest();
            }

            _context.Entry(value).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!DepartmentExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        [HttpPost]
        public async Task&lt;ActionResult&lt;Department&gt;&gt; PostDepartment(Department_Table value)
        {
          
            var val = new Department();
            val.CopyFrom(value);
            _context.Department.Add(val);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetDepartment", new { id = val.IDDepartment }, val);
        }
        [HttpDelete("{id}")]
        public async Task&lt;IActionResult&gt; DeleteDepartment(long id)
        {
            if (_context.Department == null)
            {
                return NotFound();
            }
            var item = await _context.Department.FirstOrDefaultAsync(e =&gt; e.IDDepartment==id);
            if (item == null)
            {
                return NotFound();
            }

            _context.Department .Remove(item);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool DepartmentExists(long id)
        {
            return (_context.Department.Any(e =&gt; e.IDDepartment  == id));
        }

    }    

And also a SEARCH controller

[ApiController]
[Route("[controller]/[action]")]    
public partial class AdvancedSearchDepartmentController : Controller
{
    private ISearchDataDepartment _search;
    public AdvancedSearchDepartmentController(ISearchDataDepartment search)
	{
        _search=search;
	}

    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; GetAll()
    {
        await foreach(var item in _search.DepartmentFind_AsyncEnumerable(null))
        {
            yield return (Department_Table)item!;
        }
        
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; GetWithSearch(SearchDepartment s)
    {
        await foreach(var item in _search.DepartmentFind_AsyncEnumerable(s))
        {
            yield return (Department_Table)item!;
        }
        
    }

//has one key
    [HttpGet]
    public async Task&lt;Department_Table?&gt; GetSingle(long id){
        var data=await _search.DepartmentGetSingle(id);
       if(data == null)
        return null;
       return (Department_Table)data;
    }

                  [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValues_IDDepartment( long[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.InArray,eDepartmentColumns.IDDepartment,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
     [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValues_IDDepartment( long[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.NotInArray,eDepartmentColumns.IDDepartment,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
         [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValue_IDDepartment( long  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach (var item in _search.DepartmentSimpleSearch_IDDepartment(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValue_IDDepartment( long  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach (var item in _search.DepartmentSimpleSearch_IDDepartment(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public  async IAsyncEnumerable&lt;Department_Table&gt; SimpleSearch_IDDepartment(GeneratorFromDB.SearchCriteria sc,  long value){
        await foreach(var item in _search.DepartmentSimpleSearch_IDDepartment(sc,value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNull_IDDepartment(){
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach(var item in _search.DepartmentSimpleSearchNull_IDDepartment(sc))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNotNull_IDDepartment(){
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach(var item in _search.DepartmentSimpleSearchNull_IDDepartment(sc))
        {
            yield return (Department_Table)item!;
        }
    }
              [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValues_Name( string[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.InArray,eDepartmentColumns.Name,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
     [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValues_Name( string[]  values)
    {
        string? value=null;
        if(values.Length&gt;0)
            value=string.Join( ",",values);
        var sc=SearchDepartment.FromSearch(GeneratorFromDB.SearchCriteria.NotInArray,eDepartmentColumns.Name,value);
        await foreach (var item in _search.DepartmentFind_AsyncEnumerable(sc))
        {
        
            yield return (Department_Table)item!;
        }
    }
         [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; EqualValue_Name( string  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach (var item in _search.DepartmentSimpleSearch_Name(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; DifferentValue_Name( string  value)
    {
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach (var item in _search.DepartmentSimpleSearch_Name(sc, value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public  async IAsyncEnumerable&lt;Department_Table&gt; SimpleSearch_Name(GeneratorFromDB.SearchCriteria sc,  string value){
        await foreach(var item in _search.DepartmentSimpleSearch_Name(sc,value))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNull_Name(){
        var sc = GeneratorFromDB.SearchCriteria.Equal;
        await foreach(var item in _search.DepartmentSimpleSearchNull_Name(sc))
        {
            yield return (Department_Table)item!;
        }
    }
    [HttpGet]
    public async IAsyncEnumerable&lt;Department_Table&gt; FindNotNull_Name(){
        var sc = GeneratorFromDB.SearchCriteria.Different;
        await foreach(var item in _search.DepartmentSimpleSearchNull_Name(sc))
        {
            yield return (Department_Table)item!;
        }
    }
        


    


}//end class

 

Finally , ExampleWebAPI . This will add the controllers from the ExampleControllers   to show the use .

var assControllers = typeof(UtilsControllers).Assembly;
builder.Services.AddControllers()
              .PartManager.ApplicationParts.Add(new AssemblyPart(assControllers)); ;
        

 

Of course , the name Example will be replaced , from the template, with the name of your project

Db2Code–part 1–idea

From the second year that I have started programming, I find tedious to replicate the tables structure to code and to generate classes and SQL . I started asking questions  – and no one has ever envisaged near me an ORM ( it was not a definition then ). I have made my own ORM  – and it was paying . However, I did not know how to market  – so oblivion was his fate.

Moving to this year , there are many ORM . One of those is EFCore – that has also a revers engineer /  scaffolding ready : https://learn.microsoft.com/en-us/ef/core/managing-schemas/scaffolding/ . But how generate all classes , controllers and others ? I was into https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview – but this does not allow programmers to modify what it generates. But , fortunately, this year EFCore7 has templates, based on t4 templates : https://learn.microsoft.com/en-us/ef/core/managing-schemas/scaffolding/templates?tabs=dotnet-core-cli . That means that now we could generate whatever we need!

So this is the project : A DB2Code Visual Studio Template. You can download from https://marketplace.visualstudio.com/items?itemName=ignatandrei.databasetocode  . Also, the source code is at https://github.com/ignatandrei/queryViewer/ . Download, modify the connectionDetails.txt and it will generate from you everything to WebAPI from SqlServer or Sqlite ( more providers soon). Also, it generates  different projects ( models, context, controllers) – and you can also modify the templates. It works ( for the moment) just with tables with 0 or 1 PK.

The next parts will be only about the challenges to develop such a solution.

SRE with .NET

I have found https://oschvr.com/posts/what-id-like-as-sre/  and I trying to answer from the .NET Core application .

How can I check the health of the service ?

Simple : Use https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks

How can I safely and gracefully restart the service

Simple : Use https://www.nuget.org/packages/NetCoreUsefullEndpoints#readme-body-tab

How and why would the service fail ?

Partial answer : same with the health : See sql server at https://tiltwebapp.azurewebsites.net/healthchecks-ui

Do you use appropriate logging levels depending on the environments ?

Answer: Use logging frameworks such as https://www.nuget.org/packages/NLog/ with config file –to let SRE modify levels logged at runtime

What kind of metrics are you exposing

Simple : use grafana, prometheus, azure instrumentation. See https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core?tabs=netcorenew%2Cnetcore6

Is there any documentation/design specification for the service ?

Partial answer: Use OpenAPI  / swagger https://learn.microsoft.com/en-us/training/modules/improve-api-developer-experience-with-swagger/ . For advanced uses  see https://www.nuget.org/packages/NetCore2Blockly/

How does the data flow through the service ?

Not having (yet ) an answer

What is the testing coverage ?

Simple : Use XUnit, NUnit, MsTest with Coverlet with ReportGenerator . For advanced uses see https://github.com/LightBDD/LightBDD and SpecFlow

NetCoreUsefullEndpoints-part 10- adding LongRunningTasks

In the Nuget NetCoreUsefullEndpoints I have already an  endpoint to shutdown  –  see http://msprogrammer.serviciipeweb.ro/2023/02/20/netcoreusefullendpoints-part-9-adding-endpoints-for-shutdown/ .

However, this means that there are no ( or some … )  long running operations that are running … How to identify those ?

For the starting point , I was thinking at something simple, like an IDisposable that knows when the Long Running operation is finished

[HttpGet(Name = “GetWeatherForecast”)]
         public async Task<WeatherForecast[]> Get()
         {
             using var lr = UsefullExtensions.UsefullExtensions.AddLRTS(“weather”+DateTime.UtcNow.Ticks);
             await Task.Delay(5000);
             return Enumerable.Range(1, 5).Select(index => new WeatherForecast
             {
                 Date = DateTime.Now.AddDays(index),
                 TemperatureC = Random.Shared.Next(-20, 55),
                 Summary = Summaries[Random.Shared.Next(Summaries.Length)]
             })
             .ToArray();
         }

The implementation is very simple for adding:

internal static Dictionary<string, LongRunningTask> lrts = new();
public static LongRunningTask AddLRTS(string id, string? name = null)
{
     if(lrts.ContainsKey(id))
     {
         lrts[id].Dispose();
     }
     lrts.Add(id,new LongRunningTask(id, name ?? id));
     return lrts[id];
}

And for removing itself:

public record LongRunningTask(string id, string? name = “”) : IDisposable
{
     public void Dispose()
     {
         UsefullExtensions.lrts.Remove(id);
         GC.SuppressFinalize(this);
     }

}

Also, an endpoint will be registered for the user know what are the operations

var rh = route.MapGet(“api/usefull/LongRunningTasks/”,
             (HttpContext httpContext) =>
             {
                 var data=lrts.Select(it=>new { it.Key, it.Value.name }).ToArray();
                 return data;
             });

        var rhCount = route.MapGet(“api/usefull/LongRunningTasks/Count”,
             (HttpContext httpContext) =>
             {
                 return lrts.LongCount();
             });

And that will be to register and found what are the long running tasks in ASP.NET Core

NetCoreUsefullEndpoints-part 9- adding endpoints for shutdown

In my NuGet NetCoreUsefullEndpoints package  I want to can shutdown the application . Could be hard way, like calling Environment.Exit or the easy way , like calling a cancellation token. Also, what should happen with all the requests that are coming ? ( And I do not want to mention long running tasks – I do not found a useful registration / un-registration for those)

So I come with the following implementation for forced exit

//IEndpointRouteBuilder

var rhForced = route.MapPost(“api/usefull/shutdownForced/{id:int}”,
     (int id) =>
     {
         Environment.Exit(id);
     });

For the shutdown that is requested , I come with 3 modifications:

1.In Program.cs , I put a Middleware for not serving the HTTP

builder.Services.AddSingleton<MiddlewareShutdown>();

app.UseMiddleware<MiddlewareShutdown>();

await app.RunAsync(UsefullExtensions.UsefullExtensions.cts.Token);

2.  Register the routes in order to use the cancellation token

//IEndpointRouteBuilder

var rhSec = route.MapPost(“api/usefull/shutdownAfter/{seconds}”,
             (HttpContext httpContext, int seconds) =>
             {
                 RequestedShutdownAt = DateTime.UtcNow;
                 var h = cts.Token.GetHashCode();
                 cts?.CancelAfter(Math.Abs(seconds)*1000);
                 return h;

            });

3. Use a middleware to return 418 Status Code if the application is shutting down

public class MiddlewareShutdown : IMiddleware
{
     public async Task InvokeAsync(HttpContext context, RequestDelegate next)
     {
         if (UsefullExtensions.RequestedShutdownAt != null)
         {
             context.Response.StatusCode = 418;
             await context.Response.WriteAsync(“Service is stopping at ” + UsefullExtensions.RequestedShutdownAt!.Value.ToString(“s”));
             return;
         }
         await next(context);
         return;
     }
}

And with those you can shutdown gracefully any ASP.NET Core application ( without long running tasks!)

[ADCES]React – first steps(again) and SRE and .NET

Presentation 1: React( first steps) – again
Presenter: Andrei Ignat, http://msprogrammer.serviciipeweb.ro/
Descriere : O sa fie atinse urmatoarele concepte prin exemple practice:

1. React ca librarie, nu ca framework
2. Create-react-app – cu typescript si modificarile care trebuie aduse
3. Componente si Utilizarea de Hooks
4. Comunicarea intre componente de React – de la parinte la copil si intre copii diferiti cu RxJS
4. Utilizarea de librarii: React Router, MUI

Presentation 2: What a SRE wants and how .NET provides
Descriere: We will answer to https://oschvr.com/posts/what-id-like-as-sre/ with examples and tests 😉

Va astept la https://www.meetup.com/bucharest-a-d-c-e-s-meetup/events/291291505/

NetCoreUsefullEndpoints-part 8- adding start date

In my NuGet NetCoreUsefullEndpoints package  I have had already registered the actual date as

var rh = route.MapGet(“api/usefull/dateUTC”, (HttpContext httpContext) =>
            {
                return Results.Ok(DateTime.UtcNow);
            });

Now I want to register also the start date – the date where the application has been started.

1. How to do this  ?

2. What will be the route ?

For 1 can be a static constructor or, better , the singleton in C# :

private static DateTime startDateUTC = DateTime.UtcNow;

For 2 it is more complicated

My solution ( just break compatibility, since I have not a v1 and v2) was the following

var rh = route.MapGet(“api/usefull/date/startUTC”, (HttpContext httpContext) =>
{
     return Results.Ok(startDateUTC);
});

var rhUTC = route.MapGet(“api/usefull/date/nowUTC/”,
     (HttpContext httpContext) =>
     {
         return TypedResults.Ok(DateTime.UtcNow);
     });

rhUTC.AddDefault(corsPolicy, authorization);

So now I have same routing api/usefull/date

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.