Bread.Mvc

Bread.Mvc 是一款完全支持 Native AOT 的 MVC 框架,搭配同样支持 AOT 的 Avalonia,让你的开发事半功倍。项目开源在 Gitee,欢迎 Star

1. Ioc 容器

IoC容器是 MVC 框架的核心,为了支持AOT,Bread.Mvc 框架选择使用 ZeroIoC 作为 IoC 容器。ZeroIoC 是一款摒弃了反射的 IoC 容器,具有极高的性能并且完全兼容AOT。为了支持 .net 7, 我对 ZeroIoC 代码做了零星修改,重新发布在 Bread.ZeroIoC

1.1 服务注册

由于不能使用反射,ZeroIoc 使用 SourceGenerator 技术在编译期生成注入代码,这个机制依赖 ZeroIoCContainer 来触发。ZeroIoCContainer 是部分类,并声明了 Bootstrap 方法,用户的注入注册代码必须放在这个方法中才会被自动生成。您可以将服务注册类放在项目的不同地方,或者放在不同的项目中。请参见以下代码实现自己的注册类:

using Bread.Mvc;
using ZeroIoC; namespace XDoc.Avalonia; public partial class SessionContainer : ZeroIoCContainer
{
protected override void Bootstrap(IZeroIoCContainerBootstrapper builder)
{
builder.AddSingleton<IAlertBox, AlertPacker>();
builder.AddSingleton<IMessageBox, MessagePacker>();
builder.AddSingleton<IUIDispatcher, MainThreadDispatcher>(); builder.AddSingleton<Session>();
builder.AddSingleton<SessionController>();
}
}

1.2 IoC 容器初始化

需要使用 IoC.Init 方法初始化 IoC 容器,一般推荐在程序启动之前完成服务注册和 IoC 容器的初始化操作。请参见如下代码:

using Bread.Mvc;

IoC.Init(new XDocContainer(), new SessionContainer());

为了帮助理解,可以查看 IoC.Init 函数的源代码,就是将分布在不通地方的多个注册类合并为一个,大致如下所示:

public static void Init(params ZeroIoCContainer[] containers)
{
foreach (var container in containers) {
Resolver.Merge(container);
} Resolver.End();
}

2. MVC 架构

2.1 Command

声明:

用户的输入被抽象为Command,Command 连接用户界面和 Controller。请参见如下代码声明自己的 Command :

public static class AppCommands
{
public static Command Load { get; } = new(nameof(AppCommands), nameof(Load)); public static Command Save { get; } = new(nameof(AppCommands), nameof(Save)); public static AsyncCommand<string, string> ImportAsync { get; } = new(nameof(AppCommands), nameof(ImportAsync)); public static Command Delete { get; } = new(nameof(AppCommands), nameof(Delete));
}

有两种类型的 Command, 普通 Command 和 AsyncCommand。如您所见, AsyncCommand 支持异步操作。

使用:

一般我们我在 xaml 或 axaml 的后缀代码文件中使用 Command,表示响应用户的输入。

private void UiListBox_SelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.AddedItems == null || e.AddedItems.Count == 0) return;
if (e.AddedItems[0] is not ImageItemViewModel img) return;
if (img == _session.CurrentImage) return; SessionCommands.SwitchImage.Execution(img);
} private void UiBtnRight_Click(object? sender, global::Avalonia.Interactivity.RoutedEventArgs e)
{
SessionCommands.NextImage.Execution();
} private void UiBtnLeft_Click(object? sender, global::Avalonia.Interactivity.RoutedEventArgs e)
{
SessionCommands.PreviousImage.Execution();
}

2.2 Controller

Controller 是业务逻辑的入口,您将在这里集中处理程序的各种逻辑。在上面 IoC 注册的例子中,SessionController 就是一个我们自己定义的 Controller 类。

Controller 子类能自动注入已注册过的服务(Model)。请尽可能使用组合模式以防止 Controller 代码体积膨胀。

public class SessionController : Controller, IDisposable
{
readonly AppModel _app;
readonly Session _session;
readonly ProjectModel _prj; SerialTaskQueue<Doc?> _loadTask = new(); public SessionController(AppModel app, Session session, ProjectModel prj)
{
_app = app;
_prj = prj;
_session = session; SessionCommands.SwitchData.Event += SwitchData_Event;
SessionCommands.SwitchDoc.Event += SwitchDoc_Event;
SessionCommands.SwitchImage.Event += SwitchImage_Event; SessionCommands.NextImage.Event += NextImage_Event;
SessionCommands.PreviousImage.Event += PreviousImage_Event; SessionCommands.SaveDoc.Event += SaveDoc_Event;
SessionCommands.NextDoc.Event += NextDoc_Event; _loadTask.Start(); _prj.Loaded += _prj_Loaded;
}
}

