26 November 2022

Blazor – manage disconnects

I have a Blazor Server application and if, for example, I leave the tab open without using the application or if I update my application, it is disconnected. Blazor manages these states by itself. This is very good, only I find that the user experience is not great. Here is what appears:

Blazor server disconnection message

Blazor takes care of adding a div with the id components-reconnect-modal and manage its content according to the type of disconnection (server no longer responding, connection rejected, network problem..) :

˂div id="components-reconnect-modal">
...
˂/div>

But if we add it ourselves to our project, then Blazor will use it without adding it!

Why not customize the style

Knowing the id used by Blazor and the content of what it adds we can already customize what it displays. It will already be nicer. For example, we just need to add these CSS styles to the project’s site.css file:

#components-reconnect-modal {
    display: flex !important;
    opacity: 1 !important;
    background-color: rgb(255 255 255 / 80%) !important;
}
 
    #components-reconnect-modal::before {
        content: '';
        width: 300px;
        height: 65px;
        background: #BDCBFF;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        display: block;
    }
 
    #components-reconnect-modal h5 {
        margin-top: 50px !important;
        padding: .75rem 1.25rem;
        width: 300px;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 1050;
        color: #003494;
        background-color: #BDCBFF;
        box-shadow: 0px 10px 10px 0px hsl(0deg 0% 0% / 20%);
    }
 
    #components-reconnect-modal div {
        border-color: rgb(0 52 148) rgb(89 127 222) rgb(161 180 235) !important;
        margin: auto;
        z-index: 1050;
    }
 
    #components-reconnect-modal h5 a {
        color: #092464 !important;
    }

#components-reconnect-modal button {
    color:#515151;
    padding: 4px 8px;
    font-size: .875rem;
    min-width: 64px;
    box-sizing: border-box;
    transition: background-color 250ms cubic-bezier(.4,0,.2,1) 0ms,box-shadow 250ms cubic-bezier(.4,0,.2,1) 0ms,border 250ms cubic-bezier(.4,0,.2,1) 0ms;
    font-weight: 500;
    line-height: 1;
    letter-spacing: .02857em;
    text-transform: uppercase;
    margin: auto !important;
    background:rgb(46 121 232 / 28%);
    z-index: 1500;
    border:0;
}

The result is clear, it is much better:

Change reconnection attempts

While we’re at it, we can also modify these reconnection attempts with a little script to be added in the _Layout.cshtml page. The code will speak for itself, just remember to add the attribute autostart=”false” à blazor.server.js :

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
      Blazor.start({
        reconnectionOptions: {
          maxRetries: 10,
          retryIntervalMilliseconds: 8000
        }
      });
</script>

Another solution with a Razor component

This is what I prefer. In a new Razor component, for example ReconnectDialog.razor, I add this code:

 <div id="components-reconnect-modal" class="components-reconnect-hide">
        <div class="hide">
            <div class="myDialog">
            
                <div class="components-reconnect-top">
                    <div class="oi oi-transfer p10"></div> <h5>Connected</h5>
                </div>
            </div>
        </div>
        <div class="show">
            <div class="myDialog">
                 <div class="components-reconnect-top">
                     <div class="oi oi-warning p10"></div> <h5>Attempting to connect to server</h5>
                </div>
                <div class="components-reconnect-bottom">
                    <a href="javascript:window.location.reload()" class="p5">Reload</a> the page to restore functionality.                          
                </div>  
            </div>
           
        </div>
        <div class="failed">    
            <div class="myDialog">
                <div class="components-reconnect-top">
                    div class="oi oi-wrench p10"></div> <h5>Failing to connect</h5>
                </div>    
                <div class="components-reconnect-bottom">
                    Reconnection failed, probably due to a network failure. <a href="javascript:window.Blazor.reconnect()" class="p5">Click here</a> to attempt reconnection.                          
                </div>  
            </div>
        </div>
        <div class="rejected">  
            <div class="myDialog">
                <div class="components-reconnect-top">
                    <div class="oi oi-ban p10"></div> <h5>Connection rejected</h5>
                </div> 
                <div class="components-reconnect-bottom">
                    <a href="javascript:window.location.reload()" class="p5">Reload</a> the page to restore functionality.                          
                </div>   
            </div>
        </div>
    </div>

And in the CSS page :

	.components-reconnect-show > div,
	.components-reconnect-failed > div,
	.components-reconnect-rejected > div {
		display: none;
	}

	.components-reconnect-hide > .hide,
	.components-reconnect-show > .show,
	.components-reconnect-failed > .failed,
	.components-reconnect-rejected > .rejected {
		display: block;
	}

    
     
    .p5 {
        padding-right:5px;
    }
    .p10 {
        padding-right:10px;
    }

    .myDialog {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        z-index:1050;

        background-color: #fefefe;
        margin: auto;
        border: 1px solid #888;
        height: 150px;
        width: 400px;
        border-radius: 6px;
        box-shadow: 0 10px 20px rgb(0 0 0 / 10%), 0 6px 6px rgb(0 0 0 / 16%);
    }

    .components-reconnect-top {
        background: #6422EB;
        display: flex;
        flex-direction: row;
        height: 60px;
        transition: height 300ms cubic-bezier(0.4, 0.0, 0.2, 1);
        padding: 20px;
        color:white;
        width:100%;
    }

    .components-reconnect-bottom {
        display: flex;
        flex-direction: row;
        padding: 20px;
    }

You now add this component to the top of your MainLayout.razor :

<ReconnectDialog></ReconnectDialog>

And here is what it gives. Of course the div class=”hide” part has to be removed ^^

Yes but…. let’s rethink the user experience!

If it’s disconnected, no more connections, the application is alone in the world. And as our users are not “computer scientists” 😑, we might as well find a trick to make the navitator try to reconnect itself. The good joke would be to add <meta http-equiv=”refresh” content=”30″> 🤪 .

Delete everything we have created (CSS style, div and component) and let the browser manage itself. Simply add this script to the page _Layout.cshtml :

<script src="_framework/blazor.server.js"></script>
    <script>
        // Wait until a reload button appears
        new MutationObserver((mutations, observer) => {
            if (document.querySelector(&#039;#components-reconnect-modal h5 a&#039;)) {
                // Now every 10 seconds, see if the server appears to be back, and if so, reload
                async function attemptReload() {
                    await fetch(&#039;&#039;); // Check the server really is back
                    location.reload();
                }
                observer.disconnect();
                attemptReload();
                setInterval(attemptReload, 2000);
            }
        }).observe(document.body, { childList: true, subtree: true });
    </script>

This small script that will run in the user’s browser will monitor the presence of the parent/child sequence components-reconnect-modal h5 a in the DOM and if it finds it, restart the connection itself.

⚠ If you want to create your own component, you will have to adapt the querySelector

Related link Microsoft Docs – ASP.NET Core Blazor SignalR guidance

One thought on “Blazor – manage disconnects

  1. I tried the script in the last portion of your article and it worked well the first time the page timed out and reloaded the page with user intervention. However, on the second time out it was as if the script did not exist. I think you are close to a solution. Thank you.

Leave a Reply