Category: .NET Core

RecordVisitors-idea–part1

It will be interesting to have a middleware of .NET Core that can record the name of the visitors in order to see what are the latest visitors of your site .

What will be the uses ?

  1. See when the people has visiting your site
  2. See the latest visitors
  3. If you have a site for mobile and a site for web , prevent multiple logins
  4. Fun 😉

What characteristics should have ?

  1. Record visitors
  2. Make an endpoint – recordVisitors/AllVisitors5Min to see the latest visitors as JSON
  3. Let the programmer choose how to obtain  the name of the visitor  ( via Forms, , AD Identity, JWT,  Azure , OAUTH…)
    1. Choose a default to work fast
  4. Let the programmer choose his variant of persisting ( SqlServer, Mongo , Sqlite, CSV… ) where to be recorded
    1. Choose a default  to work fast
    2. Make as an event to intercept and  add additional details

Now let’s see what is for the programming!

Dotnet diagnostics / open telemetry on CI in 6 simple steps

Prerequisites : Having Jaeger / Zipkin for visualization . The easy way for installing Jaeger is to have Jaeger all in one ( either via Docker, either run on PC)

 

Step 1: Modify the csproj to add this

<ItemGroup>
  <PackageReference Include="OpenTelemetry" Version="1.1.0-beta1" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc3" />
    <PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.1.0-beta1" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc3" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc3" />
    <PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.0.0-rc3" />
  
    <AdditionalFiles Include="../AutoMethod.txt" />
    <PackageReference Include="AOPMethodsCommon" Version="2021.5.17.2230" />
    <PackageReference Include="AOPMethodsGenerator" Version="2021.5.17.2230" />

  </ItemGroup>


Optional : To see the RSCG files, add also

  <PropertyGroup>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
  </PropertyGroup>

Step 2:

2.1  If  ASP.NET Core  app,  add the following to Startup.cs

services.AddOpenTelemetryTracing(b =>
{
    string nameAssemblyEntry = Assembly.GetEntryAssembly().GetName().Name;
    var rb = ResourceBuilder.CreateDefault();
    rb.AddService(nameAssemblyEntry);
    var data = new Dictionary<string, object>() {
                { "PC", Environment.MachineName } };
    data.Add("Exe", nameAssemblyEntry);
    rb.AddAttributes(data);
    _ = b
        .AddAspNetCoreInstrumentation(opt=>
        {
            opt.RecordException = true;
                        
        })
        .AddHttpClientInstrumentation(opt=>
        {
            opt.SetHttpFlavor = true;                        
        })
        .AddSqlClientInstrumentation(opt=>
        {
            opt.SetDbStatementForStoredProcedure = true;
            opt.SetDbStatementForText = true;
            opt.RecordException = true;
            opt.EnableConnectionLevelAttributes = true;
        })
        .AddSource("MySource")
        .SetResourceBuilder(rb)
        .AddJaegerExporter(c =>
        {
            //var s = Configuration.GetSection("Jaeger");

            //s.Bind(c);
            c.AgentHost = "localhost";//TODO: put the name of the PC with Jaeger
                        
        });


})
    ;

2.2 If .NET Core executable , add the following code to main.cs


string nameAssemblyEntry = Assembly.GetEntryAssembly().GetName().Name;
var rb = ResourceBuilder.CreateDefault();
rb.AddService(nameAssemblyEntry);
var data = new Dictionary<string, object>() {
{ "PC", Environment.MachineName } };
data.Add("Exe", nameAssemblyEntry);
rb.AddAttributes(data);

openTelemetry = Sdk.CreateTracerProviderBuilder()
.AddSource("MySource")
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.SetResourceBuilder(rb)
.AddJaegerExporter(o =>
 {
   //var s = Configuration.GetSection("Jaeger");

   //s.Bind(c);
   c.AgentHost = "localhost";//TODO: put the name of the PC with Jaeger
 })
.Build();

 

Step 3: Create near the .sln the file AutoMetods.ps1 with the following contents

function ReplaceWithAuto([string]$m )
{
$m=$m.Trim();
$firstpart= $method
$secondPart =""
#Write-Host $method
# do it fast now. Find first ( , name method is before that
#( could be done better: find from the last : add 1when found ( , add -1 when found ), stop at 0 )
# find first space before
$nr = ([regex]::Matches($m, [regex]::Escape(")") )).count
if($nr -gt 1){

$characters = [char[]]$method;
$nrOpen=0;
$iterator= $characters.Length;
do{
$iterator--;
if($characters[$iterator] -eq '('){
$nrOpen++;
}

if($characters[$iterator] -eq ')'){
$nrOpen--;
}

}while(($iterator -gt 0) -and ($nrOpen -ne 0))
$firstpart = $m.Substring(0,$iterator).Trim()
#return $firstpart
$spaceArr= $firstpart.Split(' ');
$firstpart =$spaceArr[$spaceArr.Length-1]
#return $firstpart

}
$arr = $firstpart.Split("(");
#Write-Host $arr[0]
$splitSpace = $arr[0].Split(" ")
$nameMethod = $splitSpace[$splitSpace.Length-1]
#Write-Host $nameMethod
[regex]$pattern = $nameMethod
#$method= ($method -replace [regex]::Escape($nameMethod) ,("auto"+$nameMethod))
$method= $pattern.Replace($method,("auto"+$nameMethod),1)
#Write-Host $method
return $method

}