有以下几点需要特别注意:

  • 必须继承自 Controller 类才会被 Ioc 初始化时自动实例化(避免没有显式获取时 Command 的 Event 事件不被挂接);
  • 所有Controller都是单例模式,必须使用 AddSingleton 注册,防止 Command 事件挂接后被多次触发;
  • 构造函数中的参数 Model 类也必须在 ZeroIoCContainer 中注册才会自动注入;
  • 相关 Command 的事件处理函数必须写在构造函数中;
  • Command 可挂接在不同的 Controller 中,但是不保证执行顺序;
  • SessionController 实现了 IDisposable 接口,但是无需我们显式调用 Dispose 方法。请在应用程序结束时调用 IoC.Dispose() 清理。

2.3 Model

Model 连结业务逻辑和用户界面。用户输入(鼠标、键盘、触屏动作等)通过 Command 触发 Controller 中的业务流程,

在 Controller 中更新 Model 的属性值,这些修改操作又立即触发用户界面的刷新。

逻辑是闭环的:UI->Command->Controller->Model->UI。

定义:

源代码中对 Model 的定义相当简单,只是声明必须要实现 INotifyPropertyChanged 接口。

public abstract class Model : INotifyPropertyChanged
{
public bool IsDataChanged { get; set; } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged(string name)
{
IsDataChanged = true;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}

声明:

一般我们将 Model 和相关的 Controller 声明在一个类库中,并用 internal set 修饰以防止不必要的外部修改。建议您也只在对应的 Controller 中修改 Model 的属性。不加限制的修改 Model 对象的属性,只会带来更多的屎山代码。

public class ProjectModel : Model
{
public int Volume { get; internal set; } = 3; public RangeList<Volume> Volumes { get; } = new(); public string NewDocFolder { get; internal set; } = string.Empty; public RangeList<NewDoc> NewDocs { get; } = new(); public ProjectModel()
{
}
}

推荐使用 PropertyChanged.Fody 自动实现 INotifyPropertyChanged 接口。

事实上因为实现了 INotifyPropertyChanged 接口, 您可以在xaml直接绑定 Model 中的属性。

使用:

我们使用 Watch 函数监听 Model 属性的变化,Watch 和 UnWatch 函数的原型如下:

public static void Watch(this INotifyPropertyChanged publisher, string propertyName, Action callback);
public static void Watch(this INotifyPropertyChanged publisher, Action callback, params string[] propertyNames);
public static void UnWatch(this INotifyPropertyChanged publisher, string name, Action callback);
public static void UnWatch(this INotifyPropertyChanged publisher, Action callback, params string[] propertyNames);

通常我们在 Window 或者 UserControl 的 Load 代码中完成依赖注入和属性监听。

你可以一次监听一个属性,或同时监听多个属性并在一个 Action 中响应这些属性的变化。

请记住,监听的目的是为了响应业务变化以同步更新用户界面。

private void ImageSlider_Loaded(object? sender, global::Avalonia.Interactivity.RoutedEventArgs e)
{
if (Design.IsDesignMode) return; _session = IoC.Get<Session>(); // 从 IoC 容器中取出实例, Session 必须先注册。
_session.Watch(nameof(Session.CurrentImage), Session_CurrentImage_Changed); // 监听 CurrentImage 属性的变化 uiListBox.ItemsSource = _session.Images; // UI元素直接绑定 Model 中的属性
uiListBox.SelectionChanged += UiListBox_SelectionChanged;
}

3. 其他基础设施

3.1 Avalonia

当您的应用平台是 Avalonia 时,Bread.Mvc.Avalonia 包含一些非常有用的扩展。

IUIDispatcher 接口 :UI线程注入

Bread.Mvc.Avalonia.MainThreadDispatcher 实现了 IUIDispatcher 接口。

因为当属性被外部线程修改时,Watch 机制需要使用这个接口检测当前线程是否在主线程中,并将变更 Invoke 给UI线程,所以您必须在Avalonia应用中注册这个服务。

