Apr 9 2024

Software Architecture with C# 12 and .NET 8 is out!

The fourth edition of my book is out! You can buy it on Amazon

fourthedition

If you are a an aspiring .NET software architect, or a C# developer wishing to jump into the world of enterprice applications and cloud, this is the right book for you!

Software Architecture with C# 12 and .NET 8 puts high-level design theory to work in a .NET context, teaching you the key skills, technologies, and best practices required to become an effective .NET software architect.

This fourth edition puts emphasis on a case study that will bring your skills to life. You'll learn how to choose between different architectures and technologies at each level of the stack. You'll take an even closer look at Blazor and explore OpenTelemetry for observability, as well as a more practical dive into preparing .NET microservices for Kubernetes integration.

Divided into three parts, this book starts with the fundamentals of software architecture, covering C# best practices, software domains, design patterns, DevOps principles for CI/CD, and more. The second part focuses on the technologies, from choosing data storage in the cloud to implementing frontend microservices and working with Serverless. You'll learn about the main communication technologies used in microservices, such as REST API, gRPC, Azure Service Bus, and RabbitMQ. The final part takes you through a real-world case study where you'll create software architecture for a travel agency.

What’s new in this edition?

Topics are analyzed in greater detail and updated for .NET 8 and the latest Azure components. We have also added a new practical chapter on developing .NET applications for Kubernetes.

Finally, the book has been organized into three parts, creating a flow that will guide you in your journey to becoming a software architect: architectural fundamentals, .NET technologies, and practical coding with a great case study.

Highlights from the World Wide Travel Club case study:

  • Code examples in past editions restructured and re-organized into a case study
  • Examining user needs and managing requirements with Azure DevOps
  • Understanding the application domains and choosing cloud data storage
  • Implementing worker microservices with gRPC and RabbitMQ

How does this book differ from other books on C# 12 and .NET 8?

Although we are using .NET 8, the current Long Term Support version of .NET, and the book’s programming language is C# 12, we don’t only talk about technology. We connect different modern topics needed to design an enterprise application, and we enable you to understand how these techniques work together. This means the book focuses more on architectures, patterns, and design techniques, than on the syntax of the language and its features.

In a few words, the book assumes you already have basic knowledge of .NET and C#, driving you toward their usage for implementing cutting-edge applications based on microservices and modern architectures and design techniques.

Table of Contents

  1. Understanding the Importance of Software Architecture
  2. Non-Functional Requirements
  3. Managing Requirements
  4. Best Practices in Coding C# 12
  5. Implementing Code Reusability in C# 12
  6. Design Patterns and .NET 8 Implementation
  7. Understanding the Different Domains in Software Solutions
  8. Understanding DevOps Principles and CI/CD
  9. Testing Your Enterprise Application
  10. Deciding on the Best Cloud-Based Solution
  11. Applying a Microservice Architecture to Your Enterprise Application
  12. Choosing Your Data Storage in the cloud
  13. Interacting with Data in C# - Entity Framework Core
  14. Implementing Microservices with .NET
  15. Applying Service-Oriented Architectures with .NET
  16. Working with Serverless - Azure Functions
  17. Presenting ASP.NET Core
  18. Implementing Frontend Microservices with ASP.NET Core
  19. Client Frameworks: Blazor
  20. Kubernetes
  21. Case Study
  22. Case Study Extension: Developing .NET Microservices for Kubernetes

DO NOT MISS IT!

