Automatgenererad kod med Roslyn

May 26th, 2024

The presentation I held at the p2p-k1darch conference

Ola Fjelddahl Göteborgskontoret
https://selfelected.com https://github.com/LosManos/

10. Me, myself and I

11. Me, myself and I

Idag skall vi prata om automatgenererad kod i allmänhet
och Roslyn code generator i synnerhet

20. T4

Kör när filer ändras. Minns inte hur man kopplar det.
Också Framework.
https://learn.microsoft.com/en-gb/previous-versions/visualstudio/visual-studio-2015/modeling/code-generation-and-t4-text-templates

30. Roslyn

Analyzer och Code generator

Vid “kompilering”

Känner endast till syntaxen.
Plus något sorts kompilierings-context

Skapa kod via sträng eller kod/syntaxträd.

Lägger endast till kod

Borde inte kunna användas för AOP

Gamla sättet

Vid kompilering, får allt. Du söker ut det intressanta.

Inkrementellt

Nya inkrementella sättet, får anrop vid kompilering.

40. Var man startar

https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview
Den skapar dock enligt gamla sättet.

Gå vidare till Andrew Lock
https://andrewlock.net/creating-a-source-generator-part-1-creating-an-incremental-source-generator/

Skapa kod med kod
https://carlos.mendible.com/2017/03/02/create-a-class-with-net-core-and-roslyn/
https://roslynquoter.azurewebsites.net

Min kod
https://github.com/LosManos/FactoryMethodCodeGenerato

100. Frågor på detta?

Och nu…
Kod!

Unit testing Azure service bus

February 19th, 2024

Due to non-mockability it is a bit tricky to setup a unit test for Azure service bus.

I have found a resource to help me: https://www.mrjamiebowman.com/software-development/dotnet/net-unit-testing-azure-service-bus/

Reading through said article while writing code solved the issue for me. And you hopefully.

Convert sample Maui app to MVVM

May 1st, 2023

Create base from Maui template

Open Visual studio, create new project from “.NET MAUI App”. Name it “MauiApp1”.

Set target to “Windows Machine” and press play just to make sure all dots are connected.

The start and result are on Github:LosManos/MauiApp1_MVVM.

If you prefer video instead of text James Montemagno has a similar Youtube video.

Plans

Investigate the code.
In MainPage.xaml there is a tight coupling from the xaml file to the code behind through

1
Clicked="OnCounterClicked"
. That method has code like
1
CounterBtn.Text = $"Clicked {count} time";
which means it is tightly coupled with the GUI.

When running the code, the counter and button text, will be updated by the code above.

Let’s get rid of the tight coupling.

Note: I am not satisfied how the click command will be wired up in the final solution. Please comment and help me make it better.

View model

MVVM works by GUI logic writing to an object/data structure (the view model) and the GUI graphic reading from it. And vice verse. It can be compared to a data contract with talking over the wire

This means we can test the logic without having a GUI and the GUI won’t have to rely on complicated GUI logic.

Create a class “MainPageViewModel.cs”, in a new folder “ViewModel”. We want to replace the counter.

