Category: .NET Core

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

Nuget packages and Github repositories

You can see the Github repositories that you have worked  last year by going to https://docs.github.com/en/graphql/overview/explorer and entering

query ContributionGraph {
   user(login: “ignatandrei”) {
     contributionsCollection(
       from: “2022-01-01T00:00:00+00:00”
       to: “2022-12-31T00:00:00+00:00”
     ) {
       commitContributionsByRepository(maxRepositories:100){
         repository{
           nameWithOwner
           url
           updatedAt
         }
       }     
     }
   }
}

Mine are

ignatandrei/TILT
ignatandrei/BlocklyAutomation
ignatandrei/QueryViewer
ignatandrei/RSCG_AMS
ignatandrei/rxDemo
ignatandrei/NetCoreUsefullEndpoints
ignatandrei/Presentations
ignatandrei/NETCoreBlockly
ignatandrei/FunctionsDI
ignatandrei/GeneratorOfHelp
ignatandrei/MicroservicesPortChooser
ignatandrei/AOP_With_Roslyn
ignatandrei/RSCG_TimeBombComment
ignatandrei/RSCG_Examples

And those  are the NuGet packages:

programmerall
GOH
QueryGenerator
RSCG_FunctionsWithDI_Base
RSCG_FunctionsWithDI
AMS_Base
AMSWebAPI
RSCG_AMS
AOPMethodsGenerator
AOPMethodsCommon
RSCG_TimeBombComment
NetCoreBlockly

[Programmer Tools]Dotnet tools (local and global)

The list have been obtained with the help of VisualAPI, former BlocklyAutomation.
You can obtain a list on your device by running

<code>
dotnet tool update –global programmerall –no-cache
programerall
</code>

Then browse to http://localhost:37283/blocklyAutomation/automation/loadexample/dotnetTool and press Execute
( You can have automation the same for your site – just contact me)

Those are my .NET Tools ( either local or global)

  1. coveralls.net:
    Coveralls.io uploader for .NET Code Coverage. Supports opencover and visual studio’s codecoverage.exe on windows, and monocov for mono
  2. coverlet.console:
    Coverlet is a cross platform code coverage tool for .NET, with support for line, branch and method coverage.
  3. csharptotypescript.clitool:
    Convert C# Models, ViewModels and DTOs into their TypeScript equivalents.
  4. dotmorten.omdgenerator:
    Automatically generates an HTML Document with an object model diagram for your C# library<
  5. dotnet-aop:
    Simple AOP for .NET with RoslynTo use Install as global tool asdotnet tool install -g dotnet-aopgo to your solution folder rundotnet aopFor customizing , see https://github.com/ignatandrei/AOP_With_Roslyn/blob/master/AOPR
  6. dotnet-coverage:
    Dynamic code coverage tools.
  7. dotnet-depends:
    Dependency explorer for .NET
  8. dotnet-ef:
    Entity Framework Core Tools for the .NET Command-Line Interface.Enables these commonly used dotnet-ef commands:dotnet ef migrations adddotnet ef migrations listdotnet ef migrations scriptdotnet ef dbcontext infodotnet ef
  9. dotnet-outdated-tool:
    A .NET Core global tool to update project dependencies.
  10. dotnet-project-licenses:
    Package Description
  11. dotnet-property:
    .NET Core command-line (CLI) tool to update project properties and version numbers on build.
  12. dotnet-repl:

    dotnet-repl

    This project is an experiment using .NET Interactive / Polyglot Notebooks and Spectre.Console to create a polyglot .NET REPL for use on

  13. dotnet-reportgenerator-globaltool:
    ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov or lcov into human readable reports in various formats. The reports do not only
  14. dotnet-sonarscanner:
    The SonarScanner for .Net Core from version 2.1 allows easy analysis of any .NET project with SonarCloud/SonarQube.
  15. dotnet-suggest:
    Package Description
  16. dotnet-versioninfo:
    Display version information of .NET Core assemblies
  17. dotnetthx:
    Dotnet tool listing all authors of packages you are using in your project
  18. grynwald.mddocs:
    MdDocs is a tool generate documentation as markdown files. This package provides MdDocs as .NET Core global tool.
  19. markdownsnippets.tool:
    .NET Core Global Tool for merging code snippets with markdown documents
  20. microsoft.dotnet.mage:
    Package Description
  21. moniker.cli:
    Moniker CLI is a tiny .NET Core Global Tool for generating fun names.
  22. nswag.consolecore:
    NSwag: The OpenAPI/Swagger API toolchain for .NET and TypeScript
  23. powershell:
    PowerShell global tool
  24. programmerall:

    ProgrammerAll is a collection of HTTP API Utils, that can be used by Swagger or VisualAPI
    How to install
    Install as .NET Tool ( local or global)
    dotn

  25. run-script:

    dotnet-run-script

    A dotnet tool to run arbitrary commands from a project’s “scripts” object.

  26. skbkontur.typescript.contractgenerator.cli:
    A tool that can generate TypeScript types from C# classes
  27. ubiety.versionit:
    VersionIt is for automatically generating versions and changelog based on conventional commits.
  28. xunit-cli:
    A global .NET Core tool for running xunit tests.Installation: dotnet tool install -g xunit-cliUsage: xunit [additionalArgs]    This package was build from source code at https://github.com/natemcmaster/xuni

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.