.NET MAUI Blazor Hybrid: Ръководство за хибридни мобилни и десктоп приложения

Научете как да създавате хибридни мобилни и десктоп приложения с .NET MAUI Blazor Hybrid. Архитектура, споделен UI чрез RCL, достъп до нативни API-та и новостите в .NET 10 с практически примери.

Какво е .NET MAUI Blazor Hybrid и защо набира популярност

Ако сте се чудили дали може да съчетаете нативно мобилно приложение с уеб технологии, без да правите компромиси — отговорът е Blazor Hybrid. Накратко, това е архитектурен подход, при който вграждате Blazor компоненти (HTML, CSS и C#) директно в нативно .NET MAUI приложение чрез WebView контрола.

Вместо да избирате между XAML и браузър — получавате и двете.

Ключовата разлика спрямо Blazor WebAssembly или Blazor Server е, че при Blazor Hybrid няма уеб сървър и няма WebAssembly. Razor компонентите се изпълняват директно в .NET процеса на устройството — „на метала", както обичат да казват в общността. На практика това означава, че вашият Blazor код има пълен достъп до нативните API-та: камера, GPS, сензори, файлова система и каквото още .NET MAUI предоставя.

С .NET 10 и нарастващата подкрепа от Microsoft, Blazor Hybrid се превърна в сериозен избор за екипи, които искат да споделят UI код между уеб и нативни платформи. И честно казано — подходът работи изненадващо добре.

Архитектура на Blazor Hybrid приложение

За да разберете как работи всичко под капака, нека разгледаме основните компоненти на архитектурата.

BlazorWebView контролата

BlazorWebView е .NET MAUI контрола, която хоства Blazor съдържание вътре в нативен WebView. На Android това е WebView, на iOS/macOS — WKWebView, а на Windows — WebView2 (базиран на Chromium). Цялата комуникация между Blazor компонентите и .NET MAUI минава чрез локален interop канал — без HTTP заявки, без латентност. Което е доста готино, ако ме питате.

Razor Class Library (RCL) — споделеният UI слой

Типичната структура на Blazor Hybrid решение включва три проекта:

  • MyApp.MAUI — нативното приложение за Android, iOS, Windows и macOS
  • MyApp.Web — Blazor Web App за браузъра
  • MyApp.Shared (RCL) — Razor Class Library, съдържаща споделените компоненти, услуги и статични ресурси

RCL е сърцето на цялата работа. Тук поставяте Razor компонентите, CSS стиловете и JavaScript файловете, които се използват и от нативното приложение, и от уеб версията. Пишете веднъж, използвате навсякъде — звучи като клише, но тук наистина е така.

Визуална схема на архитектурата

┌─────────────────────────────────────────┐
│            MyApp.Shared (RCL)           │
│  ┌───────────┐ ┌──────────┐ ┌────────┐ │
│  │  Razor    │ │ Services │ │ Models │ │
│  │Components │ │(Interfaces)│ │       │ │
│  └───────────┘ └──────────┘ └────────┘ │
└────────────┬──────────────┬─────────────┘
             │              │
    ┌────────▼─────┐  ┌────▼──────────┐
    │ MyApp.MAUI   │  │  MyApp.Web    │
    │ (Нативно)    │  │  (Браузър)    │
    │ BlazorWebView│  │  Blazor Server│
    │ + Нативни API│  │  / WASM       │
    └──────────────┘  └───────────────┘

Създаване на нов Blazor Hybrid проект стъпка по стъпка

Добре, стига теория. Нека създадем реално Blazor Hybrid приложение от нулата. Ще използваме .NET 10 CLI, но процесът е почти същият и чрез Visual Studio 2026.

Стъпка 1: Създаване на решението

Отворете терминала и изпълнете:

dotnet new maui-blazor-web -n MyHybridApp
cd MyHybridApp

Тази команда създава решение с три проекта: MAUI Blazor Hybrid приложение, Blazor Web App и споделена Razor Class Library. От .NET 9 нататък шаблонът е вграден и автоматично настройва споделения UI — не е нужно да конфигурирате нищо ръчно.

Стъпка 2: Преглед на структурата

След създаването ще видите нещо подобно:

MyHybridApp/
├── MyHybridApp.Maui/           # Нативно MAUI приложение
│   ├── MauiProgram.cs          # DI регистрация и конфигурация
│   ├── MainPage.xaml           # Хост страница с BlazorWebView
│   └── wwwroot/index.html      # Входна HTML точка
├── MyHybridApp.Web/            # Blazor Web App
│   ├── Program.cs              # DI регистрация за уеб
│   └── Components/             # Уеб-специфични компоненти
├── MyHybridApp.Shared/         # Споделена RCL
│   ├── Components/Pages/       # Споделени Razor страници
│   ├── Services/               # Интерфейси на услуги
│   └── wwwroot/                # Споделени статични ресурси
└── MyHybridApp.sln

Стъпка 3: Конфигурация на BlazorWebView в MainPage.xaml

Файлът MainPage.xaml в MAUI проекта съдържа BlazorWebView контролата. Ето как изглежда:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyHybridApp.Maui.MainPage">

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

</ContentPage>

Стъпка 4: Регистрация на услуги в MauiProgram.cs

Тук регистрирате зависимостите за нативното приложение. Обърнете внимание на AddMauiBlazorWebView() — без него нищо няма да тръгне:

using Microsoft.Extensions.Logging;
using MyHybridApp.Shared.Services;

namespace MyHybridApp.Maui;

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();
        builder.Logging.AddDebug();
#endif

        // Регистрация на платформено-специфична имплементация
        builder.Services.AddSingleton<IDeviceService, MauiDeviceService>();
        builder.Services.AddSingleton<ILocationService, MauiLocationService>();

        return builder.Build();
    }
}

