Jul 28 2020

State Management and Error Recovery in Blazor WebAssembly part 3

Category: Blazor | Asp.net coreFrancesco @ 05:35

State Management and Error Recovery in Blazor WebAssembly part 2

State Management and Error Recovery in Blazor WebAssembly part 1

 

In this third an last part we will analyze how to save automatically the application state when the browser or the Blazor application tab are closed by the user.

We will also add the possibility to ask confirmation of exiting the application to the user in case there are still open tasks. Since this can only be done via JavaScript

we are forced to write some script and to connect it to C# code thanks to Blazor-JavaScript Interoperability (see here, and here). As a first step we must prepare to process and deploy JavaScript files.

Processing and deploying JavaScript files

JavaScript files contained in a library must be minimized and merged in a single file that will be deployed as a library internal resource. Minimization and merging of CSS and JavaScript files can be carried out with the help of the “BuildBundlerMinifier” Nuget package. Let install its last stable version in our “StateManager” project. After that let add a “js” folder in the project root, and let add a JavaScript file named “stateManager.js” inside of it. This file must be minimized and placed inside the library project wwwroot folder.

“BuildBundlerMinifier” is informed about which file to process and how to process them through a json configuration file that must be called “bundleconfig.json” and must be placed in the library project root. Let add it and let place the following code in it:

[
    {
        "outputFileName": "wwwroot/stateManager.min.js",
        "inputFiles": [
            "js/stateManager.js"
        ],
        "minify": {
            "enabled": true,
            "renameLocals": true
        },
        "sourceMap": true
    }
]

Each object specifies how to create an output file, in our case "wwwroot/stateManager.min.js”. The input files to merge are listed in the “inputFiles” string array, “minify” specifies the minimization options, and finally “sourceMap” whether to create or not a source-map that maps instructions in the minimized file into their source instructions in the input files, in order to allow an easy debug.

If now we right click on the state project and choose “rebuild”, the library project is rebuilt and our input JavaScript file is minimized and placed in the wwwroot folder. Clearly since our input file is empty also its minimized counterpart will be empty.

Before starting our code let finish our configuration by recalling the minimized version of our JavaScript file in main program wwwroot/index.html file:

<script src="_framework/blazor.webassembly.js"></script>
    //below the newly added JavaScript reference
    <script src="_content/StateManager/stateManager.min.js?v=6"></script>
</body>

In general a JavaScript file placed as a resource in a library can be referenced as “_content/<library name>/<file name>”.

Now we need to design both our JavaScript side and our WebAssembly side code.

We need a JavaScript function that adds both an event handler for the “beforeunload” event and for the “unload” event. We will call this JavaScript function from C# to enable the auto-save and exit confirmation features. Both JavaScript handlers, in turn, will call C# methods that will handle the events exploiting  all information available on the WebAssembly side.

The C# side code

The best place where to add our C# handler is the TaskStateService class. The unload handler must save the state in a default location, and possibly call a custom event. The code below do the job:

public event Func<Task> BeforeUnload;
public string  UnloadKey { get; set; }
[JSInvokable]
public async  Task OnUnload()
{

    if (BeforeUnload != null)
        await BeforeUnload.Invoke();
    if (IsDirty())
        await Save(UnloadKey);
}

In the UnloadKey the user can place the name of the localStorage where to store the application state, BeforeUnload is an event where the developer can place some custom unload code, and finally the OnUnload method is the handler that will be called by JavaScript. The “[JSInvokable]” attribute makes it callable from JavaScript.

OnUnload invokes custom events, if any, and then, if the application state is not empty, saves it to local storage.

The beforeunload handler must return a not null, and not empty string if the user must be prompted for confirmation of leaving the application. In old browsers the string returned is used as prompt, but modern browser, instead, use a standard message:

public string UnloadPrompt { get; set; }
[JSInvokable]
public  string OnBeforeUnload()
{
    if (IsDirty()) return UnloadPrompt;
    else return string.Empty;
}

Both the above methods must be called by JavaScript handlers installed with “window.addEventListener”, as we will see in the next section.

The JavaScript side code

The JavaScript code consists in a “stateManager.AddUnloadListeners” function that receives as input a .Net reference to a TaskStateService instance and installs the JavaScript handlers that call the TaskStateService methods we defined in the previous section on this instance:

(function () {
    window["stateManager"] = {
        "AddUnloadListeners": function (netObject) {
        try {
            window.addEventListener("beforeunload",
                function (event) {
                    var res = null
                    try {
                        res = netObject.invokeMethod('OnBeforeUnload');
                    }
                    catch (ex) {
                        console.error(ex);
                    }
                    if (res) {
                        event.preventDefault();
                        event.returnValue = res;
                    }
                    else delete event["returnValue"];
                });
            window.addEventListener("unload",
                function () {
                    try {
                        netObject.invokeMethod('OnUnload');
                    }
                    catch (ex) {
                        console.error(ex);
                    }

                });
        } catch (e) {
            console.error(e);
        }
    }
    }
})();

The code above is quite standard, the only peculiar snippets are the ones used to call the two .Net instance methods:

res = netObject.invokeMethod('OnBeforeUnload');

 

netObject.invokeMethod('OnUnload');

 

Done! We need just to define a .Net function that invokes “stateManager.AddUnloadListeners”. We can define it as an extension method in the “Extensions/StateHandling.cs” file:

public static async Task<IServiceProvider>
    EnableUnloadEvents(this IServiceProvider services)
{
    var state = services.GetRequiredService<TasksStateService>();
    IJSRuntime jSRuntime = services.GetRequiredService<IJSRuntime>();
    await jSRuntime.InvokeVoidAsync(
            "stateManager.AddUnloadListeners", state.JsTeference);
    return services;
}

We are now ready to test our library.

Putting everything together

As a first step we must call the EnableUnloadEvents from the program.cs file of the main application:

 

await built.Services.EnableUnloadEvents();
await built.RunAsync();

 

Now in the same file we need to update our InitializeState method, so that it set also the UnloadKey and the prompt for asking the user if he would like

to quit the application or not :

private  async static Task InitializeState(IServiceProvider services,
    string errorStateKey,
    string exitConfirm)
{
    var state=services.GetRequiredService<TasksStateService>();
    state.ErrorKey = errorStateKey;
    state.UnloadKey = errorStateKey;
    state.UnloadPrompt = exitConfirm;
    if (await state.Load(errorStateKey))
    {
        await state.Delete(errorStateKey);
    }

}

 

After this change the main becomes:

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("app");
            
    builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    builder.Services.AddStateManagemenet();
    var built = builder.Build();
    await InitializeState(built.Services,
        "stateSaved", "There are unsaved changes. Quit anyway?");
    await built.Services.EnableUnloadEvents();
    await built.RunAsync();
}

Now just start the application, increment the counter, and then close the browser. You will be prompted for confirmation, confirm. The reopen the browser, you will see the same count you left before!

It works!

 

The updated code is available on Github here.

 

Important: Some mobile browsers for smart phones are bugged and do not trigger the unload event when a browser tab is closed but only when you move to a different site. Therefore, when you target these devices you must provide also manual save.

 

Here, you can see a complex application that shows what you can do with the state management technique described. The demo is based on the Blazor Controls Toolkit, which uses basically the same code described in these posts for its management. The trick for achieving similar results  is a careful design of the component state during each component design.

 

That’s all for now! The next post will be about a new interesting subject.

Francesco

Tags: ,

Jul 3 2020

Blazor Controls Toolkit is out!

 Tree

Start with all comforts, save 70% of your development time: a few declarations are enough to generate the markup you need  and  the whole server interaction code: forget about input fields, the right input filed is generated automatically based on the property type and ViewModel attributes! Changes tracking, to compute changes for the server, query tools to query local and remote data (also server side query tools to power-up your API controllers are included) , advanced components that include grids, detail lists and trees. Basically all components can be Drag and Drop enabled, you don’t need to handle the Drag and Drop process, because you drag automatically the data that are bound to your markup!

Don’t worry about how to prevent the application you send to the client browser from being copied by hackers: you may rely on a double protection:

  1. Your code is protected by the best protector available on the market: Reactor, that is bundled with Blazor Controls Toolkit.
  2. Once Blazor Controls Toolkit is included as a dependency the whole application works properly only if it is validated by your server side Asp.net Core application with a complex cryptographic challenge. As a consequence your application is enabled to run properly only when it is connected with your web server!

