Category: .NET Core

TILT–specifications –part 2

This is an application to store what I have learned today / each day .

It will be one note string per day , note will be no more than 140 characters.

It has tags – programming, life, and so on. Can add one link to the note.

Can be saved on local ( desktop, mobile )or on cloud ( site). If wanted will be sync by having url part and password

Will have a calendar to see every day what have I learned – missing days will have an red X

Reports:

Can be exported by date end -date start into Excel Word PDF

Will show what you have learned one year before

Default are all public – if you want to be private , should pay or copy the application.

Tools used:

notepad

NetCoreUsefullEndpoints-4 Nuget package

or NugetPackages there is a simple .NET Pack CLI command  and many configurations

However, it is not so simple . See https://docs.microsoft.com/en-us/nuget/create-packages/package-authoring-best-practices .

In practice, I have a readme.md file ( for showing on nuget.org ) and a readme.txt file( for the programmer to show help after installing the package) THis is what I have in the csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

	<ItemGroup>
		<FrameworkReference Include="Microsoft.AspNetCore.App" />
		 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />   
		<PackageReference Include="RSCG_Static" Version="2021.12.18.2037" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
		<None Include="../../../README.md" Pack="true" PackagePath="\" />
		<None Include="../../../docs/nuget.png" Pack="true" PackagePath="\" />    
		<None Include="../readme.txt" Pack="true" PackagePath="\" />
	</ItemGroup>
	<PropertyGroup>
		<Version>6.2022.722.712</Version>
		<Authors>Andrei Ignat</Authors>
		
		<Description>Some usefull extensions: error, environment, user</Description>
		<Title>NetCoreUsefullEndpoints</Title>
		<PackageId>NetCoreUsefullEndpoints</PackageId>
		<PackageTags>C#;.NET;ASP.NET Core;</PackageTags>
		<PackageReadmeFile>README.md</PackageReadmeFile>
		<RepositoryUrl>https://github.com/ignatandrei/NetCoreUsefullEndpoints</RepositoryUrl>
		<PackageProjectUrl>https://github.com/ignatandrei/NetCoreUsefullEndpoints</PackageProjectUrl>
		<RepositoryType>GIT</RepositoryType>
		<Copyright>MIT</Copyright>
		<PackageLicenseExpression>MIT</PackageLicenseExpression>
		<IncludeSymbols>true</IncludeSymbols>
		<PublishRepositoryUrl>true</PublishRepositoryUrl>
		<EmbedUntrackedSources>true</EmbedUntrackedSources>
		<Deterministic>true</Deterministic>
		<DebugType>embedded</DebugType>

	</PropertyGroup>
	<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
		<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
	</PropertyGroup>
	<PropertyGroup>
	  <TreatWarningsAsErrors>False</TreatWarningsAsErrors>
	  <PackageIcon>nuget.png</PackageIcon>
	</PropertyGroup>

</Project>

Also, the versioning will be a mixture of patron major version + calendar version FOr example there are 2 versions of https://www.nuget.org/packages/NetCoreUsefullEndpoints : 6.2022.722.712 – means for ASP.NET Core 6, on year 2022, month 7, day 22, hour 7, min 12 6.2022.721.1154 – means for ASP.NET Core 6, on year 2022, month 7, day 21, hour 11, min 54

You can see

  1. as Swagger at https://netcoreusefullendpoints.azurewebsites.net/swagger

  2. As BlocklyAutomation at https://netcoreusefullendpoints.azurewebsites.net/BlocklyAutomation

  3. As package at https://www.nuget.org/packages/NetCoreUsefullEndpoints

NetCoreUsefullEndpoints-2–MVP

So let’s start the implementation for user / error / environment.

The only difficulty resides in the fact that IEndpointRouteBuilder is not defined into a usual library, but the dll csproj must contain

	<ItemGroup>
		<FrameworkReference Include="Microsoft.AspNetCore.App" />
    </ItemGroup>