Достъп до нативни API-та чрез Dependency Injection

Тук става интересно. Едно от най-мощните предимства на Blazor Hybrid е, че можете да достъпвате нативни API-та директно от Razor компонентите. Ключът е в използването на интерфейси, дефинирани в споделената RCL, с различни имплементации за всяка платформа.

Дефиниране на интерфейс в RCL

В папката Services/ на споделената библиотека създайте интерфейс:

// MyHybridApp.Shared/Services/IDeviceService.cs
namespace MyHybridApp.Shared.Services;

public interface IDeviceService
{
    string GetPlatformName();
    string GetDeviceModel();
    bool IsNativeApp { get; }
}

// MyHybridApp.Shared/Services/ILocationService.cs
namespace MyHybridApp.Shared.Services;

public interface ILocationService
{
    Task<(double Latitude, double Longitude)?> GetCurrentLocationAsync();
}

Имплементация за .NET MAUI

В MAUI проекта създайте конкретната имплементация. Забележете колко лесно е да извикате нативните API-та — DeviceInfo и Geolocation са директно достъпни:

// MyHybridApp.Maui/Services/MauiDeviceService.cs
using MyHybridApp.Shared.Services;

namespace MyHybridApp.Maui.Services;

public class MauiDeviceService : IDeviceService
{
    public string GetPlatformName() => DeviceInfo.Platform.ToString();

    public string GetDeviceModel() => DeviceInfo.Model;

    public bool IsNativeApp => true;
}

// MyHybridApp.Maui/Services/MauiLocationService.cs
using MyHybridApp.Shared.Services;

namespace MyHybridApp.Maui.Services;

public class MauiLocationService : ILocationService
{
    public async Task<(double Latitude, double Longitude)?>
        GetCurrentLocationAsync()
    {
        try
        {
            var location = await Geolocation.Default.GetLocationAsync(
                new GeolocationRequest(GeolocationAccuracy.Medium,
                    TimeSpan.FromSeconds(10)));

            if (location is not null)
                return (location.Latitude, location.Longitude);
        }
        catch (PermissionException)
        {
            // Потребителят е отказал разрешение
        }
        catch (FeatureNotSupportedException)
        {
            // GPS не се поддържа на това устройство
        }

        return null;
    }
}

Имплементация за уеб приложението

В уеб проекта предоставяте различна (и значително по-проста) имплементация:

// MyHybridApp.Web/Services/WebDeviceService.cs
using MyHybridApp.Shared.Services;

namespace MyHybridApp.Web.Services;

