Category: .NET Core

Templating Roslyn Source Code Generators

I want that , when I generate code with Roslyn, to have a template that I can easy modify to generate code . Also, I want to let the user ( the programmer , in this case) modify this template  – maybe he has better ideas than me.

For reading from the RSCG, is pretty simple: Make as an embedded resource and read with

internal class EmbedReader
{
     static Assembly assembly;
     static EmbedReader()
     {
         assembly = Assembly.GetExecutingAssembly();

    }
     public static string ContentFile(string name)
     {
         var resourceName = name;

        using (Stream stream = assembly.GetManifestResourceStream(resourceName))
         using (StreamReader reader = new StreamReader(stream))
         {
             string result = reader.ReadToEnd();
             //this is the content
         }
     }
}

For let the programmer user put his template, put as as additional file in the csproj

<ItemGroup>
         <AdditionalFiles Include=”context.txt”  />

</ItemGroup>

and read with

var file = context.AdditionalFiles.Where(it => it.Path.EndsWith($”{val}.txt”))
                     .Select(it => it.GetText())
                     .FirstOrDefault();

            if (file != null)
             {
                 content = string.Join(Environment.NewLine, file.Lines.Select(it => it.ToString()));
             }

To do both,if the result of the first is empty, read the second (or opposite ? )

[NuGet]: Transplator

This is a Nuget that it is what Razor should have been. It is a Roslyn Source Code Generator that transforms template into code.

Link: https://www.nuget.org/packages/Transplator 

Site: https://github.com/atifaziz/Transplator

What it does:  Takes a template and generates source code for outputting anything inside.

Usage:

Somewhere in the csproj:

<ItemGroup>
   <CompilerVisibleProperty Include=”DebugTransplator” />
   <CompilerVisibleItemMetadata Include=”AdditionalFiles” MetadataName=”SourceItemType” />
   <CompilerVisibleItemMetadata Include=”AdditionalFiles” MetadataName=”Name” />
   <AdditionalFiles Include=”ASM.txt” SourceItemType=”Transplate” KeepMetadata=”Name” />

</ItemGroup>

Somewhere in a .cs file

var response = new ASMTemplate().Render();

And the template

partial class ASMTemplate
{
     StringBuilder sb = new StringBuilder();
     public void WriteText(string text)
     {
         sb.AppendLine(text);
     }
     public void WriteValue(int text)
     {
         sb.AppendLine(“”+text);
     }  
     public void WriteValue(int? text)
     {
         if(text != null)
             sb.AppendLine(“” + text);
     }
     public void WriteValue(DateTime text)
     {
         sb.AppendLine(text.ToString(“yyyy MMMM dd HH:mm:ss”));
     }
     public void WriteValue(string text)
     {
         sb.AppendLine(text);
     }
     public string Render()
     {        
         this.RenderCore();
         return sb.ToString();
     }
}

[RSCG]–About My Software

I have made a new improvement to my Roslyn Source Code Generator, AMS . Until now it just said the time and the commit date in a CI scenario  ( Git ( GitLab, GitHub), Azure …)

Now –  what if it can list all the merge requests between 2 dates  , so you can see what is new ? 

Now you can have – see this:

What you need to do  ?

Just add the following codes

using AMS_Base;
[assembly:VersionReleased(Name=”PreviousReleases”,ISODateTime =”2022-03-31″,recordData = RecordData.Merges)]
[assembly: VersionReleased(Name = “WithVersioning”, ISODateTime = “2022-04-02”, recordData = RecordData.Merges)]
[assembly: AMS_Base.VersionReleased(Name = “FutureRelease”, ISODateTime = “9999-04-16”, recordData = AMS_Base.RecordData.Merges)]

( Of course , you should read first how to add the NuGet package – see https://github.com/ignatandrei/RSCG_AMS/blob/main/README.md )

How it is made  ?

First, we should find Git –  start a process with

Where git.exe

or

which git

to find the location.

Second, start a process with the location of the git find below

log –merges –pretty=””%an|%cs|%H|%s

to see the merges . Then merge with the dates

assembly:VersionReleased 

that are registered before – and this is all !

EventId in logging

Finally I have realized why it is necessary to have EventId logged and it is not the same life without him – especially in the microservices context.

So why it is not enough

public static void LogError (this Microsoft.Extensions.Logging.ILogger logger, Exception? exception, string? message, params object?[] args);

and I strongly recommend

public static void LogError (this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, string? message, params object?[] args);

?

  Let’s assume that you have something like a log aggregator , such as ElasticSearch

You want to see from the error message , at a glace the details of the problem – like the root cause , some additional data – without going into details of the object. Something like “Drive C:  too full to write”  or “Drive D:  too full to write”  .

Also, you want to see how many error of this kind occurs – how many “ too full to write “ have occured ?

You ca put the message like “ Drive too full to write – look into details ” – and count those – but the investigators will have to click on more time to find what drive it is.

Welcome to EventID – https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.eventid?view=dotnet-plat-ext-6.0 .

You can have a

var evt = new EventId(7000,”DriveTooFull” );

logger.LogError(evt, “Drive C: too full to write” )

Now you have the message detaisl and you can count how many 7000 ( or DriveTooFull ) errors you have

[NuGet]: HealthCheck

This is a Nuget that I use in all projects when I am making any ASP.NET Core prohect

Link: https://www.nuget.org/packages/AspNetCore.HealthChecks.UI/

Site: https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks 

What it does:  Implements health checks for your site and/or dependencies

Usage:

public void ConfigureServices(IServiceCollection services)
{

//code
     services.AddHealthChecks()
         .AddSqlServer(myConnectionString)
  }

[NuGet]: Polly

This is a Nuget that I use in all projects when I am making HTTP calls..

Link: https://www.nuget.org/packages/polly

Site: https://github.com/App-vNext/Polly

What it does:  Implements all kind of policies for retrying – see https://github.com/App-vNext/Polly/wiki/Transient-fault-handling-and-proactive-resilience-engineering

Usage: – copied from https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-http-call-retries-exponential-backoff-polly

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
     return HttpPolicyExtensions
         .HandleTransientHttpError()
         .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
         .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2,
                                                                     retryAttempt)));
}