Otherwise , it is work as usual. For example, for seeing a user

 public static void MapUser(this IEndpointRouteBuilder route)
{
    ArgumentNullException.ThrowIfNull(route);
    route.MapGet("api/usefull/user/", (HttpContext httpContext) =>
    {
        return Results.Ok(httpContext.User?.Identity?.Name);
    }).RequiresAuthorization();
}

More difficult was for Environment – that is a Static class. Hopefully, there is a NUGET for this – https://www.nuget.org/packages/rscg_static – that allows to generate an interface from a static class. So I have added this

	<ItemGroup>
		<FrameworkReference Include="Microsoft.AspNetCore.App" />
    </ItemGroup>

And code for generating the interface

public partial class Helper
{
    public partial ISystem_Environment FromStaticEnvironment();

}

So, when I want to get the values from Environment, I use this

public static void MapUsefullEnvironment(this IEndpointRouteBuilder route, string? corsPolicy = null, string[]? authorization = null)
{
    ArgumentNullException.ThrowIfNull(route);

    var rh=route.MapGet("api/usefull/environment/", (HttpContext httpContext) =>
    {
        return Results.Ok(new Helper().FromStaticEnvironment());
    });
    rh.AddDefault(corsPolicy, authorization);

}

You can see

  1. as Swagger at https://netcoreusefullendpoints.azurewebsites.net/swagger

  2. As BlocklyAutomation at https://netcoreusefullendpoints.azurewebsites.net/BlocklyAutomation

  3. As package at https://www.nuget.org/packages/NetCoreUsefullEndpoints

NetCoreUsefullEndpoints–1- Idea

For every web Api that I produce I want to see if it is well configured . That means

  1. The error flows how it should, where it should ? ( API for error )

  2. The user is authorized and authenticated ?
  3. What is the current environment that I have ? ( name of the host )

Maybe it is good to have the current date of the PC and the configuration

And – why repeat when you can create this ?

You can see the Nuget result at NuGet Package

You can see

  1. as Swagger at https://netcoreusefullendpoints.azurewebsites.net/swagger

  2. As BlocklyAutomation at https://netcoreusefullendpoints.azurewebsites.net/BlocklyAutomation

  3. As package at https://www.nuget.org/packages/NetCoreUsefullEndpoints

DIForFunctions – Improving constructor–part 5

I have received a suggestion : what if we just put into constructor what we need , and everything else ( such as ILogger ) are into fields ?

The Roslyn Source Code Generator will generate a constructor that calls the this constructor  and will assign fields needed.

Let’s give an example : We wrote

public partial class TestDIFunctionAdvWithConstructor2Args
    {
        [RSCG_FunctionsWithDI_Base.FromServices]
        private TestDI1 NewTestDI1;

       public TestDI2 NewTestDI2 { get; set; }

       public readonly TestDI3 myTestDI3;

       private TestDIFunctionAdvWithConstructor2Args(TestDI3 test, TestDI2 a)
        {
            myTestDI3 = test;
            NewTestDI2 = a;
        }

   }

and the generator will generate a new constructor with the required  field

public partial class TestDIFunctionAdvWithConstructor2Args
{
public TestDIFunctionAdvWithConstructor2Args  
(TestDI3 test, TestDI2 a, TestDI1 _NewTestDI1) : this (test,a)
{
this.NewTestDI1 = _NewTestDI1;
}//end constructor

}//class

The code is non trivial  – to find if a constructor exists, take his fields, generate new constructor with all fields.

But , as anything in IT , it is doable .

DIForFunctions–what it does- part 4

You can find a demo at https://github.com/ignatandrei/FunctionsDI/tree/main/src/FunctionsWithDI  – see TestCOnsoleAPP. But let’s write here also

Generate (constructor) and functions calls similar with ASP.NET Core WebAPI ( [FromServices] will be provided by DI ) Also, verifies for null .

Usage

Reference into the csproj

<ItemGroup>
    <PackageReference Include="RSCG_FunctionsWithDI" Version="2022.6.19.1605" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
    <PackageReference Include="RSCG_FunctionsWithDI_Base" Version="2022.6.19.1605" />
</ItemGroup>	

Then for every class you can write [FromServices]

using RSCG_FunctionsWithDI_Base;
//namespace if necessary
public partial class TestDIFunction
{
    public bool TestMyFunc1([FromServices] TestDI1 t1, [FromServices] TestDI2 t2, int x, int y)
    {
        return true;
    }
    //more functions
}

generates the constructor with needed details

public partial class TestDIFunction
{ 
private TestDI1 _TestDI1;
private TestDI2 _TestDI2;
public TestDIFunction  (TestDI1 _TestDI1,TestDI2 _TestDI2) //constructor generated with needed DI
 { 
this._TestDI1=_TestDI1;
this._TestDI2=_TestDI2;

 } //end constructor 

//making call to TestMyFunc1
public bool TestMyFunc1(int  x,int  y){ 
var t1 = this._TestDI1  ;
if(t1 == null) throw new ArgumentException(" service TestDI1  is null in TestDIFunction ");
var t2 = this._TestDI2  ;
if(t2 == null) throw new ArgumentException(" service TestDI2  is null in TestDIFunction ");
return  TestMyFunc1(t1,t2,x,y);
}

so you can call

var test=serviceProvider.GetService<TestDIFunction>();
Console.WriteLine(test.TestMyFunc1(10,3)); // calling without the [FromServices] arguments

DIForFunctions–NuGet- part3

The important part now is to make public – that means NuGet and documentation, The NuGet is pretty simple – with

dotnet pack

and with GitHub Actions – in order to do automatically every time I modify the main. For now, this is the action

name: .NET

on:

  push:

    branches: [ “main” ]

  pull_request:

    branches: [ “main” ]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:

    – uses: actions/checkout@v3

    – name: Setup .NET

      uses: actions/setup-dotnet@v2

      with:

        dotnet-version: 6.0.x

    – name: Restore dependencies

      run: |

        cd src

        cd FunctionsWithDI

        dotnet tool restore

        dotnet pwsh readme.ps1

        dotnet restore

    – name: Build

      run: |

        cd src

        cd FunctionsWithDI

        dotnet build –no-restore

    – name: TestConsoleProject

run:  |

        cd src

        cd FunctionsWithDI

        cd TestConsoleApp

        dotnet run  –no-build

    – name: create package

if: ${{ github.ref == ‘refs/heads/main’ }}

run: |

        cd src

        cd FunctionsWithDI

        echo ‘now aop’

        #dotnet pwsh AOPMethod.ps1

        #dotnet clean 

        #dotnet build

        echo ‘now pack’

        dotnet pack RSCG_FunctionsWithDI/RSCG_FunctionsWithDI.csproj                        -o nugetPackages  –include-symbols –include-source

        dotnet pack RSCG_FunctionsWithDI_Base/RSCG_FunctionsWithDI_Base.csproj              -o nugetPackages  –include-symbols –include-source

    – name: ‘Upload nuget’

      if: ${{ github.ref == ‘refs/heads/main’ }}

      uses: actions/upload-artifact@v2

      with:

        name: RSCG_FunctionsWithDI_${{github.run_number}}

        path: src/FunctionsWithDI/nugetPackages