function VerifyFolder()
{
Write-Host "starting"
$fileName = Get-ChildItem "*.cs" -Recurse
#$varItem = [regex]::Escape( "AOPMarkerMethod")
$varItem = "AOPMarkerMethod"
$filename | %{

$fileContent = gc $_
$x= ($fileContent -imatch "AOPMarkerMethod" )
#Write-Host $x.Length
if($x.Length -gt 0){
Write-Host $_.fullname
$LineNumbers =@( Select-String $_ -Pattern $varItem| Select-Object -ExpandProperty 'LineNumber')
Write-Host "found " $LineNumbers
Foreach ($LineNumber in $LineNumbers){
$method = $fileContent[$LineNumber]

Write-Host "Line where string is found: "$LineNumber
Write-Host $method

#Write-Host ReplaceWithAuto $method
$fileContent[$LineNumber] = ReplaceWithAuto($method)

Write-Host $fileContent[$LineNumber]
}

$fileContent |Set-Content $_.fullname

}
#(gc $_) -replace $varItem,"auto" |Set-Content $_.fullname

}
}

VerifyFolder

Step 4 : Create near the .sln Automethod.txt with the following content:

//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.CodeDom.Compiler;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Linq;
using OpenTelemetry.Trace;
namespace {{NamespaceName}} {

[GeneratedCode("AOPMethods", "{{version}}")]
[CompilerGenerated]
public partial class {{ClassName}}{

public static readonly ActivitySource MyActivitySource = new ActivitySource("MySource");

{{~ for mi in Methods ~}}
//{{mi.Name}}
{{

strAwait = ""
strAsync =""
if mi.CouldUseAsync == true
strAwait = " await "
strAsync = " async "
end
separator = ""
if(mi.NrParameters > 0)
separator = ","
end
returnString = ""
if mi.CouldReturnVoidFromAsync == false
returnString = " return "
end
}}
public {{strAsync}} {{mi.ReturnType}} {{mi.NewName}} ({{mi.parametersDefinitionCSharp }}
{{separator}}
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0){
var sw=Stopwatch.StartNew();
using var span = MyActivitySource.StartActivity("{{mi.NewName}}", ActivityKind.Producer);

span.Start();

//parameters

{{~ for miArg in mi.Parameters ~}}
var val{{ miArg.Key }} = System.Text.Json.JsonSerializer.Serialize({{ miArg.Key }});
span.SetTag("Argument_{{ miArg.Key }}",val{{ miArg.Key }});
{{ end}}

span.SetTag("method","{{mi.NewName}}");
span.SetTag("called from ",memberName);
span.SetTag("called from file",sourceFilePath);
span.SetTag("called from line",sourceLineNumber);
var utcTime =System.DateTime.UtcNow;
span.SetTag("UTCDate",utcTime.ToString("yyyyMMddHHmmss"));
var userEmail = ((System.Security.Claims.ClaimsPrincipal)System.Threading.Thread.CurrentPrincipal)?.Claims?.Where(c => c.Type == "email")?.SingleOrDefault()?.Value;
userEmail ??= "NoUserConfigured";
span.SetTag("user",userEmail);
span.SetStatus(Status.Ok);
try{
Console.WriteLine("--{{mi.Name}} start ");
{{returnString}} {{ strAwait }} {{mi.Name}}({{ mi.parametersCallCSharp }});
}
catch(Exception ex){
Console.WriteLine("--{{mi.Name}} exception ");
span?.RecordException(ex);
span.SetStatus(Status.Error);
throw;
}
finally{
Console.WriteLine("--{{mi.Name}} finally ");
span.SetTag("ElapsedMilliseconds",sw.Elapsed.TotalMilliseconds);
span.Stop();
//span.Duration= sw.Elapsed.TotalMilliseconds;

}
}//end {{mi.NewName}}

{{ end}}
}
}

Step 5: Decorate your method with [AOPMarkerMethod]  ( namespace AOPMethodsCommon )

Something like this


[AOPMarkerMethod]
private NewCountry[] ArrangeCountriesSearch(NewCountry[] data)

And in the class definition
[AutoMethods(template = TemplateMethod.CustomTemplateFile,CustomTemplateFileName ="Automethod.txt", MethodSuffix = "bup")]

Step 6: Modify your CI to run

pwsh AutoMetods.ps1

or

powershell AutoMetods.ps1

 

 

Enjoy, that will be all !

Open Tracing instrumentation for running process

Open tracing allows you to trace calls between (micro)services . It has also calls for HTTP and Sql. For a ASP.NET Core application the code is at follows ( for exporting at Jaeger, for example):


services.AddOpenTelemetryTracing(b =&gt;
{
string nameAssemblyEntry = Assembly.GetEntryAssembly().GetName().Name;
var rb = ResourceBuilder.CreateDefault();
rb.AddService(nameAssemblyEntry);
var data = new Dictionary&lt;string, object&gt;() {
{ "PC", Environment.MachineName } };
data.Add("Exe", nameAssemblyEntry);
rb.AddAttributes(data);
_ = b
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddSource("MySource")
.SetResourceBuilder(rb)
.AddJaegerExporter(c =&gt;
{
var s = Configuration.GetSection("Jaeger");

s.Bind(c);

});


})
;

