.NET MAUI Blazor Hybrid: Οδηγός για Κοινό UI σε Web, Mobile και Desktop

Μάθετε πώς να δημιουργείτε cross-platform εφαρμογές με .NET MAUI Blazor Hybrid. Κοινό UI μεταξύ web και native, πρόσβαση σε native APIs μέσω Razor components, dependency injection, και πρακτικά παραδείγματα με .NET 10.

Εισαγωγή

Φανταστείτε να γράφετε ένα σετ Razor components και αυτά να τρέχουν παντού — Android, iOS, macOS, Windows, ακόμα και στον browser. Χωρίς ξεχωριστά projects για κάθε πλατφόρμα, χωρίς διπλό κώδικα UI, χωρίς ατελείωτο headache. Ακούγεται πολύ καλό για να είναι αληθινό; Κι όμως, αυτό ακριβώς προσφέρει το .NET MAUI Blazor Hybrid.

Αν έρχεστε από τον κόσμο του Blazor, γνωρίζετε ήδη τη δύναμη των Razor components. Αν πάλι έρχεστε από το .NET MAUI, ξέρετε τι σημαίνει native πρόσβαση σε κάμερα, GPS και αισθητήρες. Το Blazor Hybrid ενώνει αυτούς τους δύο κόσμους: παίρνετε web UI (HTML, CSS, Razor) μέσα σε μια native εφαρμογή, χωρίς WebAssembly, χωρίς server — τα πάντα τρέχουν τοπικά στη συσκευή.

Ειλικρινά, όταν δοκίμασα πρώτη φορά αυτή την προσέγγιση, εντυπωσιάστηκα με το πόσο απλά δουλεύει.

Σε αυτόν τον οδηγό θα δούμε βήμα-βήμα πώς δουλεύει η αρχιτεκτονική Blazor Hybrid, πώς στήνετε ένα project από το μηδέν, πώς μοιράζεστε UI μεταξύ web και native εφαρμογών μέσω Razor Class Library, και πώς αξιοποιείτε native APIs (κάμερα, τοποθεσία) μέσα από Razor components. Κάθε ενότητα συνοδεύεται από πρακτικά παραδείγματα κώδικα — ready to copy-paste στα δικά σας projects.

Πώς Λειτουργεί το Blazor Hybrid

Ας ξεκαθαρίσουμε κάτι κρίσιμο εξαρχής: στο Blazor Hybrid, τα Razor components δεν τρέχουν στον browser. Δεν υπάρχει WebAssembly. Δεν υπάρχει web server. Τα components τρέχουν native, στο ίδιο .NET runtime με την υπόλοιπη MAUI εφαρμογή σας, και απλώς κάνουν render σε ένα embedded WebView.

Η αρχιτεκτονική βασίζεται σε τρία βασικά στοιχεία:

  • BlazorWebView: Ένα MAUI control που φιλοξενεί το embedded WebView και συνδέει τα Razor components με τη native εφαρμογή.
  • Razor Components: Τα UI components γραμμένα σε HTML/CSS/C# που κάνουν render μέσα στο WebView.
  • Local Interop Channel: Ο μηχανισμός επικοινωνίας μεταξύ native .NET κώδικα και WebView — χωρίς HTTP, χωρίς latency.

Τι σημαίνει αυτό στην πράξη; Ότι τα Razor components έχουν πλήρη πρόσβαση σε native APIs μέσω του .NET platform. Θέλετε να διαβάσετε GPS; Να τραβήξετε φωτογραφία; Να στείλετε push notification; Μπορείτε — απευθείας μέσα από τον C# κώδικα του component σας.

Η Διαφορά από Blazor Server και Blazor WebAssembly

