.NET MAUI

How to use the .NET CLI clean-up tools on macOS

How to use the .NET CLI clean-up tools on macOS

Earlier this week, the .NET SDK and Runtimes received some updates. Together with that, also Visual Studio for Mac was updated. Once I got past the installation of all updates, both Visual Studio and Rider were no longer restoring the required NuGet packages for my .NET MAUI project running on .NET 6.

I eventually fixed that issue by cleaning up all the .NET SDKs, Runtimes, workloads and NuGet caches on my MacBook Pro. Read on to learn about the tools I used.

.NET uninstall tool

I have been using the .NET uninstall tool in the past. Unlike on Windows, you have to download the executable from GitHub in its zipped form.

While the releases page shows some terminal commands to unpack and run the tool, they never worked for me as stated there. While I was able to make the new directory with the mkdir command, the unpacking always shows an error. So I opened up Finder and unzipped it manually with the Archive Utility app that ships with macOS.

dotnet-core-uninstall unzipping

After switching to the folder in Terminal, the tool is supposed to show the help. Instead, I got an error showing me that I am not allowed to run this app for security reasons. The OS blocks the execution. If the same happens for you, right click on the extracted executable and select “Open With” followed by “Terminal.app (default)“. This will prompt you with this screen:

app downloaded from the internet message

Once you click on “Open“, a new Terminal window appears. Close this window, it is unusable as we are already in the exited state. Instead, open a new Terminal and change to the installation folder and call the help command:

cd ~/dotnet-core-uninstall
./dotnet-core-uninstall -h
Terminal with dotnet-core-uninstall help

Now that we are able to run the tool, let’s have a look what we have installed by running the dotnet --list command. We need to call the command twice, once for the installed -sdks and once for the installed -runtimes:

dotnet --list-sdks
dotnet --list-runtimes

You may be as surprised (I was, at least) how many versions you are accumulating over time. They never get removed by newer versions (it’s by design, according to Microsoft). To get rid of all versions except the latest, run the following commands with the uninstall tool (again once for — sdk, once for –runtime):

sudo ./dotnet-core-uninstall remove --all-but-latest --sdk
sudo ./dotnet-core-uninstall remove --all-but-latest --runtime

After uninstalling all previous versions, you may have to reinstall the latest .NET 6 SDK again. You could also use the –-all-but [Versions] command to specify the versions explicitly. No matter which way you’re going, if you run the dotnet --list commands again, you should see something similar to this:

dotnet --list command

Download: https://github.com/dotnet/cli-lab/releases

Documentation: https://learn.microsoft.com/en-us/dotnet/core/additional-tools/uninstall-tool?tabs=macos#step-3—uninstall-net-sdks-and-runtimes

dotnet workload command

As I had problems getting the required NuGet packages for my MAUI app, I decided to uninstall all .NET MAUI workloads as well. First, I had a look what is installed with the list command:

dotnet workload list
dotnet workload list result

Once you have that list, you need to call the uninstall command for every single installed workload:

sudo dotnet workload uninstall macos maui-maccatalyst maui-ios maui-android ios maccatalyst maui tvos android

Once they are uninstalled, I cleared the Terminal and installed them all again using the install command:

sudo dotnet workload install macos maui-maccatalyst maui-ios maui-android ios maccatalyst maui tvos android
dotnet workload install result in Terminal

Now we have the latest .NET MAUI workload installed as well as the platform specific workloads as well.

Documentation: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-workload

dotnet nuget locals

The final clean-up step involves all NuGet caches on your machine. Yes, you read that right, multiple caches. To see them all, run the following command:

dotnet nuget locals all --list

This will get you something like this:

dotnet nuget locals cache results in terminal

Now let’s get rid of all those old NuGet packages:

sudo dotnet nuget locals all --clear

If you’re lucky, you will see this message:

local nuget caches cleared in terminal

My first attempt was not that successful. I needed to open the global packages’ folder in Finder and delete some remaining packages manually. Only after that, I was able to run the clear command with success.

Conclusion

