.NET Multi-platform App UI (MAUI) — это кроссплатформенная среда пользовательского интерфейса для создания собственных и современных приложений на C#. Это позволяет разработчикам создавать единую кодовую базу для нескольких платформ. Blazor — это фреймворк веб-интерфейса для создания интерактивных веб-приложений на стороне клиента с помощью .NET. Это позволяет разработчикам писать код C#, который запускается в браузере с помощью WebAssembly. При совместном использовании .NET MAUI и Blazor представляют собой мощную комбинацию для создания кроссплатформенных приложений, которые могут работать на нескольких платформах, включая настольные компьютеры, Интернет и мобильные устройства. В этой статье мы покажем, как создать гибридное приложение Blazor с Dynamsoft Barcode SDK. Приложение сможет сканировать линейные и двухмерные штрих-коды в Windows, macOS, iOS и Android. .
Предпосылки
Начало работы с Blazor WebAssembly
Поскольку компоненты пользовательского интерфейса Blazor могут совместно использоваться проектами Blazor WebAssembly и .NET MAUI Blazor, мы начнем с создания проекта Blazor WebAssembly. Для этого откройте Visual Studio 2022 и создайте новый проект Blazor WebAssembly App.
Чтобы сэкономить время на написании кода для веб-считывателя и сканера штрих-кодов, мы будем использовать репозиторий https://github.com/yushulx/javascript-barcode-qr-code-scanner. В этом репозитории представлены примеры, созданные с использованием Dynamsoft JavaScript Barcode SDK.
Шаги по интеграции пакета SDK штрих-кода JavaScript в проект Blazor WebAssembly следующие:
- Создайте два компонента Razor в папке Pages: Reader.razor и Scanner.razor.
- Скопируйте код пользовательского интерфейса HTML5 из примеров в компоненты Razor.
- Reader.razor: загрузите файл изображения с помощью компонента InputFile и отобразите изображение в элементе img. Элемент canvas используется для рисования местоположения штрих-кода и текста штрих-кода. Элемент p используется для отображения текста штрих-кода.
@page "/barcodereader" @inject IJSRuntime JSRuntime <InputFile OnChange="LoadImage" /> <p class="p-result">@result</p> <div id="imageview"> <img id="image" /> <canvas id="overlay"></canvas> </div> @code { String result = ""; private DotNetObjectReference<Reader> objRef; private async Task LoadImage(InputFileChangeEventArgs e) { result = ""; var imageFile = e.File; var jsImageStream = imageFile.OpenReadStream(1024 * 1024 * 20); var dotnetImageStream = new DotNetStreamReference(jsImageStream); await JSRuntime.InvokeAsync<byte[]>("jsFunctions.setImageUsingStreaming", objRef, "overlay", "image", dotnetImageStream); } protected override void OnInitialized() { objRef = DotNetObjectReference.Create(this); } [JSInvokable] public void ReturnBarcodeResultsAsync(String text) { result = text; StateHasChanged(); } public void Dispose() { objRef?.Dispose(); } }
- Scanner.razor: элемент select используется для выбора источника видео. Элемент div используется для отображения видеопотока. Элемент canvas используется для рисования местоположения штрих-кода и текста штрих-кода.
@page "/barcodescanner" @inject IJSRuntime JSRuntime <div class="select"> <label for="videoSource">Video source: </label> <select id="videoSource"></select> </div> <div id="videoview"> <div class="dce-video-container" id="videoContainer"></div> <canvas id="overlay"></canvas> </div> @code { String result = ""; private DotNetObjectReference<Scanner> objRef; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { objRef = DotNetObjectReference.Create(this); await JSRuntime.InvokeAsync<Boolean>("jsFunctions.initScanner", objRef, "videoContainer", "videoSource", "overlay"); } } [JSInvokable] public void ReturnBarcodeResultsAsync(String text) { result = text; StateHasChanged(); } public void Dispose() { objRef?.Dispose(); } }
3. Скопируйте код JavaScript из примеров в файл wwwroot/jsInterop.js.
window.jsFunctions = { setImageUsingStreaming: async function setImageUsingStreaming(dotnetRef, overlayId, imageId, imageStream) { const arrayBuffer = await imageStream.arrayBuffer(); const blob = new Blob([arrayBuffer]); const url = URL.createObjectURL(blob); document.getElementById(imageId).src = url; document.getElementById(imageId).style.display = 'block'; initOverlay(document.getElementById(overlayId)); if (reader) { reader.maxCvsSideLength = 9999 decodeImage(dotnetRef, url, blob); } }, initSDK: async function () { if (reader != null) { return true; } let result = true; try { reader = await Dynamsoft.DBR.BarcodeReader.createInstance(); await reader.updateRuntimeSettings("balance"); } catch (e) { console.log(e); result = false; } return result; }, initScanner: async function(dotnetRef, videoId, selectId, overlayId) { let canvas = document.getElementById(overlayId); initOverlay(canvas); videoSelect = document.getElementById(selectId); videoSelect.onchange = openCamera; dotnetHelper = dotnetRef; try { scanner = await Dynamsoft.DBR.BarcodeScanner.createInstance(); await scanner.setUIElement(document.getElementById(videoId)); await scanner.updateRuntimeSettings("speed"); let cameras = await scanner.getAllCameras(); listCameras(cameras); await openCamera(); scanner.onFrameRead = results => { showResults(results); }; scanner.onUnduplicatedRead = (txt, result) => { }; scanner.onPlayed = function () { updateResolution(); } await scanner.show(); } catch (e) { console.log(e); result = false; } return true; }, };
Эти функции JavaScript можно вызывать из компонентов Razor. Параметр dotnetRef
используется для вызова методов .NET в компоненте Razor.
4. В файл index.html
добавьте следующий код для загрузки SDK Dynamsoft JavaScript Barcode и файла jsInterop.js.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dbr.js"></script> <script src="jsInterop.js"></script>
5. После этого вы можете запустить приложение Blazor Web Barcode Reader.
Чтобы развернуть проект на GitHub Pages, вы можете использовать следующий файл рабочего процесса:
name: blazorwasm on: push: branches: [ master ] pull_request: branches: [ master ] workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup .NET Core SDK uses: actions/setup-dotnet@v2 with: dotnet-version: '6.0.x' include-prerelease: true - name: Publish .NET Core Project run: dotnet publish BlazorBarcodeSample.csproj -c Release -o release --nologo - name: Change base-tag in index.html from / to blazor-barcode-qrcode-reader-scanner run: sed -i 's/<base href="\/" \/>/<base href="\/blazor-barcode-qrcode-reader-scanner\/" \/>/g' release/wwwroot/index.html - name: copy index.html to 404.html run: cp release/wwwroot/index.html release/wwwroot/404.html - name: Add .nojekyll file run: touch release/wwwroot/.nojekyll - name: Commit wwwroot to GitHub Pages uses: JamesIves/[email protected] with: GITHUB_TOKEN: $ BRANCH: gh-pages FOLDER: release/wwwroot
Пожалуйста, измените BlazorBarcodeSample.csproj
и blazor-barcode-qrcode-reader-scanner
в соответствии с именами вашего проекта и репозитория.
Миграция Blazor WebAssembly в .NET MAUI Blazor
Чтобы создать новый проект .NET MAUI Blazor, выполните следующие действия.
- Сравните структуру проекта .NET MAUI Blazor со структурой Blazor WebAssembly, чтобы понять сходство.
- Скопируйте папки
wwwroot
иPages
из проекта Blazor WebAssembly в новый проект .NET MAUI Blazor, чтобы быстро запустить его.
Важно отметить, что в отличие от веб-приложений, приложения .NET MAUI Blazor — это нативные приложения, которые изолированы и требуют разрешения пользователя для доступа к камере. Поэтому необходимо добавить следующий код C# в файл Scanner.razor
, чтобы запросить разрешение на доступ к камере.
protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { var status = await Permissions.CheckStatusAsync<Permissions.Camera>(); if (status == PermissionStatus.Granted) { isGranted = true; } else { status = await Permissions.RequestAsync<Permissions.Camera>(); if (status == PermissionStatus.Granted) { isGranted = true; } } if (isGranted) { StateHasChanged(); objRef = DotNetObjectReference.Create(this); await JSRuntime.InvokeAsync<Boolean>("jsFunctions.initScanner", objRef, "videoContainer", "videoSource", "overlay"); } } }
Следующим шагом является рассмотрение определенных соображений, связанных с платформой. Поскольку мы работаем с Windows, Android, iOS и macOS, важно отметить, что каждый из них может вести себя по-разному.
Запрос разрешений камеры в .NET MAUI Blazor
Windows
Никаких дополнительных работ не требуется.
Android
- Создайте собственный класс
WebChromeClient
в файлеPlatforms/Android/MyWebChromeClient.cs
:
using Android.Content; using Android.Webkit; namespace BarcodeScanner.Platforms.Android { public class MyWebChromeClient : WebChromeClient { private MainActivity _activity; public MyWebChromeClient(Context context) { _activity = context as MainActivity; } public override void OnPermissionRequest(PermissionRequest request) { try { request.Grant(request.GetResources()); base.OnPermissionRequest(request); } catch (Exception ex) { Console.WriteLine(ex); } } public override bool OnShowFileChooser(global::Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams) { base.OnShowFileChooser(webView, filePathCallback, fileChooserParams); return _activity.ChooseFile(filePathCallback, fileChooserParams.CreateIntent(), fileChooserParams.Title); } } }
Вы должны переопределить методы OnPermissionRequest
и OnShowFileChooser
. Метод OnPermissionRequest
используется для предоставления разрешения на доступ к камере. Метод OnShowFileChooser
используется для запуска действия по выбору файла.
2. В файле MainActivity.cs
добавьте следующий код, чтобы получить возвращенный файл изображения и активировать метод обратного вызова:
public class MainActivity : MauiAppCompatActivity { private IValueCallback _filePathCallback; private int _requestCode = 100; protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { if (_requestCode == requestCode) { if (_filePathCallback == null) return; Java.Lang.Object result = FileChooserParams.ParseResult((int)resultCode, data); _filePathCallback.OnReceiveValue(result); } } public bool ChooseFile(IValueCallback filePathCallback, Intent intent, string title) { _filePathCallback = filePathCallback; StartActivityForResult(Intent.CreateChooser(intent, title), _requestCode); return true; } }
3. Создайте файл MauiBlazorWebViewHandler.cs
для настройки пользовательского веб-представления:
namespace BarcodeScanner.Platforms.Android { public class MauiBlazorWebViewHandler : BlazorWebViewHandler { protected override global::Android.Webkit.WebView CreatePlatformView() { var view = base.CreatePlatformView(); view.SetWebChromeClient(new MyWebChromeClient(this.Context)); return view; } } }
4. Зарегистрируйте MauiBlazorWebViewHandler
в файле MauiProgram.cs
:
using Microsoft.AspNetCore.Components.WebView.Maui; #if ANDROID using BarcodeScanner.Platforms.Android; #endif namespace BarcodeScanner; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }).ConfigureMauiHandlers(handlers => { #if ANDROID handlers.AddHandler<BlazorWebView, MauiBlazorWebViewHandler>(); #endif }); builder.Services.AddMauiBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); #endif return builder.Build(); } }
iOS
- Назовите от
BlazorWebView
доwebView:
<?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" xmlns:local="clr-namespace:BarcodeScanner" x:Class="BarcodeScanner.WebContentPage" Title="WebContentPage" BackgroundColor="{DynamicResource PageBackgroundColor}"> <BlazorWebView x:Name="webView" HostPage="wwwroot/index.html"> <BlazorWebView.RootComponents> <RootComponent Selector="#app" ComponentType="{x:Type local:WebContent}" /> </BlazorWebView.RootComponents> </BlazorWebView> </ContentPage>
2. Настройте свойства WKWebView
в соответствующем файле C#:
public partial class WebContentPage : ContentPage { public WebContentPage() { InitializeComponent(); webView.BlazorWebViewInitializing += WebView_BlazorWebViewInitializing; } private void WebView_BlazorWebViewInitializing(object sender, BlazorWebViewInitializingEventArgs e) { #if IOS || MACCATALYST e.Configuration.AllowsInlineMediaPlayback = true; e.Configuration.MediaTypesRequiringUserActionForPlayback = WebKit.WKAudiovisualMediaTypes.None; #endif } }
macOS
На данный момент доступ к камере невозможен в приложении .NET MAUI Blazor на macOS из-за отсутствия поддержки getUserMedia()
в WKWebView.
Создание гибридного приложения для сканирования штрих-кодов с помощью .NET и Web SDK для штрих-кодов
Мы успешно разработали кроссплатформенное приложение для сканирования штрих-кода с использованием .NET MAUI Blazor. Однако логика сканирования штрих-кода реализована в JavaScript, что может повлиять на производительность. Чтобы оптимизировать производительность, рекомендуется использовать собственный SDK штрих-кода .NET, если только нет определенных функций, которые не поддерживаются SDK, например API потока камеры для сценариев обработки изображений.
Для приложений Windows .NET MAUI декодирование штрих-кодов из файлов изображений можно выполнить с помощью страницы содержимого MAUI, а декодирование штрих-кодов из потоков камеры можно выполнить с помощью веб-представления Blazor.
Вот шаги для создания гибридного приложения для сканирования штрих-кода:
- Получите существующий пример проекта .NET MAUI со страницы https://github.com/yushulx/dotnet-barcode-qr-code-sdk/tree/main/example/maui. Проект поддерживает декодирование штрих-кодов из файлов изображений и потоков с камер с помощью BarcodeQRCodeSDK, который является собственным SDK штрих-кода .NET. Проект не может сканировать штрих-коды из потоков камеры в Windows из-за отсутствия API камеры .NET MAUI.
2. Измените файл *.csproj
, сравнив проект .NET MAUI Blazor:
- <Project Sdk="Microsoft.NET.Sdk"> + <Project Sdk="Microsoft.NET.Sdk.Razor"> + <EnableDefaultCssItems>false</EnableDefaultCssItems>
3. Измените файл MauiProgram.cs
, чтобы добавить поддержку BlazorWebView
:
using Microsoft.Maui.Controls.Compatibility.Hosting; using SkiaSharp.Views.Maui.Controls.Hosting; using Microsoft.AspNetCore.Components.WebView.Maui; namespace BarcodeQrScanner; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder.UseSkiaSharp() .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }).UseMauiCompatibility() .ConfigureMauiHandlers((handlers) => { #if ANDROID handlers.AddCompatibilityRenderer(typeof(CameraPreview), typeof(BarcodeQrScanner.Platforms.Android.CameraPreviewRenderer)); #endif #if IOS handlers.AddHandler(typeof(CameraPreview), typeof(BarcodeQrScanner.Platforms.iOS.CameraPreviewRenderer)); #endif }); builder.Services.AddMauiBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); #endif return builder.Build(); } }
4. Скопируйте папки wwwroot
, Pages
, Shared
из проекта .NET MAUI Blazor в проект .NET MAUI.
5. В проекте .NET MAUI Blazor переименуйте Main.razor
в WebContent.razor
и обновите код:
<Router AppAssembly="@typeof(WebContent).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p role="alert">Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router>
Переименуйте MainPage.xaml
в WebContentPage.xaml
и обновите код:
<?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" xmlns:local="clr-namespace:BarcodeQrScanner" x:Class="BarcodeQrScanner.WebContentPage" Title="WebContentPage" BackgroundColor="{DynamicResource PageBackgroundColor}"> <BlazorWebView x:Name="webView" HostPage="wwwroot/index.html"> <BlazorWebView.RootComponents> <RootComponent Selector="#app" ComponentType="{x:Type local:WebContent}" /> </BlazorWebView.RootComponents> </BlazorWebView> </ContentPage>
6. Скопируйте WebContent.razor
, WebContentPage.xaml
и WebContentPage.xaml.cs
в проект .NET MAUI.
7. В файле MainPage.xaml.cs
добавьте следующий код для перехода к файлу WebContentPage:
.
async void OnTakeVideoButtonClicked(object sender, EventArgs e) { if (DeviceInfo.Current.Platform == DevicePlatform.WinUI || DeviceInfo.Current.Platform == DevicePlatform.MacCatalyst) { await Navigation.PushAsync(new WebContentPage()); return; } var status = await Permissions.CheckStatusAsync<Permissions.Camera>(); if (status == PermissionStatus.Granted) { await Navigation.PushAsync(new CameraPage()); } else { status = await Permissions.RequestAsync<Permissions.Camera>(); if (status == PermissionStatus.Granted) { await Navigation.PushAsync(new CameraPage()); } else { await DisplayAlert("Permission needed", "I will need Camera permission for this action", "Ok"); } } }
8. Теперь приложение Windows .NET MAUI может декодировать штрих-коды из файлов изображений и потоков камер с использованием .NET и веб-API соответственно.
Исходный код
- Blazor WebAssembly: https://github.com/yushulx/blazor-barcode-qrcode-reader-scanner
- .NET MAUI Blazor: https://github.com/yushulx/DotNet-MAUI-Blazor-Barcode-Scanner
- Гибридное приложение: https://github.com/yushulx/dotnet-barcode-qr-code-sdk/tree/main/example/maui
Первоначально опубликовано на https://www.dynamsoft.com 12 апреля 2023 г.