DEV Community

David Ortinau
David Ortinau

Posted on

C# UI and .NET Hot Reload - A Match Made in .NET MAUI

Visual Studio 2022 and .NET 6 introduced .NET Hot Reload, the ability to continue coding C# while debugging your application, and use your changes without stopping. If you've used Edit and Continue, this probably feels similar.

  1. Start debugging your app
  2. Add or edit some C#
  3. Click the "Hot Reload" button in Visual Studio
  4. Re-trigger the code path to exercise your new code

Most of the time in .NET MAUI we (I) use XAML to declare UI, and XAML Hot Reload to avoid repetitive stopping and starting. This can work really well. Anytime you make a XAML edit, as long as the XAML compiler indicates your edit is valid code, the change is shipped to the running app AND the UI is updated while retaining state.

There are many cases when XAML requires me to stop and restart my session, thus breaking my flow. Remaining completely in C# drastically improves this situation.

Marrying C# UI and .NET Hot Reload

When you edit UI code in your C#, you must then do something to run that code again and see the impact of your change. That can become tiresome. Here's what I do.

I like the pattern of placing my UI construction in a Build method. This method sets the ContentPage.Content and is called in the page's OnNavigatedTo method when hosted within Shell or a NavigationPage.

void Build() => Content = 
        new Grid { 

        };

protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
    base.OnNavigatedTo(args);

    Build();
}
Enter fullscreen mode Exit fullscreen mode

Great! But this method is only going to get called once. I could navigate away from that page and come back. Or I could add a gesture to trigger the method manually. That gets old quickly.

What if I could make sure the Build() method anytime I triggered a hot reload, similar to XAML Hot Reload?

Turns out I can! Anytime .NET Hot Reload executes, a metadata change is triggered, and I can hook into that. To do this, I add a HotReloadService.cs to my project.

#if DEBUG
[assembly: System.Reflection.Metadata.MetadataUpdateHandlerAttribute(typeof(YourAppNamespace.HotReloadService))]
namespace YourAppNamespace { 
    public static class HotReloadService
    {
    #pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
        public static event Action<Type[]?>? UpdateApplicationEvent;
    #pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

        internal static void ClearCache(Type[]? types) { }
        internal static void UpdateApplication(Type[]? types) {
            UpdateApplicationEvent?.Invoke(types);
        }
    }
}
#endif
Enter fullscreen mode Exit fullscreen mode

Anytime a change happens, the app will now dispatch an event. In the file I'm currently working on, where I want to re-execute the build, I handle the event.

protected override void OnNavigatedTo(NavigatedToEventArgs args)
    {
        base.OnNavigatedTo(args);

        Build();

#if DEBUG
        HotReloadService.UpdateApplicationEvent += ReloadUI;
#endif
    }

    protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
    {
        base.OnNavigatedFrom(args);

#if DEBUG
        HotReloadService.UpdateApplicationEvent -= ReloadUI;
#endif
    }

    private void ReloadUI(Type[] obj)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            Build();
        });
    }
Enter fullscreen mode Exit fullscreen mode

Now give that a try! Make a change to your C# in that page, and hit the Hot Reload fire button (or if you're like me, set hot reload to execute on save). Boom!

XAML Hot Reload must be enabled to leverage this event since it works over the same tooling service. If you disable XAML Hot Reload, then your C# code will reload, but you won't receive the event that you have wired up now to trigger a UI rebuild.

I have found very few scenarios so far when I have to actually stop and restart my debugging session. As you can see in this video, I even add a new class file without stopping.

Top comments (8)

Collapse
 
smartmanapps profile image
SmartmanApps

I finally got this working, only to find it only works with a self-contained app, NOT with libraries. This isn't of any use to me unless it also checks if libraries have been updated (I even tried adding the library's project to the app's solution, so that it was multi-repo, but still didn't work). Not sure if that's something which is on the radar?

Collapse
 
egvijayanand profile image
Vijay Anand E G

This reload service event needs to be hooked-up to the RebuildUI method in the Constructor if the Hot Reload to work in the MainPage of the application as the OnNavigatedTo method won't be called for the MainPage initially.

Collapse
 
davidortinau profile image
David Ortinau

It is called on MainPage if the page is wrapped in a NavigationPage or Shell. I tested that to be sure. You're correct, it will not be called otherwise.

Collapse
 
egvijayanand profile image
Vijay Anand E G • Edited

C# Markup and Hot Reload, this duo makes it easy to develop apps with no XAML knowledge as well.

Is it possible to have a similar experience on VS Code as now debugging a .NET MAUI app works fine with C# and Comet for .NET Mobile extension enabled but no support for Hot Reloading?

Any plans to open this up on VS Code as ASP.NET and Blazor has some support over there too (devblogs.microsoft.com/dotnet/intr...)?

Collapse
 
smartmanapps profile image
SmartmanApps • Edited

Hi David!

I've never got C# hot reload to work, :-( and your comment about XAML hot reload needing to be turned on may be the reason (don't use it so switched it off). So I'm going to check that out first, but if that's not the culprit then I'll give this a go. I'm just wondering about a couple of things though (so that as far as possible I'll only need to set this up once, instead of each time)...

I have my own Blank MAUI app template that I use, as well as a BaseNavigationPage (so that every page has the same look navbar). Either of these would be a good candidate for where to stick it. The niggly doubt I have about this working is the references to the app namespace, in particular the "[assembly: .....". I guess that rules out using the BaseNavigationPage (unless there's a way for it to get that information via the constructor? Actually I have that library in it's own namespace, SmartmanApps.Controls, if that would work? But I'm guessing it needs to be the app's namespace), otherwise I could stick it in the template and manually change the namespace in each new app created from it.

Any tips on the best way to implement this in a write-once way? :-)

thanks,
Donald.

Collapse
 
freever profile image
Adrian Frielinghaus

I was actually starting to switch to XAML for my UIs for one reason and one reason only - hot reload. Now I don't have to, so THANKS!

Collapse
 
vasquezjames profile image
vasquezjames

This hot reload only works with the Android Emulator, for Windows machine don't work, in windows machine need another configuration?

Collapse
 
dukeofqueens profile image
dukeofqueens

Thanks for this. I've been missing hot reload for some time.