Για να μην μπερδευτούμε, ιδού μια γρήγορη σύγκριση:

  • Blazor Server: Τα components τρέχουν στον server, τα UI updates στέλνονται μέσω SignalR. Απαιτεί σύνδεση δικτύου.
  • Blazor WebAssembly: Τα components τρέχουν στον browser μέσω WebAssembly. Περιορισμένη πρόσβαση σε native APIs.
  • Blazor Hybrid: Τα components τρέχουν native στη συσκευή. Πλήρης πρόσβαση σε native APIs. Δεν χρειάζεται internet — τελεία και παύλα.

Δημιουργία Blazor Hybrid Project

Λοιπόν, ας πιάσουμε δουλειά. Στο .NET 10, η Microsoft παρέχει ένα ειδικό solution template που δημιουργεί ταυτόχρονα ένα MAUI Blazor Hybrid app και ένα Blazor Web App, μοιράζοντας UI μέσω κοινής Razor Class Library.

Δημιουργία Νέου Project μέσω CLI

# Δημιουργία .NET MAUI Blazor Hybrid + Web App solution
dotnet new maui-blazor-web -n MyHybridApp

# Η δομή που δημιουργείται:
# MyHybridApp/
# ├── MyHybridApp.Maui/          → MAUI Blazor Hybrid app
# ├── MyHybridApp.Web/           → Blazor Web App
# └── MyHybridApp.Shared/        → Razor Class Library (κοινό UI)

Αν θέλετε μόνο native εφαρμογή χωρίς web counterpart, υπάρχει και πιο απλό template:

# Μόνο MAUI Blazor Hybrid (χωρίς Web App)
dotnet new maui-blazor -n MyMauiApp

Δομή του MAUI Project

Το βασικό σημείο εισόδου είναι το MauiProgram.cs, όπου καταχωρούνται services και ρυθμίζεται το Blazor. Ρίξτε μια ματιά:

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

        // Προσθήκη Blazor WebView
        builder.Services.AddMauiBlazorWebView();

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

        // Καταχώρηση services
        builder.Services.AddSingleton<WeatherService>();
        builder.Services.AddSingleton<IConnectivity>(
            Connectivity.Current);

        return builder.Build();
    }
}

Τίποτα τρομακτικό εδώ — αν έχετε δουλέψει με ASP.NET Core, αυτό το pattern σας είναι ήδη γνωστό.

Ρύθμιση του BlazorWebView στη MainPage

Η MainPage.xaml φιλοξενεί το BlazorWebView control, που δείχνει στο root component της Blazor εφαρμογής:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyHybridApp.Maui"
             x:Class="MyHybridApp.Maui.MainPage">

    <BlazorWebView x:Name="blazorWebView"
                   HostPage="wwwroot/index.html">
        <BlazorWebView.RootComponents>
            <RootComponent Selector="#app"
                           ComponentType="{x:Type local:Components.Routes}" />
        </BlazorWebView.RootComponents>
    </BlazorWebView>

</ContentPage>

Το HostPage δείχνει στο static HTML αρχείο που φορτώνεται στο WebView, και ο RootComponent ορίζει ποιο Razor component θα κάνει render μέσα στο element με id #app. Αρκετά straightforward.

Κοινό UI με Razor Class Library (RCL)

Εδώ βρίσκεται η πραγματική δύναμη του Blazor Hybrid. Η ιδέα είναι απλή αλλά ισχυρή: γράφετε Razor components μία φορά και τα χρησιμοποιείτε τόσο στη native MAUI εφαρμογή όσο και στη web εφαρμογή σας. Ο μηχανισμός αυτός βασίζεται στη Razor Class Library (RCL).

Δομή της RCL

Η RCL περιέχει τα κοινά Razor components, CSS, και static assets:

MyHybridApp.Shared/
├── Components/
│   ├── Layout/
│   │   ├── MainLayout.razor
│   │   └── NavMenu.razor
│   ├── Pages/
│   │   ├── Home.razor
│   │   ├── Weather.razor
│   │   └── ProductList.razor
│   └── Routes.razor
├── Services/
│   ├── IDeviceService.cs          → Interface (platform abstraction)
│   └── ILocationService.cs        → Interface (platform abstraction)
├── wwwroot/
│   └── css/
│       └── app.css
└── _Imports.razor

Παράδειγμα Κοινού Component

Ένα τυπικό component μέσα στη RCL μοιάζει ακριβώς όπως κάθε Razor component που ξέρετε:

@page "/weather"
@inject WeatherService WeatherSvc
@inject IConnectivity Connectivity

<h1>Πρόβλεψη Καιρού</h1>

@if (_isLoading)
{
    <p>Φόρτωση δεδομένων...</p>
}
else if (_forecasts is not null)
{
    <table class="table">
        <thead>
            <tr>
                <th>Ημερομηνία</th>
                <th>Θερμοκρασία (°C)</th>
                <th>Περίληψη</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in _forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? _forecasts;
    private bool _isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        var networkAccess = Connectivity.NetworkAccess;

        if (networkAccess == NetworkAccess.Internet)
        {
            _forecasts = await WeatherSvc.GetForecastsAsync();
        }

        _isLoading = false;
    }
}

Αυτό το component τρέχει αυτούσιο τόσο στην MAUI εφαρμογή (native) όσο και στο Blazor Web App (browser). Η μαγεία βρίσκεται στο dependency injection — κάθε πλατφόρμα παρέχει τη δική της υλοποίηση για services όπως το IConnectivity. Write once, run everywhere, αλλά αυτή τη φορά στα σοβαρά.

Διαχείριση Render Modes

Εδώ υπάρχει ένα σημαντικό gotcha που πρέπει να γνωρίζετε: οι MAUI εφαρμογές τρέχουν πάντα interactively και θα πετάξουν exception αν ένα Razor component ορίζει ρητά render mode. Αυτό σας έπιασε εξ απήνης; Κι εμένα, την πρώτη φορά.

Η λύση είναι ένα helper class στη RCL:

// Στη RCL - InteractiveRenderSettings.cs
public static class InteractiveRenderSettings
{
    public static IComponentRenderMode? InteractiveServer { get; set; } =
        RenderMode.InteractiveServer;

    public static IComponentRenderMode? InteractiveAuto { get; set; } =
        RenderMode.InteractiveAuto;

    public static IComponentRenderMode? InteractiveWasm { get; set; } =
        RenderMode.InteractiveWebAssembly;
}

// Στο MauiProgram.cs - ακύρωση render modes
InteractiveRenderSettings.InteractiveServer = null;
InteractiveRenderSettings.InteractiveAuto = null;
InteractiveRenderSettings.InteractiveWasm = null;

Με αυτόν τον τρόπο, στο web app χρησιμοποιούνται τα κανονικά render modes, ενώ στη MAUI εφαρμογή αγνοούνται πλήρως. Πολύ κομψή λύση, πρέπει να πω.

Πρόσβαση σε Native APIs από Razor Components

Αυτό είναι ίσως το μεγαλύτερο πλεονέκτημα του Blazor Hybrid σε σχέση με το Blazor WebAssembly: μπορείτε να καλέσετε native APIs απευθείας από τα Razor components σας. Κάμερα, GPS, αισθητήρες, file system — τα πάντα είναι στη διάθεσή σας.

Geolocation: Ανάγνωση Τοποθεσίας

Αντί να πηγαίνετε μέσω browser Geolocation API (και να παλεύετε με permissions και JavaScript interop), χρησιμοποιείτε απευθείας το .NET MAUI Geolocation API:

@page "/location"
@using Microsoft.Maui.Devices.Sensors

<h2>Η Τοποθεσία Μου</h2>

@if (_location is not null)
{
    <p>Latitude: @_location.Latitude</p>
    <p>Longitude: @_location.Longitude</p>
    <p>Ακρίβεια: @_location.Accuracy μέτρα</p>
}
else if (_errorMessage is not null)
{
    <p class="text-danger">@_errorMessage</p>
}

<button class="btn btn-primary" @onclick="GetLocationAsync">
    Εντοπισμός Τοποθεσίας
</button>

@code {
    private Location? _location;
    private string? _errorMessage;

    private async Task GetLocationAsync()
    {
        try
        {
            var request = new GeolocationRequest(
                GeolocationAccuracy.Medium,
                TimeSpan.FromSeconds(10));

            _location = await Geolocation.Default
                .GetLocationAsync(request);
        }
        catch (PermissionException)
        {
            _errorMessage = "Δεν δόθηκε άδεια τοποθεσίας.";
        }
        catch (FeatureNotEnabledException)
        {
            _errorMessage = "Η υπηρεσία τοποθεσίας είναι απενεργοποιημένη.";
        }
    }
}

Αυτός ο κώδικας καλεί απευθείας το native Geolocation API — δεν περνάει από JavaScript ή WebAssembly. Τρέχει ως native .NET κώδικας στη συσκευή, που σημαίνει καλύτερη απόδοση και πιο αξιόπιστη λειτουργία.

Κάμερα: Αρχιτεκτονική με Interface Abstraction

Για πιο σύνθετα native features, η καλύτερη προσέγγιση είναι να ορίσετε ένα interface στη RCL και να παρέχετε platform-specific υλοποίηση. Αυτό κρατάει τον κώδικα καθαρό και testable:

// Στη RCL — ορισμός interface
public interface ICameraService
{
    Task<string?> TakePhotoAsync();
    Task<string?> PickPhotoAsync();
}

// Στο MAUI project — υλοποίηση
public class MauiCameraService : ICameraService
{
    public async Task<string?> TakePhotoAsync()
    {
        if (!MediaPicker.Default.IsCaptureSupported)
            return null;

        var photo = await MediaPicker.Default.CapturePhotoAsync();
        if (photo is null) return null;

        // Αποθήκευση σε τοπικό φάκελο
        var localPath = Path.Combine(
            FileSystem.CacheDirectory, photo.FileName);

        using var sourceStream = await photo.OpenReadAsync();
        using var localFileStream = File.OpenWrite(localPath);
        await sourceStream.CopyToAsync(localFileStream);

        return localPath;
    }

    public async Task<string?> PickPhotoAsync()
    {
        var photo = await MediaPicker.Default.PickPhotoAsync();
        if (photo is null) return null;

        return photo.FullPath;
    }
}

Καταχώρηση στο DI Container

// MauiProgram.cs
builder.Services.AddSingleton<ICameraService, MauiCameraService>();

// Για το Web App — εναλλακτική υλοποίηση
builder.Services.AddScoped<ICameraService, WebCameraService>();

Με αυτό το pattern, το ίδιο Razor component κάνει @inject ICameraService Camera και λαμβάνει διαφορετική υλοποίηση ανάλογα με την πλατφόρμα — χωρίς να αλλάξετε ούτε μία γραμμή κώδικα στο component.

Dependency Injection στο Blazor Hybrid

Η σωστή χρήση dependency injection είναι ο ακρογωνιαίος λίθος μιας καλής Blazor Hybrid αρχιτεκτονικής. Και εδώ, τα πράγματα γίνονται αρκετά ενδιαφέροντα.

Service Lifetimes

Υπάρχει μια σημαντική διαφορά στα lifetimes μεταξύ MAUI και Web που πρέπει να προσέξετε:

  • Singleton: Μία instance σε ολόκληρη τη διάρκεια ζωής της εφαρμογής. Ιδανικό για services που κρατούν κατάσταση (cache, settings).
  • Transient: Νέα instance κάθε φορά που ζητείται. Χρήσιμο για lightweight, stateless services.
  • Scoped: Εδώ είναι το tricky κομμάτι — στο MAUI, ένα scope δημιουργείται ανά BlazorWebView. Στο web, ένα scope ανά HTTP request ή ανά circuit (Blazor Server).

Pattern: Αφαίρεση Platform Differences

Ένα χρήσιμο pattern που χρησιμοποιώ συχνά:

// Στη RCL — common interface
public interface IFormFactor
{
    string GetFormFactor();
    string GetPlatform();
}

// Στο MAUI project
public class MauiFormFactor : IFormFactor
{
    public string GetFormFactor()
    {
        return DeviceInfo.Idiom == DeviceIdiom.Phone
            ? "Phone"
            : DeviceInfo.Idiom == DeviceIdiom.Tablet
                ? "Tablet"
                : "Desktop";
    }

    public string GetPlatform()
    {
        return DeviceInfo.Platform.ToString();
    }
}

// Στο Web project
public class WebFormFactor : IFormFactor
{
    public string GetFormFactor() => "Web";
    public string GetPlatform() => "Browser";
}

// Καταχώρηση
// MauiProgram.cs:
builder.Services.AddSingleton<IFormFactor, MauiFormFactor>();
// Web Program.cs:
builder.Services.AddScoped<IFormFactor, WebFormFactor>();

TryDispatchAsync: Επικοινωνία Native ↔ Blazor

Σε ορισμένες περιπτώσεις χρειάζεται ο native MAUI κώδικας να αλληλεπιδράσει με scoped Blazor services (π.χ. NavigationManager). Εδώ μπαίνει η μέθοδος TryDispatchAsync του BlazorWebView:

// Από native MAUI κώδικα, πλοήγηση μέσω Blazor NavigationManager
await blazorWebView.TryDispatchAsync(sp =>
{
    var nav = sp.GetRequiredService<NavigationManager>();
    nav.NavigateTo("/products/42");
    return ValueTask.CompletedTask;
});

Βολικό, ε; Σας επιτρέπει να γεφυρώσετε τον native και τον web κόσμο χωρίς κόπο.

Νέα Χαρακτηριστικά στο .NET 10

Το .NET 10 φέρνει αρκετές σημαντικές βελτιώσεις στο Blazor Hybrid ecosystem. Ας δούμε τι νέο υπάρχει.

Καθαρότερα XAML Namespaces

Από το .NET 10 Preview 5, τα ενοχλητικά boilerplate xmlns: declarations εξαφανίζονται. Αντί να δηλώνετε ξεχωριστά κάθε namespace στο XAML, τα ορίζετε κεντρικά σε ένα GlobalXmlns.cs αρχείο. Λιγότερος θόρυβος, λιγότερα λάθη — επιτέλους.

HybridWebView Ενισχύσεις

Το HybridWebView αποκτά νέα overload της μεθόδου InvokeJavaScriptAsync που επιτρέπει κλήση JavaScript χωρίς καθορισμό return type. Τα βίντεο σε WebView μπορούν πλέον να παίζουν full-screen με allowfullscreen στο iframe. Μικρές αλλά ευπρόσδεκτες βελτιώσεις.

.NET Aspire Integration

Αυτή ίσως είναι η πιο συναρπαστική προσθήκη: υπάρχει νέο project template που δημιουργεί ένα .NET Aspire service defaults project ειδικά για .NET MAUI. Αυτό φέρνει cloud-native δυνατότητες (telemetry, service discovery, configuration management) απευθείας στις mobile εφαρμογές σας. Για enterprise scenarios, αυτό αλλάζει τα δεδομένα.

MediaPicker Βελτιώσεις

Ο MediaPicker στο .NET 10 υποστηρίζει πλέον επιλογή πολλαπλών αρχείων και συμπίεση εικόνων. Η διαχείριση EXIF metadata γίνεται αυτόματα. Αυτά κάνουν τη δουλειά με κάμερα και gallery σημαντικά πιο εύκολη μέσα από Blazor Hybrid components.

Πρακτικό Παράδειγμα: Todo App με Κοινό UI

Αρκετά με τη θεωρία — ας δούμε ένα ολοκληρωμένο παράδειγμα. Θα φτιάξουμε μια Todo εφαρμογή που τρέχει τόσο σε mobile/desktop (MAUI) όσο και στον browser (Web), με κοινό UI.