To make it easier to react on the changes of the values we will use CommunityToolkit. To make it work the MVVM class must be public and partial. The field must be a field (not a property, and it must be with a leading lower case. Add a counter and a buttonText. Add a method to update the counter and replace the button text.

The reason for the above is that CommunityToolkit creates a Property for every field with ObservableProperty. You can find the rendered code in MauiApp1/Dependencies/.net7.0-xxxx/Analyzers.
I have some reason for the view model to be public but won’t delve into it here.

Install Nuget package CommunityToolkit.MAUI and CommunityToolkit.Mvvm.

(Sorry for the &#91; and &lt;, but I don’t want to dig through WordPress. Just replace with [ and < respectively.)

Class “MainPageViewModel.cs” will look like this:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace MauiApp1.ViewModel;

public partial class MainPageViewModel : ObservableObject
{
    public MainPageViewModel()
    {
        ButtonText = "Click Me";
    }

    &#91;ObservableProperty]
    public int myCount;

    &#91;ObservableProperty]
    public string buttonText;

    &#91;RelayCommand]
    public void IncreaseCount()
    {
        MyCount += 1;

        if (MyCount == 1)
            ButtonText = $"Clicked {MyCount} time";
        else
            ButtonText = $"Clicked {MyCount} times";
    }
}

Pressing play should work now as we only have added a class. Not changed or connected anything.

Connect

As per the readme you received when installing CommunityToolkit we have to manipulate the startup. Make it use the CommunityToolkit. Open MauiProgram.cs and add


1
    .UseMauiCommunityToolkit()

Then we need to wire up the dependency injection. Alas add


1
2
    builder.Services.AddSingleton&lt;ViewModel.MainPageViewModel&gt;();
    builder.Services.AddSingleton&lt;MainPage&gt;();

You will now have something like


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;

namespace MauiApp1;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp&lt;App&gt;()
            .UseMauiCommunityToolkit()
            .ConfigureFonts(fonts =&gt;
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        builder.Services.AddSingleton&lt;ViewModel.MainPageViewModel&gt;();
        builder.Services.AddSingleton&lt;MainPage&gt;();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}

In MainPage.xaml.cs we get rid of the OnCounterClicked method and the counter. We also need to inject the view model to the main page MainPage.xaml.cs. All that is left is:


1
2
3
4
5
6
7
8
9
10
namespace MauiApp1;

public partial class MainPage : ContentPage
{
    public MainPage(ViewModel.MainPageViewModel vm)
    {
        InitializeComponent();
        BindingContext = vm;
    }
}

Do the xaml dance

As we have removed the event handler we also need to remove it from the xaml. Find the button and get rid of “Clicked=”OnCounterClicked””. We also don’t need the name as the code does not reference the button any more. The result looks like


1
2
3
4
5
&lt;Button
  Text="Click me"
  SemanticProperties.Hint="Counts the number of times you click"
  HorizontalOptions="Center"
/&gt;

If you press play now the program will start but nothing will happen if you press play.

Add dynamic button text and the the cryptic command binding to the button


1
2
3
4
5
6
&lt;Button
  Text="{Binding ButtonText}"
  SemanticProperties.Hint="Counts the number of times you click"
  Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainPageViewModel}}, Path=IncreaseCountCommand}"
  HorizontalOptions="Center"
/&gt;

The above will not work unless we tell the xaml where to find the command. So update the ContentPage element


1
2
3
4
5
6
7
&lt;ContentPage
  xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:viewmodel="clr-namespace:MauiApp1.ViewModel"
  x:Class="MauiApp1.MainPage"  
  x:DataType="viewmodel:MainPageViewModel"
&gt;

Press play.

Every time you press the button the text button will change.

My talk about how to make code review

February 26th, 2023

The other year I held a talk about how to make code reviews that are of value.
As zip.

Förvänta dig inte att din Pull request går igenom

ola fjelddahl
göteborgskontoret

Förvänta dig inte att din Pull request går igenom

PRAISE: Nice refactoring!

ola fjelddahl
göteborgskontoret

Förvänta dig inte att din Pull request går igenom

PRAISE: Nice refactoring!
SUGGESTION: Split line.

ola fjelddahl
göteborgskontoret

Förvänta dig inte att din Pull request går igenom

PRAISE: Nice refactoring
SUGGESTION: Isn’t it an Article?, not a Device.
CHECK: Have you investigated all uses of the enum?

ola fjelddahl
göteborgskontoret

Förvänta dig inte att din Pull request går igenom

PRAISE: Nice refactoring!
SUGGESTION: Split line.
CHECK: Do you use the enum the correct way? Are all cases considered?
EXPLAIN: How do this work?

ola fjelddahl
göteborgskontoret

Förvänta dig inte att din Pull request går igenom

PRAISE: Nice refactoring!
SUGGESTION: Split line.
CHECK: Do you use the enum the correct way? Are all cases considered?
EXPLAIN: How do this work?
FIX: Check for null.

ola fjelddahl
göteborgskontoret

Förvänta dig inte att din Pull request går igenom

PRAISE: Nice refactoring!
SUGGESTION: Split line.
CHECK: Do you use the enum the correct way? Are all cases considered?
EXPLAIN: How do this work?
FIX: Check for null.
QUESTION: Is this a HasA or a IsA

ola fjelddahl
göteborgskontoret

A talk about pros and cons with O/RM

February 25th, 2023

These are my pages from my talk about pros and cons with “fat” O/RM today.
As zip.

# O/RM

myname@omegapoint.se Göteborgs-kontoret

https://selfelected.com/
https://ideas.selfelected.com/
https://github.com/LosManos

# O/RM

myname@omegapoint.se Göteborgs-kontoret


