blazor
130 TopicsConvert the standard Blazor navigation menu to a collapsible icon menu
While I admittedly love Blazor I’ve always changed the out-of-the-box navigation menu that comes with it. It’s the first manoeuvre I pull when spinning up a new Blazor app, stripping out the purple gradient and getting it in, what I consider, a “blank slate state”. The other change I’ve wanted to make to the out-the-box look is one of those deluxe collapsible menus that leave just the icons showing. Anyone that’s used Azure DevOps will know what I’m talking about. I’ve included a picture to show DevOps example of what I’d like to see in my Blazor app. It gives a load of extra screen real estate which is always a priority for me in business applications particularly with complex or intensive workflows. Plus it gives the user the option to remove the text prompts once they are familiar with the system which is supported with carefully selected icon choices. As with most tasks that I assume will be an obvious solution I hit my search engine of choice and looked to avoid reinventing the wheel. However I found no source of pre-written changes to achieve this and was directed to fairly expensive third party controls to solve this one for me, which, being tight fisted, pushed me to do it for myself. Here I hope you save you the trouble of paying a pretty penny or having to wrestle the CSS into submission and provide a guide for producing a nice collapsible icon navigation menu by altering the existing out of the box menu in Blazor. In the following example I have left all the standard styling as is with the menu and just done the changes required to make the collapsible menu. The three files that require changes are MainLayout.razor, NavMenu.razor and NavMenu.razor.css. The code changes are shown below: Firstly the NavMenu.razor requires a bool value (IconMenuActive) to indicate whether the icon menu is showing or not, then wrap the text of the each NavItem in an if statement dependent on this bool. Then a method for toggling this bool and EventCalBack to send a bool to the MainLayout.razor for shrinking the width of the sidebar. Lastly there needs to be the control for switching menu views (I used the standard io icon arrows). NavMenu.razor <div class="top-row ps-3 navbar navbar-dark"> <div class="container-fluid"> <span class="oi oi-monitor" style="color:white;" aria-hidden="true"></span> @if (!@IconMenuActive) { <a class="navbar-brand" href="">The Menu Title Here</a> } <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu"> <span class="navbar-toggler-icon"></span> </button> </div> </div> <div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> <nav class="flex-column"> <div class="nav-item px-3"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <span class="oi oi-home" aria-hidden="true"></span> @if (!@IconMenuActive) { <label>Home</label> } </NavLink> </div> <div class="nav-item px-3"> <NavLink class="nav-link" href="counter"> <span class="oi oi-plus" aria-hidden="true"></span> @if (!@IconMenuActive) { <label>Counter</label> } </NavLink> </div> <div class="nav-item px-3"> <NavLink class="nav-link" href="fetchdata"> <span class="oi oi-list-rich" aria-hidden="true"></span> @if (!@IconMenuActive) { <label>Fetch data</label> } </NavLink> </div> </nav> </div> <div class="bottom-row"> <div class="icon-menu-arrow"> @if (!@IconMenuActive) { <span class="oi oi-arrow-left" style="color: white;" @onclick="ToggleIconMenu"></span> } else { <span class="oi oi-arrow-right" style="color: white;" @onclick="ToggleIconMenu"></span> } </div> </div> @code { //bool to send to MainLayout for shrinking sidebar and showing/hide menu text private bool IconMenuActive { get; set; } = false; //EventCallback for sending bool to MainLayout [Parameter] public EventCallback<bool> ShowIconMenu { get; set; } private bool collapseNavMenu = true; private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; private void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } //Method to toggle IconMenuActive bool and send bool via EventCallback private async Task ToggleIconMenu() { IconMenuActive = !IconMenuActive; await ShowIconMenu.InvokeAsync(IconMenuActive); } } Next I add in a bit of CSS in to NavMenu.razor.css to put the arrow for toggling the menu at the bottom of the sidebar and a media query to make sure it doesn't show up in mobile view. The CSS classes added are .bottom-row and .icon-menu-arrow. NavMenu.razor.css .navbar-toggler { background-color: rgba(255, 255, 255, 0.1); } .top-row { height: 3.5rem; background-color: rgba(0,0,0,0.4); } .bottom-row { position: absolute; bottom: 0; padding-bottom: 10px; text-align: right; width: 100%; padding-right: 28px; } .icon-menu-arrow { text-align: right; } .navbar-brand { font-size: 1.1rem; } .oi { width: 2rem; font-size: 1.1rem; vertical-align: text-top; top: -2px; } .nav-item { font-size: 0.9rem; padding-bottom: 0.5rem; } .nav-item:first-of-type { padding-top: 1rem; } .nav-item:last-of-type { padding-bottom: 1rem; } .nav-item ::deep a { color: #d7d7d7; border-radius: 4px; height: 3rem; display: flex; align-items: center; line-height: 3rem; } .nav-item ::deep a.active { background-color: rgba(255,255,255,0.25); color: white; } .nav-item ::deep a:hover { background-color: rgba(255,255,255,0.1); color: white; } @media (min-width: 641px) { .navbar-toggler { display: none; } .collapse { /* Never collapse the sidebar for wide screens */ display: block; } } @media (max-width: 640px) { .bottom-row { display: block; } } Finally I add in the handler for the EventCallback to MainLayout.razor and a method to alter the width of the sidebar. MainLayout.razor @inherits LayoutComponentBase <div class="page"> <div class="sidebar" style="@IconMenuCssClass"> <NavMenu ShowIconMenu="ToggleIconMenu"/> </div> <main> <div class="top-row px-4"> <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> </div> <article class="content px-4"> @Body </article> </main> </div> @code{ private bool _iconMenuActive { get; set; } private string? IconMenuCssClass => _iconMenuActive ? "width: 80px;" : null; protected void ToggleIconMenu(bool iconMenuActive) { _iconMenuActive = iconMenuActive; } } The final product of these little changes are shown in the pictures below: I'd love to hear if anyone has tackled this in a different way to me and if they've got any ideas on making it cleaner. Have yourselves a wonderful day, Gav74KViews10likes16CommentsVersioning my Maui Android App
Previously the way to define version code for Android in Maui .csproj file was like this: <!-- Versions --> <ApplicationVersion>3.1.2</ApplicationVersion> <AndroidVersionCode>3</AndroidVersionCode> With the new release of Visual Studio Preview, this has changed to: <ApplicationVersion>3</ApplicationVersion> It works, from Visual Studio, I can bundle a new .aab package (see image below). We see the version code is defined to 3. Unfortunately the version is defined to 1.0.0 and I don't know how to change this. ...when imported on the Google Play Console, it look like this: Unfortunately, when imported I only see the versionCode 3 (which is defined in the .csproj file as ApplicationVersion). What about the versionName (in parentheses) ? Nothing is planned in the .csproj file ? When imported on the Google Play Console, I don't want to have 3 (1.0.0) but I want 3 (3.1.2)... https://developer.android.com/studio/publish/versioning versionCode — A positive integer used as an internal version number. versionName — A string used as the version number shown to users.27KViews0likes8CommentsCreating a Generic Tree View Blazor Component
I want to take a moment to show off a Blazor component that I made that can display an object recursively in a tree view. The component was made as part of my team’s project, FHIR Watch, a tool for comparing FHIR data from two different data sources: FHIR API Service and Dataverse. As such, it is particularly useful for displaying FHIR data.Creating a Notification Service and Components in Blazor with Bootstrap
Now that I’ve finally had opportunity to work on a notification service for the front-end, I wanted to share with everyone. This service was originally something that I piloted in my team’s project, Fast Pass, that was created to demonstrate how Text Analytics for Health can be used to extract medical information from physical patient documents to persist to an EMR. However, I recently improved it to a level of satisfaction worthy of sharing in an older project, FhirBlaze, which we recently updated with OpenAI to generate FHIR queries.Blazor WASM PWA – Applications updates, cache busting with notification or force refresh
My team is currently working on a Blazor application and after several bouts of confusion between developers and testers we stuck a version number on the home screen and discovered the updates weren’t going out to everyone as we were expecting. We ended up with several people on different versions of the application and fortunately we spotted this issue early in development. I’ve put together the options and info I’ve found while trying to find a solution to this that works for our particular situation in hopes that it will help someone else dealing with this and hopefully open a discussion on how people are handling this issue and find any alternative solutions. To look at what was happening behind the scenes, I set up a simple WASM PWA on .NET7 and have it hosted on an Azure App Service. I stuck a version number on the home page so I can easily see when the update has taken place. The behaviour: Blazor WASM PWA applications load the application into the browser based on the cache pulled in by the service workers (Javascript assets that act as a proxy between browsers and web servers for offline support and performance). In dev tools you can see what’s in the cache storage for the app (go to dev tools > application tab > cache storage). The picture below shows that the cache contains “blazor-resources” and “offline-cache”. The “offline-cache” is the important one that determines what the site is displaying and on pushing an update the “blazor-resources” is unaffected so for this purpose can be ignored. Now the standard behaviour on pushing an update to the app is that the service worker will install the new app cache in the background and enter the “waiting” state and will not activate the newly cached app until the current application service worker is released, by either closing the browser or hard refreshing the app (Ctrl + F5). This behaviour is by design to prevent having two tabs open with different versions of the application running at once. However users may never close the application or browser or whatever and that would mean they could be running long retired versions of your application for an indeterminant length of time. Below you can see the cache storage after an update has been pushed stacking up the old and new version of the offline-cache. Once the service worker has been released then the old cache is removed the latest will be activated. This will then display the new version of the app. The solution: Now, what if we want to change this behaviour to either prompt and update or force one? Either way we will want to remove the need to close browsers or hard refresh as that can be a bit of a tricky one to get end users to get on board with. The service workers actually have a method that can skip the “waiting” stage of the service worker lifecycle clear the old cache and activate the latest. This is the SkipWaiting method. In the Blazor solution there is the service-worker.published.js file found in the wwwroot folder. This file contains the methods for the service worker. In this file there is a OnInstall method and can be modified to the following adding in the SkipWaiting method: async function onInstall(event) { console.info('Service worker: Install'); self.skipWaiting(); // Fetch and cache all matching items from the assets manifest const assetsRequests = self.assetsManifest.assets .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); } This additional line will now allow a standard refresh to update the application rather than requiring a hard refresh or closing the browser completely. When looking at the cache storage when an update is pushed you will see the second cache appear then the old cache disappear immediately. Note: Adding this behaviour will overwrite the intended behaviour and allow the potential of different version of the application running in different tabs. However we can look at avoiding that by forcing update later. Next, regardless of whether we want to prompt the user for an update or force it on them, we need a way of letting the site know it needs an update. My javascript is less than brilliant so I’ve used a solution by Wouter Huysentruit, full description here: https://whuysentruit.medium.com/blazor-wasm-pwa-adding-a-new-update-available-notification-d9f65c4ad13 This involves adding a js file called sw-registrator.js to the wwwroot folder that will allow an event to be triggered that can link to updating the site: window.updateAvailable = new Promise((resolve, reject) => { if (!('serviceWorker' in navigator)) { const errorMessage = `This browser doesn't support service workers`; console.error(errorMessage); reject(errorMessage); return; } navigator.serviceWorker.register('/service-worker.js') .then(registration => { console.info(`Service worker registration successful (scope: ${registration.scope})`); registration.onupdatefound = () => { const installingServiceWorker = registration.installing; installingServiceWorker.onstatechange = () => { if (installingServiceWorker.state === 'installed') { resolve(!!navigator.serviceWorker.controller); } } }; }) .catch(error => { console.error('Service worker registration failed with error:', error); reject(error); }); }); window.registerForUpdateAvailableNotification = (caller, methodName) => { window.updateAvailable.then(isUpdateAvailable => { if (isUpdateAvailable) { caller.invokeMethodAsync(methodName).then(); } }); }; Within index.html in the wwwroot folder we will need to register this new file instead of the service-worker.js. So we need to replace the reference to service-worker.js with the following: <script src="sw-registrator.js"></script> This will now raise an event once the cache refresh happens that can be used to trigger a prompt on the application or a refresh of the application. Now we need a razor component that will listen for the event and either throw up a refresh prompt or just force a refresh. Firstly I’ll do the prompt. For this make a new razor component and add the following code: @inject IJSRuntime _jsRuntime @inject NavigationManager uriHelper @if (_newVersionAvailable) { <button type="button" class="btn btn-danger shadow floating-update-button" onclick="window.location.reload()"> A new version of the application is available. Click here to upgrade. </button> } @code { private bool _newVersionAvailable = false; protected override async Task OnInitializedAsync() { await RegisterForUpdateAvailableNotification(); } private async Task RegisterForUpdateAvailableNotification() { await _jsRuntime.InvokeAsync<object>( identifier: "registerForUpdateAvailableNotification", DotNetObjectReference.Create(this), nameof(OnUpdateAvailable)); } [JSInvokable(nameof(OnUpdateAvailable))] public Task OnUpdateAvailable() { _newVersionAvailable = true; StateHasChanged(); return Task.CompletedTask; } } I personally like a red bar across the top as it’s very attention grabbing so you can add the following css file called WhateverYouCalledTheComponent.razor.css: .floating-update-button { position: relative; top: 0; padding: 1rem 1.5rem; width: 100%; } This component can then be placed at the top of MainLayout.razor above the body to ensure it appears anywhere on the app the user happens to be. This will then produce the following result once an update is pushed out: Once the banner is clicked, it will reload the site and the new update will be running. This is the setup that I will be implementing on my teams project as I wouldn’t want the site to force updates in case a user was in the middle of data entry. However if a forced refresh is desired, it’s a quick change to the razor component to allow that. Change the components code to the following: @inject IJSRuntime _jsRuntime @inject NavigationManager uriHelper @code { protected override async Task OnInitializedAsync() { await RegisterForUpdateAvailableNotification(); } private async Task RegisterForUpdateAvailableNotification() { await _jsRuntime.InvokeAsync<object>( identifier: "registerForUpdateAvailableNotification", DotNetObjectReference.Create(this), nameof(OnUpdateAvailable)); } [JSInvokable(nameof(OnUpdateAvailable))] public Task OnUpdateAvailable() { uriHelper.NavigateTo(uriHelper.Uri, forceLoad: true); StateHasChanged(); return Task.CompletedTask; } } Now on pushing an update you will see that the update is installed in the cache and once the event is triggered the old cache is removed and the site reloads wherever it happens to be. I’d be really interested to hear other peoples takes on this or if they’ve found other methods of achieving this, I’ve scoured the web and this was the combination of the best methods I came across but certainly open to improvement.13KViews0likes5CommentsHow to Create Your Own Portfolio Website in Minutes with GitHub Codespaces and Blazor
Creating a portfolio website is essential for showcasing your skills and accomplishments to potential employers or clients. However, setting up a website can be time-consuming and require technical expertise. Fortunately, with GitHub Codespaces and Blazor, you can create and customize your own portfolio website in just a few minutes, without installing any tools or worrying about lengthy environment setup. Our .NET Blazor Portfolio Site project template is perfect for beginners and experienced coders alike, and you can deploy your website with Azure Static Web Apps or GitHub Pages. This project is easily customizable and perfect for anyone looking to create a portfolio site, learn web development, or test out Codespaces. Follow our instructions to launch your Codespace, customize your website, and deploy it. No experience necessary – start today!11KViews2likes0Comments.NET Conference Student Zone 7th Nov 2022
Student Zone, Monday, November 7th Are you a student wanting to learn .NET? We have a pre-conference day with a ton of content you don't want to miss! We will have two sessions, a midday session at 12:00 PM UTC and an evening session at 10:30 PM UTC. Midday session registration Evening session registration Join Our Cloud Skills Challenge and Win Swag http://aka.ms/dotnetstudententry5.6KViews2likes0CommentsCould not set or bind model property with Bootstrap Datepicker in Blazor
I am using bootstrap datepicker and the problem is that when I pick a date, it does not fire a change or input event and noting is binding with the model property Course.StartDate or Course.EndDate. The default datepicker works but does not support Afghanistan datetime. That is why I use boostrap datepicker. Blazor code: @using Microsoft.AspNetCore.Mvc.Rendering @using myproject.Data @using Microsoft.JSInterop; @inject myproject.Repository.CoursesRepository _coursesRepository @inject IJSRuntime JS <EditForm Model="@Course" OnValidSubmit="e=> { if(selectedId == 0) { addCourse(); } else { updateCourse(Course.CourseId); } }"> <div class="mb-2"> <div>@Course.StartDate</div> <label class="col-form-label" for="StartDate">@Loc["Start Date"]<span class="text-danger fs--1">*</span>:</label> <InputDate class="form-control" @bind-Value="Course.StartDate" @bind-Value:format="yyyy-MM-dd" id="StartDate" /> <ValidationMessage class="text-danger" For="(() => Course.StartDate)"/> </div> <div class="mb-2"> <label class="col-form-label" for="EndDate">@Loc["End Date"]<span class="text-danger fs--1">*</span>:</label> <InputDate class="form-control" @bind-Value="Course.EndDate" @bind-Value:format="yyyy-MM-dd" id="EndDate"/> <ValidationMessage class="text-danger" For="(() => Course.EndDate)"/> </div> </EditForm> @code { public CourseModel Course = new(); public string[] dates = new string[] { "#StartDate", "#EndDate" }; protected override void OnAfterRender(bool firstRender) { base.OnAfterRender(firstRender); loadScripts(); } void addCourse() { _coursesRepository.AddCourse(Course); FillData(); Course = new(); var title = "Course"; Swal.Success(title : Loc[$"{title} added successfully"],toast : true); } // initializes the datepicker public async Task loadScripts() { await JS.InvokeVoidAsync("initializeDatepicker", (object) dates); } } This is script for initializing the datepickers <script> function initializeDatepicker(dates) { dates.forEach((element) => { $(element).datepicker({ onSelect: function(dateText) { // this is not working element.value = this.value; /* tried this and still not working $(element).trigger("change"); also tried this and still not working $(element).change(); */ // this is working console.log("Selected date: " + dateText + "; input's current value: " + this.value); }, dateFormat: 'yy-mm-dd', changeMonth: true, changeYear: true }); }); } </script>Solved4.7KViews0likes2Comments