Below more details about Blazor Controls Toolkit features, but you can try also all of them live:

  • The equivalent of all Bootstrap JavaScript components.
  • A complete set of input controls that includes all Html5 inputs or their fallback when they are not supported by user browser, dropdowns and autocompletes.
  • Validation attributes , and other metadata attributes that enable the developer to define how to render and edit data in a purely declarative way! You just put a generic edit or display component in your markup and the Blazor Controls Toolkit generate automatically for you the markup that implements the behavior declared with your model attributes: space to reserve for each field, the right Html5 input and / or auto-complete to use, and so on.
  • Default validation messages, and default button text/labels are already localized in various languages, and the develpèer can customize all of them. Moreover, each customer can require the localization kit to insert his language in the Blazor Controls Toolkit, in case it is not supported, yet. This demo application shows all localized default messages, and labels. However, with the only exception of this introduction page all other application-specific contents are not localized.
  • Changes tracking, tracking of invalid objects, and model transactions. All handled with uniform interfaces throghout all components. They prevent futher processing on invalid objects, enable the user to revert changes, and computes all changes to send to the server (additions, deletes and modifications).
  • Utility components, like modals, Tabs, pagers, and navigation tools
  • All components and html fragments can be drag-drop enabled, and bound to models. As a result of the drop operation list elements or grid rows are moved to different positions or modify the model bound to the drop target.
  • Behaviors that enrich existing Html element and other components with further features like collapsing, fading, input focus, visibility spy, anchor target spy.
  • Complex customizable components like DataGrids,TreeViews, Detail views, and Detail lists. Each component has its default row and column template that the developer can replace with custom ones.
  • Query tools for filtering, sorting, and aggregating data.These tools include components that enable the user to select the desired operations, attributes to declare how to build each filter view, and OData tools to translate the desired operations in OData format, to retrieve data from OData enabled servers, and to OData-enable your Asp.net controllers.
  • All components are accessible, and can be operated easily with just the keyboard. After each operation, the focus is moved where more appropriate for continuing keyboard processing. If possible accessibility is enforced also on developer provided custom templates

Tags: , ,

Jun 24 2020

State Management and Error Recovery in Blazor WebAssembly part 2

State Management and Error Recovery in Blazor WebAssembly part 1

 

In the first post of this series we discussed the various options available to represent state information in Blazor WebAssembly. Our conclusion was that the best option is to represent state information in data structures held in Blazor Dependency Injection engine, and to store them on disk (IndexedDb, localStorage) only when the user leaves the application or when the application is about to crash.

We gave also a way to catch unhandled exceptions in order to save the application state before the application crashes. However, are we sure there are no other events that might cause the in-memory state be lost? Of course there are! User might inadvertently navigate to an external page, or he/she might close the application browser page / tab. Luckily, in all these circumstances we can exploit the onbeforeunload and onunload events. More specifically, in case there are uncompleted tasks and the user is about to leave the application we can use the onbeforeunload event to ask confirmation, and in case the user decides to proceed we can use the onunload event to save the state of all uncompleted tasks.

So now we have a complete plan! We need to decide just how to represent all state tasks and how to save them on disk.

 

Representing Tasks State

We have seen that it is convenient to enclose each Task state in a containe classr, so each processor can easily detect when a task is being processed just by looking if the container is empty or not. In case a container is empty a new task can be created from scratch and put in the container so that also other pages can find it.

Let modify the code of the first post to add a TasksStateService class. As a first step let  create a State folder in the StateManager library project. Then let move the StateContainer.cs file that is in the client project to this new folder. After that, let modify the file namespace to “StateManager” to yield:

namespace StateManager
{
    public abstract class StateContainer
    {
        public abstract object RoughState {get;}
    }
    public class StateContainer<T> : StateContainer
    {
        public T State { get; set; }
        public override object RoughState => State;
    }
}

We can add also a method that verify if a task is executing:

namespace StateManager
{
    public abstract class StateContainer
    {
        public abstract object RoughState {get;}
        public abstract bool IsRunning {get;}
    }
    public class StateContainer<T> : StateContainer
    {
        public T State { get; set; }
        public override bool IsRunning { get {
                return State != null && !State.Equals(default); } }
        public override object RoughState => State;
    }
}

We can index all task States with a dictionary enclosed in the  TasksStateService class. Let add this class to the same state folder and let replace the scaffolded code with the following code:

namespace StateManager
{
    public class TasksStateService
    {
        private Dictionary<string, StateContainer>
            OverallState
            = new Dictionary<string, StateContainer>();
        public bool IsRunning(string x)
        {
            return OverallState.TryGetValue(x, out var state)
                && state.IsRunning;
        }
        public bool IsDirty()
        {
            return OverallState.Values
                .Any(x => x.IsRunning);
        }
    }
}

The IsRunning method returns whether the Task indexed by string x was started and not yet completed, while IsDirty returns whether there is or not at least

a task that is running and needs to be saved.

Now we can add also a method to get an existing state or create it if it doesn’t exist:

public T Get<T>(string x, bool createNew)
{
    if (OverallState.TryGetValue(x, out var state))
    {
        if (state.IsRunning) return (T)state.RoughState;
        else if (createNew)
        {
            var res = Activator.CreateInstance<T>();
            ((StateContainer<T>)state).State = res;
            return res;
        }
        else return default;
    }
    else if (createNew)
    {
        var container = new StateContainer<T>
        {
            State = Activator.CreateInstance<T>()
        };
        OverallState[x] = container;
        return container.State;
    }
    else return default;
}