public class WebDeviceService : IDeviceService
{
    public string GetPlatformName() => "Web";
    public string GetDeviceModel() => "Browser";
    public bool IsNativeApp => false;
}

Използване в Razor компонент

Сега можете да инжектирате и използвате услугата в споделен Razor компонент. Забележете, че компонентът не знае (и не се интересува) дали работи на телефон или в браузър:

@* MyHybridApp.Shared/Components/Pages/DeviceInfo.razor *@
@page "/device-info"
@using MyHybridApp.Shared.Services
@inject IDeviceService DeviceService
@inject ILocationService LocationService

<h3>Информация за устройството</h3>

<div class="card">
    <div class="card-body">
        <p><strong>Платформа:</strong> @DeviceService.GetPlatformName()</p>
        <p><strong>Модел:</strong> @DeviceService.GetDeviceModel()</p>
        <p><strong>Нативно приложение:</strong> @(DeviceService.IsNativeApp ? "Да" : "Не")</p>
    </div>
</div>

@if (DeviceService.IsNativeApp)
{
    <button class="btn btn-primary mt-3" @onclick="GetLocationAsync">
        Вземи текущата локация
    </button>

    @if (_location is not null)
    {
        <div class="alert alert-success mt-2">
            <p>Ширина: @_location.Value.Latitude</p>
            <p>Дължина: @_location.Value.Longitude</p>
        </div>
    }
}

@code {
    private (double Latitude, double Longitude)? _location;

    private async Task GetLocationAsync()
    {
        _location = await LocationService.GetCurrentLocationAsync();
    }
}

Споделяне на UI между уеб и нативно приложение: практически модели

Един от най-ценните аспекти на Blazor Hybrid е споделянето на UI код. Ето няколко доказани модела, които наистина работят в реални проекти (не само в демота на конференции).

Модел 1: Условно съдържание чрез интерфейси

Когато част от UI трябва да се различава между платформите, инжектирайте интерфейс и рендерирайте условно:

@inject IDeviceService DeviceService

@if (DeviceService.IsNativeApp)
{
    <button @onclick="ScanBarcode">Сканирай баркод</button>
}
else
{
    <InputFile OnChange="HandleFileUpload"
               accept="image/*" />
    <p>Качете снимка на баркода</p>
}

Модел 2: CSS за визуални разлики

За стилови разлики между платформите — използвайте CSS вместо C# логика. По-просто е и по-лесно се поддържа:

/* Основни стилове */
.nav-menu {
    padding: 1rem;
}

/* Стилове само за нативно приложение */
.native-app .nav-menu {
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
}

/* Стилове само за уеб */
.web-app .nav-menu {
    max-width: 1200px;
    margin: 0 auto;
}

Модел 3: InteractiveRenderSettings за рендер режими

Тук има един капан, в който много хора попадат. MAUI приложенията винаги работят интерактивно и ще хвърлят грешка, ако Razor компонент изрично зададе рендер режим. Решението е помощен клас InteractiveRenderSettings:

// В RCL: Helpers/InteractiveRenderSettings.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace MyHybridApp.Shared.Helpers;

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 просто задайте стойностите на null:

// В MauiProgram.cs
InteractiveRenderSettings.InteractiveServer = null;
InteractiveRenderSettings.InteractiveAuto = null;
InteractiveRenderSettings.InteractiveWasm = null;

Елегантно, нали?

Новости в .NET 10 за Blazor Hybrid

.NET 10 донесе доста подобрения, които правят ежедневната работа с Blazor Hybrid по-приятна. Ето какво е ново:

XAML Source Generator

.NET MAUI вече включва source generator за XAML, който ускорява компилацията и дава по-добра IntelliSense поддръжка. На практика генераторът създава силно типизиран код от XAML файловете по време на компилация, което намалява runtime натоварването. Разликата се усеща особено при по-големи проекти.