Neither Visual Studio nor the .NET installer perform clean-up tasks on macOS. Until Microsoft changes their mind here, we will have to clean-up old packages manually to keep our system smoothly running. Luckily, there are at least CLI tools around to help us with that job. As always, I hope this blog post will be helpful for some of you.

Until the next post, happy coding, everyone!

Posted by msicc in Dev Stories, macOS, MAUI, Xamarin, 1 comment
My annual review (2022) [Editorial]

My annual review (2022) [Editorial]

In the beginning of 2022, I would never have thought of such a turbulent year. After all, we went more or less over the pandemic, and there were at least some positive signs that were sparking some hope. Pretty fast, the year turned into a memorable one, but for very ugly reasons.

Ukraine war

Russia began a war in Ukraine. We all have to battle the consequences of this war. Because of the ongoing war, prices of living have raised in all areas, be it groceries, energy costs and also entertainment costs. No day passes by without getting notified about the horrible things Russian troops are doing in Ukraine. I still pray (and you should too, if you’re into that) for the war to be finally over soon.

Twitter take-over

The second impacting event was the take-over of Twitter by Elon Musk. While he made Tesla a profitable company by focusing on the product outcome and made space travel less expensive with SpaceX, he is currently about to destroy Twitter. While I still was somehow neutral back in April when the deal became more real, I lost hope for my favourite social network the day he fired half of the company’s staff.

At the time of the take-over, I was actively working on my app TwistReader, which was a reader app for Twitter lists. I had already a beta running on TestFlight when things began to turn bad on Twitter. After UniShare (which was in the process of being ported to Android and iOS when it died), I had to take the though decision to let go also this app. I cancelled the domain I bought for the app and shut down all Azure resources already. If someone wants to continue the project, I am open to talk about it.

TwistReader promotional image

This is now the second time I had to stop an app for social media. Ultimately, I decided I will not develop against any of the social networks from now on (even though I have several ideas to improve my social flow).

As we all know, things on Twitter aren’t becoming better. My presence on the bird site serves now solely as a guide to other social media I am active on. I decided to not delete my two main accounts, but to lock them for new followers, and stopped using the service. I am mostly active on Mastodon, followed by LinkedIn (although the later one needs some more attention).

NASA is flying to the moon again

Besides all the negative stuff, there were also some good news for all of us space fans. The NASA finally sent a space-ship to the moon again. They are playing the save game and did an unmanned launch, letting the capsule orbit the Moon and come back to Earth. They made some really awesome photos along the way, and the mission was a full success.

New blog series #CASBAN6

Besides working on TwistReader, I also started to port my portfolio website away from WordPress to a self written website in ASP.NET Core with Razor pages. The site itself is already published, with links to my apps in the stores, but the news section still needs a blog. I evaluated all the options, like existing CMS plugins and other blogging platforms.

In the end, I opted into learning something new by using some bits of what I already know – and I started my recent #CASBAN6 blog series about creating a serverless blog engine on Azure. This is now my main side project.

Other dev stuff

While I am focusing on the serverless blog engine, I also have some libraries I made and use internally for Xamarin.Forms that I need to port to .NET MAUI. Some parts can be easily removed and replaced with Essentials and CommunityToolkit. There is still plenty of code worth porting left, though.

At work, I broke up the internally used libraries to be more modular and finished implementing the service templates that use them. I also continued to push source control management within the team. Besides that, I wrote some interfaces for our customers that took advantage of these things, but needed additional items as well. Over all, I was able to use some of my learnings at work and vice versa.

I also decided to not cancel my Parallels subscription. I used it around 10 time throughout the year, which is not worth paying more than 100 bucks for the yearly licence.

Furthermore, I will use the freed budget to buy a Jetbrains Ultimate licence instead, which I started to use recently. The experience in writing code is far ahead of what Microsoft offers with Visual Studio on Mac, so I guess that’s a good investment.

Sports

