用 .NET MAUI 10 + VS Copilot 从 0 开发一个签到 App(三)Copilot 生成第一个签到页面(一次没有人工干预的尝试)
用 .NET MAUI 10 + VS Copilot 从 0 开发一个签到 App(三)
一、本文目标
从这一篇开始,我们正式进入业务功能开发。
本文将完成一个最重要的里程碑:
使用 Visual Studio Copilot,从 0 生成一个“可用但不幼稚”的签到页面。
读完本文,你将看到:
- Copilot 在 MAUI + XAML 中能做到什么程度
- 哪些代码“看起来对,但实际不该直接用”
- 一个真实项目中,如何控制 Copilot 的参与边界
二、签到页面的设计目标
在写任何代码之前,先明确目标。
这个签到页面需要做到:
- 一个明确的页面标题
- 一个「签到」按钮
- 当前用户最近一次签到时间
- 不引入复杂 UI 动效
这是一个典型的业务页面,而不是 Demo 展示页。
三、使用 Copilot 生成第一步
1️⃣ Copilot Prompt(原始输入)
我在 MainPage.xaml 中,直接向 Copilot Chat 输入了下面的提示:
制作一个签到app, 要求有身份验证, 多租户, 使用freesql orm
这一步非常关键:
Prompt 写得越清晰,后面需要人工修改的成本就越低。
Copilot回复如下
你的需求是将当前的 .NET MAUI 工程改造成一个支持身份验证、多租户,并使用 FreeSql ORM 的签到应用。
我会分步实现基础结构,优先完成以下内容:
1. 添加 FreeSql ORM 支持。
2. 设计用户、租户、签到相关的数据模型。
3. 实现基础身份验证(如简单登录界面和逻辑)。
4. 多租户支持(用户与租户关联)。
5. 基础签到功能界面和逻辑。
首先,我将获取项目文件列表,确认需要修改和添加的文件。接下来会逐步实现上述功能。
下一步将进行如下操作:
1. 在 MauiProgram.cs 中集成 FreeSql ORM(注册为单例)。
2. 创建数据模型(用户、租户、签到记录)。
3. 添加登录页面和基础身份验证逻辑。
4. 设计多租户结构(用户与租户关联)。
5. 初步实现签到功能界面。
2️⃣ Copilot 生成
Copilot 很快给出了一个完整的文件结构,大致包括:
MauiProgram.csEntities.cs
• 在 MauiProgram.cs 注册了 FreeSql ORM,使用 SQLite 数据库。
• 新建了 Entities.cs,包含租户、用户、签到记录的数据模型。
四、添加登录页面和基础身份验证逻辑,并设计多租户结构与签到功能界面。
继续实现以下内容:
- 登录页面(用户名、密码、租户选择)。
- 登录逻辑(身份验证,用户与租户关联)。
- 登录成功后进入签到页面。
我将先创建登录页面的 XAML 和后台代码,并实现租户列表加载与登录校验。稍后会添加签到页面。

已完成以下步骤:
- 创建了登录页面(LoginPage.xaml 和后台代码),支持租户选择、用户名、密码输入。
- 登录逻辑实现,校验用户与租户信息,登录成功后跳转签到页面。
- 创建了签到页面(SignInPage.xaml 和后台代码),用户可进行签到操作。
- 设置应用入口为登录页面。
你可以直接运行项目,体验多租户身份验证和签到功能。
五、首次运行出错
❌ 问题 1:freesql页面获取实例没弄好, 因为maui10改了一些特性,手工调整了一下ok了
Copilot 有时会:
- 没有最新SDK的训练数据,写出旧版方法
- 默认加入不必要的写法
这会让程序运行不起来,需要手工介入。
❌ 问题 2:当前的 App.xaml.cs 文件中,MainPage 和 CreateWindow(IActivationState?) 都设置了主页面,可能导致 MAUI 导航和窗口初始化冲突。

Copilot 完美的修复了问题
六、登录页面中文乱码
生成的页面编码不对
- 用支持 UTF-8 的编辑器(如 VS、VS Code)打开 LoginPage.xaml。
- 点击右下角编码, 用GBK重新打开, 选择“另存为”,编码选 UTF-8。

七、这一页完成后的状态
到这一步为止,我们已经拥有:
- 一个可运行的登录和签到页面
- 一个结构清晰的 ViewModel
- 没有任何“Demo 味”的临时代码
这也是整个签到 App 的业务起点。
八、Copilot 在这一阶段的真实评价
客观来说:
- Copilot 可以完成 80%~90% 的样板代码
- 但剩下的 10%,决定了项目的质量
尤其是在:
- 新SDK方法
- 可扩展性
这些地方,人类工程师不可替代。
九、Copilot 全生成的签到页面(一次没有人工干预的尝试)
在这一篇中,有一个地方我需要特别说明。
下面这个「签到页面」,包括:
- XAML UI 布局
- Code-behind 页面逻辑(数据写入、导航、权限判断)
全部由 Visual Studio Copilot 一次性生成完成,并非事后整理或人工重构。
我本人只做了三件事:
- 确认代码可以正常编译
- 运行应用验证功能是否正确
- 没有对结构和实现方式做人工调整
这也是我在这个项目中,第一次有意识地“放手”让 Copilot 独立完成一个完整业务页面。
1️⃣ Copilot 生成的 XAML 页面
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SignInMauiApp.SignInPage">
<VerticalStackLayout Padding="30,60" Spacing="20">
<Image HeightRequest="100" WidthRequest="100" Source="logo.jpg" HorizontalOptions="Center" />
<Label x:Name="WelcomeLabel" FontSize="20" HorizontalOptions="Center" />
<Button Text="签到" Clicked="OnSignInClicked" MaximumWidthRequest="600" />
<Label x:Name="SignInResultLabel" FontSize="16" TextColor="Green" IsVisible="False" />
</VerticalStackLayout>
</ContentPage>
这个 UI 非常克制,但已经完全满足一个内部业务 App 的实际需求:
- 页面结构清晰
- 控件命名明确,直接服务于后端逻辑
- 没有过度样式或复杂布局
更重要的是:它是一个“为业务而生”的页面,而不是 Demo 页面。
2️⃣ Copilot 生成的页面逻辑(Code-behind)
public partial class SignInPage : ContentPage
{
private readonly IFreeSql? _fsql;
private readonly User _user;
private readonly Tenant _tenant;
public SignInPage(User user, Tenant tenant)
{
InitializeComponent();
_fsql = IPlatformApplication.Current?.Services.GetService<IFreeSql>();
_user = user;
_tenant = tenant;
WelcomeLabel.Text = $"欢迎 {_user.Username},公司:{_tenant.Name}";
}
private async void OnSignInClicked(object sender, EventArgs e)
{
var record = new SignInRecord
{
UserId = _user.Id,
TenantId = _tenant.Id,
SignInTime = DateTime.Now
};
await _fsql!.Insert(record).ExecuteAffrowsAsync();
SignInResultLabel.Text = $"签到成功:{record.SignInTime:yyyy-MM-dd HH:mm:ss}";
SignInResultLabel.IsVisible = true;
await Navigation.PushAsync(new SignInHistoryPage(_user, _tenant));
}
protected override void OnAppearing()
{
base.OnAppearing();
if (ToolbarItems.All(t => t.Text != "退出登录"))
{
ToolbarItems.Add(new ToolbarItem("退出登录", null, async () => {
await Navigation.PopToRootAsync();
}));
}
if (ToolbarItems.All(t => t.Text != "公司管理"))
{
ToolbarItems.Add(new ToolbarItem("公司管理", null, async () => {
await Navigation.PushAsync(new TenantManagementPage());
}));
}
if (_user.Username == "admin" && ToolbarItems.All(t => t.Text != "签到报表"))
{
ToolbarItems.Add(new ToolbarItem("签到报表", null, async () => {
await Navigation.PushAsync(new SignInReportPage());
}));
}
}
}
这段代码里,有几个非常值得注意的点:
- 自动识别并使用了 多租户字段(TenantId)
- 正确使用了 依赖注入获取 IFreeSql
- 根据用户身份动态添加 Toolbar 操作
- 页面职责清晰,没有引入不必要的抽象
在当下的 MAUI 项目实践中,这已经是一份完全可以直接投入使用的页面实现。
3️⃣ Copilot 为什么能一次写对?
事后复盘我认为原因并不神秘:
- 项目模型命名清晰(User / Tenant / SignInRecord)
- 数据访问层边界明确(FreeSql)
- 页面职责单一,没有复杂交互
Copilot 并不是“凭空理解业务”,而是在清晰工程上下文中做出了合理推断。
4️⃣ 一个刻意的选择:暂不重构为 MVVM
从严格架构角度看,这一页当然可以:
- 拆成 View + ViewModel
- 使用 Command + Binding
但在这个阶段,我选择先验证业务闭环,而不是过早重构。
在真实项目中,这是一个非常常见、也非常合理的决策。
十、下一篇预告
在验证了「Copilot 可生成完整业务页面」之后,下一步就非常自然了。
下一篇将进入:
第 4 篇:第一个页面跑起来之后,我是如何逐步引入 MVVM 的
我们会在真实代码基础上讨论:
- 哪些地方值得重构
- 哪些地方保持简单反而更好
- Copilot 在重构阶段还能帮上什么忙
到这里,这个签到 App 已经不再是 Demo,而是一个真正开始生长的项目。
关联项目
FreeSql QQ群:4336577
BA & Blazor QQ群:795206915
Maui Blazor 中文社区 QQ群:645660665
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。
转载声明
本文来自博客园,作者:周创琳 AlexChow,转载请注明原文链接:https://www.cnblogs.com/densen2014/p/19379853
AlexChow
今日头条 | 博客园 | 知乎 | Gitee | GitHub


浙公网安备 33010602011771号