        retention-days: 1

that generates at every run the packages

You will find the sources at https://github.com/ignatandrei/functionsdi  and the nuget at https://www.nuget.org/packages/RSCG_FunctionsWithDI

DI for Functions- work–part 2

Let’s begin with tests  – we need to have a class with multiple functions that have multiple [FromServices} parameter. Like

public bool TestMyFunc1([FromServices] TestDI1 t1, [FromServices] TestDI2 t2, int x, int y)
         {
             return true;
         }
         public bool TestMyFunc2([FromServices] TestDI1 t12,  int x, int y)
         {
             return true;
         }

// more others

Because there are multiple functions, I need to generate very fast  – so Incremental generators to the rescue . They are documented here : https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md  . And a good tutorial is to be found at https://andrewlock.net/creating-a-source-generator-part-1-creating-an-incremental-source-generator/ .

Basically, this is the code

IncrementalValuesProvider<MethodDeclarationSyntax> paramDeclarations = context.SyntaxProvider
             .CreateSyntaxProvider(
                 predicate: static (s, _) => IsSyntaxTargetForGeneration(s),
                 transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
             .Where(static m => m is not null)!; // filter out attributed enums that we don’t care about

            IncrementalValueProvider<(Compilation, ImmutableArray<MethodDeclarationSyntax>)> compilationAndEnums = context.CompilationProvider.Combine(paramDeclarations.Collect());

            context.RegisterSourceOutput(compilationAndEnums,
             static

and the  idea is to find the parameters of the function that has attributes – and one of those is [FromServices] . After that , find the methods that are the parent – and then the class. After that , is simple to generate a constructor with all (distinct) the [FromServices]parameters and construct the similar method with just the non-DI parameters.

Bonus : We can verify if the parameters are null and throw exception

I could do a template for defining , but – wait to see if gain some traction to modify .

You can find the sources at https://github.com/ignatandrei/functionsdi and the NuGet packages ( one with generator, one with [FromServices] ) at https://www.nuget.org/packages/RSCG_FunctionsWithDI and https://www.nuget.org/packages/RSCG_FunctionsWithDI_Base

Enjoy!

DI for Functions–idea – part 1

Looking at ASP.NET Core , there is a wonderful feature that  gives you thinking :  you can put in any action for a controller FromServices argument and the framework will complete from, well, services: :

public ActionResult Test([FromServices] MyFunction

What if  you can do the same with any function from any class ?

It will be good, but … how  ?  ASP.NET Core instantiate himself the functions, but I cannot do this. 

I can generate with Roslyn a function that takes not DI arguments . For example , from

public bool TestMyFunc1([FromServices] TestDI1 t1, [FromServices] TestDI2 t2, int x, int y)

Roslyn can generate this

public bool TestMyFunc1(int  x,int  y)

And call the previous function – but HOW we can retrieve the arguments ?

As I see , there are 2 options:

1.  Generate a constructor that have as a parameter the ServiceProvider and find the services from ServiceProvider

2. Generate a constructor that have the DI arguments and assign them as fields .

Now go to work!

app.Use vs Middleware–and scoped services

When you want to use a fast middleware , you can use ( pun intended)

app.Use(

However, if you want to use some of scoped services , e.g.

app.Use(async (context, next) =>
{
     var st= app.Services.GetRequiredService<IServerTiming>();

//code

}

then it gives an error

Cannot resolve scoped service  from root provider

For this you should create  a scope:

app.Use(async (context, next) =>
{
     //var st= app.Services.GetRequiredService<IServerTiming>();
     using var sc = app.Services.CreateScope();
     var st = sc.ServiceProvider.GetRequiredService<IServerTiming>();
     st.AddMetric((decimal)0.002, “yrequest”);
     await next(context);
});

However, that means it will NOT be the same scope as the original app . How we can have the same scope ?  By using the middleware

public class ServerTiming : IMiddleware
{
     private readonly IServerTiming serverTiming;

    public ServerTiming(IServerTiming serverTiming)
     {
         this.serverTiming = serverTiming;
     }
     public async Task InvokeAsync(HttpContext context, RequestDelegate next)
     {
         this.serverTiming.AddMetric((decimal)0.001, “startRequest”);
         await next(context);              
     }
}

and registering ( code for .net 6 , you can easy make it for .net <6 )

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

This way we are sure that we have the same scoped data.

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.