Dependent Framework Versioning

There are multiple ways to version a software . I have used SemanticVersioning ( https://semver.org/ ) and Calendar Versioning (  https://calver.org/ )

Of course , there are others  – please read https://en.wikipedia.org/wiki/Software_versioning   – interesting versioning based on e or PI .

However , I want to propose a new standard :

Dependent Framework Versioning

The major version of a  package is the same of the major version of the framework that is installed on . For the others , can be calver or semver or any other – depending on your choice.

For example , I have made a BlocklyAutomation  package for .NET Core 3 – and it is versioned like

For .NET Core 3  –  https://www.nuget.org/packages/NetCore2Blockly/3.2022.224.16

For .NET Core 5https://www.nuget.org/packages/NetCore2Blockly/5.2022.210.2007

Why is this ? To be easy identified by the users of the package. If I have one user that have an app  on .NET Core 3 and other on .NET Core 5, how can they identify easy what is the latest version for this package  ? With this approach , this can be done just looking on the major version corresponding with the framework version (and yes, I use calver versioning for the rest – yyyy.Md.Hm)

[NuGet]:Scriban

This is a Nuget that I use in almost every .NET Core project to get the error details ( 500)

Link: https://www.nuget.org/packages/Scriban/

Site: https://github.com/scriban/scriban

What it does:  Like Razor –  a template intepreter

Usage:

var template = Template.Parse(@"
<ul id='products'>
  {{ for product in products }}
    <li>
      <h2>{{ product.name }}</h2>
           Price: {{ product.price }}
           {{ product.description | string.truncate 15 }}
    </li>
  {{ end }}
</ul>
");
var result = template.Render(new { Products = this.ProductList });

Using files in a ASP.NET Core nuget package

To use static (html) files in a NUGET ASP.NET Core package to be displayed I have used the following solution

1. Put the files in a folder ( in my case , blocklyAutomation )

2. Put in the .csproj to embed the files

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

3.  Create an extension to use it

;

public static void UseBlocklyUI(this IApplicationBuilder appBuilder)
{
    var manifestEmbeddedProvider =
            new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly());
    var service = appBuilder.ApplicationServices;
    mapFile("blocklyAutomation", manifestEmbeddedProvider, appBuilder);


}

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(dirName.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);
            });
        });
    }

}

4. That is all

Windows terminal + Powershell to run IDE, Angular, .NET Core

I work at http://github.com/ignatandrei/BlocklyAutomation – and every time I need to run Angular , Visual Studio Code IDE and .NET Core run ( and Visual Studio  sometimes  – it is more web based)

Also, sometimes I need to run in a container – to install globally something that I do not want to have in my PC.

So  I have 1 windows terminal command to run those :

First one

wt new-tab   –title RootSource –suppressApplicationTitle -p “Windows PowerShell” -d . cmd /k “cd src && code . && powershell” ;split-pane  –title Angular –suppressApplicationTitle -V -p “Windows PowerShell” -d . cmd /k “cd src &&  npm run start –watch && powershell” ;split-pane  –title LocalAPI_NetCore –suppressApplicationTitle -V -p “Windows PowerShell” -d . cmd /k “cd src/Local/LocalAPI/LocalAPI && dotnet watch run”

As you see , I have

code .

to run VSCode,

npm run start –watch

to run Angular and

dotnet watch run

to run dotnet.

Second one:

wt new-tab -p “Windows PowerShell” -d . ;split-pane -p “Windows PowerShell” -d . cmd /k “cd src && devcontainer open .”

The second it just works with devcontainer  – if you are interested in sources, see https://github.com/ignatandrei/BlocklyAutomation/tree/main/src/.devcontainer 

With those, I can start my developer environment fast ( how fast, that depends on my PC)

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.