Βήμα 1: Ορισμός Interface και Model στη RCL

// Models/TodoItem.cs
public class TodoItem
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

// Services/ITodoService.cs
public interface ITodoService
{
    Task<List<TodoItem>> GetAllAsync();
    Task AddAsync(TodoItem item);
    Task ToggleAsync(int id);
    Task DeleteAsync(int id);
}

Βήμα 2: Razor Component στη RCL

@page "/todos"
@inject ITodoService TodoService

<h2>Λίστα Εργασιών</h2>

<div class="input-group mb-3">
    <input @bind="_newTitle" @bind:event="oninput"
           class="form-control"
           placeholder="Νέα εργασία..." />
    <button class="btn btn-primary"
            @onclick="AddTodoAsync"
            disabled="@string.IsNullOrWhiteSpace(_newTitle)">
        Προσθήκη
    </button>
</div>

<ul class="list-group">
    @foreach (var todo in _todos)
    {
        <li class="list-group-item d-flex align-items-center">
            <input type="checkbox"
                   checked="@todo.IsCompleted"
                   @onchange="() => ToggleAsync(todo.Id)"
                   class="me-2" />
            <span class="@(todo.IsCompleted ? "text-decoration-line-through" : "")">
                @todo.Title
            </span>
            <button class="btn btn-sm btn-outline-danger ms-auto"
                    @onclick="() => DeleteAsync(todo.Id)">
                ✕
            </button>
        </li>
    }
</ul>

@code {
    private List<TodoItem> _todos = new();
    private string _newTitle = string.Empty;

    protected override async Task OnInitializedAsync()
    {
        _todos = await TodoService.GetAllAsync();
    }

    private async Task AddTodoAsync()
    {
        var item = new TodoItem { Title = _newTitle };
        await TodoService.AddAsync(item);
        _todos = await TodoService.GetAllAsync();
        _newTitle = string.Empty;
    }

    private async Task ToggleAsync(int id)
    {
        await TodoService.ToggleAsync(id);
        _todos = await TodoService.GetAllAsync();
    }

    private async Task DeleteAsync(int id)
    {
        await TodoService.DeleteAsync(id);
        _todos = await TodoService.GetAllAsync();
    }
}

Βήμα 3: MAUI Υλοποίηση με SQLite

// MAUI project - SqliteTodoService.cs
public class SqliteTodoService : ITodoService
{
    private readonly SQLiteAsyncConnection _db;

    public SqliteTodoService()
    {
        var dbPath = Path.Combine(
            FileSystem.AppDataDirectory, "todos.db3");
        _db = new SQLiteAsyncConnection(dbPath);
        _db.CreateTableAsync<TodoItem>().Wait();
    }

    public async Task<List<TodoItem>> GetAllAsync()
        => await _db.Table<TodoItem>()
            .OrderByDescending(t => t.CreatedAt)
            .ToListAsync();

    public async Task AddAsync(TodoItem item)
        => await _db.InsertAsync(item);

    public async Task ToggleAsync(int id)
    {
        var item = await _db.GetAsync<TodoItem>(id);
        item.IsCompleted = !item.IsCompleted;
        await _db.UpdateAsync(item);
    }

    public async Task DeleteAsync(int id)
        => await _db.DeleteAsync<TodoItem>(id);
}

// MauiProgram.cs
builder.Services.AddSingleton<ITodoService, SqliteTodoService>();

Στο MAUI, τα δεδομένα αποθηκεύονται τοπικά σε SQLite. Στο Web App, μπορείτε να χρησιμοποιήσετε Entity Framework με SQL Server ή in-memory storage. Το Razor component; Παραμένει ακριβώς το ίδιο. Αυτό είναι το ωραίο με αυτή την αρχιτεκτονική.

Βέλτιστες Πρακτικές

