[Bread.Mvc] 开源一款自用 MVC 框架,支持 Native AOT

这篇具有很好参考价值的文章主要介绍了[Bread.Mvc] 开源一款自用 MVC 框架,支持 Native AOT。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 文件读写。文章来源地址https://www.toymoban.com/news/detail-684730.html

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的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Taurus .Net Core 微服务开源框架:Admin 插件【4-3】 - 配置管理-Mvc【Plugin-MicroService 微服务】

    继上篇:Taurus .Net Core 微服务开源框架:Admin 插件【4-2】 - 配置管理-Mvc【含请求日志打印】 本篇继续介绍下一个内容:  界面如下: 简要说明: 下面对配置进行说明: 必要配置说明: 需要在 appsettings.json 或 web.config 配置该选项,指明类型,如: 其余选项,可采用默认值,

    2024年02月11日
    浏览(31)
  • Taurus .Net Core 微服务开源框架:Admin 插件【4-7】 - 配置管理-Mvc【Plugin-Metric 接口调用次数统计】

    继上篇:Taurus .Net Core 微服务开源框架:Admin 插件【4-6】 - 配置管理-Mvc【Plugin-Doc 接口测试及文档】 本篇继续介绍下一个内容: 配置界面如下: 打开开关时,可以通过访问Metric菜单查看统计项:   默认不统计。 如果为true,则写入硬盘。 时间单位为秒。  配置的是相对路径

    2024年02月04日
    浏览(53)
  • Taurus .Net Core 微服务开源框架:Admin 插件【4-8】 - 配置管理-Mvc【Plugin-Limit 接口访问限制、IP限制、Ack限制】

    继上篇:Taurus .Net Core 微服务开源框架:Admin 插件【4-7】 - 配置管理-Mvc【Plugin-Metric 接口调用次数统计】 本篇继续介绍下一个内容: 配置界面如下: 限制目前提供以下三个类别的限制: 对三种类别限制都有效。 对三种类别限制都有效。 对三种类别限制都有效。 对三种类别

    2024年02月04日
    浏览(34)
  • [框架]Spring MVC框架

    目录 关于Spring MVC框架 Spring MVC框架的依赖项 使用Spring MVC框架接收请求 关于@RequestMapping注解 关于RESTful 关于@RequestParam注解 @RequestParam注解是添加在方法的参数上的,它的作用主要有:   其它 MVC : M odel + V iew + C ontroller   Spring MVC框架主要解决了接收请求、响应结果及相关问

    2024年02月11日
    浏览(37)
  • 自定义 MVC 框架思想

    目录 一、MVC设计模式 1. 什么是MVC 2. 三层架构与MVC的区别 二、自定义MVC框架 1. 为什么要学习自定义MVC框架 2. 自定义MVC的工作原理 3. 自定义MVC框架的优势 三、自定义MVC实例流程 1. mvc三层架构的弊端 2. 自定义MVC的工作流程      2.1 子控制器(分发任务) 2.2 子控制器子类

    2024年02月12日
    浏览(31)
  • MVC框架知识详解

    ✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:Java案例分享专栏 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:MVC框架知识详解

    2024年01月20日
    浏览(27)
  • 关于Spring MVC框架

    目录 Spring MVC框架的作用 关于控制器  关于统一处理异常的方法: 在处理异常的类中,关于处理异常的方法: Knife4j框架 添加依赖 关于Knife4j的配置类: Spring MVC框架的基础依赖项是spring-webmvc。 Spring MVC框架主要解决了接收请求、响应结果及相关的问题。 控制器(Controller)是

    2024年02月05日
    浏览(26)
  • Spring MVC框架

    Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts 2(一

    2024年02月05日
    浏览(33)
  • JavaWeb框架:Spring MVC介绍

    MVC(Model View Controller,模型-视图-控制器) ,作为一种设计模式,用于应用程序的分层开发。 Spring MVC ,由 Spring 框架提供的基于 MVC 设计模式的一个轻量级 Web 开发框架。Spring MVC 提供了一个前端控制器 DispatcherServlet 来分派请求,然后通过配置处理程序映射、视图解析等,使

    2024年02月12日
    浏览(30)
  • PHP Smarty模板如何与MVC框架集成?

    首先,让我们来了解一下这两个工具。PHP Smarty模板是一种模板引擎,它可以帮助我们分离模板和逻辑,让代码更加清晰和易于维护。而MVC(Model-View-Controller)是一种常用的Web应用程序架构模式,它将应用程序分为三个主要组成部分,使得代码组织更加有序,同时也方便了团队

    2024年02月12日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包