 builder.AddSingleton<IUIDispatcher, Bread.Mvc.Avalonia.MainThreadDispatcher>();

Reactive

为了简化 Watch 操作,我们为常见的控件准备了更易用的绑定方法。


public interface IEnumDescriptioner<T> where T : Enum
{
string GetDescription(T value);
} public partial class SettingsPanel : UserControl
{
SpotModel _spot = null!; public SettingsPanel()
{
InitializeComponent(); if (Design.IsDesignMode) return; _spot = IoC.Get<SpotModel>(); // combox initted by enum which LanguageHelper implements IEnumDescriptioner
uiComboxLanguage.InitBy(new LanguageHelper(), Language.Chinese,
Language.English, Language.Japanese, Language.Japanese); uiComboxLanguage.BindTo(_spot, m => m.Language); // ComboBox uiNUDAutoSave.BindTo(_app, x => x.AutoSave); // NumericUpDown
uiTbRegCode.BindTo(_app, x => x.RegCode); // TextBox
uiTbFilePath.BindTo(_app, x => x.FilePath); // TextBlock uiSlider.BindTo(_app, x => x.Progress); // Slider uiSwitchAutoSpot.BindTo(_spot, m => m.IsAutoSpot); // SwitchButton
uiTbtnChannel.BindTo(_app, x => x.IsLeftChannel); // ToggleButton uiCheckSexual.BindTo(_app, x => x.IsMale); // CheckBox
}
}

3.2 WPF

3.3 日志

Bread.Utility 中提供了一个简单的日志类 Log。

public static class Log
{
/// <summary>
/// 打开日志
/// </summary>
/// <param name="path">日志文件名称</param>
/// <param name="expire">日志文件目录下最多保存天数。0表示不删除多余日志</param>
/// <exception cref="ArgumentNullException"></exception>
public static void Open(string path, int expire = 0); /// <summary>
/// 关闭日志文件
/// </summary>
public static void Close(); public static void Info(string info, string? category = null,
[CallerFilePath] string? className = null,
[CallerMemberName] string? methondName = null,
[CallerLineNumber] int lineNumber = 0); public static void Warn(string warn, string? category = null,
[CallerFilePath] string? className = null,
[CallerMemberName] string? methondName = null,
[CallerLineNumber] int lineNumber = 0); public static void Error(string error, string? category = null,
[CallerFilePath] string? className = null,
[CallerMemberName] string? methondName = null,
[CallerLineNumber] int lineNumber = 0); public static void Exception(Exception ex);
}

3.4 配置文件读写

内置 Config 类用于 ini 文件读写。

public class CustomController : Controller
{
Config _appConfig;
readonly AppModel _app;
readonly ProjectModel _prj; public AppController(AppModel app, ProjectModel prj)
{
_app = app;
_prj = prj; _appConfig = new Config(Path.Combine(app.AppFolder, "app.data")); AppCommands.Load.Event += Load_Event;
AppCommands.Save.Event += Save_Event;
} private void Load_Event()
{
_appConfig.Load();
_app.LoadFrom(_appConfig);
_prj.LoadFrom(_appConfig);
} private void Save_Event()
{
_app.SaveTo(_appConfig);
_prj.SaveTo(_appConfig);
_appConfig.Save();
}
}
public class AppModel : Model
{
public string Recorder { get; internal set; } = string.Empty; public ReadOnlyCollection<string> RecentList { get { return _recentList.AsReadOnly(); } } List<string> _recentList = new(); public AppModel()
{
} public override void LoadFrom(Config config)
{
config.Load(nameof(AppModel), nameof(Recorder), (string value) => { Recorder = value; }); var list = config.LoadList(nameof(RecentList));
foreach (var item in list) {
if (File.Exists(item)) {
_recentList.Add(item);
}
}
OnPropertyChanged(nameof(RecentList));
} public override void SaveTo(Config config)
{
base.SaveTo(config); config[nameof(AppModel), nameof(Recorder)] = Recorder;
config.SaveList(nameof(RecentList), _recentList);
}
}

4. 限制

  • 只支持 .net 7 及之后的版本;
  • 不支持 asp.net core;

[Bread.Mvc] 开源一款自用 MVC 框架,支持 Native AOT的更多相关文章

  1. 开源:Taurus.MVC-Java 版本框架 (支持javax.servlet.*和jakarta.servlet.*双系列,内集成微服务客户端)