Francesco

    Tags: , , , , , ,

    Apr 1 2022

    Software Architecture with C# 10 and .NET 6 is out!

    The third edition of my book is out! You can buy it on Amazon

    imagebook3_thumb[1]

    If you are a C# developer wishing to jump into the world of enterprice applications and cloud, this is the right book for you!

    From how to collect requirements, and how to master DevOps, selecting the right cloud resources, Web API, front-end frameworks (ASP.Net MVC and Blazor) to microservices design principles and practice,. This new edition updates all subjects to the last cloud and .Net features and adds new chapters:

    • A detailed description of gRPC and of how to use it from .NET
    • A new chapter that explains in detail how to implement a worker microservice with  ASP.NET + gRPC, and .NET hosted services + RabbitMQ
    • An introduction to Artificial Intelligence and Machine Learning
    • An introduction to native clients (including a short review of .NET MAUI)

    Most of chapters give enough details to cover 90% of all practical applications and give all links and pointers to get more details.The only exceptions are the chapters about artificial intelligence and native clients that are just introductions to big subjects. However,  also there you will find complete learning paths to follow to become an expert.

    The first three chapters describe modern development processes, and how to collect and document functional and not-functional requirements. Example of requirement collection and management with Azure DevOps are given.

    Then the books moves to the basic cloud concepts and describes how to select the right cloud resources for each application.

    Chapter 5 explains the whole therory behind Microservices design, and lists .NET resources that plays a foundamental role in the .NET implementation of Microservices. A self-contained description of Docker, is given, too.

    Chapter 6 is dedicated to Kubernetes. There you will find all basic concepts and enough details to cover 90% of all practial applications.

    Chapter 7 and 8 are dedicated to data storage and how to interact with them with Entity Framework Core and other clients. There, you will find .the whole theory behind distributed databases, how to maximize read and write parallelism and how to choose between SQL and Not-SQL databases.

    Chapter 9 is about serverless and Azure functions. There, you  will find enough details to cover simple-middle complexity functions, and pointers on how to implement more complex functions.

    Chapter 10 is dedicated to  the concept of pattern and describes various patterns used throghout the book.

    Chapter 11 describes Domain Driven Design, that is the most used design methodology for microservices. Related patterns and their practical usage in .NET layered applications are given, too.

    Chapter 12 describes typical patterns of code reusability used in .NET applications.

    Chapter 14 gives a detailed description of gRPC and of its usage in .NET applications. Then, a complete implementation of a worker microservice with gRPC and ASP.NET CORE is given. Finally the same example is implemented with a .NET worker service and RabbitMQ.

    Further chapters describe SOA architectures  and their implementation with ASP-NET Core (13), ASP.NET Core and ASP.NET Core MVC(15) and Blazor (17).

    Chapter 16 puts in practice all concepts learned with ASP.NET Core MVC and Domain Driven Design with the implementation of a front-end microservice.

    Chapter 18 is a introduction to native .NET clients that includes also a first look to .NET MAUI. The description is not detailed since a detailed description would require another complete book, but native clients are compared with browser-based clients and frameworks (like Blazor) and complete learning paths are given.

    Chapter 19 is an introduction to artificial intelligence and machine learning. The basic principles of the main AI techniques are described, without going deep into the implementation details.Also. the basic theory about machine learning is given. Here the focus is on understanding which problems can be solved with machine learning and how many examples they require. A practical example of supervised learning is given.

    Chapter 20 is dedicated to best practices and code metrics.

    Chapter 21 and 22 are dedicated to DevOps and to the usage of Azure DevOps and GitHub actions.

    Finally chapter 23 is dedicated to testing, test-driven design, unit-tests and functional/acceptance tests.The chapter gives the complete theory and describes in detail xUnit, and Moq.Practical examples of functional tests based on AngleSharp and Selenium are given, too.

    DO NOT MISS IT!

    Francesco

    Tags:

    Dec 15 2021

    Move quickly your team to Asp.NET Core 6!

    Asp.NET Core online interactive course Start quickly with Asp.NET Core 6, with an interactive online course on the Educative platform

    Takeaway Skills

    ASP.NET Core
    MVC Pattern
    Razor and Razor tag-helpers
    Localization and globalization
    Option framework
    Dependency injection and hosted services
    Authentication and Authorization

    Start Now!

    Course Contents

    1. Razor Template Language

    2. Tag Helpers

    3. Controllers and ViewModels

    4. Validation and Metadata

    5. Dependency Injection

    6. Configuration and Options Framework

    7. Localization and Globalization

    8. Factoring out UI Logic

    9. Authentication and Authorization

    10. Publishing Your Web Application

    Try it|

    Tags: , , ,

    Nov 2 2021

    My ASP.NET Core Course is Out!

    Category: Francesco @ 04:29

    aspnetcorecourse

    Learn ASP:NET Core in one week! My ASP:NET Core course on educative is out!

    It includes:

    • Razor

    • ASP.NET Core MVC

    • ASP:NET Core REST API

    • ASP.NET Core pipeline

    • Authorization and Authentication with cookies and Bearer token.

    • Globalization

    • Configuration and options

    • Model binding and formatters

    • Validation and custom validation attributes

    • MetaData usage and controller filters

    Tags:

    Aug 27 2021

    What’s new in Blazor WebAssembly 6

    Category: Blazor | Blazor Controls ToolkitFrancesco @ 11:30

    With the new .Net 6.0, tat is still in preview,  Blazor WebAssembly gained new interesting features, such as error boundaries, AOT compilation, query-string handling, and the dynamic component. Each of the upcoming new feature will be described in a separate section.

    Error boundaries

    As a default when an error in a component occurs, the exception is intercepted by the .Net runtime that automatically makes visible the error code contained in index.html:

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">??</a>
    </div>

    In the new version component exceptions can be intercepted by the new <ErrorBoundary> component, as shown in the example below:

    @foreach (var forecast in forecasts)
    {
        <ErrorBoundary>
            <ChildContent>
            <tr>
                <td>@forecast.Date.ToShortDateString()</td>
                <td>@forecast.TemperatureC</td>
                <td>@forecast.TemperatureF</td>
                <td>@forecast.Summary</td>
            </tr>
            </ChildContent>
            <ErrorContent>
            <tr>
                <td colspan="4" class="my-error">Nothing to see here. Sorry!</td>
                        
            </tr>
            </ErrorContent>

        </ErrorBoundary>
    }

    The <ChildContent> template contains the markup to check. Whenever, an exception is thrown by a component inside <ChildContent> the markup inside <ErrorContent> is shown and the exception is caught.

    AOT Compilation

    Once uploaded in the Browser, .Net assemblies are not compiled Just-In-Time (JIT) at their first execution as it is the case for other platforms. Instead, they are interpreted by a very fast interpreter. Just the .Net runtime is pre-compiled and uploaded in the Browser directly in WebAssembly.

    JIT compilation is avoided since it would considerably increase the application start time, that already is quite high because of the high application download size (about 10Mb). In turn, the download size is high due to the .Net libraries that any Blazor application needs to work properly.

    In order to reduce download size, during the compilation in release mode, Blazor .Net assemblies are tree-shaken to remove all unused types and methods. However, notwithstanding this tree-shaking, the typical download size remains quite high. A good download improvement is achieved with the default caching of the .Net runtime which reduces the download size to 2-4 Mb. However, the download size still remains high the first time a Blazor application is visited.

    Staring from .Net 6, Blazor offers an alternative to JIT compilation: Ahead-Of-Time (AOT) compilation. With AOT all application assemblies are compiled into a unique WebAssembly file during the application publication. On the average AOT compilation makes the code 4 times faster (from 2 times to 10 times faster). Unluckily, AOT compilation increases more than twice the download size, since the compiled code is more verbose than the source .Net code. Therefore, AOT should be adopted only in performance-critique application that can trade a higher start time for a better performance.

    AOT compilation is very slow and may last something like 10 minutes also in the case of small applications. On the other side, it must be executed only once when the application is published, so the compilation time doesn’t impact on the application start time.

    .NET WebAssembly AOT compilation requires an additional build tool that must be installed as an optional .NET SDK workload in order to use. The first time you can install it with the shell command below:

    dotnet workload install microsoft-net-sdk-blazorwebassembly-aot

    Instead, when a new .Net version is installed, it is enough to launch the following command to update all previously installed workloads:

    dotnet workload update

    Once the AOT workload has been installed AOT compilation can be enabled on per-project basis by adding the <RunAOTCompilation>true</RunAOTCompilation> declaration to the Blazor project file, as shown below:

    <PropertyGroup>
      <TargetFramework>net6.0</TargetFramework>
      <RunAOTCompilation>true</RunAOTCompilation>
    </PropertyGroup>

    Page URLs Support Query Strings

    In the new .Net 6 version pages can extracts parameters also from the URL query string. Thus, for instance, if a page with URL @page “OrderItem/{id}” is invoked with the OrderItem/3?quantity = 123 URL, then it can capture the quantity parameter. This parameter can be captured by a page property math matches “quantity” in a case-insensitive comparison, and that is decorated with the [SupplyParameterFromQuery] attribute, as shown below:

    @code {
        [Parameter]
        [SupplyParameterFromQuery]
        public int Quantity { get; set; } = 0;

    Dynamic Component

    The new <DynamicComponent> makes easy to a variable content to a page. More specifically, <DynamicComponent> is passed the type and the parameters of a component and invokes it as shown below:

    <DynamicComponent Type="@componentType" Parameters="@parameters" />

    @code {
        private Type componentType = ...;
        private IDictionary<string, object> parameters = ...;
    }

    The component actually rendered by the dynamic component can be accessed trough its Instance property, and, when needed, refreshed as shown in the example below:

    <DynamicComponent Type="@componentType" Parameters="@parameters" @ref="dc" />

    @code {
        private DynamicComponent dc;
        

        private Type componentType = ...;
        private IDictionary<string, object> parameters = ...;
        ...
        private Task Refresh()
        {
            return (dc.Instance as IRefreshable)?.Refresh();
        }
        
    }

     

    Multi-Select Support

    In version .Net 6 when a <InputSelect> component is bound to an IEnumerable<T> it is rendered as a multi-select, thus enabling the user to select multiple values.

     

    That's all

     

    Francesco Abbruzzese

    Tags: ,

    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: ,

    May 25 2020

    State Management and Error Recovery in Blazor WebAssembly part 1

    Category: BlazorFrancesco @ 04:47

    Last week, Microsoft released the first production-ready version of Blazor WebAssembly, version 3.2. As typical the release was announce during Microsoft Build. This is the first of a series of posts about Blazor WebAssembly. These posts will not be tutorials, since Internet is already full of Blazor tutorials, so there is no need to add further ones. Instead these posts will offer original solutions to common and important problems of Blazor programming. Readers interested in quick starts and tutorials can find them in Blazor official documentation since it is totally organized in a tutorial-like fashion. 

    These first post is about state management and error recovery. You will learn how to let several Blazor pages cooperate during an overall task execution, and how to avoid the user looses his previous work when an irreversible error occurs before data are sent to the server. As discussed below the solution I propose is quite different form the ones proposed by others, and has the advantage of requiring just a minimum code overhead. So you can adopt it with a minimal coding effort.

     

    Where to store your state

    Blazor is all about components, that are continuously and automatically created, updated and destroyed while the user interacts with the application. These means that you can use them just for displaying and processing data, but not for storing state information, since that information might be lost if the component is destroyed an re-created during a rendering operation.

    Interfering with the automatic Blazor rendering to prevent its destruction, is possible, but in general it is not advised as a general solution to the problem of maintaining a reliable state. In fact, disabling component update/destruction is possible only if we are sure that the HTML handled by the component will not change during some processing. That is, it is a means to improve component performance, and not a way to freeze information contained in the component.

    However, there are some constraints on when and how a component might be destroyed, more specifically components may be destroyed only if there are big changes in their ancestor components, that is in the hierarchy of components containing them. The main consequence of this, is that Blazor pages can be destroyed only when the user changes page because the user clicks a link, or activates in some other way Blazor native navigation logic.

    Accordingly, if a whole task is confined within a single page all state information of that task con be stored in the page. The overall page state can be made available to all components in the page, and to their sub-components by cascading it with a “CascadingValue” tag that encloses all page components:

    <CascadingValue IsFixed="true" Value="myState">
       @*
            all page components here     
       *@
    </CascadingValue>

    The “IsFixed“ parameter set to true prevent changes propagation in case the instance contained in myState property changes, but improves a lot performance.We can avoid to change the value of “myState” if we define a container class that remains tied to the page for all its lifetime, and reset/start a new state instance by changing just the content enclosed within this container:

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

    It is a best practice to define a non-generic super-class when defining similar generic utility classes.

    Once state has been put in place this way, page components may interact with “myState” either by having its properties/sub-properties as parameters, or by defining a “CascadingParameter”:

    @code{
        ....

        [CascadingParameter]
        StateContainer<T> pageStatus { get; set; }
    }

     

    So, in case a whole task remains confined within a single page the above setup solves the state management problem…..but, …but:

    1. What if a task spans more pages?
    2. What if the user want to move to another page, for some reason, and then he want to resume its previous task?
    3. What if an irreversible error suddenly crashes the application?

    In all cases above the solution based on  a page-maintained state fails! Once left the page the state is lost! Leaving the page and returning back to the same page clears the page state!

    Problems 1 and 2 can be easily solved by defining the state shared between one or more pages as a singleton in the Dependency Injection engine as shown below:

    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("app");
        builder.Services.AddSingleton <StateContainer<MyStateClass>>();

     

    After that, each page that operate on that state needs just to inject this singleton with the simple declaration below:

    @inject StateContainer<MyStateClass> myState

     

    After that “myState” can be used to pass parameters to all components in the page. As with “CascadingValue”, some components can also inject themselves the state without being passed parameters by the Blazor page.

    Having encapsulated the actual state inside the “StateContainer<T>” container we can remove it when a task is completed and replace it with a new fresh copy, so the user can start a new task.

    However, also sharing all state objects as singletons defined in dependency Injection engine doesn’t solve the problem of loosing all information in case the Blazor application might crash, since state information are held in volatile memory. We analyze this problem in the next section.

    Error Handling and State Recovery

    Since unlikely Asp.net Core, Blazor WebAssembly has no centralized handling of exceptions any exception thrown during a component rendering  can crash the application, causing the whole application state be completely lost.

    In order to overcome this problem many authors propose to take advantage of any opportunity (form submit, field successful validation, etc,) to save the application state in some permanent memory, either locally or on the server.

    However, by performing continuous communications with the server we would renounce to most of the advantages of Single Page Applications, such as fast response to all user actions, less load on the server, and a more simplified and modular server application layer.

    In general, interleaving business logic with continuous saving operations would undermine code modularity, and the single responsibility principle, producing difficult to maintain “spaghetti” code.

    Modularity, could be recovered by encapsulating the whole save logic in all form submit operations or in the getters and setters of state objects properties. 

    The first option is easily achieved by implementing a form component that automatically save on disk state information after each successful submit. Modern browsers offer several options for saving information to disk with no need to send them to the server, such as IndexedDb, and LocalStorage, but notwithstanding this, this approach appears not very appealing, since it is not clear when information saved on disk can be deleted and which rules to use for deciding when to recover memory state from the disk. In a few words, we have two versions of each state, but we have no well defined rules on how to synchronize them. In fact, it is not clear when a task is finished and all associated disk information can be deleted to avoid they are used also in the next task.

    The problem with the above approach is that we have no explicit representation of tasks, but we attach information saving to forms that know nothing about the overall business logic.

    Hacking each object property to a permanent structure on disk  avoids having two different versions of the same state, since information is not saved at all in memory, but has other heavy drawbacks, namely:

    1. Performance overhead due to the continuous disk operations.
    2. The only way to map unique in-memory instances to unique disk instances is to assign an unique string or numeric Id to each state object. While instances that need to be sent to the server already have their unique keys, this way we are forced to assign primary keys also to intermediate results of client processing.
    3. While in-memory instances that are not used anymore are automatically garbage collected, there is no easy way to garbage collect their images on disk. Moreover, since user can close the application by simply closing the browser, the only chance to clear old unused disk images is when the application is started, but the application has not enough information to decide what to do when it starts. In fact, since we don’t intercept unhandled exceptions, we can’t store somewhere that the application crashed, and that at next start state must be recovered. Moreover, since the save automatism is not tied to actual tasks start and end, the application has no way to understand by inspection that disk state contains incomplete tasks that must be recovered.
    4. All properties of state classes must be redefined manually so that they call adequate disk write and disk read routines…. This is an actual nightmare! This disadvantage could be overcome by hacking input fields on blur events instead of class properties, but it is not easy to inform onblur events about the id of the object being processed. Moreover, input fields are not the only way the application can change the properties of state objects.

    The above discussion about error recovery strategies clarifies that any efficacious state recovery strategy needs two important capabilities:

    1. The capability to recognize when the application crashes, and to trigger adequate adequate actions, in that case.
    2. An explicit representation of tasks starts and completions. In fact, a state must be recovered only if its associated task has been interrupted by an application crash.

    While Blazor WebAssembly has no centralized exception handling logic, all errors are logged to .Net Core standard logging system,  so we can implement ourselves  a custom centralized error handling with the help of a custom logger, that is,  by providing a custom implementation of the two interfaces below:

    public interface ILogger
    {
        IDisposable BeginScope<TState>(TState state);
        bool IsEnabled(LogLevel logLevel);
        void Log<TState>(LogLevel logLevel, EventId eventId,
                TState state, Exception exception,
                Func<TState, Exception, string> formatter);
    }

    public interface ILoggerProvider : IDisposable
    {
        ILogger CreateLogger(string categoryName);
    }

    “ILoggerProvider” is a provider whose only purpose is to create an implementation of  ILogger, when it is required, possibly passing it instances of other classes taken from the Dependency Injection engine.

    When an unhandled exception occurs the “ILogger.Log” function is called and the exception is passed as third argument. However, since “ILogger.Log” might be called also to log simple information, we need to check also the first argument (“logLevel”) that contains the log severity level. More specifically, a log call inform us about an unhandled exception only if :

    logLevel >= LogLevel.Error

    Thus, our custom logger must perform error handling actions just in this case.

    In the next section we explains in detail how to perform centralized error handling with a custom logger.

    Implementing Centralized Error Handling

    As a first step let create a new Blazor WebAssembly project. In order to operate and debug Blazor WebAssembly 3.2 you need a Visuals Studio 2019 installation that is updated at least to version 16.6. You can download the last free version of Visual Studio here.

    Let open Visual Studio and choose “Create new project”. Then, select the Blazor project template, as shown below:

    project

    When the project name appears, name the project “StateManagement”. Finally, in the last screen select “Blazor WebAssembly”, and ensure that both “Configure for Https” and “Asp.net Core hosted” are checked while “Enable Docker support” and authentication are both disabled, as shown below:

    Configuration

    Once everything is ready run the project to verify that the initial WebAssembly example application runs.

    If everything is ok, we can move to the second step: adding a library project where to implement our error handling and state management, so we can reuse it also in other projects,

    Right click on the root solution in the “Solution Explorer” tab and select “Add new project”, after that select the “Razor Class Library” template:

    StateManager

    Then name the library “StateManager”. In the final screen, ensure that  “Support pages and views” and all other options are unchecked.

    After the library project is ready right click on the dependencies of the “StateManagement.Client” WebAssembly project and add the library project as a dependency.

     

    Now we are ready to start writing our code!

     

    Implementing the error logic

    We will first implement a general purpose error handling, logic, and then we will implement our custom logger to trigger the error handling process. We an opt in for the simple interface shown below:

    public interface IErrorHandler
        {
            event Func<Exception, Task> OnException;
            Task Trigger(Exception ex);
            
        }

    Basically, a centralized place where all application parts that need to  be notified about an unhandled exception can register their handlers with a “+=” on the “OnException” event. On the other side “Eceptions detectors” like our custom logger can run all these handlers by calling the “Trigger” method of this interface.

     

    The interface implementation is straightforward:

    public class DefaultErrorHandler: IErrorHandler
    {
        public event Func<Exception, Task> OnException;
        public async Task Trigger(Exception ex)
        {
            if (OnException != null) await OnException(ex);
        }
    }

    Let define both the interface and its implementation under a newly created folder of our library project called “ErrorHandling”.

    The basic idea is to register both the interface and its implementation in the dependency engine as a singleton with something like:

    services.AddSingleton<IErrorHandler, DefaultErrorHandler>();

    But we will do this later, by encapsulating this instruction inside an “AddStateManagement”  extension method.

    Nest step is the implementation of the ILogger.

    Implementing the custom logger

    Let name our custom logger “ErrorRecoveryLogger”, and let place it under the same “ErrorHandling” folder we defined before.

    The code is quite simple, but contains some fake stuffs since we don’t need all “ILogger” functionalities:

        public class ErrorRecoveryLogger : ILogger
        {
            IErrorHandler errorHendler;
            public ErrorRecoveryLogger(IErrorHandler errorHendler)
            {
                this.errorHendler = errorHendler;
            }
            public IDisposable BeginScope<TState>(TState state)
            {
                return new FakeScope();
            }

            public bool IsEnabled(LogLevel logLevel)
            {
                return logLevel >= LogLevel.Error;
            }

            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
            {
                if (logLevel < LogLevel.Error) return;
                errorHendler.Trigger(exception);
            }
        }

        public class FakeScope : IDisposable
        {
            public void Dispose()
            {
                
            }
        }

    The “FakeScope” class is an IDisposable fake class implemented just because “BeginScope” must return an IDisposable. We coded a fake implementation of this method since we don’t need it.

    “IsEnabled” must return true just when “loglevel” is at least “LogLevel.Error”, to inform all callers that this logger is interested just in errors.

    When the “Log” method is called we verify that “logLevel” is at least “LogLevel.Error” and then we call the “Trigger” method of the previously defined “IErrorHandler” interface.

    This interface is passed in the logger constructor that stores it in a private property.

    The “ILoggerProvider” implementation just creates our custom logger and passes it the “IErrorHandler” interface:

     

    public class ErrorRecoveryLogProvider : ILoggerProvider
    {
        IErrorHandler errorHandler;
        public ErrorRecoveryLogProvider(IErrorHandler handler)
        {
            errorHandler = handler;
        }
        public ILogger CreateLogger(string categoryName)
        {
            return new ErrorRecoveryLogger(errorHandler);
        }

        public void Dispose()
        {
                
        }
    }

    “IErroHandler” will be passed in the constructor of our “ILoggerProvider” by the Dependency Injection engine, and our “ILoggerProvider”, in turn, will use it to create its loggers.

    Putting everything in place with an Extension method.

    As a final step of our error handling logic implementation we define an  “IServiceCollection”  extension method that we will call form inside the Blazor application “main” in oder to setup the whole error handling logic we designed.

    Let create new “Extensions” folder in the library project and let add it our extension class called “StateHandling.cs”. The implementation is as follows:

     

    public static ILoggingBuilder
            CustomLogger(this ILoggingBuilder builder)
        {
            builder.Services
                .AddSingleton<ILoggerProvider, ErrorRecoveryLogProvider>();  
            return builder;
        }
        public static IServiceCollection AddStateManagemenet(
            this IServiceCollection services)
        {
            services.AddSingleton<IErrorHandler, DefaultErrorHandler>();
            services.AddLogging(builder => builder.CustomLogger());
            return services;
        }

    AddStateManagement adds our “IErrorHandler” implementation to the Dependency Injection engine, and then add our custom logger

    to all other pre-existing loggers by calling “AddLogging”. The logger is added with the “builder pattern”, that is by defining its setup with a “ILoggingBuilder” extension. This is a standard pattern to create loggers.

    Testing Our Centralized Error Handling

    In order to verify that our error handling logic works let follow the following steps:

    1. Let add a call to AddStateManagemenet() in the Program.cs file of the “StateManagement.Client” Blazor WebAssembly project , as shown below:

      public static async Task Main(string[] args)
      {
          var builder = WebAssemblyHostBuilder.CreateDefault(args);
          ...
          builder.Services.AddStateManagemenet();
          await builder.Build().RunAsync();
      }
    2. Then, let place a breakpoint in our custom  logger implementation on the line where it calls the “Trigger” method of “IErrorHandler” interface, as shown below:

      breakpoint
    3. Finally, let open the “Counter.blazor” page that is under the pages folder of the “StateManagement.Client” WebAssembly project, and modify the “IncrementCounter” method so that it throws an exception when the increment button is clicked, as shown below:
      private void IncrementCount()
      {
          throw new Exception();
          currentCount++;
      }
    4. Let run the solution, then go to the counter page, and click the “Click me” button.

    When the button is clicked the Blazor application UI signals the errors, and immediately after, the breakpoint we places is hit! Our logger logic was able to

    detect the unhandled exception, and to inform our centralized error handling logic.

    That’s all for now!

    In the second part of this article we will see how to share state information among Blazor pages, an how to save on disk and then how to recovery the  state of all uncompleted tasks in case of exceptions.

    Ckick here to access the whole code on GitHub!

     

    Francesco

    Tags: ,

    Feb 6 2020

    A complete learning path for becoming a .Net Core developer

    Category: Francesco @ 01:30

    I am always looking for good books to use in my courses, and when I don’t find good ones I consider writing them myself. That’s why I recently wrote a book to introduce developers to the architectures that emerged as a consequence of the diffusion of the cloud. I am speaking, of distributed  databases, Serverless architectures, Micro-services, Domain Driven Design with its related architectures,  Docker, REST architectures, and so on.  The book is described in detail in this post.

    However, after this big effort I was still looking for some teaching material for complete .Net Core learning path. A learning path that assume just a general proficiency in programming as its unique prerequisite. A learning path that starts with the basics of C# and covers how a complete multilayer application can be developed with .Net Core.

    Finally, I discovered “C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development: Build applications with C#, .NET Core, Entity Framework Core, ASP.NET Core, and ML.NET using Visual Studio Code”. The book was written by Mark J. Price and published by the same publisher of my books, Packt. That’s why I decided to give a short review this book here.

    NETCoreBook

    The book is able to provide in-depth details of C#8 and how all its features have evolved throughout the language’s entire history. It is written in such a way that it holds the interest of the reader, almost like a thriller. Moreover, it covers all subjects involved in the building of a modern, layered .Net Core application, from the data layer with Entity Framework Core, to the presentation layer with Asp.net Core, Windows Desktop, and Xamarin (native mobile applications). It gives also a short introduction to machine learning with ML.net.

    Also important side topics such as security and cryptography are covered in the book.

    All chapters are easy and interesting to read, and the explanation never becomes boring or confusing. I recommend this book not only to all developers that are new to C# and .Net Core and want to become operative in this technology, but also to developers that are fluent in old versions of C# and in classic .Net and that would like to move toward .Net Core.

     

    So, what are you waiting for? Let go read this book to enter .Net Core world, and then learn modern cloud based architectures with my book!

    Francesco

    Tags: