本章导读:在建筑领域,经验丰富的工程师不会从零开始设计每一栋房子的每一个部件。相反,他们会使用预制构件——标准化的梁柱、门窗、管道系统——这些组件经过精心设计和反复验证,可以快速组装成安全可靠的建筑。软件工程领域同样如此:优秀的框架应该提供这些"预制构件",让开发者专注于独特的业务逻辑,而不是重复造轮子。本章将带你深入了解 Uno Extensions 这套官方增强包,看看它如何为你的跨平台应用提供企业级的"预制构件",让你的开发效率产生质的飞跃。
在早期的跨平台开发实践中,开发者往往面临着一系列令人头疼的基础设施问题。如何在不同平台上统一日志输出?如何优雅地管理配置文件?如何实现声明式的导航系统?这些问题看似琐碎,却占据了大量的开发时间,而且如果处理不当,会在项目后期成为难以维护的技术债务。
Uno 团队深刻理解这些痛点,于是推出了 Uno Extensions——一套受到 ASP.NET Core 设计思想启发的全家桶工具包。这套扩展包的核心理念非常简单而深刻:让跨平台客户端开发拥有和服务器端开发一样丝滑的架构体验。如果你曾经使用过 ASP.NET Core 的依赖注入、配置系统或日志框架,你会发现 Uno Extensions 提供了完全一致的开发体验。
第一性原理:为什么客户端应用需要像服务器端应用那样复杂的架构?答案在于现代应用的复杂度已经今非昔比。今天的移动应用可能需要调用多个后端 API、处理离线数据同步、支持实时推送通知、实现复杂的导航逻辑……这些需求如果全部手动实现,不仅工作量巨大,而且容易出错。Uno Extensions 提供了一套经过验证的解决方案,让开发者可以站在巨人的肩膀上。Uno Extensions 包含多个独立的 NuGet 包,每个包专注于一个特定的领域。
Uno.Extensions.Hosting 提供了应用生命周期管理的基础设施;Uno.Extensions.Logging 统一了跨平台的日志系统;Uno.Extensions.Configuration 将 ASP.NET Core 风格的配置管理引入客户端;Uno.Extensions.Navigation 实现了声明式的导航系统;Uno.Extensions.Localization 简化了多语言支持的实现。这些组件既可以独立使用,也可以无缝集成,形成一个完整的企业级应用骨架。
Uno.Extensions.Hosting 是整个扩展生态系统的灵魂所在。它将 .NET 的 IHost 概念引入了 Uno 应用,让你能够以声明式的方式配置应用的各种服务和中间件。这种模式的优雅之处在于,它将复杂的基础设施配置代码从业务逻辑中分离出来,让 App.xaml.cs 变得极其整洁。
// App.xaml.cs - 使用 Uno.Extensions.Hosting 的应用启动代码
// 这段代码展示了如何使用 Host Builder 模式配置应用的各种服务
using Uno.Extensions.Hosting;
public partial class App : Application
{
// 用于跟踪应用是否已初始化
// 在某些平台(如 Android)上,OnLaunched 可能被多次调用
private IHost? _host;
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
// 使用 CreateBuilder 创建主机构建器
// 这会将当前 Application 实例与构建器关联起来
var builder = this.CreateBuilder(args);
// 配置主机服务
// 这是一个链式调用,每个 Use* 方法添加一种服务能力
builder.Configure(host => host
// 启用统一日志系统
// 默认会输出到调试控制台,支持添加更多日志提供程序
.UseLogging(configure: logBuilder =>
{
// 配置日志级别
// 在调试模式下输出详细日志,发布模式下只输出警告和错误
logBuilder.SetMinimumLevel(
LogLevel.Debug
);
})
// 启用配置管理系统
// 支持从 JSON 文件、环境变量、命令行参数读取配置
.UseConfiguration(configure: configBuilder =>
{
// 嵌入式配置:直接在代码中定义默认值
configBuilder
.Section<AppConfig>() // 将配置绑定到强类型对象
.UseAppSettings(); // 读取 appsettings.json
})
// 启用声明式导航系统
// 支持区域导航、深链接、返回按钮自动处理
.UseNavigation(
RegisterRoutes: routes =>
{
// 注册页面与视图模型的映射关系
// 导航时只需要指定视图模型类型,框架会自动创建对应的页面
routes.Register<
MainViewModel, // 视图模型类型
MainPage // 对应的页面类型
>();
routes.Register<
SettingsViewModel,
SettingsPage
>();
routes.Register<
ProfileViewModel,
ProfilePage
>();
}
)
// 启用国际化支持
// 自动检测系统语言,支持运行时切换
.UseLocalization()
// 注册自定义服务到依赖注入容器
.ConfigureServices(services =>
{
// 注册应用级别的服务
services.AddSingleton<IDataService, DataService>();
services.AddSingleton<IAuthService, AuthService>();
// 注册视图模型(使用 Transient,每次导航创建新实例)
services.AddTransient<MainViewModel>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<ProfileViewModel>();
})
);
// 构建并运行主机
// Build() 创建 IHost 实例
// RunAsync() 启动主机并返回一个 Task
_ = builder.Build().RunAsync();
}
}
Host Builder 模式的核心思想是将应用的配置与服务注册集中管理。在传统的 Uno 应用中,你可能需要在多个地方初始化不同的服务:日志系统在 App 构造函数中初始化,导航系统在 OnLaunched 中配置,依赖注入容器散落在各个角落。这种分散的配置方式不仅难以维护,而且在需要添加新服务时容易遗漏或重复。
使用 Host Builder 模式后,所有的基础设施配置都集中在 Configure 方法中。这种声明式的配置方式让应用的依赖关系一目了然,也使得在不同环境(开发、测试、生产)之间切换配置变得轻而易举。更重要的是,这种模式强制执行了一套优秀的工程化规范,即使团队有新人加入,也能快速理解应用的整体架构。
费曼技巧提问:如果我是一个初学者,如何理解 Host Builder 的价值?想象你在搭建一个复杂的 Lego 模型。没有 Builder 模式时,你需要从一堆零件中自己找、自己拼,很容易遗漏零件或拼错位置。有了 Builder 模式,就像有了详细的组装说明书,每一步该做什么都清清楚楚,最终的产品也更容易保证质量。
在跨平台开发中,日志系统的统一是一个典型的痛点。每个平台都有自己独特的日志输出方式:iOS 使用 NSLog,Android 使用 Logcat,WebAssembly 只能输出到浏览器的 console.log。如果直接使用平台原生 API,你的代码将充斥着条件编译指令,变得难以维护。
Uno.Extensions.Logging 通过抽象层彻底解决了这个问题。你只需要在代码中使用标准的 ILogger<T> 接口,扩展包会自动根据当前运行的平台将日志路由到正确的系统控制台。更强大的是,它支持通过配置一键开启远程日志上传,将日志发送到 Sentry、Application Insights 或其他日志聚合服务。
// 使用依赖注入接收 Logger 实例
// 这是在服务或视图模型中使用日志的标准模式
using Microsoft.Extensions.Logging;
public class DataService : IDataService
{
// 泛型参数 T 指定日志的类别名称
// 通常使用当前类名,这样在日志输出中能清晰地看到来源
private readonly ILogger<DataService> _logger;
// 通过构造函数注入 Logger
// 依赖注入容器会自动解析并创建 Logger 实例
public DataService(ILogger<DataService> logger)
{
_logger = logger;
}
public async Task<IEnumerable<Product>> GetProductsAsync()
{
// 使用 LogInformation 记录信息级别日志
// 大括号 {} 是结构化日志的占位符,会被自动替换为参数值
_logger.LogInformation("开始获取产品列表,页码:{Page},每页数量:{PageSize}", 1, 20);
try
{
// 模拟网络请求
var products = await FetchFromApiAsync();
// 记录操作结果
_logger.LogInformation("成功获取 {Count} 个产品", products.Count());
return products;
}
catch (HttpRequestException ex)
{
// 使用 LogError 记录异常
// 异常对象作为最后一个参数传递,会被完整记录堆栈信息
_logger.LogError(ex, "获取产品列表失败,API 地址:{ApiUrl}", "https://api.example.com/products");
// 重新抛出异常,让上层处理
throw;
}
}
private async Task<IEnumerable<Product>> FetchFromApiAsync()
{
// 实际的 API 调用实现...
await Task.Delay(100); // 模拟网络延迟
return new List<Product> { new Product { Name = "Sample" } };
}
}
// 配置日志提供程序的示例
// 在 App.xaml.cs 的 UseLogging 配置中添加
builder.Configure(host => host
.UseLogging((context, logBuilder) =>
{
// 从配置中读取日志级别
var logLevel = context.Configuration.GetValue<string>("Logging:LogLevel:Default");
// 根据配置设置最低日志级别
logBuilder.SetMinimumLevel(Enum.Parse<LogLevel>(logLevel));
// 在调试模式下,添加调试输出窗口的日志提供程序
logBuilder.AddDebug();
// 添加控制台日志(主要用于 WASM 平台)
logBuilder.AddConsole();
// 可选:添加 Application Insights 远程日志
// 需要安装 Uno.Extensions.Logging.ApplicationInsights 包
// logBuilder.AddApplicationInsights(context.Configuration["ApplicationInsights:InstrumentationKey"]);
})
);
技术术语:结构化日志(Structured Logging)是一种日志记录方法,它将日志消息与参数分离存储,而不是简单地拼接字符串。这种方式的优势在于日志聚合系统可以对参数进行索引和查询,比如"找出所有页码大于 10 的请求"。ILogger使用大括号{}作为占位符来实现结构化日志。
在 Web 开发中,appsettings.json 是一个广受欢迎的配置管理模式。它允许你将敏感的配置信息(如 API 密钥)、环境相关的设置(如服务端点)、以及功能开关集中管理,而不是硬编码在代码中。Uno.Extensions.Configuration 将这种优雅的配置体验带入了 Uno Platform。
// appsettings.json - 应用的配置文件
{
// 应用基本配置
"AppConfig": {
"ApiBaseUrl": "https://api.myservice.com/v1",
"AppVersion": "2.0.0",
"CacheDurationMinutes": 30
},
// 功能开关
"FeatureFlags": {
"EnableDarkMode": true,
"EnablePushNotifications": false,
"EnableBetaFeatures": false
},
// 日志配置
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}
// 定义强类型的配置类
// 这种方式提供了编译时类型检查和智能提示支持
public class AppConfig
{
// API 服务的基础 URL
// 所有 API 请求都会基于这个地址构建完整 URL
public string ApiBaseUrl { get; set; } = string.Empty;
// 应用版本号
// 可用于版本检查和更新提示
public string AppVersion { get; set; } = string.Empty;
// 缓存有效期(分钟)
// 数据缓存的默认过期时间
public int CacheDurationMinutes { get; set; } = 30;
}
public class FeatureFlags
{
// 是否启用深色模式
public bool EnableDarkMode { get; set; }
// 是否启用推送通知
public bool EnablePushNotifications { get; set; }
// 是否启用测试版功能
// 这些功能可能不稳定,默认关闭
public bool EnableBetaFeatures { get; set; }
}
// 在服务中使用配置
// 通过依赖注入接收配置对象
using Microsoft.Extensions.Options;
public class ApiService : IApiService
{
private readonly AppConfig _config;
private readonly FeatureFlags _features;
private readonly ILogger<ApiService> _logger;
// 使用 IOptions<T> 接收配置
// IOptions<T> 会在配置文件变化时自动更新值
public ApiService(
IOptions<AppConfig> config,
IOptions<FeatureFlags> features,
ILogger<ApiService> logger)
{
_config = config.Value; // 通过 .Value 获取配置对象
_features = features.Value;
_logger = logger;
}
public async Task<bool> CheckFeatureAvailabilityAsync(string featureName)
{
// 使用配置值进行条件判断
if (featureName == "BetaFeature" && !_features.EnableBetaFeatures)
{
_logger.LogWarning("测试版功能已禁用:{Feature}", featureName);
return false;
}
return true;
}
public string GetApiUrl(string endpoint)
{
// 使用配置中的基础 URL 构建完整地址
return $"{_config.ApiBaseUrl}/{endpoint}";
}
}
第一性原理:为什么需要配置系统而不是直接使用常量?核心原因在于"关注点分离"。配置值可能会因环境(开发、测试、生产)而变化,可能会因运营需求而调整,这些变化不应该触发代码重新编译和部署。配置系统将"变化的值"与"变化的逻辑"分离,让应用能够通过外部配置适应不同的运行环境。
虽然 WinUI 提供了丰富的基础控件,但移动应用通常需要更具"移动端风味"的交互体验。Uno.Toolkit.UI 包补齐了这些短板,提供了一系列经过精心设计的控件和辅助功能,让你的应用在各平台上都能提供原生级别的用户体验。
移动应用的导航模式与桌面应用有着显著差异。底部标签栏(TabBar)是 iOS 和 Android 应用的标准导航模式,而顶部导航栏(NavigationBar)则提供了页面级别的标题和操作按钮。Uno.Toolkit.UI 中的这些控件不仅外观精美,更重要的是它们能够与 Uno.Extensions.Navigation 无缝协作。
<!-- ShellPage.xaml - 应用主框架页面 -->
<!-- 这个页面定义了应用的整体布局:顶部导航栏 + 内容区域 + 底部标签栏 -->
<Page x:Class="MyApp.ShellPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:uen="using:Uno.Extensions.Navigation.UI">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <!-- 导航栏 -->
<RowDefinition Height="*" /> <!-- 内容区域 -->
<RowDefinition Height="Auto" /> <!-- 标签栏 -->
</Grid.RowDefinitions>
<!-- 顶部导航栏 -->
<!-- 会自动显示当前页面的标题和返回按钮 -->
<utu:NavigationBar Grid.Row="0"
Content="{x:Bind ViewModel.Title, Mode=OneWay}"
MainCommandMode="Action">
<!-- 主命令(通常是返回按钮)的点击事件 -->
<utu:NavigationBar.MainCommand>
<AppBarButton Symbol="Back"
Command="{x:Bind ViewModel.GoBackCommand}" />
</utu:NavigationBar.MainCommand>
<!-- 右侧操作按钮 -->
<utu:NavigationBar.PrimaryCommands>
<AppBarButton Icon="Setting"
ToolTipService.ToolTip="设置"
Command="{x:Bind ViewModel.NavigateToSettingsCommand}" />
</utu:NavigationBar.PrimaryCommands>
</utu:NavigationBar>
<!-- 内容区域 -->
<!-- NavigationRegion 是 Uno Extensions 导航系统的占位符 -->
<!-- 导航框架会在这里加载对应的页面 -->
<Grid Grid.Row="1"
uen:Region.Navigator="Visible">
<!-- 初始内容可以是加载提示或空白 -->
<TextBlock Text="加载中..."
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{x:Bind ViewModel.IsLoading, Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
<!-- 底部标签栏 -->
<!-- 提供 3-5 个主要功能入口 -->
<utu:TabBar Grid.Row="2"
SelectedIndex="{x:Bind ViewModel.SelectedTabIndex, Mode=TwoWay}"
Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<!-- 首页标签 -->
<utu:TabBarItem uen:Navigation.Request="Home">
<utu:TabBarItem.Content>
<StackPanel Orientation="Vertical" Spacing="4">
<FontIcon Glyph="" FontSize="20" />
<TextBlock Text="首页" FontSize="12" />
</StackPanel>
</utu:TabBarItem.Content>
</utu:TabBarItem>
<!-- 搜索标签 -->
<utu:TabBarItem uen:Navigation.Request="Search">
<StackPanel Orientation="Vertical" Spacing="4">
<FontIcon Glyph="" FontSize="20" />
<TextBlock Text="搜索" FontSize="12" />
</StackPanel>
</utu:TabBarItem>
<!-- 个人中心标签 -->
<utu:TabBarItem uen:Navigation.Request="Profile">
<StackPanel Orientation="Vertical" Spacing="4">
<FontIcon Glyph="" FontSize="20" />
<TextBlock Text="我的" FontSize="12" />
</StackPanel>
</utu:TabBarItem>
</utu:TabBar>
</Grid>
</Page>
Toolkit 还提供了一系列实用的响应式属性,其中最常用的是 SafeArea 相关功能。现代手机屏幕形态各异:iPhone 有"刘海"和"灵动岛",Android 手机有各种形状的打孔摄像头,底部还有虚拟导航栏。这些屏幕特性如果不加处理,很容易导致内容被遮挡。
<!-- 使用 SafeArea 处理屏幕安全区域 -->
<!-- 确保 UI 内容不会被系统 UI 元素遮挡 -->
<Page x:Class="MyApp.MainPage"
xmlns:utu="using:Uno.Toolkit.UI">
<!-- 方式一:使用 SafeArea 附加属性 -->
<!-- SafeArea.Inset 指定要避开哪些边 -->
<Grid utu:SafeArea.Inset="VisibleBounds">
<!-- 这个 Grid 会自动添加内边距,避开刘海和底栏 -->
<TextBlock Text="这段文字不会被刘海遮挡"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
</Grid>
<!-- 方式二:使用 ResponsiveView 根据屏幕宽度调整布局 -->
<utu:ResponsiveView>
<!-- Narrow 布局:手机竖屏 -->
<utu:ResponsiveView.NarrowTemplate>
<DataTemplate>
<StackPanel Spacing="10">
<TextBlock Text="手机布局" Style="{StaticResource TitleTextBlockStyle}" />
<!-- 垂直排列的内容 -->
</StackPanel>
</DataTemplate>
</utu:ResponsiveView.NarrowTemplate>
<!-- Wide 布局:平板或桌面 -->
<utu:ResponsiveView.WideTemplate>
<DataTemplate>
<Grid ColumnSpacing="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 并排显示的内容 -->
<TextBlock Grid.Column="0" Text="左侧内容" />
<TextBlock Grid.Column="1" Text="右侧内容" />
</Grid>
</DataTemplate>
</utu:ResponsiveView.WideTemplate>
</utu:ResponsiveView>
</Page>
费曼技巧提问:什么是"安全区域"?想象你在一张纸上画画,但这张纸的边缘有一些污渍或破损。为了确保你的画作完整美观,你需要在距离边缘一定距离的地方开始画——这个"安全距离"围成的区域就是"安全区域"。在手机屏幕上,刘海、底栏等系统元素就像纸边缘的污渍,SafeArea 帮你自动计算并避开这些区域。
理论知识的学习最终要落实到实践中。让我们通过一个完整的示例,展示如何使用 Uno Extensions 构建一个企业级应用的骨架。这个骨架包含了 MVVM 架构、依赖注入、导航系统、配置管理等核心要素。
// App.xaml.cs - 完整的企业级应用配置
// 这是一个整合了所有常用扩展的参考实现
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Uno.Extensions.Hosting;
using Uno.Extensions.Logging;
using Uno.Extensions.Navigation;
using Uno.Extensions.Configuration;
using Uno.Extensions.Localization;
public partial class App : Application
{
private IHost? _host;
private Window? _window;
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
// 创建窗口
_window = new Window();
var builder = this.CreateBuilder(args);
builder.Configure(host => host
// 1. 配置日志
.UseLogging((context, builder) =>
{
builder.AddDebug();
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug);
})
// 2. 配置设置
.UseConfiguration(configure: builder =>
{
builder.Section<AppConfig>();
builder.Section<FeatureFlags>();
builder.UseAppSettings();
builder.UseEnvironmentDefaults(); // 加载环境相关配置
})
// 3. 配置本地化
.UseLocalization()
// 4. 配置导航
.UseNavigation(ReactiveViewModelMappings.ViewModelMappings,
RegisterRoutes: routes =>
{
// 主导航路由
routes.Register<ShellViewModel, ShellPage>();
routes.Register<HomeViewModel, HomePage>();
routes.Register<SettingsViewModel, SettingsPage>();
routes.Register<ProfileViewModel, ProfilePage>();
// 对话框路由(使用 Dialog 作为导航模式)
routes.RegisterDialog<ConfirmDialogViewModel, ConfirmDialogContent>();
})
// 5. 注册服务
.ConfigureServices(services =>
{
// 单例服务(整个应用生命周期内只创建一次)
services.AddSingleton<IAuthService, AuthService>();
services.AddSingleton<IDataService, DataService>();
services.AddSingleton<ICacheService, CacheService>();
// 作用域服务(每次导航创建新作用域)
services.AddScoped<IUserContext, UserContext>();
// 视图模型(Transient = 每次请求创建新实例)
services.AddTransient<ShellViewModel>();
services.AddTransient<HomeViewModel>();
services.AddTransient<SettingsViewModel>();
services.AddTransient<ProfileViewModel>();
services.AddTransient<ConfirmDialogViewModel>();
})
);
// 构建主机
_host = builder.Build();
// 初始化窗口
_window.Content = new ShellPage { DataContext = _host.Services.GetRequiredService<ShellViewModel>() };
_window.Activate();
// 启动主机
_ = _host.RunAsync();
}
}
// ShellViewModel.cs - 主框架视图模型
// 展示了如何在 ViewModel 中使用导航服务
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Uno.Extensions.Navigation;
public partial class ShellViewModel : ObservableObject
{
private readonly INavigator _navigator;
private readonly IAuthService _authService;
private readonly ILogger<ShellViewModel> _logger;
// 当前选中的标签索引(用于双向绑定)
[ObservableProperty]
private int _selectedTabIndex;
public ShellViewModel(
INavigator navigator,
IAuthService authService,
ILogger<ShellViewModel> logger)
{
_navigator = navigator;
_authService = authService;
_logger = logger;
// 初始化时导航到首页
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
_logger.LogInformation("正在初始化应用...");
// 检查用户登录状态
var isLoggedIn = await _authService.IsAuthenticatedAsync();
if (isLoggedIn)
{
// 已登录,导航到首页
await _navigator.NavigateViewModelAsync<HomeViewModel>(this);
}
else
{
// 未登录,导航到登录页
await _navigator.NavigateViewModelAsync<LoginViewModel>(this);
}
_logger.LogInformation("应用初始化完成");
}
// 当选中的标签变化时,自动导航到对应页面
partial void OnSelectedTabIndexChanged(int value)
{
_ = NavigateToTabAsync(value);
}
private async Task NavigateToTabAsync(int tabIndex)
{
var targetViewModel = tabIndex switch
{
0 => typeof(HomeViewModel),
1 => typeof(SearchViewModel),
2 => typeof(ProfileViewModel),
_ => typeof(HomeViewModel)
};
await _navigator.NavigateViewModelAsync(this, targetViewModel);
}
}
在一个成熟的 Uno 扩展项目中,你会发现代码的组织结构清晰而优雅。视图模型是纯净的,它们只包含业务逻辑和状态管理,没有平台特定的代码。所有的服务都通过接口注入,这使得单元测试变得非常容易——你只需要用模拟实现替换真实服务。UI 是响应式的,能够自动适配各种屏幕尺寸和方向。这些优秀的设计原则共同构成了一个可维护、可测试、可扩展的企业级应用架构。
本章我们深入探索了 Uno Extensions 这套官方增强包的强大功能。从 Hosting 模块提供的应用生命周期管理,到 Logging 模块实现的跨平台统一日志,从 Configuration 模块带来的强类型配置管理,到 Toolkit.UI 提供的移动端优化控件——每一个模块都解决了一个特定的架构痛点,共同构建了一个企业级应用的基础设施。
Uno Extensions 的真正价值不仅在于它提供了哪些功能,更在于它强制执行了一套优秀的工程化规范。当你使用这套扩展包时,依赖注入、关注点分离、配置与代码分离这些软件工程的最佳实践会自然而然地融入你的代码中。这不仅提高了开发效率,更重要的是提高了代码的可维护性和可测试性。
在下一章中,我们将讨论如何保证这套架构的稳定性——探索 测试驱动开发(TDD)与单元测试 在 Uno Platform 项目中的实践方法,确保你的企业级应用在迭代演进中始终保持高质量。
动手实验:
- 创建一个新的 Uno Platform 项目,使用
dotnet new unoapp -o MyExtensionApp命令。在向导中选择Recommended配置,观察生成的项目结构和App.xaml.cs中的扩展配置。- 为你的应用添加一个
appsettings.json配置文件,定义一些配置项(如 API 地址、功能开关)。创建对应的强类型配置类,并在视图模型中使用这些配置值。- 使用
Uno.Toolkit.UI的TabBar和NavigationBar控件创建一个带有底部导航的应用主框架。实现三个标签页之间的导航切换,确保每个页面都能正确加载和显示。
还没有人回复