    版本说明: 因为之前有了Taurus.MVC-DotNet 版本框架,因此框架标了-Java后缀. .Net  版本: 开源文章:开源:Taurus.MVC-DotNet 版本框架 (支持.NET C ...

  2. Asp.net Mvc模块化开发之分区扩展框架

    对于一个企业级项目开发,模块化是非常重要的. 默认Mvc框架的AreaRegistration对模块化开发真的支持很好吗?真的有很多复杂系统在使用默认的分区开发的吗?我相信大部分asp.net的技术团 ...

  3. MVC(Model View Controller)框架

    MVC框架 同义词 MVC一般指MVC框架 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一 ...

  4. 几款开源的hybird移动app框架分析

    几款开源的Hybrid移动app框架分析 Ionic Onsen UI 与 ionic 相比 jQuery Mobile Mobile Angular UI 结论 很多移动开发者喜欢使用原生代码开发, ...

  5. 推荐一款开源的C#TCP通讯框架

    原来收费的TCP通讯框架开源了,这是一款国外的开源TCP通信框架,使用了一段时间,感觉不错,介绍给大家 框架名称是networkcomms 作者开发了5年多,目前已经停止开发,对于中小型的应用场景,够 ...

  6. MVC 插件化框架支持原生MVC的Area和路由特性

    .NET MVC 插件化框架支持原生MVC的Area和路由特性 前面开放的源码只是简单的Plugin的实现,支持了插件的热插拔,最近晚上偶然想到,原生的MVC提供Areas和RouteAtrribut ...

  7. 基于存储过程的MVC开源分页控件

    基于存储过程的MVC开源分页控件--LYB.NET.SPPager 摘要 现在基于ASP.NET MVC的分页控件我想大家都不陌生了,百度一下一大箩筐.其中有不少精品,陕北吴旗娃杨涛大哥做的分页控件M ...

  8. MVC+Spring.NET+NHibernate .NET SSH框架整合 C# 委托异步 和 async /await 两种实现的异步 如何消除点击按钮时周围出现的白线? Linq中 AsQueryable(), AsEnumerable()和ToList()的区别和用法

    MVC+Spring.NET+NHibernate .NET SSH框架整合   在JAVA中,SSH框架可谓是无人不晓,就和.NET中的MVC框架一样普及.作为一个初学者,可以感受到.NET出了MV ...

  9. Spring MVC+Spring+Mybatis+MySQL(IDEA)入门框架搭建

    目录 Spring MVC+Spring+Mybatis+MySQL(IDEA)入门框架搭建 0.项目准备 1.数据持久层Mybatis+MySQL 1.1 MySQL数据准备 1.2 Mybatis ...

  10. IDEA02 利用Maven创建Web项目、为Web应用添加Spring框架支持、bean的创建于获取、利用注解配置Bean、自动装配Bean、MVC配置

    1 环境版本说明 Jdk : 1.8 Maven : 3.5 IDEA : 专业版 2017.2 2 环境准备 2.1 Maven安装及其配置 2.2 Tomcat安装及其配置 3 详细步骤 3.1 ...

随机推荐

  1. Go开源世界主流成熟ORM框架gorm实践分享

    @ 目录 概述 定义 核心功能 声明模型与约定 gorm.Model 字段级权限 时间惯例 嵌入结构 字段标签 使用 安装 数据库链接 连接池 CRUD 接口 创建 查询 高级查询 修改 删除 原始S ...

  2. 如何通过Java代码将 PDF文档转为 HTML格式

    虽然PDF文件适合用于打印和发布,但不适合所有类型的文档.例如,包含复杂图表和图形的文档可能无法在PDF中呈现得很好.但是HTML文件可以在任何可运行浏览器的计算机上进行阅读并显示.并且HTML还具有 ...

  3. Python潮流周刊#3:PyPI 的安全问题

    你好,我是豌豆花下猫.这里记录每周值得分享的 Python 及通用技术内容,部分为英文,已在小标题注明.(标题取自其中一则分享,不代表全部内容都是该主题,特此声明.) 文章&教程 1.掌握Py ...

  4. R 语言中常见的 10 个错误,看到第 7 个会不会感觉很神奇?

    翻译:BioIT 爱好者(部分内容有调整)原文:The top 10 R errors, the 7th one will surprise you 就像你学习走路时遇到了一些问题,你在学习 R 的过 ...

  5. Windows/Linux 下功能强大的桌面截图软件

    说到桌面截图软件,很多人首先想到的是 QQ 自带的截图,或者更高级功能更强大的 Snipaste 截图工具. 独立版本的 QQ 截图至少我目前没找到官方正式的下载链接,默认需要安装和打开 QQ 才能使 ...

  6. 手牵手带你实现mini-vue

    1 前言 随着 Vue.React.Angularjs 等框架的诞生,数据驱动视图的理念也深入人心,就 Vue 来说,它拥有着双向数据绑定.虚拟dom.组件化.视图与数据相分离等等造福程序员的优点,那 ...

  7. Python开发中自动化构建项目结构样式

    摘要:在项目开发过程中,一个良好的项目结构对于团队的协作和代码的可维护性起着重要作用.通过使用自动生成项目结构文字样式的工具.不仅节省了手动编写项目结构的麻烦,还确保了结构的一致性和准确性. 本文分享 ...

  8. Hugging News #0703: 在浏览器中运行 Whisper 模型、WAIC 分论坛活动邀请报名

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  9. H5 WebGL实现水波特效

    前言 零几年刚开始玩电脑的时候,经常在安装程序上看到一种水波特效,鼠标划过去的时候,就像用手在水面划过一样,感觉特别有意思.但是后来,就慢慢很少见过这种特效了.最近突然又想起了这种特效,于是开始折磨怎 ...

  10. Auto.Core

    Auto Core (基于AspectCore) 介绍 AutoCore是基于 .Net Standard 2.1用于简化 ASP.NET Core开发,AutoCore 在 AspectCore 的 ...