Finally, we need a method that finishes a task, thus resetting its container:

public void Finish<T>(string x)
{
    if (OverallState.TryGetValue(x, out var state))
        ((StateContainer<T>)state).State = default;
}

With all this in place we can start using our TasksStateService, it is enough to add it to the Dependency Injection of the application project.

For this purpose it is enough to add it to the AddStateManagemenet extension method in the “Extensions/StateHandling.cs” file of the StateManager

library project:

public static IServiceCollection AddStateManagemenet(
    this IServiceCollection services)
{
    services.AddSingleton<IErrorHandler, DefaultErrorHandler>();
    services.AddLogging(builder => builder.CustomLogger());
    //add the line below
    services.AddSingleton<TasksStateService>();
    return services;
}

 

At this point the only missing piece is a state saving/reloading facility.

Saving and reloading the application state

Now we have to decide where and how to save the application state: localStorage can only store strings, while IndexedDb can store also object graphs that satisfy certain constraint.Unluckily, we can’t take advantage of the better opportunity offered by IndexedDb since we can’t move object graphs from WebAssembly to JavaScript, since Blazor interop features just cope with JSON-serializable graphs, that are essentially object trees.

Unluckily, in the general case the complete state of a .Net Core Task is not a tree but a more complex graph. Therefore, we are left with the only option sketched below:

  1. Serialize state information with the powerful .Net Core binary serializer.
  2. Transform bytes returned by the binary serializer into a BASE64 string that can be moved to JavaScript as a string.
  3. Once we have state information represented as a string the simplest and more efficient storage option is localStorage.

We can proceed as follows:

  1. When application start if there is a saved state we load it. We can also improve this step by asking the user if he would like to restore a previously saved  state.
  2. Once the user decides to load or discard the previous state we delete the stored state to prevent the application to load it again at next application start, in case no new state is saved.

Of course our library project will provide just the save, load, and delete methods that the developer can use as he/she prefer to build a custom state management strategy.

Serialization and Deserialization

As a first step let write two protected methods that perform serialization and deserialization of the application state. It is not convenient to serialize the whole dictionary containing state information, it is better to filter just KeyValue pairs containing uncompleted tasks. The serialize method code is straightforward:

protected string Serialize()
{
    var toSerialize=OverallState
        .Where(m => m.Value.IsRunning)
        .ToList();
    string result=null;
    using (var stream = new MemoryStream())
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(stream, toSerialize);
        stream.Flush();
        result=Convert.ToBase64String(stream.ToArray());
    }
    return result;
}

The deserialize method is the exact converse:

 

protected void Deserialize(string s)
{
    byte[] binary = Convert.FromBase64String(s);
    using (var stream = new MemoryStream(binary))
    {
        var formatter = new BinaryFormatter();
        var res = formatter.Deserialize(stream)
            as List<KeyValuePair<string, StateContainer>>;
        OverallState = res.ToDictionary(m => m.Key, m => m.Value);
    }
}

BinaryFormatter requires that all classes to be serialized/de-serialized  be marked with the [Serializable] attribute. Therefore we must add it to both StateContainer, and StateContainer<T>:

    [Serializable]
    public abstract class StateContainer

 

    [Serializable]
    public class StateContainer<T> : StateContainer

 

Now we miss just the routines to store retrieve, and delete serialized strings from localStorage.

Interacting with the localStorage

We need to call methods of window.localStorage from TasksStateService C# code, so we must inject the IJSRuntime interface in its constuctor (see here for more information about the usage of the IJSRuntime interface). Moreover, TasksStateService needs also an instance of the IErrorHandler interface to add an event handler to its OnException event that saves the state in case of errors.

Therefore, let add both interfaces to the TasksStateService constructor, and let save both interfaces in private variables:

IJSRuntime JSRuntime;
IErrorHandler ErrorHandler;
public TasksStateService(IJSRuntime jSRuntime,
    IErrorHandler errorHandler)
{
    JSRuntime = jSRuntime;
    ErrorHandler = errorHandler;
    ErrorHandler.OnException += SaveError;
}

SaveError is the error handler that saves the state in case of exceptions. Let write just its skeleton, we will complete it in a short time:

public async Task SaveError(Exception ex)
{
            
}

The SaveError handler must be detached when TasksStateService is destroyed, so TasksStateService must implement IDisposable:

public class TasksStateService: IDisposable

 