Resultat: Ifrågasätt

# O/RM

myname@omegapoint.se Göteborgs-kontoret

Föredraget förutsätter en vag förståelse av ORM.
Fet orm (EF/Hibernate/cache). Micro-orm. (mappning av data och datatyper)
Att det är en översättning från RDb:ns mängdbaserade struktur till det imperativa språkets list-baserade; och tvärtom.

# O/RM

myname@omegapoint.se Göteborgs-kontoret

3 skäl
* Typsäkerhet
* Höger-vänster
* Refaktorering

# O/RM

myname@omegapoint.se Göteborgs-kontoret

Ökad komplexitet

Utsuddade gränser

Choke point

# O/RM

myname@omegapoint.se Göteborgs-kontoret

3 skäl
Typsäkerhet
ändras inte så ofta det så när det väl är mappat
Höger-vänster
ändras ofta i början men tenderar att stabiliseras
Refaktorering
man kan bara automatrefaktorera namn och typer. strukturer är manuellt arbete

# O/RM

myname@omegapoint.se Göteborgs-kontoret

https://www.selfelected.com/the-tree-reasons-to-use-an-o-rm/
https://dapper-tutorial.net/dapper

Data contracts should be findable / enumerable

January 16th, 2023

The idea is this: There are certain requirements on a data contract; like it being serializable and whatnot.

To verify this automagically the test program must be able to find them.
It can be done through:
1) they are all in the same namespace.
2) their names are all suffixed with “DataContract”
3) they all implement “IDataContract”

Then through reflection/RTTI a test can get all data contracts and verify they are all ok.

Unfortunately Aspnet does not have a way to verify that an endpoint returns a data contract through unit testing; it has to be solved through integration testing or surveillance of production.

Update: I created a dotnet utility for finding all classes decorated with a certain attribute to verify they all had a default constructor. It is called “Constructor.IsDefaultImplemented” and look here to see how to use it in your unit test.

Error message “No process is associated with this object.”

May 17th, 2022

If, when starting your web application/service in Visual studio, you get:

—————————
Microsoft Visual Studio
—————————
No process is associated with this object.
—————————
OK
—————————

Check the output, or try starting in debug mode, and a more ´valuable error messag might pop up.

Better type-ahead in Powershell

May 5th, 2022

1
Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete

makes for a better type-ahead like:

To make it autostart, add it to your profile. Here is how to get to it quickly with VSCode:

1
code $profile

Just add the Set-PSReadlineKeyHandler thingy above.

Use IHttpClientFactory (or HttpClientFactory) from a console

April 25th, 2022

IHttpClientFactory is bould to Microsoft’s DI.

So create services and retrieve it.


1
2
var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
var httpClientFactory = serviceProvider.GetService&lt;IHttpClientFactory&gt;();

Honour those who should be honoured.

Health middleware in AspnetCore, or rather Dotnet6 throws TaskCanceledException

January 21st, 2022

I added some health check to my aspnet site to trigger from Kubernetes startupProbe and sometimes got

{
	"EventId": 1,
	"LogLevel": "Error",
	"Category": "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware",
	"Message": "An unhandled exception has occurred while executing the request.",
	"Exception": "System.Threading.Tasks.TaskCanceledException: A task was canceled.    
        at Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService.CheckHealthAsync(Func`2 predicate, CancellationToken cancellationToken)    
        at Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckMiddleware.InvokeAsync(HttpContext httpContext)    
        at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)    
        at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)    
        at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.<Invoke>g__Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)",
	"State": {
		"Message": "An unhandled exception has occurred while executing the request.",
		"{OriginalFormat}": "An unhandled exception has occurred while executing the request."
	}
}

There is nothing in my health checking code that can throw (famous last words…). In the stack above there is none of my code.

There might be a call to cancel that results in a TaskCanceledException or something with authentication/authorisation as can be seen in the stack.

To make a long story short I moved the calls to UseHealthCheck from below UseEndpoints to above UseAuthentication as my endpoints are anonymous (they are very lightweight).

app.UseRouting();

app.UseHealthChecks("/api/article/health/alive", new HealthCheckOptions
{
    Predicate = check => true
});
app.UseHealthChecks("/api/article/health/ready", new HealthCheckOptions
{
    Predicate = check => true
});
app.UseHealthChecks("/api/article/health/startup", new HealthCheckOptions
{
    Predicate = check => true
});

app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});