Prerequisites

  • Operating system: Windows, macOS or Linux
  • IDE: Works with all IDEs, like Visual Studio, Rider, VSCode, or any other .NET development tool
  • Project type: any project that supports .NET Standard 2.0
  • .NET Core 3.0 or later

Installation

  • Install the LiveSharp NuGet package into the project where you need the hot-reload functionality. Typically, this would be a .NET Standard project for Xamarin.Forms solution or the main assembly for ASP.NET Core.
  • Open the terminal and run dotnet tool install --global livesharp.server

Running LiveSharp

Before starting your application, open the terminal and run command. This should start the LiveSharp Server dotnet tool. After that, you are free to run your application. LiveSharp should automatically connect and you will be able to use hot-reload in its full capacity.

Configuration

By default, every method in your application is available for runtime updates. This might be undesirable in some cases since LiveSharp adds a small overhead to the execution. You can specify what methods to inject by using LiveSharpInject attributes. Open LiveSharp.dashboard.cs that was created in the root folder of your project, and you should see something like this:

using LiveSharp;

[assembly: LiveSharpInject("*")]

namespace LiveSharp 
{
    class LiveSharpDashboard : ILiveSharpDashboard
    {
        public void Configure(ILiveSharpRuntime app) 
        {
            app.Config.SetValue("disableBlazorCSS", "false");
            app.UseDefaultBlazorHandler();
        }
        
        public void Run(ILiveSharpRuntime app)
        {
        }
    } 
}

Notice LiveSharpInject attribute containing * rule. This means that all of the methods in the current project are enabled for LiveSharp. In some cases, you might want to include only certain types. For example, when doing Xamarin Forms UI development, these could be types deriving from ContentPage. You can use different overloads of this attribute to specify the exact types and methods injected by LiveSharp. You can also use multiple attributes to specify multiple rules.

Start rule

LiveSharp needs a starting point to initialize itself. For example, with Xamarin Forms it should be your App type constructor. With Console projects, this should be Program.Main.

Usually, LiveSharp can detect a proper starting point and will inject initialization code automatically, but in case it doesn’t work, you can assign the initialization method manually:

[assembly: LiveSharpStart(typeof(Program), nameof(Program.Main), typeof(string[]))]

Xamarin.Forms and UNO hot-reload

By default LiveSharp expects users to define a Build method for hot-reload to work. If you want to use another method, you can use the following code in your LiveSharp.dashboard.cs

app.Config.SetValue("pageHotReloadMethod", "MyBuildMethod");

Blazor hot-reload

LiveSharp calls StateHasChanged on components that were updated. You don’t need to do anything to make it work.

By default LiveSharp will also hot-reload the CSS files in your project. You can disable this functionality using the following line

app.Config.SetValue("disableBlazorCSS", "true");

Using LiveSharp API to create your custom hot reload

LiveSharp can’t support every .NET project architecture in existence. That’s why LiveSharp gives you the tools to create your own hot-reload that will work for your specific project. Also, you might want to extend the default hot-reload or disable it entirely and just use the code update functionality.

Let’s see how you can create the most basic hot-reload for your app.

// We ignore the methods argument for now
app.OnCodeUpdateReceived(methods =>
{
    var myApp = MyApp.Instance;
    myApp.Dispose();
    myApp.Initialize();
});

This means that for any code update received from LiveSharp Server we will first call Dispose and then Initialize. Of course, this handler assumes that you have a globally accessible instance of your application object and both disposing and initializing methods. That might not be the case in your project.

Let’s fix the problem with the global instance. LiveSharp allows you to intercept method calls from methods that have been injected. We can use that to capture the instance we will further use for hot-reload.

MyApp myApp = null;

app.OnMethodCallIntercepted(typeof(MyApp), (methodIdentifier, instance, args) => {
    // Instance will be `null` for static methods
    if (instance != null) 
        myApp = (MyApp)instance;
});

app.OnCodeUpdateReceived(methods => {
    if (myApp != null) {
        myApp.Dispose();
        myApp.Initialize();
    }
});

Whenever some method from MyApp type gets called, we will now capture the instance. You might want to use a WeakReference for the instance or even create a WeakReference list. By the way, you can also capture the arguments, but more on that later.

LiveSharp also allows you to update resources marked with Content or EmbeddedResource content type. The syntax is similar to what we saw above.

app.OnResourceUpdateReceived((path, content) => {
    if (path == "app.settings") {
        myApp.LoadSettings(content);
    }
});

Creating a custom diagnostic panel

I believe that diagnosing issues in runtime will be a must-have for any future development environment. The way we do it now, using the debugger sometimes feels clunky and unproductive. There needs to be a way to write code that describes exactly what you need to know.

Imagine you have a ASP.NET Core Web API project and you want to log all requests coming to a specific controller. This is how you do it with LiveSharp.

app.OnMethodCallIntercepted(typeof(WeatherForecastController), (identifier, instance, args) =>
{
    var arguments = string.Join(", ", args.Select(a => $"'{a}'"));
    
    app.UpdateDiagnosticPanel("Requests", $"{identifier} called with arguments {arguments}");
});

UpdateDiagnosticPanel will set the provided text to the appropriate panel in the LiveSharp Dashboard. You can create as many panels as you like, all you need is to give them a unique name.

You can also use HTML to create custom diagnostic layout. The following will output the current request with provided headers.

app.OnMethodCallIntercepted(typeof(WeatherForecastController), (identifier, instance, args) =>
{
    var controller = (Controller)instance;
    var sb = new StringBuilder();

    sb.Append($"<strong>{controller.Request.Path}</strong><br/>");
    sb.Append("<table>");

    foreach (var header in controller.Request.Headers)
        sb.Append($"<tr><td>{header.Key}</td><td>{header.Value}</td></tr>");

    sb.Append("</table>");

    app.UpdateDiagnosticPanel("Headers", sb.ToString());
});

Menu