Category: di

DIForFunctions–improving generating of a constructor for a existing class with Roslyn

It is not so difficult to generate a new constructor for a existing class with Roslyn – however , there are some difficulties:

1. The original class must be declared “partial” – you cannot declare the second as partial if the first was not

2.  The new class must be in the same namespace ( or without namespace) as the original

3. The constructor must have the types declared with  full names  / or get the usings from the existing class ( or , maybe , put the developer to use global usings: https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10

For 1 the dotnet compiler shows an error  – but you can add yours too:

private static bool HasPartial(ClassDeclarationSyntax cds)
         {
             if (cds.Modifiers.Count < 1)
                 return false;
             foreach(var modif in cds.Modifiers)
             {
                 if (modif.Text == “partial”)
                     return true;
             }
             return false;
         }

if (!HasPartial(cds))
                 {
                     var dd = new DiagnosticDescriptor(DiagnosticId, Title, “Please add partial to ” + cds.Identifier.Text, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: “Please add partial to ” + cds.Identifier.Text);
                     Location? loc = cds.GetLocation();
                     var dg = Diagnostic.Create(dd, loc);
                     context.ReportDiagnostic(dg);
                     return;
                 }

For 2 is somehow more difficult

private static Tuple<string,string> NameAndNameSpace(ClassDeclarationSyntax c)
         {
             var nameClass = c.Identifier.Text;
             var p = c.Parent;
             var namespaceClass = “”;
             while (true)
             {
                 if (p is BaseNamespaceDeclarationSyntax bnsds)
                 {
                     namespaceClass = bnsds.Name.ToFullString();
                     break;
                 }
                 p = p?.Parent;
                 if (p == null)
                     break;
             }
             return Tuple.Create(nameClass, namespaceClass);
         }

And verify if the namespace is null – if it is, do NOT generate namespace declaration

For 3 it is more complicated

private static string? FullTypeVar(SemanticModel? sem,TypeSyntax? ts)
         {
             if (sem == null)
                 return null;
             if (ts is null)
                 return null;
             var typeInfo = sem.GetTypeInfo(ts);
             if (typeInfo.Type is null)
                 return null;

            var theType = ((INamedTypeSymbol)typeInfo.Type);

            var typeField = theType?.ToDisplayString();
             return typeField;
         }

And that it is!

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–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!

Dependency injection choice

For a personal pet project I have needed a DI framework . It relates to WebApi – I need to switch the provider for web api between a console and web – mostly authentication / logging different.

Some years ago was only StructureMap – but now there are a lot.

So I started to investigate to choose between the DI frameworks.

What I have wanted:

 

1. open license to use in a project – and source code too – to can upgrade.

2. Updated to the last .NET framework ( so , for .NET 10.0 , I will not be left to upgrade myself the source code – I am lazy too)

3. Speed

4. Last but not least:  easy to use – have some simple example to start me with

 

The first link found was:

http://fukyo-it.blogspot.ro/2012/10/comparing-net-di-ioc-frameworks.html  – it helps about license . Recomends Autofac. I was not very sure about.

Let’s see who have updated the source code to .NET 4.5 . I started looking at github and google code – and , yes, all are updating the source to the latest framework.

For speed I consider relevant http://www.palmmedia.de/Blog/2011/8/30/ioc-container-benchmark-performance-comparison  – if you look down , latest update was(quoting):

“17.11.2013: Added Grace. Updated several containers.”

Recommends  Simple Injector . And from his page seems simple to use.

So my choice is Simple Injector . If you use a DI, please say in the comments what DI and  why.

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.