This code was made with

 <PackageReference Include=”OpenTelemetry” Version=”1.1.0-beta1″ />
<PackageReference Include=”OpenTelemetry.Instrumentation.AspNetCore” Version=”1.0.0-rc3″ />
<PackageReference Include=”OpenTelemetry.Exporter.Jaeger” Version=”1.1.0-beta1″ />
<PackageReference Include=”OpenTelemetry.Extensions.Hosting” Version=”1.0.0-rc3″ />
<PackageReference Include=”OpenTelemetry.Instrumentation.Http” Version=”1.0.0-rc3″ />
<PackageReference Include=”OpenTelemetry.Instrumentation.SqlClient” Version=”1.0.0-rc3″ />

For a .NET Core console, the code is :

string nameAssemblyEntry = Assembly.GetEntryAssembly().GetName().Name;
var rb = ResourceBuilder.CreateDefault();
rb.AddService(nameAssemblyEntry);
var data = new Dictionary<string, object>() {
{ "PC", Environment.MachineName } };
data.Add("Exe", nameAssemblyEntry);
rb.AddAttributes(data);

openTelemetry = Sdk.CreateTracerProviderBuilder()
.AddSource("MySource")
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.SetResourceBuilder(rb)
.AddJaegerExporter(o =>
 {
 var s = config.GetSection("Jaeger");
 s.Bind(o);
                   })
.Build();

But how to transmit data for a Web that calls a process and not modify the command line ? Simple : via environment variable

The code for running the process is :

var pi = new ProcessStartInfo();
pi.FileName = ...;
pi.WorkingDirectory = ...;

var act = Activity.Current;
if (act != null)
{
 pi.EnvironmentVariables.Add(nameof(act.TraceId), act.TraceId.ToHexString());
 pi.EnvironmentVariables.Add(nameof(act.SpanId), act.SpanId.ToHexString());
}
var p = Process.Start(pi);

For the console, the code is:

 using (var act = MyActivitySource.StartActivity("StartProcess", ActivityKind.Producer))
            {
                act.SetTag("proc", "main");
               
                var traceStr = (Environment.GetEnvironmentVariable(nameof(act.TraceId)));
                if (!string.IsNullOrWhiteSpace(traceStr))
                {
                    var trace = ActivityTraceId.CreateFromString(traceStr);
                    var span = ActivitySpanId.CreateFromString(Environment.GetEnvironmentVariable(nameof(act.SpanId)));
                    act.SetParentId(trace, span, ActivityTraceFlags.Recorded);
                }

                try
                {
                    //executing code
                    act.SetStatus(Status.Ok);
                }
                catch (Exception ex)
                {
                    act.SetStatus(Status.Error);
                    throw;
                }
                
            }  

What I should return from WebAPI ?

 

There are several approaches when returning code from WebAPI .  Let’s frame the problem: say we have a Person controller with 2 actions:

– a GET {id}  – that retrieves the Person with id

– a POST {Peron}  – that tries to verify the validity of the Person and then saves to database.

We will answer to some questions:

1.What we will return if the person with id does not exists ?

2. What happens with the validation about the Person  ?
Let’s see the Person class.

public class Person: IValidatableObject
{
    public int ID { get; set; }
    public string  Name { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        yield return new ValidationResult("not valid not matter what");
    }
}

 

First approach: Do nothing

What I mean is just return the Person , even if it is null.

1.1. The GET

[HttpGet("{id}")]
public Person GetPerson(int id)
{
var p = RetrieveFromDatabase(id);
return p;
}

The problem is that when we return null from an action, there are no response data from the HTTP call-  and the javascript should handle it .

 

1.2 The POST – no problem . [ApiController] should handle the validation and return BadRequest if not validate

 

Second approach:  Return standard HttpCodes

2.1 The GET


[HttpGet("{id}")]
public ActionResult<Person> GetPerson404(int id)
{
    var p = RetrieveFromDatabase(id);
    if (p == null)
        return NotFound($"{nameof(Person)} with {id} are not found");

    return p;

}

We intercept the null and return standard 404 HttpCode . In that case, I strongly suggest to add a message to the end-caller – to know that the reason is not that the http call was a problem, but the Person was not found

2.2 The POST – no problem . [ApiController] should handle the validation and return BadRequest if not validate

 

Third approach: Return OK with a custom ReplyData class

The ReplyData can look this way

public class ReplyData<T>
{
    public bool Success { get; set; }
    public string Message { get; set; }
    public T ReturnObject { get; set; }
}

2.1 The GET

This action is over simplified – the code just returns the ReplyData from the database.

[HttpGet("{id}")]
public ReplyData<Person> GetWithReply(int id) {

    return RetrieveWithReplyFromDatabase(id);
}

