Category: AOPMethods

TILT-Server Timing in Browser-part 19

For see fast what is happening with your server, you can use the Server Timing API.

One of the implementations for .NET is https://github.com/tpeczek/Lib.AspNetCore.ServerTiming .

Not a big deal – implemented separately with AOPMethodsCommon – but I think it is a good idea to use it.

The implementation was straightforward

builder.Services.AddScoped<ServerTiming>();
var app = builder.Build();
app.UseServerTiming();

And then use DI to get the instance of the class.

With this occasion , look how is different calling same method twice

vs

If you do not realize, there are 50 ms vs 1 ms . That difference is from caching

private async Task<TILT_Note_Table[]?> privateLatestTILTs(string urlPart, int numberTILTS)
{
    if (cache.TryGetValue<TILT_Note_Table[]>(urlPart, out var result))
    {
        return result;
    }
//code
}

Nice, right ? See the timing right in your browser.

Tools used

https://github.com/tpeczek/Lib.AspNetCore.ServerTiming

https://github.com/ignatandrei/AOP_With_Roslyn/

TILT–Telemetry–part 11

I want to see what is happening in my application – i.e. having the Stack Trace for any kind of errors.

I have add with

    [AutoMethods(CustomTemplateFileName = "../AutoMethod.txt", MethodPrefix = "auto", template = TemplateMethod.CustomTemplateFile)]
    [AutoGenerateInterface]
    public partial class AuthUrl : IAuthUrl
    {

        [AOPMarkerMethod]
        private async Task<string?> privateLogin(string url, string secret)
        {
//code

And inside the method

var act=Activity.Current;
using var span = MyActivitySource.StartActivity("{{mi.NewName}}", ActivityKind.Client,act?.Context??default(ActivityContext));
{
try{
                //call original method
            }
            catch(Exception ex){
                span?.RecordException(ex);
                span?.SetTag("exceptionMessage",ex.Message);
                span?.SetStatus(Status.Error);
                throw;
            }
            finally{
                span?.Stop();
                
            }

The final result is ( showing an error into the database)


Tools used

AppInsights

Powershell ( AOPMethods.ps1)

OpenTelemetry.Api

OpenTelemetry.Contrib.Instrumentation.EntityFrameworkCore

OpenTelemetry.Extensions.Hosting

Azure.Monitor.OpenTelemetry.Exporter

OpenTelemetry.Instrumentation.AspNetCore

OpenTelemetry.Instrumentation.Http

OpenTelemetry.Instrumentation.SqlClient

Visual Studio

Passing from .NET 5 to .NET 6

First, read the document here: Migrate from ASP.NET Core 5.0 to 6.0 | Microsoft Docs .\

So those were my steps ( fully compile after each step ):

1. Replace in csproj net5.0 with net6.0

<TargetFramework>net6.0</TargetFramework>

2.  Update all nugets reference to the latest version

3.  Add globals.cs with various usings

4. Add globals.cs . Mine looks this way ( without the necessary for the project)

global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.Mvc.ApiExplorer;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Options;
global using Microsoft.OpenApi.Models;
global using Swashbuckle.AspNetCore.SwaggerGen;
global using Microsoft.AspNetCore.Builder;
global using AMSWebAPI;
global using appSettingsEditor;
global using HealthChecks.UI.Client;
global using Hellang.Middleware.ProblemDetails;
global using Microsoft.AspNetCore.Diagnostics.HealthChecks;
global using Microsoft.AspNetCore.HttpOverrides;
global using NetCore2BlocklyNew;

5.  Move away from Startup – put all in program .cs  – easy : services moved to app.Services , and instead of parameters in configure, use the app.Services – example

var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();

6.  Tests

I have a problem with  “ The server has not been started or no web application was configured. “  -when I configure WebApplicationFactory<Program> 

Reading Minimal APIs in .NET 6 but where are the Unit Tests? – Scott Hanselman’s Blog 

Testing in .NET 6 with WebApplicationFactory (including Minimal APIs) | by Lee Dale | Medium

Supporting integration tests with WebApplicationFactory in .NET 6: Exploring .NET 6 – Part 6 (andrewlock.net)

Integration tests in ASP.NET Core | Microsoft Docs

No avail yet.  Should study ore.

Benchmarking RSCG vs Reflection

I make a microservices Buffet . In this I consider having email as a service . When the DevOps wants email, he can choose between various plugins ( simple smtp email, gmail, exchange, others). Those plugins can have various properties – that must be edited by the primary administrator of the microservice. The properties can be discovered at runtime ( via Reflection ) o r at build time ( via Roslyn Source Code Generators  – RSCG ).

But – we should see what is faster, right ?   And the feeling is that RSCG is always faster – but it is , really ? Let’s see…

So = let’s make a test with https://github.com/dotnet/BenchmarkDotNet . You can have the source code by going to https://github.com/ignatandrei/AOP_With_Roslyn/tree/master/AOPMethods .

First , the class that is tested

public partial class EmailSmtpClientMS 
{

    public EmailSmtpClientMS()
    {

        Port = 25;

    }
    public string Name { get; set; }


    public string Type
    {
        get
        {
            return this.GetType().Name;
        }
    }
    public string Host { get; set; }
    public int Port { get; set; }

    public string Description
    {
        get
        {
            return $"{Type} {Host}:{Port}";
        }
    }
}

 

Second, with AOPMethods I generate the read properties values – properties that  you can read – via a dictionary and via a switch. This can be achieved simply :

[AutoMethods(template = TemplateMethod.CustomTemplateFile, CustomTemplateFileName = "ClassToDictionary.txt")]
    public partial class EmailSmtpClientMS 

And this will be generated by RSCG for the switch

protected object GetValueProperty(string propName)
{
    switch (propName)
    {
        //true true
        case "Name":
            return this.Name;


        //true false
        case "Type":
            return this.Type;


        //true true
        case "Host":
            return this.Host;


        //true true
        case "Port":
            return this.Port;


        //true false
        case "Description":
            return this.Description;



        default:
            throw new ArgumentException("cannot find property " + propName);
    }
}

and for the dictionary

private IDictionary<string, PropertyHelper> MyProperties()
{
    var data = new Dictionary<string, PropertyHelper>();
    PropertyHelper ph;
    ph = new PropertyHelper();
    ph.Name = "Name";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !false;

    ph.Value = this.Name;

    data.Add("Name", ph);
    //Name string     
    ph = new PropertyHelper();
    ph.Name = "Type";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !true;

    ph.Value = this.Type;

    data.Add("Type", ph);
    //Type string     
    ph = new PropertyHelper();
    ph.Name = "Host";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !false;

    ph.Value = this.Host;

    data.Add("Host", ph);
    //Host string     
    ph = new PropertyHelper();
    ph.Name = "Port";
    ph.Type = "int";
    ph.CanRead = !false;
    ph.CanWrite = !false;

    ph.Value = this.Port;

    data.Add("Port", ph);
    //Port int     
    ph = new PropertyHelper();
    ph.Name = "Description";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !true;

    ph.Value = this.Description;

    data.Add("Description", ph);
    //Description string     

    return data;

}

 

The spec for benchmark are :


BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1052 (21H1/May2021Update)
Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=5.0.301
  [Host]     : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
  DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT

 

Third, I benchmark obtaining one single property – the Host – via the 3 methods:

public partial class EmailSmtpClientMSOneProperty: EmailSmtpClientMS
{
    [Benchmark]
    public string GetHostReflection()
    {
        return this.GetType().GetProperty("Host").GetValue(this).ToString();
    }
    [Benchmark]
    public string GetHostViaDictionary()
    {
        return this.ReadMyProperties()["Host"].ToString();
    }
    [Benchmark]
    public string GetHostViaSwitch()
    {
        return this.GetValueProperty("Host").ToString();
    }
}

And in Program.cs

BenchmarkRunner.Run<EmailSmtpClientMSOneProperty>(
    ManualConfig
        .Create(DefaultConfig.Instance)
        .WithOption(ConfigOptions.DisableOptimizationsValidator, true)
        );

( of course, I have added on the class
//[SimpleJob(RuntimeMoniker.Net50)]
//[ShortRunJob(RuntimeMoniker.Net50)]
//[DryJob(RuntimeMoniker.Net50)]
[Orderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared)]
//[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[RPlotExporter]
[CsvMeasurementsExporter]
[MemoryDiagnoser]
[HtmlExporter]
[MarkdownExporterAttribute.GitHub]
)
The results are in ns –so, the less/smaller , that means  better results.

The results are here in HTML form :

 

 

Method Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
GetHostViaSwitch 18.07 ns 0.434 ns 0.549 ns 18.02 ns
GetHostReflection 144.13 ns 2.582 ns 5.501 ns 142.13 ns
GetHostViaDictionary 451.59 ns 12.363 ns 33.635 ns 441.72 ns 0.3057 640 B

The graphic may be more interesting:

 

Surprised ? The RSCG Switch Property is indeed the fastest one – but the Reflection is faster than RSCG Property Dictionary ( or , at least , for my implementation).

However, I realized that in real life , I will retrieve all properties in a Dictionary to be edited . So all implementations should occur the penalty of creating a Dictionary . Time for next benchmark . This time , the code is

[Benchmark]
public IDictionary<string,object> GetHostReflection()
{
    var props = this.GetType()
        .GetProperties()
        .Where(it=> it.CanWrite)
        .ToDictionary(it => it.Name, it=>it.GetValue(this));
        ;
    return props;
                
}
[Benchmark]
public IDictionary<string, object> GetHostViaDictionary()
{
    var props = this.ReadMyProperties();
    return props;
            
}
[Benchmark]
public IDictionary<string, object> GetHostViaSwitch()
{
    var props = ReadProperties
        .ToDictionary(it => it, it => GetValueProperty(it));
    return props;
            
            
}

 

and the results are:

Method Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
GetHostViaDictionary 462.1 ns 14.70 ns 40.97 ns 453.6 ns 0.3052 640 B
GetHostViaSwitch 479.5 ns 7.34 ns 7.54 ns 479.9 ns 0.2708 568 B
GetHostReflection 973.0 ns 78.35 ns 231.01 ns 911.5 ns 0.1984 416 B

 

Now the graphic will help:

 

Interesting , right ?

Reflection = as normal –  is the slowest one. But the difference between RSCG switch and RSCG Dictionary is not too much…

Conclusion 1:  the feeling  was right at the end. But – the first result was deceiving

Conclusion 2: Creating a dictionary is more time consuming than a simple reflection for one property retrieved

Conclusion 3: I do prefer RSCG Dictionary vs RSCG switch – less work for me as a programmer and similar time results.

Conclusion 4: do not over engineer if you do not feel the need . For just one property, Reflection is better….

Conclusion 5: This is not final. I should also write the values of the properties . Maybe next time a new benchmark….

AOPMethods–dogfooding

I was trying to apply AOPMethods to – surprise! –  AOPMethods project itself. And I have discovered a new reason: I do not want to make the methods public. I just want to put try/catch around them to know what is wrong.

The fast – and not so good – idea was to transform

MethodPrefix =”pub”

into a

Dictionary<MethodsPrefix: string,  VIsibilty: string>

in order to pass something like that

{ “pub”, “public” } , {“prv”, “private”}

The second idea was better : what if I allow multiple instances and generate accordingly  ?

So I came up with this definition

[AutoMethods(template = TemplateMethod.MethodWithPartial, MethodPrefix =”pub”)]
[AutoMethods(template = TemplateMethod.CustomTemplateFile,CustomTemplateFileName =”privateTryCatch.txt”,  MethodSuffix = “bup”)]
partial class Person

And the code  processes both attributes – and generates the code. One slightly problem: Cannot have GeneratedCode and CompilerGenerated on both generated files. But – it works!

AOPMethods–adding partial functions and enums

I was finishing the AOPMethods  – and what I have been thinking is – why not add partial functions ? I have added Console.Write, but … this seems more general… So , now , this is the Person class definition with the partial function definition

[AutoMethods(template = TemplateMethod.MethodWithPartial, MethodPrefix =”pub”, MethodSuffix =”bup”)]
partial class Person
{
     partial void Method_Start(string methodName)
     {
         Console.WriteLine($”start {methodName}”);
     }
     partial void Method_End(string methodName)
     {
         Console.WriteLine($”end {methodName}”);
     }

And, because I have finished the AOPMethods , I have been thinking about the problems with enums  – every time I parse the enum. What about a autogenerated function ? So now , with this definition:

[AutoEnum(template = EnumMethod.GenerateExtensionCode)]
     /// <summary>
     /// my test
     /// </summary>
     public enum Test:long
     {
         a,
         //the b should be 1
         b,
         /// <summary>
         /// x should be 2
         /// </summary>
         x=5,
         y=7

    }

I can do this

long y = 7;
var s = y.ParseExactTest2();
var q=y.ToString().ParseExactTest2();
var s1 = s.ToString().ParseExactTest2();

For reference, the generated code with Roslyn AOPMethods is:

public static AOPMethodsTest.Test2 ParseExactTest2(this long value, AOPMethodsTest.Test2? defaultValue = null)
{
     if (0 == value)
         return AOPMethodsTest.Test2.a1;
     if (1 == value)
         return AOPMethodsTest.Test2.b1;
     if (5 == value)
         return AOPMethodsTest.Test2.x1;
     if (7 == value)
         return AOPMethodsTest.Test2.y1;

    if (defaultValue != null)
         return defaultValue.Value;

    throw new ArgumentException(“cannot find ” + value + ” for AOPMethodsTest.Test2  “);
}

public static AOPMethodsTest.Test2 ParseExactTest2(this string value, AOPMethodsTest.Test2? defaultValue = null)
{
     //trying to see if it is a value inside
     //if(!string.IsNullOrWhiteSpace)
     if (long.TryParse(value, out long valueParsed))
     {
         return ParseExactTest2(valueParsed);
     }

    if (0 == string.Compare(“a1”, value, StringComparison.InvariantCultureIgnoreCase))
         return AOPMethodsTest.Test2.a1;
     if (0 == string.Compare(“b1”, value, StringComparison.InvariantCultureIgnoreCase))
         return AOPMethodsTest.Test2.b1;
     if (0 == string.Compare(“x1”, value, StringComparison.InvariantCultureIgnoreCase))
         return AOPMethodsTest.Test2.x1;
     if (0 == string.Compare(“y1”, value, StringComparison.InvariantCultureIgnoreCase))
         return AOPMethodsTest.Test2.y1;

     if (defaultValue != null)
         return defaultValue.Value;

    throw new ArgumentException(“cannot find ” + value + ” for AOPMethodsTest.Test2  “);
}

AOP Methods–Problems in running and solving

The problems that I have encountered were:

1.  The ThisAssembly  RoslynGenerator that I use should not be put as reference in the nuget. I have fixed this by adding

<PackageReference Include=”ThisAssembly.AssemblyInfo” Version=”1.0.0″ ReferenceOutputAssembly=”false” />

2. I have problems generating code to async code with Task . The problem was that I have added logging ( very basic – Console.WriteLine) and the order of the logging was errating. I figured out that I should must use the async /await and then my template was modified as

strAwait = “”
strAsync =””
if mi.IsAsync == true
     strAwait = ” await “
     strAsync  = ” async “
end

public {{strAsync}} {{mi.ReturnType}} {{mi.NewName}} ({{mi.parametersDefinitionCSharp }} {{separator}} 

//more code

{{
     if mi.ReturnsVoid == false
}}
     return
{{
     end
}}
{{  strAwait }}

{{mi.Name}}({{ mi.parametersCallCSharp }});

And this is worth noting.

AOP Methods–Code

The code is not so much different from SkinnyControllers : Implement ISourceGenerator , putting the Generator attribute on the class

[Generator]
    public partial class AutoActionsGenerator : ISourceGenerator

inspecting the classes if they have the common attribute , generating code with Scriban

The problem was : How can the AOPMethods can

  1. differentiate between the private function that must be made public
  2. generate the code for a similar public function, with same parameters (and maybe more) , but with different name ?

So I decide to go the route of convention: The programmer will declare the private function that he wants to autmatically make public ( and add templating code ) with a prefix or a suffix . This will be declared into the attribute of the class – and that will be all.

For example , I have this method

public  string FullName()
{
            
     return FirstName + ” ” + LastName;
}

That I want to monitor ( add logs, info and so on ) . I will make it private and start with pub

private string pubFullName()
{
            
     return FirstName + ” ” + LastName;
}

Then , on the class , I will declare the pub prefix

[AutoMethods(template = TemplateMethod.CallerAtttributes, MethodPrefix =”pub”, MethodSuffix =”bup”)]

And the following will be generated

public string FullName(
[CallerMemberName] string memberName = “”,
[CallerFilePath] string sourceFilePath = “”,
[CallerLineNumber] int sourceLineNumber = 0)
{
     try
     {
         Console.WriteLine(“–pubFullName start “);
         Console.WriteLine(“called class :” + memberName);
         Console.WriteLine(“called file :” + sourceFilePath);
         Console.WriteLine(“called line :” + sourceLineNumber);

        return

    pubFullName();
     }
     catch (Exception ex)
     {
         Console.WriteLine(“error in pubFullName:” + ex.Message);
         throw;
     }
     finally
     {
         Console.WriteLine(“——–pubFullName end”);
     }

}

The caller attributes templates can be found at  AOP_With_Roslyn\AOPMethods\AOPMethods\templates\CallerAtttributes.txt

//——————————————————————————
// <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.CodeDom.Compiler;
using System.Runtime.CompilerServices;

namespace {{NamespaceName}} {

    /// <summary>
     /// Generates code from  {{ClassName}}
     /// </summary>
   [GeneratedCode(“AOPMethods”, “{{version}}”)]
   [CompilerGenerated]
   partial class {{ClassName}}{
    /*
     public int id(){
     System.Diagnostics.Debugger.Break();
     return 1;
     }
     */
     {{~ for mi in Methods ~}}
         {{
             separator = “”
             if(mi.NrParameters > 0)
                 separator = “,”
             end
             strAwait = “”
             strAsync =””
             if mi.IsAsync == true
                 strAwait = ” await “
                 strAsync  = ” async “
             end
         }}
         public {{strAsync}} {{mi.ReturnType}} {{mi.NewName}} ({{mi.parametersDefinitionCSharp }} {{separator}} 
         [CallerMemberName] string memberName = “”,
         [CallerFilePath] string sourceFilePath = “”,
         [CallerLineNumber] int sourceLineNumber = 0){
             try{
                 Console.WriteLine(“–{{mi.Name}} start “);
                 Console.WriteLine(“called class :”+memberName );
                 Console.WriteLine(“called file :”+sourceFilePath );
                 Console.WriteLine(“called line :”+sourceLineNumber );
             {{
                 if mi.ReturnsVoid == false
             }}
                 return
             {{
                 end
             }}
             {{  strAwait }}

            {{mi.Name}}({{ mi.parametersCallCSharp }});
             }
             catch(Exception ex){
                 Console.WriteLine(“error in {{mi.Name}}:” + ex.Message);
                 throw;
             }
             finally{
                 Console.WriteLine(“——–{{mi.Name}} end”);
             }

         }

     
     {{~ end ~}}   
    
   }
}             

You can add logging , security, anything else that is a vertical to the business

AOP Methods–Introduction

As I have done with Roslyn for SkinnyControllers , I said – what about generating public methods at compile time ?

For example, what if this method

private string pubFullName()
{

return FirstName + ” ” + LastName;

}

 

is transformed into this

public string FullName(
[CallerMemberName] string memberName = “”,
[CallerFilePath] string sourceFilePath = “”,
[CallerLineNumber] int sourceLineNumber = 0)
{
try
{
Console.WriteLine(“–pubFullName start ” + _cc);
Console.WriteLine(“called class :” + memberName);
Console.WriteLine(“called file :” + sourceFilePath);
Console.WriteLine(“called line :” + sourceLineNumber);

return pubFullName();
}
catch (Exception ex)
{
Console.WriteLine(“error in pubFullName:” + ex.Message);
throw;
}
finally
{
Console.WriteLine(“——–pubFullName end”);

}
}

automatically, based on a template ? And all methods will have this ?

Enter AOP Methods : https://www.nuget.org/packages/AOPMethodsGenerator/  and https://www.nuget.org/packages/AOPMethodsCommon/

The first one is the generator. The second one is containing the attribute that tells to transform.

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.