Содержание
Blazor Hybrid — это, прежде всего, фреймворк для создания кроссплатформенных приложений. Поэтому не исключены ситуации, когда разрабатываемый вами сервис должен использоваться на всех платформах, но использовать особенности каждой из платформ. В этом случае нам придется каким-либо образом обеспечить вызов кода конкретной платформы.
В качестве примера мы разработаем свой сервис, который будет работать в Windows и Android и, при этом, возвращать различные результаты в зависимости от платформы на которой запускается приложение. Например, пусть сервис показывает текущую дату и время, но в Windows — это будет полная дата и время, а в Android — только дата.
Мультинацеливание в Blazor Hybrid
Прежде, чем мы перейдем к разработке кроссплатформенного сервиса, стоит напомнить некоторые особенности проекта Blazor Hybrid. Когда мы создаем новый проект, то в его структуре находится папка Platforms
В каждой из подпапок находятся файлы (ресурсы, классы и т.д.) специфичные для конкретной платформы. Когда мы собираем приложение под конкретную платформу, то в сборку попадают все файлы, находящиеся за пределами папки Platforms, например, компоненты из папки Components, и только те файлы, которые находятся в папке для платформы. Например, при компиляции под Windows будут использоваться файлы из папки Platforms/Windows, а файлы из папок Platforms/Android, Platforms/iOS и т.д. будут проигнорированы. Такой подход называется мультинацеливанием. И благодаря этому подходу мы можем разрабатывать без лишних сложностей кроссплатформенные сервисы.
Разработка кроссплатформенного сервиса
Создадим новое приложение Blazor Hybrid, добавим в корень проекта папку Services и разместим в ней сервис ITimeService
:
Код сервиса максимально простой:
namespace BlazorPartialServices.Services { public interface ITimeService { public DateTime GetDateTime(); } }
Теперь нам необходимо создать реализации этого сервиса под каждую платформу. Начнем с Windows. Создадим в папке Platforms/Windows
папку Services
и разместим в ней класс TimeService
:
namespace BlazorPartialServices.Platforms.Windows.Services { public class TimeService : ITimeService { public DateTime GetDateTime() { return DateTime.Now; } } }
Теперь создадим папку Services в папке Platforms/Android и разместим в ней класс также с именем TimeService
:
Однако, код класса будет немного другим:
namespace BlazorPartialServices.Platforms.Android.Services { public class TimeService : ITimeService { public DateTime GetDateTime() { return DateTime.Now.Date; } } }
Обратите внимание, что у нас получилось два класса с одинаковыми именами, НО, расположенные в разных пространствах имен.
Теперь нам необходимо зарегистрировать сервис в MauiProgram.cs. Вы можете это сделать обычным образом — добавив следующую строку кода:
builder.Services.AddSingleton<ITimeService, TimeService>();
Но, как только вы зарегистрируете таким образом сервис, то вы, находясь в Windows, автоматически подключите пространство имен, относящееся к Windows. То есть весь код файла будет вот таким:
using BlazorPartialServices.Platforms.Windows.Services; //пространство имен для Windows using BlazorPartialServices.Services; using Microsoft.Extensions.Logging; namespace BlazorPartialServices { 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.AddSingleton<ITimeService, TimeService>(); //регистрация сервиса builder.Services.AddMauiBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); builder.Logging.AddDebug(); #endif return builder.Build(); } } }
Таким образом, наше приложение будет компилироваться только под Windows, а при попытке скомпилировать его под Android мы будем получать ошибку «Пространство имен не обнаружено». Чтобы решить эту проблему мы можем использовать два варианта.
Использование условной компиляции
Для управления условной компиляцией используются четыре директивы препроцессора.
#if
: открывает условную компиляцию, где код компилируется, только если определен указанный символ.#elif
: закрывает предыдущую условную компиляцию и открывает новую на основе того, определен ли указанный символ.#else
: закрывает предыдущую условную компиляцию и открывает новую, если указанный символ не определен.#endif
: закрывает предыдущую условную компиляцию.
Для управления условной компиляцией в Blazor Hybrid уже определены символы, соответствующие определенной операционной системе. Перепишем код MauiProgram.cs, используя условную компиляцию:
#if WINDOWS using BlazorPartialServices.Platforms.Windows.Services; //пространство имен для Windows #elif ANDROID using BlazorPartialServices.Platforms.Android.Services; //пространство имен для Android #endif using BlazorPartialServices.Services; using Microsoft.Extensions.Logging; namespace BlazorPartialServices { 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.AddSingleton<ITimeService, TimeService>(); //регистрация сервиса builder.Services.AddMauiBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); builder.Logging.AddDebug(); #endif return builder.Build(); } } }
Теперь наше приложение будет компилироваться следующим образом:
- если приложение компилируется под Windows, то подключается пространство имен BlazorPartialServices.Platforms.Windows.Services
- если приложение компилируется под Android, то подключается пространство имен BlazorPartialServices.Platforms.Android.Services
Теперь мы можем запустить приложение под двумя операционными системами и получить следующие результаты
Android
Как видите, в различных операционных системах используется свой код сервиса (своя реализация). Однако, использование условной компиляции — это не единственный вариант разработки кроссплатформенного сервиса. Также мы можем задействовать для этой задачи второй вариант — использование разделяемых классов и методов.
Использование разделяемых классов и методов
Условная компиляция — хороший вариант, когда нам требуется использование минимум директив #if
в коде, например, как в нашем случае — директивы условной компиляции потребовались всего в одном месте. Однако, если код реализации сервиса достаточно большой и требуется смешивать код характерный для Windows и Android плюс код, подходящий под обе платформы, то использование условной компиляции может привести к тому, что код класса будет трудночитаемым. В этом случае лучше потратить немного больше времени на разработку и воспользоваться возможностью создания разделяемого класса.
Внесем следующие изменения в наш проект:
1. Добавим в папку Services разделяемый класс TimeService
:
namespace BlazorPartialServices.Services { public partial class TimeService : ITimeService { public partial DateTime GetDateTime(); } }
Этот разделяемый класс реализует наш сервис (интерфейс) и, при этом, метод GetDateTime()
также определен как разделяемый.
2. Изменим классы TimeService
в папках платформ следующим образом:
Android
namespace BlazorPartialServices.Services { public partial class TimeService { public partial DateTime GetDateTime() { return DateTime.Now.Date; } } }
Обратите внимание на то, что помимо того, что мы добавили ключевое слово partial
в объявление класса и метода, мы также изменили название пространства имен. Все части разделяемого класс должны находится в одном пространстве имен. Это важно.
Windows
namespace BlazorPartialServices.Services { public partial class TimeService { public partial DateTime GetDateTime() { return DateTime.Now; } } }
Здесь аналогичные изменения — добавили partial
и изменили пространство имен. Теперь TimeService
будет собираться при компиляции в класс, реализующий интерфейс. Например, под Windows мы получим вот такой класс:
public class TimeService: ITimeService { public DateTime GetDateTime() { return DateTime.Now; } }
3. Изменим файл MauiProgram.cs
using BlazorPartialServices.Services; //одно пространство имен для всех реализаций сервиса using Microsoft.Extensions.Logging; namespace BlazorPartialServices { 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.AddSingleton<ITimeService, TimeService>(); //регистрация сервиса builder.Services.AddMauiBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); builder.Logging.AddDebug(); #endif return builder.Build(); } } }
Теперь нам нет необходимости использовать условную компиляцию, поэтому мы указали пространство имен BlazorPartialServices.Services
, а в качестве реализации сервиса мы указали наш частичный класс:
builder.Services.AddSingleton<ITimeService, TimeService>(); //регистрация сервиса
Теперь компилятор будет собирать класс TimeService
, основываясь на том, под какую платформу собирается приложение. Можно снова запустить приложение под разными платформами и убедиться, что все работает как и ранее.
Итого
В этой части мы разработали сервис, который использует код платформы под которой запускается приложение. Вызов кода платформы в сервисах Blazor Hybrid может осуществляться двумя способами: с использованием условной компиляции и с использованием разделяемых классов и методов.