Telerik blogs

Blazor can drive UI/UX across web and native platforms, but implementations can vary.

Blazor is eating the world. .NET developers have understandably been excited about Blazor—a modern web framework allowing for C# code front and back. Blazor can run server-side or entirely client-side with WebAssembly, and the Blazor component/rendering model inspires confidence with stability and extensibility.

With .NET Multi-platform App UI (.NET MAUI), Blazor goodness is not confined to just web apps, but now very welcome on native cross-platforms apps for mobile and desktop. Even though Blazor components/styles can render the same user interface (UI) across web, desktop and mobile, the user experience (UX) should not be the same. In fact, developers will want to have the flexibility to do different things with Blazor across various platforms. Let’s explore how a single shared codebase allows Blazor code to have varying implementations across platforms—sharing across differences is caring.

PS: This article is a part of the C# Advent Calendar, run by Matthew Groves and Calvin Allen. Tune in for wonderful .NET/C# content everyday leading up to Christmas!

Blazor With .NET MAUI

Blazor is the free, open-source and much beloved web framework for building modern web apps. Developers wanting to stay away from JavaScript can leverage the power of .NET, C# and modern tooling to build interactive beautiful web apps. The Blazor component model, rendering engine and styling mechanisms offer flexibility—Blazor apps can run server-side in ASP.NET Core or fully client-side with WebAssembly.

.NET MAUI is the evolution of .NET cross-platform strategy—a new framework for creating native mobile and desktop apps with C#/XAML. However, .NET MAUI brings in something magical called the BlazorWebView component—an abstraction that finds evergreen browsers on respective platforms and allows for rendering of web artifacts within the shell of native apps.

Blazor and .NET MAUI are almost made for each other, sharing the exact .NET runtime—.NET 6 yesterday and .NET 7 today. Blazor apps can now be easily hosted inside .NET MAUI apps, with full native platform integration. Blazor Hybrid apps, as they are called, enable a lot of code sharing across web and native apps on mobile/desktop.

Shared Codebase

There are templates to get developers started using Blazor with .NET MAUI—the resulting project brings in Blazor web artifacts inside .NET MAUI native apps. While helpful, a more realistic scenario is for developers to keep maintaining web apps built with Blazor, while serving up the same UI on native .NET MAUI apps. This is doable by bringing in Blazor web projects (server/client/both) and .NET MAUI projects with Blazor—under a single solution, so Blazor runs everywhere from shared codebase.

Finder menu for Solution has BlazorEverywhere with entries below it for BlazorForNative which is bolded, BlazorForWeb.Client, BlazorForWeb.Server, BlazorForWeb.Shared, BlazorSharedUI

The goal, however, is not reinventing the wheel—the same Blazor components and styles should power UI for web and native apps. The trick is to move the shared pieces out of the Blazor web or .NET MAUI projects into a referenced shared class library.

Circled under BlazorSharedUI are Pages with Counter.razor and Index.razor; Shared with MainLayout, NavMenu and SurveyPrompt; and wwwroot with css, which contains bootstrap folder, open-iconic folder and app.css

Platform Variations

It’s a great story to have shared Blazor components/styles drive common UI across web and native apps. However, a web browser and platforms like iOS/Android/Windows/macOS are very different form factors—and the UI/UX should likely not be the same.

Blazor can power connected experiences on the web—tap into JavaScript goodness as needed or support hosting models on server/client. While running on native mobile/desktop apps through .NET MAUI, Blazor can do some very platform-specific things.

Bottom line is web and native apps are different canvases—even though the shared UI is powered by Blazor, the implementations should vary. So what’s called for from a shared codebase is a common API surface for consumption, with platform-specific differences in implementations. Sounds very much like what a key programming metaphor is built for—yup, interfaces.

Interfaces Help

Let’s say we’ve got Blazor powering a piece of UI for web and also native apps through .NET MAUI. We want to show information about the name of device the app is running—this is very platform-specific across web and mobile/desktop. And we want the window size in which our app is rendering—also very different connotations between web browsers and the .NET MAUI hosted WebView inside a mobile/desktop app.

We want to keep our code clean with a consistent API, but obviously have different implementations across platforms.

The first step might be to define an interface in our shared UI library, like so:

Circled under BlazorSharedUI is SharedServices with IPlatformInfo.cs

using System;

namespace BlazorSharedUI.SharedServices
{
    public interface IPlatformInfo
    {
        public string GetPlatformName();
        public Task<string> GetWindowSize();
    }
}

Web Implementation

Now, let us look to implement our interface for Blazor UI running on the web, AKA in a web browser. The easiest way to get the window dimensions of a browser is through JavaScript—so let’s write some in a simple little file dropped in our project.

Circled under BlazorForWeb.Client are Services with BrowserService.cs and PlatformInfo.cs, and WindowDimension.js under wwwroot

window.getDimensions = function() {
    return {
    width: window.innerWidth,
            height: window.innerHeight
        };
};

How does our Blazor C# code talk to JavaScript? Simple—it is built into Blazor runtime as a JavaScript Interop. We can invoke JS from .NET and .NET from JS—let’s use our newly defined JS function inside BrowserService class, like so:

using System;
using Microsoft.JSInterop;
using System.Threading.Tasks;

namespace BlazorForWeb.Client.Services
{
    public class BrowserService
    {
        private readonly IJSRuntime _js;

        public BrowserService(IJSRuntime js)
        {
            _js = js;
        }

        public async Task<BrowserDimension> GetDimensions()
        {
            return await _js.InvokeAsync<BrowserDimension>("getDimensions");
        }

    }

    public class BrowserDimension
    {
        public int Width { get; set; }
        public int Height { get; set; }
    }
}

Now we can get to implementing the Interface for Blazor code running on web browsers—the platform name is given and we can retrieve browser window dimension from BrowserService, which in turn invokes our JS function.

using System;
using BlazorSharedUI.SharedServices;
using System.Threading.Tasks;

namespace BlazorForWeb.Client.Services
{
    public class PlatformInfo : IPlatformInfo
    {
        BrowserService myService;

        public PlatformInfo(BrowserService service)
        {
            myService = service;
        }

        public string GetPlatformName()
        {
            return "Browser";
        }

        public async Task<string> GetWindowSize()
        {
            var dimension = await myService.GetDimensions();
            int Height = dimension.Height;
            int Width = dimension.Width;
            string WindowDimension = Width + "x" + Height;

            return WindowDimension;
        }
    }
}

Native Implementation

Now let’s get to the interface implementation for Blazor code running through .NET MAUI on mobile/desktop. Even though Blazor UI is being rendered inside a browser-based WebView, it is within the shell of a .NET MAUI truly native app.

This is where Blazor and .NET MAUI shine together—they have the exact same .NET runtime. So our Blazor C# code can tap into .NET MAUI APIs—abstracted for each platform the app runs on. Getting to device name and native window dimensions is a piece of cake with .NET MAUI APIs, like so:

Circled under BlazorForNative is Services with PlatformInfo.cs

using System;
using BlazorSharedUI.SharedServices;

namespace BlazorForNative.Services
{
    public class PlatformInfo : IPlatformInfo
    {
        public string GetPlatformName()
        {
            return DeviceInfo.Current.Platform.ToString();
        }

        public Task<string> GetWindowSize()
        {
            double Width = DeviceDisplay.MainDisplayInfo.Width;
            double Height = DeviceDisplay.MainDisplayInfo.Height;

            string WindowSize = Width + "x" + Height;
            return Task.FromResult(WindowSize);
        }
    }
}

Shared UI

With interface implementations done for both web and native apps, it’s time to wire things up so we leverage the implementations. With platform differences tucked away, we can now write generic Blazor markup/C# in our Shared UI project that make use of the consistent Interface APIs.

Circled under BlazorSharedUI Pages with Counter.razor and Index.razor, which contains Index.razor.cs

First, let’s add the device name and window dimensions information to the Index.razor page—this is shared Blazor UI for web and .NET MAUI app.

@page "/" 

<PageTitle>Index</PageTitle>

<h1>Hello, World!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

Blazor running on Platform: @PlatformInfo?.GetPlatformName()
<br />
<br />
Window Dimensions: @_windowDimensions

