RSCG–AMS – About My software –Documentation– part 7
Now it is time to let others know about the project. And the first step is to make documentation. And , because a picture is worth many words, here is the picture:
Also, instructions about how to use will help the programmers:
For a DLL it is simple :
<ItemGroup> <PackageReference Include="AMS_Base" Version="2021.6.29.1820" /> <PackageReference Include="RSCG_AMS" Version="2021.6.29.1820" ReferenceOutputAssembly="false" OutputItemType="Analyzer" /> </ItemGroup>
For an ASP.NET Core application:
<PackageReference Include="AMSWebAPI" Version="2021.6.29.1820" /> <PackageReference Include="AMS_Base" Version="2021.6.29.1820" /> <PackageReference Include="RSCG_AMS" Version="2021.6.29.1820" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
and the code will be
app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.UseAMS(); });
[ADCES]WebAPI Return & StateMachine with Stateless
I have presented what you can return from WebAPI at https://www.meetup.com/Bucharest-A-D-C-E-S-Meetup/events/278058186/ . The presentation is at https://ignatandrei.github.io/Presentations/WebAPIReturnsprez.html
RSCG–AMS – About My software –Reading csproj– part 6
Now it is time to put some more data – like authors and version. I have read a lot ( and tried a lot) about CompilerVisibleProperty and CompilerVisibleItemMetadata ( see https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md ) . However, I was unable to get the data ( Authors and Version) from there .
So this is what I was get, to read the csproj near the program:
private ItemsFromCSPROJ TryGetPropertiesFromCSPROJ(GeneratorExecutionContext context) { var ret= new ItemsFromCSPROJ(); try { var dirFolder = ((dynamic)(context.Compilation)).Options?.SourceReferenceResolver?.BaseDirectory; if (string.IsNullOrWhiteSpace(dirFolder)) return ret; var file = Directory.GetFiles(dirFolder, "*.csproj"); if (file.Length != 1) throw new ArgumentException($"find files at {dirFolder} :{file.Length} "); var xmldoc = new XmlDocument(); xmldoc.Load(file[0]); XmlNode node; node = xmldoc.SelectSingleNode("//Authors"); ret.Authors = node?.InnerText; node = xmldoc.SelectSingleNode("//Version"); ret.Version = node?.InnerText; return ret; } catch(Exception ) { //maybe log warning? return ret; } }
Next time I will show how it looks
RSCG–AMS – About My software –NuGet– part 5
The problem with RSCG is to differentiate between the generator and the code generated. In my case , the base class should be in one nuget, the generator in other ( to can remove it from build) and the WebAPI in another.
That took me a whole day and the result is ok . Pain Points:
https://turnerj.com/blog/the-pain-points-of-csharp-source-generators
CI action and Deploy to nuget
PackageReference Include=”Microsoft.VisualStudio.Web.CodeGeneration.Design
Now it works for WebAPI with
<PackageReference Include=”AMSWebAPI” Version=”2021.6.26.1937″ />
<PackageReference Include=”AMS_Base” Version=”2021.6.26.1937″ />
<PackageReference Include=”RSCG_AMS” Version=”2021.6.26.1937″ ReferenceOutputAssembly=”false” OutputItemType=”Analyzer” />
And I hve seen that I am not the only one to differentiate between CI servers – for example,
https://github.com/VerifyTests/DiffEngine/blob/master/src/DiffEngine/BuildServerDetector.cs
But now the work is done and you can access all AMS via web ,
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.UseAMS();
});
either to AMS/index.html , either to AMS/all .
Friday Links 431
RSCG–AMS – About My software –WebAPI– part 4
Now it should be an easy way to see in the WebAPI. First, return the data for all software that respected that :
public static IEndpointRouteBuilder UseAMS(this IEndpointRouteBuilder endpoints) { endpoints.MapGet("/ams/All", async app => { var data = AboutMySoftware.AllDefinitions.Select(it => it).ToArray(); await app.Response.WriteAsJsonAsync(data); }); return endpoints; }
Now, how can I make a small html to display things ? I can do with Razor Library – but it is too big and maybe the developers do not want to have this dependency. So I decided for https://www.nuget.org/packages/Transplator/ – fairly easy to use. And is another RSCG that converts template code into C# code.
So now the code looks like this:
public static IEndpointRouteBuilder UseAMS(this IEndpointRouteBuilder endpoints) { endpoints.MapGet("/ams/All", async app => { var data = AboutMySoftware.AllDefinitions.Select(it => it).ToArray(); await app.Response.WriteAsJsonAsync(data); }); endpoints.MapGet("/ams/index", app => { var response = new ASMTemplate().Render(); app.Response.ContentType = "text/html"; return app.Response.WriteAsync(response); }); return endpoints; }
where the ASMTemplate is
<style> table { font-family: arial, sans-serif; border-collapse: collapse; width: 100%; } td { border: 1px solid #dddddd; text-align: left; padding: 8px; } th{ background-color: black; color: white; border: 1px solid #dddddd; text-align: left; padding: 8px; } tr:nth-child(even) { background-color: #dddddd; } </style> <table> <tr> <th>Nr</td> <th>Component</th> <th>Date</th> <th>Commit</th> <th>RepoUrl</th> </tr> {%~ int i=1; ~%} {%~ foreach(var item in AMS.AboutMySoftware.AllDefinitions){ %} <tr> <td>{% i++ %}</td> <td>{% item.Key %} </td> <td>{% item.Value.DateGenerated %} </td> <td>{% item.Value.CommitId %} </td> <td>{% item.Value.RepoUrl %}</td> </tr> {% } %} </table>
It is time now to make the nuget packages.
RSCG–AMS – About My software –Multiple assemblies– part 3
The problem that I face now – and must be solved – is what to do if I have multiple assemblies / dlls / asp.net core that wants to have the About My Software listed ? It will be a name conflict between the classes – or, if we put in different namespaces, will be difficult to find them to be listed .
For the second problem – it is relatively clear – we can have a Dictionary with the key AssemblyName and the value the instance of the AMS class for this assembly.
But how to initialize ?
First thing that I thought – static constructor . In the static constructor for AMS in the each assembly class – add to the above Dictionary the instance.
But , but … the static constructor is not called unless a class instance /static method is called. So … ?
So ModuleInitializer to the rescue:
The code generated is now ( for an assembly with the name AMSConsole)
public class AboutMySoftware_AMSConsole : AboutMySoftware { [System.Runtime.CompilerServices.ModuleInitializer] public static void Add_AboutMySoftware_AMSConsole() { AboutMySoftware.AllDefinitions.Add("AMSConsole", new AboutMySoftware_AMSConsole()); } public AboutMySoftware_AMSConsole() { AssemblyName = "AMSConsole"; DateGenerated = DateTime.ParseExact("20210624191615", "yyyyMMddHHmmss", null); CommitId = "not in a CI run"; RepoUrl = "not in a CI run"; } }
The code to retrieve is modified like
Console.WriteLine("Show About My Software versions"); var amsAll = AboutMySoftware.AllDefinitions; foreach (var amsKV in amsAll) { var ams = amsKV.Value; Console.WriteLine($"{amsKV.Key}.{nameof(ams.AssemblyName)} : {ams.AssemblyName}"); Console.WriteLine($"{amsKV.Key}.{nameof(ams.DateGenerated)} : {ams.DateGenerated}"); Console.WriteLine($"{amsKV.Key}.{nameof(ams.CommitId)} : {ams.CommitId}"); Console.WriteLine($"{amsKV.Key}.{nameof(ams.RepoUrl)} : {ams.RepoUrl}"); }
So far so good. Next implementation for WebAPI
RSCG–AMS – About My software –work– part 2
So now it is time to work at implementation This will be a standard RSCG – generating code. I make also a test console to display the values.
The implementation will consider the fact that we can have many Source Control providers – each one with his ideas about variables. So I made 2 classes – one base abstract
abstract class AMS { public AMS(GeneratorExecutionContext context) { AssemblyName = context.Compilation.AssemblyName; GeneratedDate = DateTime.UtcNow; } public string AssemblyName { get; internal set; } public DateTime GeneratedDate { get; internal set; } public string CommitId { get; internal set; } public string RepoUrl { get; internal set; } }
and one implementation for Github
//https://docs.github.com/en/actions/reference/environment-variables class AMSGitHub : AMS { public AMSGitHub(GeneratorExecutionContext context):base(context) { CommitId = Environment.GetEnvironmentVariable("GITHUB_SHA"); RepoUrl = Environment.GetEnvironmentVariable("GITHUB_SERVER_URL") + "/" + Environment.GetEnvironmentVariable("GITHUB_REPOSITORY"); } }
The code for generator is a bit more complicated:
var nameSpace = "AMS"; var ams = new AMSGitHub(context); var classDef=$@" using System; namespace {nameSpace} {{ public class AboutMySoftware{{ public string AssemblyName {{ get {{ return ""{ams.AssemblyName}"" ; }} }} public DateTime DateGenerated {{ get {{ return DateTime.ParseExact(""{ams.GeneratedDate.ToString("yyyyMMddHHmmss")}"", ""yyyyMMddHHmmss"", null); }} }} public string CommitId {{ get {{ return ""{ams.CommitId}"" ; }}}} public string RepoUrl {{ get {{ return ""{ams.RepoUrl}"" ; }}}} }} }}";
The console to test has the following code
static void Main(string[] args) { Console.WriteLine("Show About My Software versions"); var ams = new AboutMySoftware(); Console.WriteLine($"{nameof(ams.AssemblyName)} : {ams.AssemblyName}"); Console.WriteLine($"{nameof(ams.DateGenerated)} : {ams.DateGenerated}"); Console.WriteLine($"{nameof(ams.CommitId)} : {ams.CommitId}"); Console.WriteLine($"{nameof(ams.RepoUrl)} : {ams.RepoUrl}"); }
and the output , in GitHub actions , is
Show About My Software versions AssemblyName : AMSConsole DateGenerated : 06/24/2021 03:16:51 CommitId : d8cb041470d93f68a4dc7fca7d131c207db8ab69 RepoUrl : https://github.com/ignatandrei/RSCG_AMS
RSCG–AMS – About My software –idea – part 1
Every product should have an About page . In the About page should be listed
- The product name
- The version of the product
- Link to latest version ?
- Built date+ time
- The commit ID
- The authors
- Link to the License
- Other components version and link to about
- Third Party notices
- Repository link ( github, gitlab, …)
- Documentation Link
- Release Notes link
- Maybe log file ?
- Maybe latest errors ?
- Maybe system.info ?
This should be available for
- any dll – as a class
- any console project – as Console.WriteLine
- for any ASP.NET Core app
- as a class
- as a WebAPI
- as an HTML UI
You can see an example at https://netcoreblockly.herokuapp.com/AMS