Подобрения в HybridWebView

  • Извикване на JavaScript без тип на връщаната стойност — нов InvokeJavaScriptAsync overload, който не изисква информация за типа на резултата
  • Препращане на JavaScript изключения — изключения от JS кода автоматично се препращат към .NET и се хвърлят като .NET изключения (най-накрая!)
  • Прихващане на уеб заявки — можете да прихванете заявки от BlazorWebView и HybridWebView, за да модифицирате headers, пренасочите заявки или предоставите локални отговори
  • Събития за инициализация — нови събития за платформено-специфична конфигурация преди и след инициализация

Опростени XAML пространства от имена

.NET 10 Preview 5 въвежда глобални XAML пространства от имена. Това премахва почти всички повтарящи се xmlns: редове от началото на XAML файловете. Ако сте писали MAUI XAML, знаете колко досадни бяха тези редове.

.NET Aspire интеграция

Новият шаблон за .NET Aspire и .NET MAUI предоставя extension методи за телеметрия и service discovery. Дебъгването и наблюдаемостта стават значително по-лесни — особено когато приложението ви комуникира с множество backend услуги.

Кога да изберете Blazor Hybrid вместо чист .NET MAUI

Това е въпросът, който повечето екипи си задават. И отговорът (както винаги) е: зависи.

Изберете Blazor Hybrid когато:

  • Имате съществуващо Blazor уеб приложение и искате да го разширите към мобилни платформи без пренаписване на UI
  • Екипът ви е силен в HTML/CSS и предпочита уеб технологии пред XAML
  • Имате нужда от споделен UI между уеб и нативни приложения
  • Разработвате бизнес приложение (LOB), където функционалността е по-важна от pixel-perfect нативен вид
  • Искате по-лесно тестване — Blazor Hybrid приложенията могат да се тестват и в браузър, което ускорява цикъла доста

Изберете чист .NET MAUI (XAML) когато:

  • Производителността е критична — нативният UI рендеринг си остава по-бърз от WebView
  • Нужен ви е 100% нативен вид — pixel-perfect съответствие с платформените guidelines
  • Не се нуждаете от уеб версия — ако приложението е само мобилно, RCL е излишно усложнение
  • Разработвате consumer приложение, където визуалната идентичност с нативния UI е от първостепенно значение

Сравнителна таблица

Критерий Blazor Hybrid Чист .NET MAUI (XAML)
UI технология HTML/CSS чрез WebView Нативни UI контроли
Производителност Добра, леко по-ниска Най-висока (нативен рендеринг)
Споделяне на код Уеб + нативно Мобилно/десктоп
Крива на учене По-лека (за уеб разработчици) Изисква XAML познания
Достъп до нативни API Да (чрез DI) Да (директно)
Публикуване в магазин Да Да
Най-подходящо за Бизнес приложения, екипи с уеб опит Consumer приложения, максимална производителност

Практически съвети за production-ready Blazor Hybrid приложения

От реалния опит с Blazor Hybrid проекти — ето няколко съвета, които ще ви спестят време (и вероятно няколко часа дебъгване).

1. Задавайте Interactivity на Global

MAUI приложенията винаги работят интерактивно. Ако Razor компонент изрично зададе рендер режим (например @rendermode InteractiveServer), MAUI ще хвърли изключение. Научих го по трудния начин. Използвайте глобален интерактивен режим или помощния клас InteractiveRenderSettings, описан по-горе.

2. Не споделяйте index.html

Входният HTML файл (wwwroot/index.html) е специфичен за всяко приложение и не трябва да се поставя в RCL. MAUI и уеб приложението имат различни изисквания за зареждане — опитът да ги обедините ще доведе до трудни за диагностициране проблеми.

3. Регистрирайте услугите правилно

В MAUI проекта използвайте AddSingleton за услуги, свързани с устройството. В уеб проекта — AddScoped за потребителски контекст:

// MauiProgram.cs
builder.Services.AddSingleton<IDeviceService, MauiDeviceService>();

// Program.cs (Web)
builder.Services.AddScoped<IDeviceService, WebDeviceService>();

4. Внимавайте с навигацията

Навигацията може да стане объркваща, когато комбинирате Blazor маршрутизация (вътре в WebView) с .NET MAUI Shell навигация. Моят съвет: за по-прости приложения поддържайте цялата навигация вътре в Blazor. За по-сложни сценарии — дефинирайте ясни граници между двете системи и не се опитвайте да ги смесвате.