Instead of writing C# embedded with our HTML markup, we can easily have an Index.razor.cs file that holds the rest of the partial class—conventions are nice. Here we can invoke the Interface methods—the OnAfterRenderAsync() and StateHasChanged() triggers are used so that Blazor’s JS Interop is ready to invoke our JS function for web apps.

using System;
using BlazorSharedUI.SharedServices;
using Microsoft.AspNetCore.Components;

namespace BlazorSharedUI.Pages
{
    public partial class Index
    {
        [Inject]
        private IPlatformInfo? PlatformInfo { get; set; }

        private string? _windowDimensions;

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                _windowDimensions = await PlatformInfo?.GetWindowSize();
                StateHasChanged();
            }
        }
    }
}

Last but not the least, we need to wire up platform-specific interface implementations through dependency injection—here’s how in the Blazor web app’s Program.cs:

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using BlazorForWeb.Client;
using BlazorForWeb.Client.Services;
using BlazorSharedUI.SharedServices;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IPlatformInfo, PlatformInfo>();
builder.Services.AddScoped<BrowserService>();

await builder.Build().RunAsync();

We can now run the Blazor web app locally and voila—Blazor UI renders as expected and we can display platform name and window dimensions.

BlazorEverywhere app with window dimensions 1832x1040

And here is dependency injection providing the interface implementations being wired up in the .NET MAUI with Blazor app’s Program.cs:

using Microsoft.AspNetCore.Components.WebView.Maui;
using BlazorForNative.Data;
using BlazorSharedUI.SharedServices;
using BlazorForNative.Services;

namespace BlazorForNative;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

        builder.Services.AddMauiBlazorWebView();
#if DEBUG
        builder.Services.AddBlazorWebViewDeveloperTools();
#endif

        builder.Services.AddSingleton<WeatherForecastService>();
        builder.Services.AddScoped<IPlatformInfo, PlatformInfo>();

        return builder.Build();
    }
}

We should now be ready to run our .NET MAUI app on any chosen platform—on iOS, Android, Windows, macOS. It’s fun to see the platform specific device name and window dimensions update for various simulators/devices the app runs on—shared Blazor UI tapping into interface implementations and .NET MAUI APIs across platforms.

BlazorEverywhere app with window dimensions 4096x2560

’Tis the Season

It’s that time of the year—the holiday season is upon us. Whatever we celebrate, let’s try to be merry and grateful for what we have. An overzealous you may have bought a lot of wrapping paper for packing gifts for loved ones—reusing is good. While gifts may look the same wrapped up, you would be trying to cater to the people you are packing the gifts for—their tastes matter.

Blazor is understandably the most exciting web framework for .NET developers—allowing C# to run server-side or entirely client-side through WebAssembly. And .NET MAUI ushers in the next generation of native cross-platform development on .NET, effortlessly reaching mobile/desktop platforms from a single codebase. With a modern WebView UI component, .NET MAUI welcomes Blazor to native land—web components and styles can be rendered inside native apps on iOS/Android/Windows/Mac.

Even though Blazor is powering UI/UX across web and native apps, there may be differences to how things work across platforms. Generic interfaces with platform-specific implementations can allow developers to have consistent shared code that happens to work differently across web and mobile/desktop platforms. Sharing across differences is caring and keeps your codebase on the nice list.


Try Telerik

Develop new Blazor apps and modernize legacy web projects in half the time with a high-performing Telerik UI for Blazor Grid and 100+ truly native, easy-to-customize Blazor components to cover any requirement.


Or kickstart your cross-platform application development and modernize legacy projects with best-in-class Telerik UI for .NET MAUI! Code once and build native applications for Windows, macOS, Android and iOS.


Try either (or both!) for free with our 30-day trial and enjoy our industry-leading support.


Try Telerik UI for Blazor Try Telerik UI for .NET MAUI


SamBasu
About the Author

Sam Basu

Sam Basu is a technologist, author, speaker, Microsoft MVP, gadget-lover and Progress Developer Advocate for Telerik products. With a long developer background, he now spends much of his time advocating modern web/mobile/cloud development platforms on Microsoft/Telerik technology stacks. His spare times call for travel, fast cars, cricket and culinary adventures with the family. You can find him on the internet.

Related Posts

Comments

Comments are disabled in preview mode.