private ReplyData<Person> RetrieveWithReplyFromDatabase(int id)
{
    try
    {
        Person p = null;//retrieve somehow
        if (p == null)
        {
            var r = new ReplyData<Person>();
            r.Success = false;
            r.Message = "Cannot find person with id " + id;
            return r;
        }
        else
        {
            var r = new ReplyData<Person>();
            r.Success = true;
            r.ReturnObject = p;

            return r;
        }
    }
    catch(Exception ex)
    {
        var r = new ReplyData<Person>();
        r.Success = false;
        r.Message = ex.Message;
        return r;
    }
}

2.2 The POST

This is somehow complicated – the [ApiController] , if not valid, return a 400 BadRequest . So we should modify this with a custom middleware to return the same ReplyData

public class From400ValidationToReply : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
    try
    {
        var originalStream = context.Response.Body;
        var bufferStream = new MemoryStream();
        context.Response.Body = bufferStream;
        await next(context);
        bufferStream.Seek(0, SeekOrigin.Begin);
        if (context.Response.StatusCode != 400)
        {
            await bufferStream.CopyToAsync(originalStream);
            return;
        }
        var reader = new StreamReader(bufferStream);
        var response = await reader.ReadToEndAsync();
        if (!response.Contains("errors"))
        {
            await bufferStream.CopyToAsync(originalStream);
            return;
        }

        context.Response.StatusCode = 200;

        var r = new ReplyData<Person>();
        r.Success = false;
        r.Message=response;
        var text = JsonSerializer.Serialize(r);
        var bit = Encoding.UTF8.GetBytes(text);
        context.Response.ContentLength = bit.Length;
        await originalStream.WriteAsync(bit);

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }
}

And the problem is that will affect all the request pipeline….