If you have been following along for some time, you may know that I only became a non-smoker again (after 25 years of chain-smoking) two years ago. In terms of sports, I took part in three challenges this year (Run4Fun 6,8km, 10km at Winterthur marathon and Kyburglauf 2022 10.3 km (including 425 stairs just at the end of km 10). If you want to follow my running adventures, you can find me here on Strava.

Me running the 10 km at Winterthur Marathon 2022

Outlook into 2023

Next year, the roller coaster continues to ride. I will start a new role in March as a .NET mobile developer at Galliker Switzerland, which is one of the leading companies in logistics. They have a Xamarin.Forms code base and started the transition to .NET MAUI. There will be projects where I will have to do API and Web stuff as well, so this new position will help me to move towards my goal of becoming a full stack .NET developer as well. Another plus is that I am free to choose my preferred IDE – which will be most probably RIDER after my recent experiences with it.

Of course, I will continue to with my #CASBAN6 project as well. As I stated in my last post in the series, the Azure functions part is coming up next. I will have some posts on that topic alone, but I will also keep developing it further until the final product is ready to be used in production.

Besides that, I will start to port my Fishing Knots app to .NET MAUI, which will help me to learn the upgrade process and make the app ready for the future.

In terms of sports, I will continue with running, starting up with a focus on improving my average pace to get permanently below 5 min/km. On top of that, I want to run a half-marathon at the end of the next season. I will give runningCoach another try – hopefully they will be able to import my Strava results correctly this time.

Conclusion

What was your 2022 like? What are you all looking forward in 2023? Feel free to get in contact via my social media accounts or the comments section below.

What’s left is to wish all of you a

Merry Christmas and a Happy New Year!

Posted by msicc in Editorials, 0 comments
Invoke platform code in a MAUI app using the built-in Dependency Injection

Invoke platform code in a MAUI app using the built-in Dependency Injection

In Xamarin.Forms, my internal libraries for MVVM helped me to keep my applications cleanly structured and abstracted. I recently started the process of porting them over to .NET MAUI. I was quickly reaching the point where I needed to invoke platform specific code, so I read up the documentation.

The suggested way

The documentation suggests creating a partial class with partial method definitions and a corresponding partial classes with the partial method implementations (like described in the docs). I tried to follow the above-mentioned MAUI documentation and copy/pasted the code sample in there and thought everything is going to be fine. Well, it was not. I wasn’t even able to compile the solution with that code on my Mac in the first place.

In search for a possible cause of this, I did not find a solution immediately. In the end, it turned out that I needed to implement the partial class method on all platforms specified in the TargetFrameworks within the .csproj file. It should have been obvious due to the fact that MAUI is a single project with multiple target frameworks, but it wasn’t on that day.

On top of that, Visual Studio did some strange changes to the .csproj file specifying unnecessary None, Compile and Include targets that should not be generated explicitly, which added a lot to my confusion as well. After removing them from the project file and adding an implementation for all platforms, I was able to compile and test the code from the docs.

But I love my interfaces!

Likewise, that’s why I did not stop there. Following the abstraction approach, interfaces allow us to define the common surface of the API without worrying about the implementation details. That’s not the case for the partial classes approach, like the problems I had showed.

Luckily for us, .NET MAUI supports multi targeting. This means an interface can have a platform specific implementation while being defined in the shared part of the application. If you have used the MSBuildExtras package before, you know already how that works. Best part – .NET MAUI already provides the multi targeting configuration out of the box.

Show me some code!

First, let’s define a simple interface for this exercise:

namespace MAUIDITest.InterfaceDemo
{
    public interface IPlatformDiTestService
    {
        string SayYourPlatformName();
    }
}

Now we are going to implement the platform specific implementations. Go to the first platforms folder and add a new class named PlatformDiTestService. Then – and this is really important to make it work – adjust the namespace to be the same as the one of the interface. Last, but not least, implement the interface, for example like this:

namespace MAUIDITest.InterfaceDemo
{
    public class PlatformDiTestService : IPlatformDiTestService
    {
        public string SayYourPlatformName()
        {
            return "I am MacOS!";
        }
    }
}

Repeat this for all platforms, and replace the platform’s name accordingly.

Using Dependency Injection in MAUI

If you have been following along my past blog posts, you know that I recently switched to the CommunityToolkit MVVM (read more here and here). I already heard that MAUI will get the same DI container built-in, so the choice was obvious. Now let’s have a look how easy we can inject our interface into our ViewModel. Head over to your MauiProgram.cs file and update the CreateMauiApp method:

	public static MauiApp CreateMauiApp()
	{
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
				fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
			});

		//lowest dependency
		builder.Services.AddSingleton<IPlatformDiTestService, PlatformDiTestService>();
		//relies on IPlatformDiTestService
		builder.Services.AddTransient<MainPageViewModel>();
		//relies on MainPageViewModel
		builder.Services.AddTransient<MainPage>();

		return builder.Build();
	}