5. Тествайте на истински устройства

WebView контролата се държи различно на различните платформи. Емулаторите са добро начало, но преди да публикувате — задължително тествайте на физически Android и iOS устройства. Ще благодарите на себе си по-късно.

Пример: Минимално Blazor Hybrid приложение с нативен достъп

Нека обединим всичко в завършен пример — приложение, което показва информация за батерията на устройството. Тази функционалност е достъпна само на нативната платформа, което я прави идеална за демонстрация на хибридния подход:

@* Shared/Components/Pages/BatteryStatus.razor *@
@page "/battery"
@inject IDeviceService DeviceService

<h3>Статус на батерията</h3>

@if (DeviceService.IsNativeApp)
{
    <div class="battery-card">
        <p>Ниво: @_batteryLevel%</p>
        <p>Статус: @_batteryState</p>
        <p>Източник: @_powerSource</p>
        <button class="btn btn-outline-primary"
                @onclick="RefreshBatteryInfo">
            Обнови
        </button>
    </div>
}
else
{
    <div class="alert alert-info">
        Информацията за батерията е достъпна само
        в нативното приложение.
    </div>
}

@code {
    private double _batteryLevel;
    private string _batteryState = "";
    private string _powerSource = "";

    protected override void OnInitialized()
    {
        if (DeviceService.IsNativeApp)
        {
            RefreshBatteryInfo();
        }
    }

    private void RefreshBatteryInfo()
    {
        _batteryLevel = Battery.Default.ChargeLevel * 100;
        _batteryState = Battery.Default.State.ToString();
        _powerSource = Battery.Default.PowerSource.ToString();
    }
}

Компонентът работи и в двата контекста: в нативното приложение показва реални данни от батерията, а в браузъра информира потребителя, че функцията е налична само в нативния вариант. Без if/else спагети — просто чист DI.

Често задавани въпроси

Какво е разликата между .NET MAUI Blazor Hybrid и Blazor WebAssembly?

Blazor Hybrid изпълнява Razor компоненти директно в нативен .NET процес на устройството, без WebAssembly и без уеб сървър. Blazor WebAssembly пък изпълнява .NET код в браузъра чрез WebAssembly. Хибридният вариант има пълен достъп до нативни API-та (камера, GPS, файлова система), по-бърз стартов път и може да се публикува в App Store и Google Play. WebAssembly работи изцяло в браузъра и е ограничен от пясъчната му кутия.

Мога ли да споделям UI между уеб и мобилно приложение с Blazor Hybrid?

Да, и точно това е основната причина да изберете Blazor Hybrid. Чрез Razor Class Library (RCL) създавате споделени Razor компоненти, които работят и в нативното MAUI приложение, и в Blazor Web App. Платформено-специфичните разлики се абстрахират чрез интерфейси и Dependency Injection — подходът, който показахме по-горе с IDeviceService.

Какъв е недостатъкът на Blazor Hybrid спрямо чист .NET MAUI?

Основният компромис е производителността. Тъй като UI се рендерира чрез WebView (HTML/CSS), той е леко по-бавен от нативния XAML рендеринг. Навигацията също може да бъде по-сложна при комбиниране на Blazor маршрутизация с .NET MAUI Shell. За приложения, където pixel-perfect нативен вид и максимална производителност са приоритет — чист .NET MAUI е по-добрият избор.

Blazor Hybrid поддържа ли се на всички платформи?

Да, .NET MAUI Blazor Hybrid поддържа Android, iOS, macOS и Windows. На всяка платформа се използва съответната WebView контрола: WebView на Android, WKWebView на iOS/macOS и WebView2 (Chromium) на Windows. За уеб версията използвате стандартен Blazor Web App.

Какви нови функции за Blazor Hybrid носи .NET 10?

.NET 10 подобрява Blazor Hybrid с XAML Source Generator за по-бърза компилация, подобрена HybridWebView с прихващане на уеб заявки и препращане на JS изключения, опростени XAML пространства от имена и нова интеграция с .NET Aspire за телеметрия. Фокусът е върху качеството, developer experience и производителността.

За Автора Editorial Team

Our team of expert writers and editors.