Μετά από αρκετή εμπειρία με Blazor Hybrid projects, ορισμένα tips που θα σας γλιτώσουν χρόνο:

  • Κρατήστε τη RCL ελαφριά: Η RCL πρέπει να περιέχει μόνο UI components και interfaces — ποτέ platform-specific κώδικα. Αυτό είναι μη διαπραγματεύσιμο.
  • Χρησιμοποιήστε interfaces για κάθε native API: Ορίστε abstractions στη RCL, υλοποιήστε ανά πλατφόρμα. Ναι, είναι λίγο παραπάνω δουλειά, αλλά αξίζει μακροπρόθεσμα.
  • Αποφύγετε ρητά render modes σε κοινά components: Χρησιμοποιήστε το InteractiveRenderSettings pattern που είδαμε πιο πάνω.
  • Κάντε Global render mode στο Web App: Ορίστε interactivity σε Global επίπεδο αντί per-component — απλοποιεί τα πράγματα αρκετά.
  • Αξιοποιήστε CSS isolation: Κάθε .razor component μπορεί να έχει αντίστοιχο .razor.css αρχείο στη RCL.
  • Δοκιμάστε σε όλες τις πλατφόρμες: Αυτό που δουλεύει τέλεια στον browser μπορεί να συμπεριφέρεται διαφορετικά στο iOS ή Android λόγω WebView differences. Μην το παραλείψετε.

Συχνές Ερωτήσεις (FAQ)

Ποια είναι η διαφορά μεταξύ Blazor Hybrid και Blazor WebAssembly;

Στο Blazor WebAssembly, ο κώδικας τρέχει μέσα στον browser χρησιμοποιώντας WebAssembly, με περιορισμένη πρόσβαση σε native APIs. Στο Blazor Hybrid, ο κώδικας τρέχει ως native .NET process στη συσκευή, με πλήρη πρόσβαση σε native APIs (κάμερα, GPS, αισθητήρες) μέσω του .NET MAUI runtime. Δεν υπάρχει WebAssembly ούτε web server — όλα τρέχουν τοπικά.

Μπορώ να χρησιμοποιήσω JavaScript βιβλιοθήκες μέσα σε Blazor Hybrid;

Ναι, απολύτως. Εφόσον τα Razor components κάνουν render σε WebView, μπορείτε να χρησιμοποιήσετε JavaScript interop (IJSRuntime) ακριβώς όπως σε οποιαδήποτε Blazor εφαρμογή. Ωστόσο, προτιμάτε τα native .NET APIs όπου γίνεται — είναι πιο αποδοτικά και δεν απαιτούν marshalling μεταξύ managed και unmanaged κώδικα.

Λειτουργεί offline η εφαρμογή Blazor Hybrid;

Φυσικά. Εφόσον τα Razor components τρέχουν τοπικά στη συσκευή χωρίς server, η εφαρμογή λειτουργεί πλήρως offline. Βέβαια, αν κάποιο component χρειάζεται δεδομένα από API, θα πρέπει να φροντίσετε offline caching (π.χ. με SQLite, όπως δείξαμε στο Todo παράδειγμα).

Ποια έκδοση .NET χρειάζομαι;

Το Blazor Hybrid υπάρχει από το .NET 6. Ωστόσο, για τα τελευταία χαρακτηριστικά (HybridWebView βελτιώσεις, Aspire integration, MediaPicker enhancements), θα θέλετε .NET 10 — που τυχαίνει να είναι και LTS release με υποστήριξη 3 ετών.

Πώς μοιράζομαι UI μεταξύ MAUI και Web;

Δημιουργήστε μια Razor Class Library (RCL) που περιέχει τα κοινά components. Προσθέστε αναφορά σε αυτή τη βιβλιοθήκη τόσο από το MAUI project όσο και από το Web project. Ορίστε interfaces στη RCL για platform-specific λειτουργικότητα και παρέχετε ξεχωριστές υλοποιήσεις μέσω DI σε κάθε project. Είδαμε αυτό ακριβώς το pattern σε ολόκληρο τον οδηγό.

Σχετικά με τον Συγγραφέα Editorial Team

Our team of expert writers and editors.