First, add the registration of the interface and the implementation. In the sample above, the MainPageViewModel relies on the interface and gets it automatically injected by the DI handler. For testing purposes, I even inject the MainViewModel into the MainPage‘s constructor. This is very likely to change in a real world application.

For completeness, here is the MainPageViewModel class:

using System;
using System.ComponentModel;
using System.Windows.Input;
using MAUIDITest.InterfaceDemo;

namespace MAUIDITest.ViewModel
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        private readonly IPlatformDiTestService _platformDiTestService;
        private string sayYourPlatformNameValue = "Click the 'Reveal platform' button";
        private Command _sayYourPlatformNameCommand;

        public event PropertyChangedEventHandler PropertyChanged;


        public MainPageViewModel(IPlatformDiTestService platformDiTestService)
        {
            _platformDiTestService = platformDiTestService;
        }

        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public string SayYourPlatformNameValue
        {
            get => sayYourPlatformNameValue;
            set
            {
                sayYourPlatformNameValue = value;
                OnPropertyChanged(nameof(this.SayYourPlatformNameValue));
            }
        }

        public Command SayYourPlatformNameCommand => _sayYourPlatformNameCommand ??=
            new Command(() => { this.SayYourPlatformNameValue = _platformDiTestService.SayYourPlatformName(); });
    }
}

And of course, you want to see the MainPage.xaml.cs file as well:

using MAUIDITest.InterfaceDemo;
using MAUIDITest.ViewModel;

namespace MAUIDITest;

public partial class MainPage : ContentPage
{
    private readonly IPlatformDiTestService _platformDiTestService;
    int count = 0;

	public MainPage(MainPageViewModel mainPageViewModel)
	{
		InitializeComponent();

		this.BindingContext = mainPageViewModel;
    }

	private void OnCounterClicked(object sender, EventArgs e)
	{
		count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);
	}
}

Last, but not least, the updated MainPage.xaml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MAUIDITest.MainPage">
			 
    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0" 
            VerticalOptions="Center">

            <Image
                Source="dotnet_bot.png"
                SemanticProperties.Description="Cute dot net bot waving hi to you!"
                HeightRequest="200"
                HorizontalOptions="Center" />
                
            <Label 
                Text="Hello, World!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />
            
            <Label 
                Text="Welcome to .NET Multi-platform App UI"
                SemanticProperties.HeadingLevel="Level2"
                SemanticProperties.Description="Welcome to dot net Multi platform App U I"
                FontSize="18"
                HorizontalOptions="Center" />

            <Button 
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />


            <Label Text="Test of built in DI:" FontSize="Large" HorizontalOptions="Center"></Label>
            <Label x:Name="PlatformNameLbl" FontSize="Large" HorizontalOptions="Center" Text="{Binding SayYourPlatformNameValue}" />

            <Button Text="Reveal platform" HorizontalOptions="Center" Command="{Binding SayYourPlatformNameCommand}"/>

        </VerticalStackLayout>
    </ScrollView>
 
</ContentPage>

I did not change the default code that comes with the template. You can easily recreate this by using the default MAUI template of Visual Studio and copy/paste the code snippets above to play around with it.

Conclusion

I only started my journey to update my internal libraries to .NET MAUI. I stumbled pretty fast with that platform invoking code, but luckily, I was able to move along. Platform specific code can be handled pretty much the same as before, which I hope I was able to show you in this post. I’ll write more posts on my updating experiences to MAUI as they happen.

Until the next post, happy coding, everyone!
Posted by msicc in Dev Stories, MAUI, Xamarin, 4 comments