Conclusion:
I would choose 2 . But … your choice… ( and , for an advanced discussion, please read https://blog.ploeh.dk/2013/05/01/rest-lesson-learned-avoid-hackable-urls/ )

RSCG- part 16 – Many Others

There are more RSCG that you could see – here is a list that you may want to look at:

  1. AutoEmbed https://github.com/chsienki/AutoEmbed
  2. Cloneable https://github.com/mostmand/Cloneable
  3. fonderie https://github.com/jeromelaban/fonderie
  4. Generators.Blazor https://github.com/excubo-ag/Generators.Blazor
  5. Generators.Grouping https://github.com/excubo-ag/Generators.Grouping
  6. JsonMergePatch https://github.com/ladeak/JsonMergePatch
  7. MemoizeSourceGenerator https://github.com/Zoxive/MemoizeSourceGenerator
  8. MiniRazor https://github.com/Tyrrrz/MiniRazor/
  9. MockGen https://github.com/thomas-girotto/MockGen
  10. ProxyGen https://github.com/Sholtee/ProxyGen
  11. Rocks https://github.com/JasonBock/Rocks
  12. RoslynWeave https://github.com/Jishun/RoslynWeave
  13. SmallSharp https://github.com/devlooped/SmallSharp
  14. StaticProxyGenerator https://github.com/robertturner/StaticProxyGenerator
  15. ValueChangedGenerator https://github.com/ufcpp/ValueChangedGenerator
  16. Web-Anchor https://github.com/mattiasnordqvist/Web-Anchor
  17. WrapperValueObject https://github.com/martinothamar/WrapperValueObject

All RSCG

NrBlog Post
1RSCG–part 1
2RSCG- AppVersion–part 2
3http://msprogrammer.serviciipeweb.ro/2021/02/17/rsgc-enum-part-3/
4RSGC-JSON to Class- part 4
5RSGC-Constructor – Deconstructor – part 5
6RSGC – DTO Mapper – part 6
7RSGC – Skinny Controllers- part 7
8RSGC-Builder Design Pattern – part 8
9RSGC- MetadataFromObject – part 9
10RSGC- Dynamic Mock – part 10
11RSCG- Method Decorator – part 11
12RSCG – Curry – Partial function – part 12
13RSCG- part 13 – IFormattable
14RSCG- part 14 – DP_Decorator
15RSCG- part 15 – Expression Generator
16RSCG- part 16 – Many Others
17RSCG- the book
18RSCG–Template Rendering- part 17
19CI Version
20HttpClientGenerator
21Query from database
22AutoRegister
23TinyTypes
24Static2Interface
25AppSettings
26Properties
27
Roslyn Source Code Generators

RSCG- part 15 – Expression Generator

 

 

name Property Expression Generator
nuget

https://www.nuget.org/packages/AOPMethodsCommon/
https://www.nuget.org/packages/AOPMethodsGenerator/

link http://msprogrammer.serviciipeweb.ro/category/roslyn/
author Andrei Ignat

This will generate code to add function to be used with Entity Framework to search for any property of a class
 

The code that you start with is


    [AutoMethods(template = TemplateMethod.CustomTemplateFile, CustomTemplateFileName = "CreateMetadata.txt")]

    public partial class Person

    {

        public int ID { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public DateTime? DateOfBirth {get;set;}

    }


The code that you will use is



    var queryCnt = Metadata_Person.expr_FirstName_Contains("9");             

    var pers= await cnt.Person.Where(queryCnt).ToArrayAsync();

    Console.WriteLine(pers.Length);

    

    

    queryCnt = Metadata_Person.expr_LastName_NullOrWhite();

    pers = await cnt.Person.Where(queryCnt).ToArrayAsync();

    Console.WriteLine(pers.Length);

    

    

    var queryID = Metadata_Person.expr_ID_Equal(7);

    var pId = await cnt.Person.FirstOrDefaultAsync(queryID);

    Console.WriteLine(pId.FirstName);

    

    

    queryID = Metadata_Person.expr_ID_Contains(7,9);

    pers = await cnt.Person.Where(queryID).ToArrayAsync();

    Console.WriteLine(pers.Length);

    

    

    var nullBirthDateQuery = Metadata_Person.expr_DateOfBirth_Null();

    var birthNull = await cnt.Person.Where(nullBirthDateQuery).ToArrayAsync();

    Console.WriteLine(birthNull.Length);

    

    var query = Metadata_Person.FindEx("ID", SearchCriteria.Equal, 99);

    pers = await cnt.Person.Where(query).ToArrayAsync();

    Console.WriteLine(pers.Length);

    

    query = Metadata_Person.FindEx("DateOfBirth", SearchCriteria.FindNull);

    pers = await cnt.Person.Where(query).ToArrayAsync();

    Console.WriteLine(pers.Length);

 

The code that is generated is


    [CompilerGenerated]                                                                                                                                                

    public partial class Metadata_Person{

    

        

        

        //public const string prop_ID = "ID";    

        //public static readonly Func<Person,int> func_ID = (it=>it.ID);

        //public static readonly Expression<Func<Person,int>> expr_ID = (it=>it.ID);

        public static Expression<Func<Person,bool>> expr_ID_Equal(int value)=> (it=>it.ID == value);

        public static Expression<Func<Person,bool>> expr_ID_Diff(int value)=> (it=>it.ID != value);

        public static Expression<Func<Person,bool>> expr_ID_Contains(params int[] value)=> (it=> value.Contains(it.ID) );

        

        //int

        

        

        

        

        

            

        public static Expression<Func<Person,bool>> expr_ID_Greater(int value)=> (it=>it.ID > value);

        public static Expression<Func<Person,bool>> expr_ID_GreaterOrEqual(int value)=> (it=>it.ID >= value);

        public static Expression<Func<Person,bool>> expr_ID_Less(int value)=> (it=>it.ID < value);

        public static Expression<Func<Person,bool>> expr_ID_LessOrEqual(int value)=> (it=>it.ID <= value);

        

        

                

        

        //public const string prop_FirstName = "FirstName";    

        //public static readonly Func<Person,string> func_FirstName = (it=>it.FirstName);

        //public static readonly Expression<Func<Person,string>> expr_FirstName = (it=>it.FirstName);

        public static Expression<Func<Person,bool>> expr_FirstName_Equal(string value)=> (it=>it.FirstName == value);

        public static Expression<Func<Person,bool>> expr_FirstName_Diff(string value)=> (it=>it.FirstName != value);

        public static Expression<Func<Person,bool>> expr_FirstName_Contains(params string[] value)=> (it=> value.Contains(it.FirstName) );

        

        //string

        

        

            

        public static Expression<Func<Person,bool>> expr_FirstName_Null()=> (it=>it.FirstName == null);            

        

            

        

    

        public static Expression<Func<Person,bool>> expr_FirstName_NullOrWhite()=> (it=>string.IsNullOrWhiteSpace(it.FirstName));

    

        public static Expression<Func<Person,bool>> expr_FirstName_Ends(string value)=> (it=>it.FirstName.StartsWith (value));

        public static Expression<Func<Person,bool>> expr_FirstName_Starts(string value)=> (it=>it.FirstName.EndsWith(value));

        public static Expression<Func<Person,bool>> expr_FirstName_Contains(string value)=> (it=>it.FirstName.Contains(value));    

        

        

        

        

                

        

        //public const string prop_LastName = "LastName";    

        //public static readonly Func<Person,string> func_LastName = (it=>it.LastName);

        //public static readonly Expression<Func<Person,string>> expr_LastName = (it=>it.LastName);

        public static Expression<Func<Person,bool>> expr_LastName_Equal(string value)=> (it=>it.LastName == value);

        public static Expression<Func<Person,bool>> expr_LastName_Diff(string value)=> (it=>it.LastName != value);

        public static Expression<Func<Person,bool>> expr_LastName_Contains(params string[] value)=> (it=> value.Contains(it.LastName) );

        

        //string

        

        

            

        public static Expression<Func<Person,bool>> expr_LastName_Null()=> (it=>it.LastName == null);            

        

            

        

    

        public static Expression<Func<Person,bool>> expr_LastName_NullOrWhite()=> (it=>string.IsNullOrWhiteSpace(it.LastName));

    

        public static Expression<Func<Person,bool>> expr_LastName_Ends(string value)=> (it=>it.LastName.StartsWith (value));

        public static Expression<Func<Person,bool>> expr_LastName_Starts(string value)=> (it=>it.LastName.EndsWith(value));

        public static Expression<Func<Person,bool>> expr_LastName_Contains(string value)=> (it=>it.LastName.Contains(value));    

        

        

        

        

                

        

        //public const string prop_DateOfBirth = "DateOfBirth";    

        //public static readonly Func<Person,System.DateTime?> func_DateOfBirth = (it=>it.DateOfBirth);

        //public static readonly Expression<Func<Person,System.DateTime?>> expr_DateOfBirth = (it=>it.DateOfBirth);

        public static Expression<Func<Person,bool>> expr_DateOfBirth_Equal(System.DateTime? value)=> (it=>it.DateOfBirth == value);

        public static Expression<Func<Person,bool>> expr_DateOfBirth_Diff(System.DateTime? value)=> (it=>it.DateOfBirth != value);

        public static Expression<Func<Person,bool>> expr_DateOfBirth_Contains(params System.DateTime?[] value)=> (it=> value.Contains(it.DateOfBirth) );

        

        //System.DateTime?

        

        

            

        public static Expression<Func<Person,bool>> expr_DateOfBirth_Null()=> (it=>it.DateOfBirth == null);            

        

        

        

            

        public static Expression<Func<Person,bool>> expr_DateOfBirth_Greater(System.DateTime? value)=> (it=>it.DateOfBirth > value);

        public static Expression<Func<Person,bool>> expr_DateOfBirth_GreaterOrEqual(System.DateTime? value)=> (it=>it.DateOfBirth >= value);

        public static Expression<Func<Person,bool>> expr_DateOfBirth_Less(System.DateTime? value)=> (it=>it.DateOfBirth < value);

        public static Expression<Func<Person,bool>> expr_DateOfBirth_LessOrEqual(System.DateTime? value)=> (it=>it.DateOfBirth <= value);

        

        

        

    

        public static Expression<Func<Person,bool>> FindEx(string nameProp, SearchCriteria search, object value = null)

        {

            

            

            

            if(string.Compare("ID",nameProp,StringComparison.CurrentCultureIgnoreCase) == 0)

            switch(search){

                case SearchCriteria.None:

                    return null;

                

                case SearchCriteria.Equal:

                    var orig= (int) value;

                    return expr_ID_Equal(orig);

                default:

                    throw new ArgumentException("cannot find for ID case  "+search);

            }

            

                        

            

            if(string.Compare("FirstName",nameProp,StringComparison.CurrentCultureIgnoreCase) == 0)

            switch(search){

                case SearchCriteria.None:

                    return null;

                

                case SearchCriteria.FindNull:

                    return expr_FirstName_Null();

                

                case SearchCriteria.Equal:

                    var orig= (string) value;

                    return expr_FirstName_Equal(orig);

                default:

                    throw new ArgumentException("cannot find for FirstName case  "+search);

            }

            

                        

            

            if(string.Compare("LastName",nameProp,StringComparison.CurrentCultureIgnoreCase) == 0)

            switch(search){

                case SearchCriteria.None:

                    return null;

                

                case SearchCriteria.FindNull:

                    return expr_LastName_Null();

                

                case SearchCriteria.Equal:

                    var orig= (string) value;

                    return expr_LastName_Equal(orig);

                default:

                    throw new ArgumentException("cannot find for LastName case  "+search);

            }

            

                        

            

            if(string.Compare("DateOfBirth",nameProp,StringComparison.CurrentCultureIgnoreCase) == 0)

            switch(search){

                case SearchCriteria.None:

                    return null;

                

                case SearchCriteria.FindNull:

                    return expr_DateOfBirth_Null();

                

                case SearchCriteria.Equal:

                    var orig= (System.DateTime?) value;

                    return expr_DateOfBirth_Equal(orig);

                default:

                    throw new ArgumentException("cannot find for DateOfBirth case  "+search);

            }

            

            

            throw new ArgumentException("cannot find property  "+nameProp);

            

        }

    

    }

Example Code: https://github.com/ignatandrei/RSCG_Examples/tree/main/PropertyExpressionGenerator

All RSCG

NrBlog Post
1RSCG–part 1
2RSCG- AppVersion–part 2
3http://msprogrammer.serviciipeweb.ro/2021/02/17/rsgc-enum-part-3/
4RSGC-JSON to Class- part 4
5RSGC-Constructor – Deconstructor – part 5
6RSGC – DTO Mapper – part 6
7RSGC – Skinny Controllers- part 7
8RSGC-Builder Design Pattern – part 8
9RSGC- MetadataFromObject – part 9
10RSGC- Dynamic Mock – part 10
11RSCG- Method Decorator – part 11
12RSCG – Curry – Partial function – part 12
13RSCG- part 13 – IFormattable
14RSCG- part 14 – DP_Decorator
15RSCG- part 15 – Expression Generator
16RSCG- part 16 – Many Others
17RSCG- the book
18RSCG–Template Rendering- part 17
19CI Version
20HttpClientGenerator
21Query from database
22AutoRegister
23TinyTypes
24Static2Interface
25AppSettings
26Properties
27
Roslyn Source Code Generators

RSCG- part 14 – DP_Decorator

 

 

name AutoInterface
nuget

https://www.nuget.org/packages/BeaKona.AutoInterfaceGenerator

link https://github.com/beakona/AutoInterface
author beakona

Implement the Design Pattern Decorator. Based on template – you can modify the source code generated
 

The code that you start with is


    public interface ICoffee                                                                                               

    {

        public int Price { get; }

        public string Description { get; }

    }

    

    public class SimpleCoffee : ICoffee

    {

        public SimpleCoffee()

        {

            Price = 3;

            Description = "Simple Coffee";

        }

        public int Price { get; set; }

        public string Description { get; set; }

    

    public partial class MilkDecorator : ICoffee

    {

        [BeaKona.AutoInterface(TemplateLanguage = "scriban", TemplateBody = SimpleCoffee.TemplateCoffeeDecorator)]

        private readonly ICoffee coffee;

    

        public int DecoratorPrice { get; set; } = 1;

        public MilkDecorator(ICoffee coffee)

        {

            this.coffee = coffee;

        }

    

    

    

    }

    

    public partial class ChocoDecorator : ICoffee

    {

        [BeaKona.AutoInterface(TemplateLanguage = "scriban", TemplateBody = SimpleCoffee.TemplateCoffeeDecorator)]

        private readonly ICoffee coffee;

    

        public int DecoratorPrice { get; set; } = 2;

        public ChocoDecorator(ICoffee coffee)

        {

            this.coffee = coffee;

        }

    

    

    }

    


The code that you will use is



    SimpleCoffee s = new SimpleCoffee();

    Console.WriteLine(s.Description +" with Price "+ s.Price);

    ICoffee withMilk = new MilkDecorator(s);

    Console.WriteLine(withMilk.Description} +" with Price "+ withMilk.Price);

    ICoffee withMilkAndChoco = new ChocoDecorator(withMilk);

    Console.WriteLine(withMilkAndChoco.Description +" with Price "+ withMilkAndChoco.Price);

 

The code that is generated is


    partial class MilkDecorator                                                        

    {

        int ICoffee.Price

        {

            get

            {

                    return ((ICoffee)this.coffee).Price + DecoratorPrice;

            }

        }

    

        string ICoffee.Description

        {

            get

            {
                    var name = this.GetType().Name.Replace("Decorator","");

                    return ((ICoffee)this.coffee).Description + " with " + name;

            }

        }

    }

Example Code: https://github.com/ignatandrei/RSCG_Examples/tree/main/DP_Decorator

All RSCG

NrBlog Post
1RSCG–part 1
2RSCG- AppVersion–part 2
3http://msprogrammer.serviciipeweb.ro/2021/02/17/rsgc-enum-part-3/
4RSGC-JSON to Class- part 4
5RSGC-Constructor – Deconstructor – part 5
6RSGC – DTO Mapper – part 6
7RSGC – Skinny Controllers- part 7
8RSGC-Builder Design Pattern – part 8
9RSGC- MetadataFromObject – part 9
10RSGC- Dynamic Mock – part 10
11RSCG- Method Decorator – part 11
12RSCG – Curry – Partial function – part 12
13RSCG- part 13 – IFormattable
14RSCG- part 14 – DP_Decorator
15RSCG- part 15 – Expression Generator
16RSCG- part 16 – Many Others
17RSCG- the book
18RSCG–Template Rendering- part 17
19CI Version
20HttpClientGenerator
21Query from database
22AutoRegister
23TinyTypes
24Static2Interface
25AppSettings
26Properties
27
Roslyn Source Code Generators

RSCG- part 13 – IFormattable

 

 

name IFormattable
nuget

https://www.nuget.org/packages/AOPMethodsCommon/
https://www.nuget.org/packages/AOPMethodsGenerator/

link http://msprogrammer.serviciipeweb.ro/category/roslyn/
author Andrei Ignat

This will generate code to add IFormattable to any class, based on the properties of the class
 

The code that you start with is


    [AutoMethods(CustomTemplateFileName = "CreateFormattable.txt", template = TemplateMethod.CustomTemplateFile)]                    

    partial class Department

    {

        public int ID { get; set; }

        public string Name { get; set; }

    

    }

    [AutoMethods(CustomTemplateFileName = "CreateFormattable.txt", template = TemplateMethod.CustomTemplateFile)]

    partial class Employee

    {

        public int ID { get; set; }

        public string Name { get; set; }

    

        public Department dep { get; set; }

        

    }


The code that you will use is



    var e = new Employee();

    e.ID = 1;

    e.Name = "Andrei";

    e.dep = new Department();

    e.dep.Name = "IT";

    

    Console.WriteLine(e.ToString("for employee with id = {id} the name is {name} and department is {dep?.Name}", null)); 

    

    e.dep = null;

    

    Console.WriteLine(e.ToString("for employee with id = {id} the name is {name} and department is {dep?.Name}", null));

    

 

The code that is generated is


    [GeneratedCode("AOPMethods", "2021.2.27.640")]                                             

    [DebuggerDisplay(" ID = {ID} Name = {Name} dep = {dep}")]

    partial class Employee: IFormattable{

        public object ValueProperty(string val){

            val = val.Replace("?","");

                if(string.Compare("ID",val,StringComparison.CurrentCultureIgnoreCase)==0) {

                    return this.ID;

                }

                if(string.Compare("Name",val,StringComparison.CurrentCultureIgnoreCase)==0) {

                    return this.Name;

                }

                if(string.Compare("dep",val,StringComparison.CurrentCultureIgnoreCase)==0) {

                    return this.dep;

                }

            throw new ArgumentException("cannot find "+ val);

        }

        

        //adapted from https://haacked.com/archive/2009/01/14/named-formats-redux.aspx/

        private object Eval(string expression,IFormatProvider formatProvider)

        {

            if (expression.Contains("."))

            {

                var splut = expression.Split(".");

                bool canBeNull=splut[0].Contains("?");

                dynamic d = ValueProperty(splut[0]);

                if(canBeNull && d == null)

                    return null;

                for(var i=1; i<splut.Length;i++){

                    canBeNull=splut[i].Contains("?");

                    d=d.ToString("{"+splut[i]+"}",formatProvider);

                    if(canBeNull && d == null)

                        return null;

                }

                return d;

                

            }

    

            return ValueProperty(expression);

    

        }

    

    

        

        public string ToString(string format, IFormatProvider formatProvider)

        {

            if (format == null)

                throw new ArgumentNullException("format");

    

            List<object> values = new List<object>();

            string rewrittenFormat = Regex.Replace(format,

                delegate (Match m)

                {

                    Group startGroup = m.Groups["start"];

                    Group propertyGroup = m.Groups["property"];

                    Group formatGroup = m.Groups["format"];

                    Group endGroup = m.Groups["end"];

    

                    values.Add((propertyGroup.Value == "0")

            ? this

            : Eval(propertyGroup.Value, formatProvider));

    

                    int openings = startGroup.Captures.Count;

                    int closings = endGroup.Captures.Count;

    

                    return openings > closings || openings % 2 == 0

                ? m.Value

                : new string('{', openings) + (values.Count - 1)

                + formatGroup.Value

                + new string('}', closings);

                },

                RegexOptions.Compiled

                | RegexOptions.CultureInvariant

                | RegexOptions.IgnoreCase);

    

            return string.Format(formatProvider, rewrittenFormat, values.ToArray());

        }

    

    }

Example Code: https://github.com/ignatandrei/RSCG_Examples/tree/main/IFormattable

All RSCG

NrBlog Post
1RSCG–part 1
2RSCG- AppVersion–part 2
3http://msprogrammer.serviciipeweb.ro/2021/02/17/rsgc-enum-part-3/
4RSGC-JSON to Class- part 4
5RSGC-Constructor – Deconstructor – part 5
6RSGC – DTO Mapper – part 6
7RSGC – Skinny Controllers- part 7
8RSGC-Builder Design Pattern – part 8
9RSGC- MetadataFromObject – part 9
10RSGC- Dynamic Mock – part 10
11RSCG- Method Decorator – part 11
12RSCG – Curry – Partial function – part 12
13RSCG- part 13 – IFormattable
14RSCG- part 14 – DP_Decorator
15RSCG- part 15 – Expression Generator
16RSCG- part 16 – Many Others
17RSCG- the book
18RSCG–Template Rendering- part 17
19CI Version
20HttpClientGenerator
21Query from database
22AutoRegister
23TinyTypes
24Static2Interface
25AppSettings
26Properties
27
Roslyn Source Code Generators

RSCG – Curry – Partial function – part 12

 

 

name PartiallyApplied
nuget

https://www.nuget.org/packages/PartiallyApplied/

link https://github.com/JasonBock/PartiallyApplied
author Andrei Ignat

This will generate curry for your functions
 

The code that you start with is


    public class Accounting                                            

    {

        public static float Discount( float discount, float price)

        {

            var val= price * (1- discount);

            return val;

        }

    }


The code that you will use is



    var disc10Percent = Partially.Apply(Accounting.Discount, 1/10f);

    Console.WriteLine(disc10Percent(disc10Percent(100)));

 

The code that is generated is


    public static partial class Partially

    {

           public static Func<float, float> Apply(Func<float, float, float> method, float discount) =>

                  new((price) => method(discount, price));

    }

Example Code: https://github.com/ignatandrei/RSCG_Examples/tree/main/PartiallyFunction

All RSCG

NrBlog Post
1RSCG–part 1
2RSCG- AppVersion–part 2
3http://msprogrammer.serviciipeweb.ro/2021/02/17/rsgc-enum-part-3/
4RSGC-JSON to Class- part 4
5RSGC-Constructor – Deconstructor – part 5
6RSGC – DTO Mapper – part 6
7RSGC – Skinny Controllers- part 7
8RSGC-Builder Design Pattern – part 8
9RSGC- MetadataFromObject – part 9
10RSGC- Dynamic Mock – part 10
11RSCG- Method Decorator – part 11
12RSCG – Curry – Partial function – part 12
13RSCG- part 13 – IFormattable
14RSCG- part 14 – DP_Decorator
15RSCG- part 15 – Expression Generator
16RSCG- part 16 – Many Others
17RSCG- the book
18RSCG–Template Rendering- part 17
19CI Version
20HttpClientGenerator
21Query from database
22AutoRegister
23TinyTypes
24Static2Interface
25AppSettings
26Properties
27
Roslyn Source Code Generators

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.