public void Dispose()
{
    ErrorHandler.OnException -= SaveError; ;
}

 

Now we have everything in place to write our Save method:

public async Task Save(string key)
{
            
    try
    {
        var s = Serialize();
        await JSRuntime
            .InvokeVoidAsync("window.localStorage.setItem", key, s);
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

In case of errors we just write the error message in the browser console.

Load is completely analogous:

public async Task<bool> Load(string key)
{
    try
    {
        var s = await JSRuntime
                .InvokeAsync<string>("window.localStorage.getItem", key);
        if (s != null)
        {
            Deserialize(s);
            return true;
        }
        else return false;
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.Message);
        return false;
    }
                
}

In case no state is found in local storage, Load returns false.

We need also a Delete method to delete a previously recorder state after its recovery:

public async Task Delete(string key)
{
    await JSRuntime
        .InvokeVoidAsync("window.localStorage.removeItem", key);
}

Finally, we need a default name for the location where to store the saved state in case of unhandled exceptions, so we can fill also the SaveError method skeleton:

public string ErrorKey { get; set; }
public async Task SaveError(Exception ex)
{
    await Save(ErrorKey);
}

Our Save/Load state Ready! Now, we need just to modify our test application so we can test our TasksStateService class.

Testing the overall state management / error recovery logic

As a firs step let add the initialization logic of our state management/recovery to the program.cs file of our Blazor client application. For this purpose let add the following method to the Program class:

private  async static Task InitializeState(IServiceProvider services,
    string errorStateKey)
{
    var state=services.GetRequiredService<TasksStateService>();
    state.ErrorKey = errorStateKey;
    if (await state.Load(errorStateKey))
    {
        await state.Delete(errorStateKey);
    }

}

It gets the TasksStateService singleton, sets the name chosen for saving the state in case of crashes, and then tries to load a previously saved state. If it succeeds the Load method returns true, and the saved state is removed from localStorage.

This method must be called, at application start as soon as the application host has been built. Therefore, we need to replace the default code scaffolded by Visual Studio below:

await builder.Build().RunAsync()

With:

var built = builder.Build();
await InitializeState(built.Services,
    "stateSavedBeforeError");
await built.RunAsync();

We can test everything in the Counter page. As a first Step we must create a state class for the state of this page:

namespace StateManagement.Client
{
    [Serializable]
    public class CounterStatus
    {
        public int Counter { get; set; }
    }
}

State types MUST be reference types so that when the a component modifies a copy it gets from TasksStateService, the copy held in the TasksStateService is automatically updated.

Moreover, each state object must have a default constructor, so it can be created, when needed, by the TasksStateService with the Activator class. Finally, it must be marked with the [Serializable] attribute for our serialization methods to work properly. Optionally, we may replace the [Serializable] attribute with the implementation of the ISerializable interface in order to provide a custom serialization logic.

When the page starts it must require a state instance to the TasksStateService singleton that must be injected in the page with something like:

private CounterStatus currentCount;
protected override void OnInitialized()
{
    currentCount = tasksStateService
        .Get<CounterStatus>("counter", true);
}

If a state object has been already created, that one is returned, otherwise a fresh one is created because we set the createNew parameter to true. The page will have a button to increment the counter and a button that throws an exception, so we can verify the state recovery capabilities of the application. The full code of the page is shown below:

@page "/counter"
@inherits ComponentBase
@inject StateManager.TasksStateService tasksStateService
<h1>Counter</h1>

<p>Current count: @currentCount.Counter</p>

<button class="btn btn-primary"
        @onclick="IncrementCount">
    Click me
</button>
<button class="btn btn-primary"
        @onclick="TryException">
    TryException
</button>

@code {
    private CounterStatus currentCount;
    protected override void OnInitialized()
    {
        currentCount = tasksStateService
            .Get<CounterStatus>("counter", true);
    }
    private void IncrementCount()
    {
        currentCount.Counter++;
    }
    private void TryException()
    {
        throw new Exception();
    }
}

Now let start the application, go to the counter page, increment a few times the counter, and then let click the “TryException” button. The application crashes and we are prompted to reload the browser page. When we reload the page the value of the counter is retained! We were able to recovery the application state.

If you don’t want to mark all state objects with a [Serializable] attribute, you can mark the Serialize and Deserialize methods as virtual, and override them with third parties serializer. Among them a good one is SharpSerializer. It overcomes the needs for the [Serializable] attribute but has other limitations.

 

Thats all for now! The code respository of the first post has been updated with the new code.

In the next post of this series we will add support for the onbeforeunload and onunload events.

Francesco

Tags: ,