⭐️ .NET 要在国内真正发展起来,必须得有一些追逐梦想的人在做着不计付出的事情,而我希望自己能贡献一份微薄之力。 ⭐️
Skip to main content

1.6 更新日志

Furion 框架升级/发版规则

升级前重点关注可能造成【破坏性】的标签类型

版本号规则:主版本号.次版本号.修订版本号

  • 只要【确认】为框架 bug,则当天修复,当天发版,修订版本号 加 1
  • 只要 .NET SDK 版本更新,则当天升级,当天发版,修订版本号 加 1
  • 如果 .csproj 文件有变更,则当天发版,修订版本号 加 1
  • 如果新增 拓展包,为了版本号统一,则当天发版,修订版本号 加 1
  • 如果涉及到代码重构,则当天发版,次版本号 加 1,修订版本号 清 0
  • 如果 .NET SDK 主版本号升级,则当天发版,主版本号 加 1

如有意外不能当天发版,则会在 Issue 中说明具体发版时间,正常不会超过 3 天。

v4.5.2(当前版本)

版本细节
  • 新特性

    •   .AddConsoleFormatter() 拓展简化控制台日志模板配置 4.5.0 #I5TCMO
    •   控制台和文件日志时间默认显示 星期几 4.5.1 #I5TKL5
    •   控制台和文件日志支持配置 options.DateFormat 日期格式化 4.5.1 #I5TKL5
    •   控制台日志带颜色输出,比如高亮 日志级别 4.5.1 #I5TKL5
    •   控制台格式化配置 options.WriteHandler 完全自定义配置 4.5.2 7fb3036
    •   日志输出 JSON 格式化配置 4.5.2 #I5TWC1 #I5OUT1
    •   数据库日志写入独立日志模板配置、独立日期格式配置 4.5.2 #I5TWC1
    •   LogMessage 结构类 LogDateTimeThreadIdState 属性 4.5.2 #I5TWC1
    •   LoggingMonitor 可配置 JsonWriterOptions 属性 4.5.3 #I5U375
  • 突破性变化

    •   LoggingMonitor 底层逻辑,移除原来的 .ScopeContext 存储监听信息设计 4.5.2 #I5TWC1
  • 问题修复

    •   字符串日志拓展带泛型方法不能正确显示 CategoryName 日志类别 4.5.0 #I5TBKL
    •   控制台日志设置了 .ScopeContext 无效问题 4.5.2 7fb3036
    •   LoggingMonitor 同时配置了局部和全局日志监听触发两次问题 4.5.2 a1a97e8
    •   v4.4.8+ 版本更新导致远程请求在个别情况下出现并发问题 4.5.2 #I5TWL3
    •   LoggingMonitor 配置了 ReturnValueThreshold 之后 Json 被截断引发有效性检测异常 4.5.3 #I5U375
  • 其他更改

    •   文件日志默认模板,默认对日志时间进行格式化并显示星期几 4.5.1 #I5TKL5
    •   脚手架代码,默认启用 services.AddConsoleFormatter() 4.5.1 #I5TLI6
    •   Serve.Run() 代码,默认启用 services.AddConsoleFormatter() 4.5.1 #I5TLI6
  • 文档

    •   选项监听出现触发多次的解决方案 #I5T9PR
    •   日志记录文档、动态 WebAPI 文档
  • 本期亮点

    1. 支持日志配置 JSON 格式化输出
查看更改
// 控制台services.AddConsoleFormatter(options =>{    options.MessageFormat = LoggerFormatter.Json;});// 文件services.AddFileLogging("mytemplate.log", options =>{    options.MessageFormat = LoggerFormatter.Json;});// 数据库services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>{    options.MessageFormat = LoggerFormatter.Json;});
    1. 支持 LoggingMonitor 输出 JSON 格式
查看更改
  1. 全局/局部启用 Json 输出配置
// 全局services.AddMonitorLogging(options =>{    options.JsonBehavior = Furion.Logging.JsonBehavior.OnlyJson;});// 局部[LoggingMonitor(JsonBehavior = Furion.Logging.JsonBehavior.OnlyJson)]
关于 JsonBehavior

只有设置为 JsonBehavior.OnlyJson 时才不会输出美观的日志。

  1. 写入存储介质
using Furion.Logging;namespace Your.Core;public class DatabaseLoggingWriter : IDatabaseLoggingWriter{    // 支持构造函数注入任何实例,会自动释放任何服务,比如注入 IRepository,或者 SqlSugarClient    public DatabaseLoggingWriter()    {    }    public void Write(LogMessage logMsg, bool flush)    {        // 如果 JsonBehavior 配置为 OnlyJson 或者 All,那么 Context 就包含 loggingMonitor 的值        // 如果 JsonBehavior 配置为 OnlyJson,那么可直接通过 logMsg.Message 获取结果就是 json 格式        if (logMsg.LogName == "System.Logging.LoggingMonitor")        {            var jsonString = logMsg.Context.Get("loggingMonitor");        }        // 这里写你任何插入数据库的操作,无需 try catch    }}

Json 输出格式如下:

{  "controllerName": "test-logger",  "controllerTypeName": "TestLoggerServices",  "actionName": "person",  "actionTypeName": "GetPerson",  "areaName": null,  "displayName": "Furion.Application.TestLoggerServices.GetPerson (Furion.Application)",  "localIPv4": "0.0.0.1",  "remoteIPv4": "0.0.0.1",  "httpMethod": "GET",  "requestUrl": "https://localhost:5001/api/test-logger/person/2",  "refererUrl": "https://localhost:5001/api/index.html?urls.primaryName=数据库操作演示",  "environment": "Development",  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.53",  "requestHeaderAuthorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsIkFjY291bnQiOiJhZG1pbiIsImlhdCI6MTY2NDQ1MDUwNSwibmJmIjoxNjY0NDUwNTA1LCJleHAiOjE2NjQ0NTE3MDUsImlzcyI6ImRvdG5ldGNoaW5hIiwiYXVkIjoicG93ZXJieSBGdXJpb24ifQ.-xocNcDQGoXClceoVU5QAHIkTcOZ7ZXo0hEbzghDfFI",  "timeOperationElapsedMilliseconds": 55,  "authorizationClaims": [    {      "type": "UserId",      "valueType": "integer",      "value": "1"    },    {      "type": "Account",      "valueType": "string",      "value": "admin"    },    {      "type": "iat",      "valueType": "integer",      "value": "1664450505"    },    {      "type": "nbf",      "valueType": "integer",      "value": "1664450505"    },    {      "type": "exp",      "valueType": "integer",      "value": "1664451705"    },    {      "type": "iss",      "valueType": "string",      "value": "dotnetchina"    },    {      "type": "aud",      "valueType": "string",      "value": "powerby Furion"    }  ],  "parameters": [    {      "name": "id",      "type": "System.Int32",      "value": 2    }  ],  "returnInformation": {    "type": "Furion.UnifyResult.RESTfulResult`1[[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]",    "actType": "Furion.Application.Persons.PersonDto",    "value": {      "StatusCode": 200,      "Data": {        "Id": 2,        "Name": null,        "Age": 0,        "Address": null,        "PhoneNumber": null,        "QQ": null,        "CreatedTime": "0001-01-01T00:00:00+00:00",        "Childrens": null,        "Posts": null      },      "Succeeded": true,      "Errors": null,      "Extras": null,      "Timestamp": 1664450517341    }  },  "exception": {    "type": "System.DivideByZeroException",    "message": "Attempted to divide by zero.",    "stackTrace": "   at Furion.Application.TestLoggerServices.测试日志监听8(Int32 id) in D:\\Workplaces\\OpenSources\\Furion\\samples\\Furion.Application\\TestLoggerServices.cs:line 78\r\n   at lambda_method103(Closure , Object , Object[] )\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)\r\n   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)"  },  "validation": {    "errorCode": null,    "originErrorCode": null,    "message": "出错了啊。。。。"  }}

v4.4.9(已发布)

  • 新特性

    •   友好异常可控制是否输出错误日志配置 LogError: true 4.4.0 #I5PKJH
    •   DateOnlyJsonConverterDateOnlyOffsetJsonConverter 序列化转换器 !565
    •   事件总线 LogEnabled 配置,可控制是否输出服务日志 #I5QLY5
    •   可实现任何多套规范化结果功能,支持特定控制器,特定方法 #I5QZ37
    •   ILoggerFactory 日志工厂动态批量添加文件日志拓展 #I5R9PO
    •   App.GetCommandLineConfiguration(args) 解析命令行参数静态方法 803542c
    •   Sql 代理支持返回受影响行数 #I5REJ9
    •   任意自定义日志文件名支持滚动日志删除功能 #I5RFBQ
    •   .pcd 图片类型 MIMEimage/x-photo-cd 支持 5fafc84
    •   默认日志输出当前线程 Environment.CurrentManagedThreadId b8fe2cd
    •   app.UseInject(Action<UseInjectOptions>) 重载方法,简化配置 4.4.8 0b645fe
  • 突破性变化

    •   框架适配 .NET 6.0.9.NET 7.0 RC1 be5b40 1eee77b
    •   远程请求 .SetBodyBytes.SetFiles #I5PMS5 #I5PIYI
    •   FS.InitialContentTypeProvider() 名称为 FS.GetFileExtensionContentTypeProvider() 5fafc84
    •   远程请求 [BodyBytes] 设计,采用 HttpFile 方式 #I5PMS5 #I5PIYI
查看更改
public interface IHttp : IHttpDispatchProxy{    [Post("https://www.furion.icu/upload", ContentType = "multipart/form-data")]    Task<HttpResponseMessage> PostXXXAsync(HttpFile file);    // 支持多个文件    [Post("https://www.furion.icu/upload", ContentType = "multipart/form-data")]    Task<HttpResponseMessage> PostXXXAsync(HttpFile[] files);    // 支持多个文件    [Post("https://www.furion.icu/upload", ContentType = "multipart/form-data")]    Task<HttpResponseMessage> PostXXXAsync(IList<HttpFile> files);}
// bytes 可以通过 File.ReadAllBytes(文件路径) 获取var res = await "https://www.furion.icu/upload".SetContentType("multipart/form-data")                                               .SetFiles(HttpFile.Create("file", bytes, "image.png")).PostAsync();// 支持多个文件var res = await "https://www.furion.icu/upload".SetContentType("multipart/form-data")                                               .SetFiles(HttpFile.CreateMultiple("files", (bytes, "image1.png"), (bytes, "image2.png"))).PostAsync();
    •   所有的 AddInjectUseInject 参数设计 #I5QCF0
查看更改
public void ConfigureServices(IServiceCollection services){    services.AddInject(options =>    {        options.ConfigureSwaggerGen(gen =>        {            // ...        });    });}
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {      app.UseInject(configure: options =>      {          options.ConfigureSwagger(swg =>          {              // ...          });          options.ConfigureSwaggerUI(ui =>          {             // ...          });      });}
    •   远程请求所有 xxxAsStreamAsync 返回值 #I5QVEB
查看更改

由:

var stream = await "https://www.furion.icu/".GetAsStreamAsync();

改为:

var (stream, encoding) = await "https://www.furion.icu/".GetAsStreamAsync();
    •   .Inject() 支持配置更多参数,开放底层更多权限 4.4.9 1182283
查看更改
.Inject((builder, options) => {    options.ConfigureAppConfiguration((context, config) =>    {    });    options.ConfigureServices((context, services) =>    {    });});
  • 问题修复

    •   远程请求代理模式非泛型参数导致数组溢出问题 #I5Q3SN
    •   LoggingMonitor 客户端 IP 记录错误 #I5QCU1 !562
    •   远程请求响应报文中包含 charset=gbk 进行序列化后乱码问题 #I5QVEB
    •   文件日志断电时丢失日志问题 db7d51b
    •   动态 WebAPI 或控制台贴了 [ApiDescriptionSettings(Tag = "")] 标签之后导致注释丢失 #I5REVF #I5RE4J
    •   启用数据库日志但是没有配置配置文件出现空异常问题 33817be
    •   控制台日志过滤无法过滤默认主机日志问题 33817be
    •   脚手架错误的日志配置问题 33817be
    •   高频压测情况下写日志并设置日志上下文导致并发更新出现 System.AggregateException 异常问题 #I5RFBQ
    •   日志文件名因 WindowsLinux 路径分隔符不一致导致日志文件创建失败问题,Linux 只支持 / 不支持 \ #I5RFBQ
    •   Oops.Oh/Bah 设置 .WithData 之后无效问题 !580
    •   基于 Redis 重写事件存储器序列化 IEventSource 实例异常问题 4.4.7 3e45020
    •   使用 Log 静态类超高频率下写日志导致 CPU 激增问题 4.4.7 #I5SDK5
    •   远程请求超高频率下发送请求导致 CPU 激增问题和异常问题 4.4.8 #I5SJJR
    •   集成第三方配置中心时获取的不是最新数据问题 4.4.9 2cdef6b
  • 其他更改

    •   JWTEncryption 静态类,支持无需注册 services.AddJwt() 使用 #I5PPKE #I5POLZ
    •   事件总线默认日志类名为 System.Logging.EventBusService #I5QLY5
  • 文档

    •   .NET6 升级 .NET7 文档
    •   ASP.NET 7 集成文档
    •   集成第三方配置中心文档 4.4.9
    •   第三方事件总线和 Furion 集成文档 4.4.9
    •   事件总线集成 Kafka 文档 #I5P5UG
    •   友好异常文档、日志记录文档、远程请求文档、依赖注入文档、即时通讯文档、事件总线文档、Worker Service 文档、单元测试文档、入门指南文档、数据库新增文档

v4.3.9(已发布)

  • 新特性

    •   AppSettings 配置的 ExcludeAssemblies 属性,支持忽略指定程序集扫描 7b7747f
    •   Oops.OhOops.Bah 支持设置额外数据 .WithData(data) #I5O38E
    •   定时任务 Crontab.GetSleepMilliseconds(baseTime) 获取下一个发生时间的时间差 d024fae
    •   友好异常默认打印异常日志,避免生产环境漏掉重要异常信息 6e3a5bd
    •   日志静态类 Log.CreateLoggerFactory() 静态方法 75c672a
    •   多语言 SharedResource 模式,避免硬编程 18e80c7
    •   事件总线 MessageCenter 静态类,解决从 Fur v1.x 版本升级问题 a29fc7c
    •   组件化 IWebComponent 模式,支持 .NET5+ 08a44c3
    •   远程请求设置自己的 HttpClient 功能 #I5PBR3 !545
    •   LoggingMonitor 支持添加更多自定义配置 #I5PEPA
    •   LoggingMonitor 可配置 WithReturnValueReturnValueThreshold #I5PFJ1 #I5PFOW
    •   LoggingMonitor 可配置 MethodsSettings 更多信息 #I5PFJ1 #I5PFOW
查看更改
Serve.Run(RunOptions.Default     .AddWebComponent<XXXWebComponent>());public class XXXWebComponent : IWebComponent{    public void Load(WebApplicationBuilder builder, ComponentContext componentContext)    {        // ....    }}
  • 突破性变化

    •   Furion 程序集 PublicKeyToken 强签名 26b12c0
    •   事件总线 IEventBusFactory 事件工厂方法 AddSubscriber -> SubscribeRemoveSubscriber -> Unsubscribe a29fc7c
    •   .AddInject().UseInject() 配置选项名称,移除 Configure 后缀 b6953cd
    •   远程请求 请求拦截响应拦截异常拦截 委托签名,新增 HttpClient 参数 #I5OWBO
查看更改
[Interceptor(InterceptorTypes.Request)]static void OnRequest(HttpClient client, HttpRequestMessage req){}[Interceptor(InterceptorTypes.Response)]static void OnResponsing(HttpClien client, HttpResponseMessage res){}[Interceptor(InterceptorTypes.Exception)]static void OnException(HttpClient client, HttpResponseMessage res, string errors){}
  • 问题修复

    •   生成包含 中文JWT Token 解密后出现乱码问题 #I5O397
    •   HttpRequestMessage 拓展中追加查询参数时的空引用异常 #I5PENW !547
    •   日志模块配置多个 IDatabaseLoggingWriter 只有一个生效 #I5PFQ2 #I5PFJ1
  • 其他更改

    •   默认输出文件日志模板,使其更加美观 #1518cf3
    •   默认规范化结果验证处理也支持状态码设置 2eb9390
    •   SqlSugarCore 拓展包和脚手架至 5.1.2.6 版本 #I5PCXK
    •   JSON Schema 关于 LoggingMonitor 更多配置 #I5PFJ1
  • 文档

    •   RabbitMQ 事件总线文档
    •   AppSettings 配置文档、事件总线文档、多数据库配置文档、日志文档、定时任务文档、MessageCenter 文档、远程请求文档、组件化文档、入门指南、多语言文档。

v4.2.13(已发布)

  • 新特性

    •   事件总线工厂,支持运行时动态添加订阅程序和移除订阅程序 #I5NNQX
    •   事件总线 [EventSubscribe] 事件 Id 支持正则表达式匹配 #I5NNQX
    •   事件总线 [EventSubscribe] 支持局部失败重试配置 #I5NNQX
    •   Log 全局静态类,方便随时随地记录日志 ba9b1f1
    •   事件总线 options.AddSubscriber(Type) 重载 42446078
    •   ValidationMetadata 类型 FirstErrorPropertyFirstErrorMessage 属性 #I5MFJT
    •   Serve.Run() 模式 WithArgs(args) 方法 #I5MOJB
    •   [UnitOfWork] 分布式事务 TransactionScope 支持 #I5MRTY
    •   16 位 MD5 加密支持 #I5N8RC
  • 突破性变化

    •   Scoped.Create(async (f,s) => {}) 异步创建作用域方法名称为 CreateAsync,避免一些情况下无法区分,同步方法不变 #I5N9XY
查看更改

由:

// Scoped.CreateUow 一样await Scoped.Create(async (f, s) => {});

改为:

// Scoped.CreateUowAsync 一样await Scoped.CreateAsync(async (f, s) => {});
    •   .NET 6.0.8.NET 7 Preview 7 842d4f7
    •   [LoggingMonitor] 命名空间为 System,因为使用频率越来越高 b879861
    •   在非 Web 环境中不正确使用字符串拓展方法检测机制 6389cbd
    •   所有 .Default 静态属性为 .Default() 方法 6389cbd
    •   工作单元 IUnitOfWork 所有方法参数类型,由 ActionExecutingContextActionExecutedContext 改为 FilterContext #I5MHX5
查看更改
public interface IUnitOfWork{    void BeginTransaction(FilterContext context, UnitOfWorkAttribute unitOfWork);    void CommitTransaction(FilterContext resultContext, UnitOfWorkAttribute unitOfWork);    void RollbackTransaction(FilterContext resultContext, UnitOfWorkAttribute unitOfWork);    void OnCompleted(FilterContext context, FilterContext resultContext);}
  • 问题修复

    •   日志上下文数据多次写入被清空问题以及数据库日志出现异常后停止写入 #I5LIWF
    •   个别情况下跨域默认配置的响应缓存导致嵌入式资源异常问题 7a57efe
    •   远程请求传入不合法的请求报文头数据触发校验失败问题 #I5LPFE
    •   多线程中使用静态日志写数据库日志导致连接池耗光问题 8d5cdd6
    •   EFCore 6.0 之后 IModelCacheKeyFactory 接口方法改变导致分表分库异常问题 #I5MCZ6 EFCore#25154 EFCore!3305
    •   ValidationMetadata 对象 Message 字符串类型出现 \"\" 问题 #I5MFJT
    •   [IfException] 覆盖 Oops.Oh/Bah 错误消息问题 4bbd854
    •   数据库日志写入循环写入和频繁创建数据库连接池问题 9ce214c
    •   Razor Pages 不支持全局异常拦截问题 #I5MHX5
    •   Razor Pages 不支持全局数据验证问题 #I5MHX5
    •   Razor Pages 不支持工作单元 [UnitOfWork] 问题 #I5MHX5
    •   Razor Pages 不支持 EFCore 自动 SaveChanges 问题 #I5MHX5
    •   Blazor Serverv4.2.2 版本更新导致的问题 #I5MNFN
    •   [IfException] 不支持多语言配置问题 #I5MPN7
    •   通过 services.AddMvcFilter<LoggingMonitorAttribute>() 方式注册无效问题 8d1477d
    •   事件总线默认 Channel 管道初始化时机过晚问题,解决部分第三方依赖使用问题 #I5MM3O
    •   主机停止时写入日志异常问题 #I5N7S2
    •   数据库上下文手动释放导致 AutoSaveChange 特性出现释放异常问题 #I5NFWC
    •   [LoggingMonitor] 循环引用序列化问题 #I5NRT9
    •   远程请求传入 null Body 参数抛出空异常问题 #I5NTUE
    •   事件总线默认开启模糊匹配(正则表达式)导致不必要的订阅 #I5NVOP
  • 其他更改

    •   事件总线默认 Channel 管道初始化时机,解决部分第三方依赖使用问题 #I5MM3O
    •   底层迭代改进优化
    •   规范化文档获取控制器、方法分组、标签信息 66d8d54
  • 文档

    •   全局日志静态类 Log 文档 ba9b1f1
    •   Nuget 本地测试包文档
    •   日志文档、静态类文档、数据校验文档、Worker Service 文档、工作单元文档、依赖注入文档

v4.1.14(已发布)

  • 新特性

    •   Furion.Xunit 拓展包,正式实现 Xunit 单元测试完整支持 Furion 063a034e
    •   services.AddMonitorLogging() 日志监视器服务,支持非常灵活的日志操作 81df742
    •   Serve.Run(silence: true) 等一系列强大的静默启动功能 #I5JBSQ #I5J98T 7cced4
    •   SpecificationDocumentBuilder.GetOpenApiGroups() 方法获取底层的规范化接口分组信息 4ff03c5
    •   logger.ScopeContext() 配置日志上下文功能 #I5JC0D
    •   跨域配置 CorsAccessorSettings.SignalRSupport 配置选项,支持配置 SignalR 跨域 #I5JREM
    •   事件总线 UseUtcTimestamp 选项配置,可选择使用 DateTime.UtcNow 还是 DateTime.Now,默认是 DateTime.Now #I5JSEU
    •   规范化文档 [OperationId] 配置,解决自定义 Swagger UI 不能正确显示路由问题 #I5K1IB
    •   远程请求 IHttpDispatchProxy 方式全局拦截支持多态(继承) #I5K8FS
  • 突破性变化

    •   Furion.Xunit 拓展包,正式实现 Xunit 单元测试完整支持 Furion 063a034e
    •   Furion.Extras.DatabaseAccessor.SqlSugar 拓展插件中的 [SqlSugarUnitOfWork] 工作单元特性,将使用通用工作单元替换,查看最新实现文档
    •   Inject.Create() 方法,再也不需要了,框架提供了无敌强大的 Serve.Run() 静默启动方式 200848e
    •   Serve.RunConfigureConfiguration 方法参数,由 configuration => {} 改为 (environment, configuration) => {} 83c97bb
查看更改
// 由Serve.Run(RunOptions.Default.ConfigureConfiguration(configuration => {}));// 改为:Serve.Run(RunOptions.Default.ConfigureConfiguration((environment, configuration) => {}));
  • 问题修复

    •   [LoggingMonitor] 异常消息日志级别为 Information 错误问题 ab46cdf
    •   新版本日志组件频繁提示文件占用问题,将文件独占锁改为共享锁 #I5J3S6
    •   配置数据库日志读写器为 EFCore 时控制台出现无限打印问题 #I5J474
    •   [LoggingMonitor] 针对 byte[] 类型参数输出过大问题 5380f35
    •   友好异常和规范化结果丢失了原始 ErrorCode 问题 #I5IX2R
    •   新版本日志组件自定义数据库读写器注入 IRepository 仓储导致死循环问题 #I5IX2R
    •   Mvc 默认手动验证和 Furion 全局验证冲突问题 2a06c39
    •   Serve.Run() 模式不支持 SuperSocket 第三方包问题,原生是支持的。186ca0a
    •   SignalR 跨域错误问题 #I5JREM
    •   [LoggingMonitor]Oops.OhOops.Bah 记录到了错误日志中,默认应该是 Information 且提供可配置 #I5JZ1H
    •   自定义 Swagger UI 之后个别 UI 要求必须配置 operationId,否则出现 guid 序号 #I5K1IB
    •   主动抛出 NotFoundResultNotFoundObjectResult 无效问题 #I5KALZ
    •   [LoggingMonitor] 解析方法参数但前端未传入时出现错误问题 #I5KC5P
    •   [LoggingMonitor] 无法序列化 IQueryable 返回值问题 #I5KJD1
    •   [LoggingMonitor] 不能记录全局验证错误问题 b44087d
    •   [LoggingMonitor] 存在注册顺序差异问题 b44087d
  • 其他更改

    •   底层的规范化文档 SpecificationDocumentBuilder 部分方法,提供更加便捷的第三方 Swagger UI 集成 10f0f01
  • 文档

    •   单元测试文档、入门指南文档、Worker Services 文档
  • 本期亮点

  1. Serve.Run() 彻彻底底支持全平台,提供非常强大的静默模式

启用静默模式可以实现无阻塞方式执行程序,而且还能体验完整的 Furion 功能。

查看更改
大革命

有了 Serve.Run() 静默模式后,Furion 彻彻底底支持全平台,不管你是 控制台、Web、桌面、移动、单元测试,集成测试,基准测试等等应用程序

Serve.Run(silence: true);// 不会阻塞执行哦,而且从这里开始可以使用 Furion 任何功能,比如 App.Configuration....Console.WriteLine("Hello, World!");Console.ReadKey();

还有更多静默模式。

// RunOptions 方式Serve.Run(RunOptions.DefaultSilence);// LegacyRunOptions 方式Serve.Run(LegacyRunOptions.DefaultSilence);// GenericRunOptions 方式Serve.Run(GenericRunOptions.DefaultSilence);
  1. 强大的 Furion.Xunit 单元测试、集成测试
查看更改
大革命

单元测试和集成测试是保证一个系统能够持续维护和稳定运行的必备技能,但是目前现有的单元测试组件无法直接集成 Furion 的功能,最常用的就是如何在单元测试中读取配置,以及如何进行依赖注入

在过去,Furion 只能不断的去调整,以至于适配第三方单元测试写法,搞得不伦不类!

所以,这一次不再妥协,Furion 推出自己的单元测试工具,可以让现有的单元测试如 Xunit 100% 支持 Furion 所有功能,全部保证一致的写法。

单元测试中初始化 Furion

using Furion.Xunit;using Xunit.Abstractions;using Xunit.Sdk;// 配置启动类类型,第一个参数是 TestProgram 类完整限定名,第二个参数是当前项目程序集名称[assembly: TestFramework("TestProject1.TestProgram", "TestProject1")]namespace TestProject1;/// <summary>/// 单元测试启动类/// </summary>public class TestProgram : TestStartup{    public TestProgram(IMessageSink messageSink) : base(messageSink)    {        // 初始化 Furion        Serve.Run(silence: true);    }}

测试类支持完整依赖注入

using TestProject1.Services;using Xunit;namespace TestProject1;public class UnitTest1{    private readonly ICalcService _calcService;    public UnitTest1(ICalcService calcService)    {        _calcService = calcService;    }    [Fact]    public void 测试两个数的和()    {        Assert.Equal(3, _calcService.Plus(1, 2));    }}

查看新版本单元测试文档

  1. 开放底层规范化文档分组接口,使得集成第三方 Swagger UI 更加容易,如集成 IGeekFan.AspNetCore.Knife4jUI 拓展:
查看更改
安装包

只需要在 YourPoject.Web.Core 层安装 IGeekFan.AspNetCore.Knife4jUI 即可。

3.1.1 Knife4jUI 独立版本配置

var routePrefix = "api";    // 定义 swagger 路由地址,如果是跟目录,设置 string.Empty 即可app.UseKnife4UI(options =>{    options.RoutePrefix = routePrefix;  // 配置 Knife4UI 路由地址    foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups())    {        options.SwaggerEndpoint("/" + groupInfo.RouteTemplate, groupInfo.Title);    }});app.UseInject(routePrefix); // 配置 Furion 路由地址

3.1.2 Knife4jUISwagger 共存版本配置

app.UseKnife4UI(options =>{    options.RoutePrefix = "newapi";  // 配置 Knife4UI 路由地址,现在是 /newapi    foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups())    {        options.SwaggerEndpoint("/" + groupInfo.RouteTemplate, groupInfo.Title);    }});app.UseInject();  // Furion 默认 api 地址为  /api

如需实现登录之后自动将 token 添加到头部可在登录接口 AfterScript 执行以下代码:

ke.global.setAllHeader(
"Authorization",
"Bearer " + ke.response.headers["access-token"]
);
  1. 提供强大的日志上下文功能
// 写法一_logger.ScopeContext(ctx => ctx.Set("Name", "Furion").Set("UserId", 10))       .LogInformation("我是一个日志 {id}", 20);// 写法二_logger.ScopeContext(new Dictionary<object, object> {    { "Name", "Furion" },    { "UserId", 10 }}).LogInformation("我是一个日志 {id}", 20);// 写法三_logger.ScopeContext(new LogContext {    // ....}).LogInformation("我是一个日志 {id}", 20)

v4.0.0(重新起航)💖

不忘初心,感恩遇见,感恩信任

2020 年 09 月 01 日,一个叫 Fur 的开源项目在 Gitee 的襁褓中悄然诞生,她的出生仿佛带着某种使命,没有包袱,无限可能。

她缓缓的张开双眼,干净雪亮的眼睛似乎对这个世界充满了好奇,任何事物在她眼前晃过都像是直击灵魂的思想碰撞,这些在她看来都是非常宝贵的财富。她貌似有用不完的精力,一路汲取知识,升级打怪,不断奔跑,乐此不疲。

记得 2020 年 11 月 11 日的单身节,她迎来了“一岁(v1.0.0)”生日,自那以后,IT 这个大银幕上频繁出现她的身影,越来越多 .NET5 开发者转粉,像是告诉这个世界,她就是 IT 界大明星。

每一个明星都有一个好听的艺名,她当然也不例外,2020 年 11 月 20 日,经纪人百小僧为她起名为 Furion

2021 年 11 月 09 日起,她进入了每个孩子都经历过的叛逆期,年少轻狂喜新厌旧,抛弃了曾经支持她的 .NET5 粉丝们,投入到新的 .NET6 拥趸者怀抱中,自此过上了奢靡富足的生活。

但她过的不开心,时常在夜里想起 .NET5 的粉丝们,内心非常自责,但在双重工作压力下她毅然选择了忽视他们的诉求,仿佛他们就是累赘。

时间真的是好东西,曾经认为是对的,经过岁月的蹉跎历磨,渐渐的明白:不忘初心,方能始终。

这一次,不落下一人(.NET5.NET6,...,.NET N),携手共进,重新起航,感恩遇见,感恩信任。

  • 新特性

    • v4.0.0 支持 .NET5.NET6,...,.NET N,所有的 Furion 项目都能够升级到该版本,重新起航,实现大统。

v3.9.2(已发布)

关于日志

日志模块是任何应用系统都必备的功能,可以说是最重要的模块!在 .NET 社区中有 Log4NETNLogSerilog 等日志组件,它们无一不是优秀的开源项目。

但由于这些日志组件历史悠久,内部兼容的 .NET 版本非常多,功能随着时间推移变得极其强大复杂,在实际项目使用中,发现每一个日志组件配置总是不那么友好,特别是在使用上不够简单

Furion 作为全栈开发框架,在过去版本并没有提供足以满足开发者需求的日志模块,转而推荐大家集成第三方组件,如 Serilog导致后续无法实现自定义功能和也增加了不少维护成本。

这一次,Furion 不再妥协,彻底重构了日志模块,实现日志功能/需求完全自主可控,提供给开发者几乎所有日志功能的需求!

Furion 的使用者们,是时候“更换”掉第三方日志组件,让我们一起迭代出更强更好的日志组件吧! 🍖

  • 新特性

    •   日志模块,内置写入控制台、文件、数据库功能,再也无需引入第三方日志了!日志源码
    •   强大的调试日志 [LoggingMonitor] 32dfc1
  • 突破性变化

    •   日志模块,内置写入控制台、文件、数据库功能,再也无需引入第三方日志了!日志源码
    •   TP.Wrapper 规范化日志模板算法,由过去的 [属性] 改为 ##属性##,解决 JSON 内容冲突问题 394ecec
    •   未来 Furion.Extras.Logging.Serilog 拓展将不再继续维护,因为 Furion 有强大的日志组件了!
  • 问题修复

    •   修复脱敏模块模型绑定个别情况下空异常问题 #I5IM5C
  • 文档

    •   日志文档、静态类文档、数据校验文档
  • 本期亮点

  1. 极易使用且强大的日志模块
查看更改
// 写入文件services.AddFileLogging("logs/application.log");// 写入数据库services.AddDatabaseLogging<DatabaseLoggingWriter>();

查看更多日志文档

  1. 强大的 [LoggingMonitor] 调试日志
查看更改
using Furion.Logging;namespace Furion.Application;public class TestLoggerServices : IDynamicApiController{    [LoggingMonitor]    public PersonDto GetPerson(int id)    {        return new PersonDto        {            Id = id        };    }}

支持控制器、操作或全局注册拦截

输出日志为:

┏━━━━━━━━━━━  Logging Monitor ━━━━━━━━━━━┣ Furion.Application.TestLoggerServices.GetPerson (Furion.Application)┣ 控制器名称:              TestLoggerServices┣ 操作名称:                GetPerson┣ 路由信息:                [area]: ; [controller]: test-logger; [action]: person┣ 请求地址:                https://localhost:44316/api/test-logger/person/11┣ 来源地址:                https://localhost:44316/api/index.html┣ 浏览器标识:              Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62┣ 客户端 IP 地址:          0.0.0.1┣ 服务端 IP 地址:          0.0.0.1┣ 服务端运行环境:          Development┣ 执行耗时:                31ms┣ ━━━━━━━━━━━━━━━  授权信息 ━━━━━━━━━━━━━━━┣ JWT Token:               Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsIkFjY291bnQiOiJhZG1pbiIsImlhdCI6MTY1ODcxNjc5NywibmJmIjoxNjU4NzE2Nzk3LCJleHAiOjE2NTg3MTc5OTcsImlzcyI6ImRvdG5ldGNoaW5hIiwiYXVkIjoicG93ZXJieSBGdXJpb24ifQ.VYZkwwqCwlUy3aJjuL-og62I0rkxNQ96kSjEm3VgXtg┣ UserId (integer)1┣ Account (string):        admin┣ iat (integer)1658716797┣ nbf (integer)1658716797┣ exp (integer)1658717997┣ iss (string):            dotnetchina┣ aud (string):            powerby Furion┣ ━━━━━━━━━━━━━━━  参数列表 ━━━━━━━━━━━━━━━┣ Content-Type:id (Int32)11┣ ━━━━━━━━━━━━━━━  返回信息 ━━━━━━━━━━━━━━━┣ 类型:                    Furion.Application.Persons.PersonDto┣ 返回值:                  {"Id":11,"Name":null,"Age":0,"Address":null,"PhoneNumber":null,"QQ":null,"CreatedTime":"0001-01-01T00:00:00+00:00","Childrens":null,"Posts":null}┗━━━━━━━━━━━  Logging Monitor ━━━━━━━━━━━

v3.8.9(已发布)

  • 新特性

    •   规范化结果 ExceptionMetadataValidationMetadata 都可以获取 ErrorCode 属性 #I5GJ6D
    •   ValidationMetadataStatusCode 属性 #I5HB5L
    •   远程请求对 Url 是否编码设置,[Get(WithEncodeUrl = false)]WithEncodeUrl(false) #I5GOBC
    •   更强大的 JWTEncryption.SecurityReadJwtToken('token') 读取解析 Token 静态方法 574eeb6
    •   ValiationTypes.Html 验证 Html 标签 #I5HBKC
    •   EFCore.NamingConventions 支持,可自定义生成表名,字段名风格,比如小驼峰,蛇形命名等 #I5HBEI
    •   INamedServiceProvider 命名服务提供器,可解析接口多实现 #I5HF98
    •   脱敏处理模块方法参数单个值处理 a22ec3c
    •   脱敏词库支持 | 分割词语 3106b1d
  • 突破性变化

    •   所有 .NET 依赖包至 6.0.7 版本
    •   Serilog.AspNetCore 包至 6.0.0 版本
    •   JWT Token 刷新逻辑 #I5GXML 574eeb6
    •   ExceptionMetadata 命名空间为 Furion.FriendlyException 3105d16
    •   Retry.Invoke(Func<Task>...) 为:Retry.InvokeAsync(Func<Task>...) 3b78999
    •   脱敏处理模块,大大提高性能和准确率,同时支持方法参数单个值处理 a22ec3c
  • 问题修复

    •   Rider 开发工具对同名脚手架 (EFCoreSqlSugar) 只显示一个问题 !518
    •   UnitOfWork 工作单元在 EFCore 中失效问题 #I5H0T3
    •   JWTToken 如果存在数组类型的值时,刷新 Token 后丢失了历史值 #I5GXML
    •   远程请求 WithEncodeUrl 无法在 [HttpMethod] 设置问题 574eeb6
    •   Serve.Run() 模式下添加自定义配置导致 EFCore 无法获取自定义配置文件问题 #I5GZ0F
    •   Oops.Bah 进入全局异常拦截器问题 #I5H47S
    •   AddDbPool/AddDb 扩展未根据配置 Key 路径读取问题 #I5H6S4 !520
    •   ValiationTypes.Url 正则表达式覆盖不全问题 #I5HBKC
    •   v3.5.x 版本导致集成 EFCore.NamingConventions 失效问题 #I5HBEI
    •   Swagger 长路由不支持问题以及 [Required] 配置 AllowEmptyStrings 无效问题 c014330
    •   远程请求上传文件时请求报文 boundaryContent-Disposition 设置不正确问题 #I5HEF0
    •   脱敏模块替换敏感词汇出现多替换问题 a22ec3c
  • 其他更改

    •   多语言默认处理逻辑,允许不配置任何语言,过去版本会报错 #I5GRD9 5077c5d
    •   规范化文档 Swagger 性能
    •   调整 MongoDB 仓储 TDocument 泛型约束 3f49055
  • 文档

    •   远程请求文档,日志记录文档、多数据库文档、PM2 部署文档、Visual Studio 高效率文档

v3.7.11(已发布)

  • 新特性

    •   Minimal API 应用支持:.AddInjectMini() #I4KOQ5
    •   跨域 WithExposedHeaders 默认配置 access-tokenx-access-token 42ebdfd
    •   脚手架默认启用 app.UseHttpLogging() HTTP 日志 42ebdfd
    •   FurionASP.NET Core 完整 json 配置的 JSON Schema 架构 JSON Schema
    •   Sql 代理支持返回单个类类型参数 1d7fb5b
    •   Sql 代理支持返回 ValueTuple 单个类类型参数 876a2f5
    •   组件化设计模块,支持比 AppStartup 更灵活便捷的设计 #components
    •   独立工作单元模块,支持任何第三方 ORM a02413d
    •   跨域 FixedClientToken 配置参数 bd01638
    •   throw Oops.Bah 可以手动触发规范化验证失败处理 83f0036
    •   FriendlyExceptionSettingsThrowBah 配置,可标记 Oops.Oh 不进入异常处理 76ffa7f
  • 突破性变化

    •   Minimal API 应用支持:.AddInjectMini() #I4KOQ5
    •   FurionASP.NET Core 完整 json 配置的 JSON Schema 架构 JSON Schema
    •   组件化设计模块,支持比 AppStartup 更灵活便捷的设计 #components
    •   独立工作单元单元模块,支持任何第三方 ORM a02413d
    •   DataValidationFilterFriendlyExceptionFilter,解决不支持手动抛出业务异常问题 83f0036
    •   .AddDb<>.AddDbPool<> 自定义委托参数签名,由 Action<DbContextOptionsBuilder> 改为:Action<IServiceProvider, DbContextOptionsBuilder>
查看更改
// 由:options.AddDbPool<TDbContext>(DbProvider.MySql, opt => {});// 改为options.AddDbPool<TDbContext>(DbProvider.MySql, (services, opt)=> {})
  • 问题修复

    •  v3.6.3 版本依赖,执行原生 Sql 添加了参数校验导致存储过程执行错误问题 #I5ERMQ
    •   tools/cli.ps1 脚本工具出现数据库链接被占用问题
    •   JWTSettings 算法配置 JSON Schema 错误问题,感谢 @gitwentao #I5G27B !516
    •   基于策略授权在不配置 Policy 的情况下出现空异常问题 #I5EVF2
    •   启用数据库实体跟踪时导致新增实体多次查询数据库问题 #I4J2LZ
    •   不启用规范化结果导致验证失效,异常失效问题 cdb3f57
    •   验证异常和友好异常冲突问题 83f0036
    •   CentOS 7.9 系统部署无法指定命令 --urls 参数问题 8cc8ee
  • 其他更改

    •   脚手架所有 .json 文件,默认添加 JSON Schema 支持
  • 文档

    •   组件化启动文档
    •   Vue/React/Angular 请求代理文档
    •   JSON Schema 文档,支持配置智能提示和验证
    •   跨域文档、规范化文档、配置文档、日志文档、IIS 部署文档
  • 本期亮点

  1. 新增 JSON Schema 支持,所有 .json 文件支持智能提示和验证
查看更改
{  "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",  "Logging": {    "LogLevel": {      "Default": "Information",      "Microsoft": "Warning",      "Microsoft.Hosting.Lifetime": "Information",      "Microsoft.EntityFrameworkCore": "Information"    }  },  "AllowedHosts": "*"}

查看更多 JSON Schema 文档

  1. 根据 Swagger 生成 Vue/React/Angular 前端请求代码

查看文档

  1. Sql 代理支持返回单个类类型参数
查看更改
public interface ISql : ISqlDispatchProxy{    // 集合类型    [SqlExecute("select * from person")]    List<Person> GetPersons();    // 自 v3.7.3+ 版本支持返回单个类类型参数    [SqlExecute("select * from person where id=@id")]    Person GetPerson(int id);}
  1. Sql 代理支持返回 ValueTuple 单个类类型参数
查看更改
public interface ISql : ISqlDispatchProxy{    [SqlExecute(@"            select * from person where id =@id;            select * from person")]    (Person, List<Person>) GetData(int id); // 注意返回值是 `(Person, List<Person>)` 组合}
  1. 支持 Minimal API 应用
查看更改

了解 Minimal API 应用

var builder = WebApplication.CreateBuilder(args).Inject();// 注册 Minimal 服务builder.Services.AddInjectMini();var app = builder.Build();app.UseHttpsRedirection();app.UseInject(string.Empty);app.MapGet("/hello", () =>{    return "Hello, Furion";});app.Run();

v3.6.9(已发布)

  • 新特性

    •   Serve.Run() 极简主机模式,真正实现极速入门。95cac5b
    •   TP.Wrapper(...) 拓展方法,主要用来生成规范化的日志模板 427999a
    •   项目类型为 <Project Sdk="Microsoft.NET.Sdk"> 的控制台项目 fb08a65
    •   BadPageResult 错误页面类型 !494
    •   [SchemaId] 特性,解决不同程序集相同的类名生成 SwaggerSchemaId 冲突问题 #I5D3CU
    •   远程请求 options.ApproveAllCerts() 忽略所有客户端证书拓展 eb7d18a
    •   判断是否是单文件环境部署静态属性 App.SingleFileEnvironment de556f0
    •   WebApplicationBuilder.UseSerilogDefault() 拓展 e02524c
  • 突破性变化

    •   项目类型为 <Project Sdk="Microsoft.NET.Sdk"> 的控制台项目 fb08a65
    •   Serve.Run() 极简主机模式,真正实现极速入门。95cac5b
    •   未启用规范化结果时,MVC 验证失败返回 BadPageResult() 页面类型 !494
  • 问题修复

    •   修复默认注册的 services.AddResponseCaching(); 服务导致 .axd 内嵌资源请求错误问题 !495
    •   Oracle 数据库执行 sql 必须要求命令参数和 sql 语言参数数量一致 #I5D057
    •   IHostService 类型不能自动注册问题,之前只扫描了 BackgroundService 派生类 968344
    •   国产芯片主机不能识别 dotnet run --urls 参数问题 6d4398
    •   远程请求上传文件不支持特定文件后缀问题,如 .pem 文件 ba42198
    •   一些程序集已破坏或程序集不完整导致主机无法启动问题 d2dc3e4
    •   远程请求传入 headers 时类型为 Dictionary<string, string> 导致转换异常问题 #I5DHL9
    •   Serilog 单文件发布不生成日志文件 I5DQ2B
  • 其他更改

    •   远程请求默认客户端不检查 SSL 证书 eb7d18a
    •   开放验证服务选项 SuppressModelStateInvalidFilter 属性为可配置 !494
  • 文档

    •   Serve.Run() 文档
    •   HttpContext 文档
    •   GlobalUsings 文档
    •   TP 全局静态类文档
    •   中间件文档、筛选器文档、审计日志文档
    •   跨域文档、远程请求文档
  • 精彩贡献

    • !494 优秀 Pull Request 辩论典范
  • 本期亮点

  1. 极速入门
查看更改
Program.cs
Serve.Run();[DynamicApiController]public class HelloService{    public string Say()    {        return "Hello, Furion";    }}

启动浏览器查看效果,惊呆了吗!

  1. 内置错误页
查看更改
using Furion.FriendlyException;public IActionResult Add(Person person){    if(!ModelState.IsValid)    {        return new BadPageResult();    }}
  1. Swagger 支持 Markdown
查看更改
/// <summary>/// 测试 Markdown/// </summary>/// <remarks>/// # 测试 `Markdown` 注释////// ![](https://localhost:44316/images/logo.png)////// ```cs/// Serve.Run();////// [DynamicApiController/// public class HelloService/// {///     public string Say()///     {///         return nameof(Furion);///     }/// }/// ```////// 功能还不错!!!////// | 商品         | 价格     | # 其他 |/// |--------------|-----------|------------|/// | Juicy Apples | 1.99      | *7*        |/// | Bananas      | **1.89**  | 5234       |/// | Bananas      | **1.89**  | 5234       |/// | Bananas      | **1.89**  | 5234       |////// -----////// # Furion 探索版////// > 在过去一年,实现 `Furion` 从无到有,编写文档已逾百万字,过程心酸开源人自知。/// >/// > 这一路日夜兼程,嘲讽批评常伴眼耳,即便辛苦无奈、想过放弃,但为了那微不足道的存在感依然努力着。/// >/// > 当然,也收获了不少...越来越多拥趸者,越发精湛技术能力,更高层次思维模式,还有许多跨界跨行朋友。/// >/// > 在 《[开源指北] (https://gitee.com/opensource-guide/comments/)》中,我曾说道:“开源如同人的脸,好坏一面便知,缺点可能会受到嘲讽批评,优点也会收获赞扬尊重。别担心,他们正在塑造更好的你。”/// >/// > 所以,这一次重新起航,重塑 `Furion` 重塑自己。也许未来在某个 IT 圈但凡有人谈起 `.NET` 还能瞟到 `Furion` 的身影。////// ---////// 🎉 探索 Furion 未来更多可能性,实现无第三方依赖的版本,所有模块功能按需安装,按需加载。////// - 作者:[百小僧] (https://gitee.com/monksoul)/// - 日期:2021 年 08 月 30 日////// ## 环境////// - IDE :[Microsoft Visual Studio Enterprise 2022 Preview(64 位) 版本 17.0.0 Preview 3.1] (https://visualstudio.microsoft.com/zh-hans/vs/preview/)/// - SDK :[.NET SDK 6] (https://dotnet.microsoft.com/download/dotnet/6.0)/// - 语言:[C# 10](https://docs.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-10)////// ## 包说明////// - `Furion.Core`:无第三方依赖,可在 `.NET 6` 所有项目类型中运行。/// - `Furion`:内部依赖 `Furion.Core` 且无第三方依赖,**聚焦 `Web` 应用**,采用共享框架 `<FrameworkReference Include="Microsoft.AspNetCore.App" />` 模式///  </remarks>public void MarkdownTest(){    // ....}

v3.5.7(已发布)

  • 新特性

    •   Options 选项属性支持自定义 Key 名称,[MapSettings("key")] #I5B2HN
    •   EventBus 模块事件 Id 支持枚举类型 2f328aa
    •   EventBus 模块发布者 PublishAsyncPublishDelayAsync 重载 2f328aa
    •   EventBus 模块拓展方法:Enum.ParseToString()String.ParseToEnum() 2f328aa
    •   FurionSqlSugar 脚手架 🆕🆕🆕 8d9293d
    •   Dapper 拓展全局配置委托 #I5AYFX
    •   sql 转实体支持多种命名策略(纯大写,纯小写,带下划线分割等等),如 Oracle 数据库 a90e245
    •   FS.InitalContentTypeProvider() 拓展方法,获取系统内所有支持的 Content-Type 文件提供器 6099900
  • 突破性变化

    •   彻底解决了 Furion 不能单文件发布的问题 7e8e0b7
  • 问题修复

    •   框架规范化文档 Swagger 不支持 Controller 派生类 api 路由问题,原生 ASP.NET 是支持的 29e47bc
    •   基于 Schema 多租户配置无效问题 6f820ce
    •   指定实体 [Table(schema:"dbo")] 特性后 Schema 无效问题 6f820ce
    •   数据库视图不支持 Schema 配置问题 6f820ce
    •   规范化结果极端情况下出现 空异常 问题 c9b0ef
  • 其他更改

    •   axios-utils.tsangular-utils.ts ,新增请求拦截携带刷新 Token 的时机判断 82f89bd
    •   规范化文档 Swagger 加载继承注释 <inheritoc /> 性能小优化 5f06880
    •   脚手架模板,新增 GlobalUsings.cs 模式
    •   对象映射默认支持忽略大小写 !486
  • 文档

    •   Furion 单文件发布文档
    •   Furion + SqlSugar 脚手架文档
    •   事件总线文档、选项文档、即时通讯文档、.NET5 升级 .NET6 文档、依赖注入文档、跨域文档、数据加解密文档
  • 本期亮点

  1. 事件总线 Id 支持枚举类型
查看更改
EventSubscribe("TO:DO")]  // 字符串类型public async Task EventHandler1(EventHandlerExecutingContext context){    // ....}[EventSubscribe(YourEnum.Some)] // 枚举类型public async Task EventHandler2(EventHandlerExecutingContext context){    var eventEnum = context.Source.EventId.ParseToEnum(); // 将事件 Id 转换成枚举对象    // ....}
  1. 事件总线发布支持更简单调用
查看更改
// 旧版本await _eventPublisher.PublishAsync(new ChannelEventSource("ToDo:Create", name));// 新版本await _eventPublisher.PublishAsync("ToDo:Create", name);await _eventPublisher.PublishAsync(YourEnum.Some); // 也支持枚举
  1. 选项支持属性自定义配置 Key
查看更改
"AppInfo": {    "Name": "Furion",    "Version": "1.0.0",    "Company_Name": "Baiqian" // 可以和属性不一样}
public class AppInfoOptions : IConfigurableOptions{    public string Name { get; set; }    public string Version { get; set; }    [MapSettings("Company_Name")] // 支持自定义    public string Company { get; set; }}
  1. 日志规范化模板
查看更改
// 生成模板字符串var template = TP.Wrapper("Furion 框架", "让 .NET 开发更简单,更通用,更流行。",    "[作者] 百小僧",    "[当前版本] v3.5.3",    "[文档地址] https://furion.icu",    "[Copyright] 百小僧, Baiqian Co.,Ltd.");Console.WriteLine(template);

输出结果

┏━━━━━━━━━━━  Furion 框架 ━━━━━━━━━━━┣ 让 .NET 开发更简单,更通用,更流行。┣ 作者:        百小僧┣ 当前版本:     v3.5.3┣ 文档地址:     https://furion.icu┣ Copyright:   百小僧, Baiqian Co.,Ltd.┗━━━━━━━━━━━  Furion 框架 ━━━━━━━━━━━

v3.4.2(已发布)

  • 新特性

    •   规范化文件 EnableAllGroups 功能,可以将多个分组合并到一个分组中 9277b98
    •   angular-utils 客户端工具库,专门处理 angular 项目接口代理问题 6c70584
    •   Swagger 支持单个接口更多描述功能(支持 htmle5e1db0
    •   Swagger 接口 [Obsolete] 过时支持功能 e5e1db0
    •   动态 API[ApiDescriptionSettings] 特性 和DynamicApiControllerSettings 配置 的 ForceWithRoutePrefix 参数,支持强制复写 [Route] 特性并添加 DefaultRoutePrefix #I59B74
  • 突破性变化

    •   默认内置 GBKWindows-1252, Shift-JIS, GB2312 等编码支持 c456ecb
    •   FurionSqlSugar 脚手架
  • 问题修复

    •   <inheritdoc /> 不能跨程序集问题 3b9d39c
    •   <inheritdoc /> 不支持带参数,不支持隐式实现接口注释问题 #I59A6W#note_10699021
    •   v3.3.1 版本导致 Swagger 不能显示问题 6763352
    •   远程请求、JSON以及 Web 页面不支持 GBKGB2312 等国标编码问题 c456ecb
    •   远程请求响应报文设置了 Content-Type:charset= 不能自动转换编码问题 c456ecb
  • 其他更改

    •   axios-utils.tsangular-utils.ts 多客户端支持
  • 文档

    •   GlobalUsings 文档 文档地址
    •   请求大小/上传大小限制文档 文档地址
    •   规范化文档,Worker Service 文档,动态 API 文档
  • 本期亮点

  1. 启用 All Groups 分组功能
查看更改

有时候我们为了更好的对接口进行归类,配置了 Swagger 多个分组的功能,但这样也对生成客户端请求代码造成了困扰,所以添加了新的配置:

{  "SpecificationDocumentSettings": {    "EnableAllGroups": true  }}
  1. 接口过时控制
查看更改

当我们某个接口已经过时,提示尽早调用最新接口,只需要在方法上面贴 [Obsolete] 即可,如:

[Obsolete("GetName() 已经过时,请调用 GetFrameworkName() 替代")]public string GetName(){  return nameof(Furion);}[Obsolete]public string Other(){  // ...}
  1. 单一接口更多描述
查看更改

在该版本新增了 [ApiDescriptionSettings]Description 属性,支持定义更多描述,如:

[ApiDescriptionSettings(Description = "我是一段描述,显示更多内容 <button>我是按钮</button>")]public string add(){  //....}

v3.3.3(已发布)

  • 新特性

    •   远程请求文件上传自动识别 Content-TypeMime #I57ZMN
    •   远程请求方法支持设置 Content-TypeEncoding #I57ZMN
    •   根据文件名获取 Content-TypeMime 类型 #8f78184
    •   规范化文档支持授权访问 #32aa3b6
    •   代码注释,规范化文档注释 inheritdoc 语法支持 ❤️️️️ #159A6W
    •   新增 Vue2/3React 16.8+Angular 9+ 前端请求工具库,实现后端 API 代理 axios-utils
  • 突破性变化

    •   代码注释,规范化文档注释 inheritdoc 语法支持 ❤️️️️ #159A6W
    •   .NET 所有依赖包至 v6.0.5 版本
  • 问题修复

    •   自定义全局异常 Exception 后导致获取错误行号,文件空异常问题 #I53EGM
    •   配置数据库上下文传递空委托导致空引用异常问题 #I519AW
    •   字符串模板模板 Render 拓展方法返回 void 问题,应该返回 string Github-#99
    •   远程请求文件上传出现空情况问题(原因是缺失 Content-TypeI57ZMN
  • 其他更改

    •   框架源码引入 GlobalUsings 机制,减少代码体积 #7e9cc1c
    •   跨域请求的预检设置,如果未设置,则默认为 24 小时,主要解决前端多次发送 204 预检问题 4a11e7c
    •   视图引擎反射性能
  • 文档

    •   粘土对象序列化 JSON 配置文档
    •   前端解密 JWT 文档
    •  byte[]url 文档
    •   二级虚拟目录部署文档,远程请求文档,文件上传文档,安全授权文档、规范化文档
  • 本期亮点

    • ❤️️️️ 根据文件名获取 MIMEContent-Type 类型
查看更改
var success = FS.TryGetContentType("image.png", out var contentType);  // image/png
  • ❤️️️️ 支持 Swagger 配置登录后才能访问
{  "SpecificationDocumentSettings": {    "LoginInfo": {      "Enabled": true,      "CheckUrl": "检查登录地址",      "SubmitUrl": "提交登录地址"    }  }}

查看详细文档

  • ❤️️️️ 支持代码注释继承,Swagger 文档注释也支持
查看更改
/// <inheritdoc cref="ITestInheritdoc" />public class TestInheritdoc : ITestInheritdoc, IDynamicApiController{    /// <inheritdoc cref="ITestInheritdoc.GetName"/>    public string GetName()    {        return "Furion";    }    /// <inheritdoc />    public string GetVersion()    {        return "3.3.3";    }}/// <summary>/// 测试注释继承/// </summary>public interface ITestInheritdoc{    /// <summary>    /// 获取名称    /// </summary>    /// <returns></returns>    string GetName();    /// <summary>    /// 获取版本    /// </summary>    /// <returns></returns>    string GetVersion();}

查看详细文档


v3.2.0(已发布)

  • 新特性

    •   IFormFile 拓展方法 ToByteArray() da69640
    •   规范化文档 ServeDir 虚拟目录配置功能,支持一键将一级目录切换至二级目录部署(IIS)8718392
  • 突破性变化

    •   所有依赖包至最新版
    •   依赖注入模块核心代码,移除注册服务采用反射机制,减少反射性能损耗 acdb315
  • 问题修复

    •   Swaggerschema 类型如果是 C# Object 类型无法正确生成前端代码问题 Swagger 官方 Issue 1a25274
    •   Worker Service 发布成 Windows Services 时日志绝对路径问题 感谢 @jacoat !467
    •   NginxIIS 对二级虚拟目录配置不同导致 404 问题 8718392
    •   远程请求模块未初始化 OnRequestFailded 导致空异常问题 #I54PK7
    •   依赖注入反射出现 Not found Method bug #I546L1
  • 其他更改

    •   定时任务失败后异常处理逻辑,感谢 @程小胜 !463
  • 文档

    •   定时任务文档,日志文档
    •   文件上传/下载 文档,包含单文件/多文件/Base64/Byte[]

v3.1.0(已发布)

  • 新特性

    •   远程请求模块异常 Http 状态码 !462
    •   动态 WebAPI 支持小驼峰配置 #I4W1R4
    •   远程请求 SendAsByteArrayAsync 等一系列方法,支持返回 byte[] !452
    •   远程请求 GZip 压缩支持 #I506S5
  • 突破性变化

    •   .NET6 依赖包全部升级至 Nuget 最新版 v6.0.3
  • 问题修复

    •   .NET6 WebApplication 模式二级虚拟目录问题 #I4UZLM #I4PZ0C
    •   日期验证不支持 2022-03-01 0:00:00(现在支持小时域 000) 问题 #I4Y3NT
    •   环境配置和文件配置优先级问题
    •   脱敏模块替换敏感词汇只替换最后一个 bug #I4YFA0
    •   远程请求返回字符串个别情况出现中文乱码问题 #I50GBD
    •   [DataValidate] 配置 AllowNullValueAllowEmptyString 无效问题 #I4ZZBE
  • 其他更改

    • [过时] 标记 Furion.Extras.Logging.Serilog 拓展包 IWebHost 拓展为过时状态
  • 文档

    •   优化文档体验,新增面包屑导航,重写文档缓存,提升文档访问速度
    •   更新动态 API 文档、配置文档、远程请求文档
    •   更新二级虚拟目录文档
  • 本期亮点

  1. 新增动态 WebApi 支持小驼峰路径,如 GetMyName -> getMyName
查看更改
{  "DynamicApiControllerSettings": {    "LowercaseRoute": false,    "KeepName": true,    "AsLowerCamelCase": true  }}
  1. 支持 .NET6 WebApplication 模式二级虚拟目录配置:
查看更改
Progame.cs
app.UseVirtualPath(app =>{    app.UseInject(String.Empty);  // 注意 String.Empty 只是例子,可以不填或填其他的,见一分钟入门    app.MapRouteControllers();});

v3.0.0(已发布)

v3+ 版本说明

Furion v3.x 版本采用 .NET6 构建。

  • 新特性

    •   远程请求支持 GET 请求自动转换 类类型 类型对象 #I4HR5Q
  • 突破性变化

    •   全面支持 .NET6 版本
  • 问题修复

    •   开启规范化结果并自定义全局异常导致异常经过 OnSucceeded 过滤器 bug #I4DTVL
    •   .NET5.0.5+.NET6 微软底层修改了 [ApiController] 验证失败返回 IActionResult 类型 #I4ISOK
    •   EFCore 6.0 适配 SqlServer 2005+ 出错 #I4ILA5
    •   .NET6 获取配置对象如果不存在返回 null 问题,.NET5 则返回初始对象 94ae4d
    •   Sql 命令参数传入 Clay 类型异常问题 #I4D21Q
    •   Cron 定时任务特性方式 bug #I4OJQI
  • 其他更改

    •   ToPagedList 泛型约束 d0244d
  • 文档

  • 特别鸣谢


v2.20(已发布)

v2.20+ 版本说明

Furion v2.20+ 版本后采用 Jaina 事件总线替换原有的 EventBus

  • 新特性

    •   远程请求支持 GET 请求自动转换 类类型 类型对象 #I4HR5Q
  • 突破性变化

    •   EventBus 模块,采用 Jaina 方式
  • 问题修复

    •   开启规范化结果并自定义全局异常导致异常经过 OnSucceeded 过滤器 bug #I4DTVL
    •   .NET5.0.5+ 微软底层修改了 [ApiController] 验证失败返回 IActionResult 类型 #I4ISOK
    •   远程请求上传文件异常 0c0752
    •   框架启动不支持环境变量 ASPNETCORE_HOSTINGSTARTUPASSEMBLIES 配置 !438
    •   定时任务内存和 CPU 占用及特殊情况下空异常问题 12c65de
    •   默认控制器启用规范化结果无效 bugc7a4a5e
    •   依赖注入 InjectionAttribute 特性的 ExceptInterfaces 单词拼写错误问题 !436
    •   Sql 命令参数传入 Clay 类型异常问题 #I4D21Q
  • 其他更改

    •   InjectionAttribute 代码 !435
    •   ToPagedList 泛型约束 d0244d
  • 文档

    •   事件总线新文档

v2.19(已发布)

  • 新特性

    •   定时任务监听器 ISpareTimeListener #I468Q1
    •   执行 Sql 支持 JsonElement 参数 61985d6
    •   Swagger 配置枚举及标签排序过滤器 #I46LON !404
    •   远程请求 application/octet-stream 类型默认支持 d9bad03
    •   远程请求代理模式请求报文头支持 IDictionary<string ,object> 类型。0204c0a
    •   MongoDB 拓展类,添加更多常用操作方法 !423
    •   DateTimeOffset? 转换 DateTime 拓展(包含互换) !432
  • 问题修复

    •   Scoped.CreateEFCore 进行 Add-Migration 时候报空异常问题,原因是在 PM 环境中不存在根服务0853e74
    •   定时任务执行异常后异常一直驻留内存问题,修正为执行成功自动清空过去异常 197a62b
    •   Jwt 拓展包不正确的代码导致 IOptions 失效#I46LUP
    •   Swagger 枚举 Schema 过滤器不输出值问题 #I46LON !404
    •   Swagger 处理非 int 类型枚举转换 bug #I46QJ9
    •   视图引擎编译模板生成 dll 后再次加载出现 IL 格式化错误问题 ff52d38
    •   管道 Channel 读取器无法释放 Handler 对象问题 10f4a90
    •   Worker Services 下日志不输出问题 c482548
    •   远程请求 multipart/form-data 类型对接微信小程序上传文件 问题 d9bad03
    •   工作单元上下文在某些情况下共享事务失效问题 006d439
    •   Swagger 枚举值在 GET 请求中 Schema 显示不正确问题 fb72fd7
    •   远程请求 404 不走异常过滤器问题 !426
    •   自定义事件总线并发情况下调用完成后无法正确处理队列数据 !429
    •   v2.19+ 版本之后模块化开发加载外部程序集失效问题 !433
    •   定时任务 SpareTime 频繁检查导致 CPU 增高问题 aa0a2ee
  • 特别鸣谢


v2.18(已发布)

  • 新特性

    •   Furion.Tools.CommandLine 拓展库 查看源码
    •   基于 AsyncLocal<T>CallContext 实现 9057a21
    •   远程请求可配置请求异常重试策略 656da87
    •   远程请求 OnRequestFailded 事件 4a3da4b
  • 突破性变化

    •   Scoped 所有带返回值方法 656da87
    •   ConfigureService 中调用 App.GetOptions<>() 获取配置逻辑 afa4ac3

在过去,很多开发者总是喜欢在 Startup.cs 配置服务的 ConfigureService 方法中解析服务,这样导致内存存在溢出风险,GC 无法回收。 正确的方式是尽可能的避免 ConfigureService 中解析服务。如果需要在【启动时】获取 配置选项,请使用 App.GetConfig<TOptions>(路径, true) 代替 App.GetOptions<TOption>

  • 问题修复

    •   v2.16+ 版本重构 AppDbContextBuilder 之后写错实体类型 #I45E6M
    •   远程请求单个值序列化错误处理方式 3282eba
    •   v2.17.3+ 单元测试创建 TestServer bug #I45JR3
    •   Retry.Invoke 正常方法死循环 bug !392
    •   刷新 Token 生成新 Token 存在数组/集合类型导致 Key 重复异常问题 aeea2b1
    •   远程请求序列化引用类型对象(不含 string)不正确的处理 93cf63a
    •   AppDbContext 默认租户属性受工作单元影响问题 e51557f
  • 文档

    •   包管理工具文档
    •   模板引擎、Sql 操作,数据库上下文 等等文档

v2.17(已发布)

  • 新特性

    •   IPC(Inter-Process Communication,进程间通信) 模块功能,目前提供进程内通信和共享内存进程外通讯 ProcessChannel
    •   远程请求 application/xmltext/xml 默认支持 4753a1a
    •   控制台全局异常拦截 4a4fe1f
    •   支持自定义 .json 配置文件扫描目录 3e2910a
    •   支持数据库实体接口显式实现接口配置 9610a0a
    •   控制台应用程序全局拦截 [IfException] 支持 4a4fe1f
    •   依赖注入模块接口可以限制实现类生存周期,实现类也支持复写生存周期 d2ce089
  • 突破性变化

    •   Oops.Retry() 重试策略功能至新类:Retry.Invoke() 6a7bbd0
    •   IHttpContextAccessor.SigninToSwagger() 拓展,请使用 IHttpContextAccessor.HttpContext.SigninToSwagger(),退出也一样
    •   全局处理 Request Body 重复读处理 Request.EnableBuffering() d92c24b
  • 问题修复

    •   规范化状态码过滤逻辑错误问题 #I44JYS
    •   非关系型数据库(内存数据库)注册及操作异常 e167651
    •   远程请求默认序列化问题 a55603b
    •   定时任务零点/整点提前一秒触发问题 #I4321L
    •   友好异常在子类重写抽象类方法内部抛异常无法获取的问题 4a4fe1f
    •   修复非 Web 项目抛异常问题 4a4fe1f
    •   数据库实体模型贴 [NotMapper] 特性无效 #I44MNO
  • 其他更改

    •   Swagger 生成泛型 SchemaIds 默认连接符,由 Of 改为 _ 81946b6

v2.16(已发布)

  • 新特性

    •   MVC 控制器支持规范化处理 #I427Z2
    •   throw Oops.Bah() 抛出业务异常(状态码 400
    •   UnifyResultSettings 规范化 json 配置选项 #I42NY7
    •   多语言自定义配置资源文件名及自定义程序集 #I434YJ
  • 突破性变化

    •   规范化结果 IUnifyResultProvider 参数 (破坏性更改) #I427Z2
    •   IJsonSerializerProvider 接口参数,去掉 inherit 参数 a55603b
查看更改
新版本自定义规范化结果
using Furion.DataValidation;using Furion.DependencyInjection;using Furion.UnifyResult.Internal;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Filters;using System;using System.Threading.Tasks;namespace Furion.UnifyResult{    /// <summary>    /// RESTful 风格返回值    /// </summary>    [SuppressSniffer, UnifyModel(typeof(RESTfulResult<>))]    public class RESTfulResultProvider : IUnifyResultProvider    {        /// <summary>        /// 异常返回值        /// </summary>        /// <param name="context"></param>        /// <param name="metadata"></param>        /// <returns></returns>        public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata)        {            return new JsonResult(RESTfulResult(metadata.StatusCode, errors: metadata.Errors));        }        /// <summary>        /// 成功返回值        /// </summary>        /// <param name="context"></param>        /// <param name="data"></param>        /// <returns></returns>        public IActionResult OnSucceeded(ActionExecutedContext context, object data)        {            return new JsonResult(RESTfulResult(StatusCodes.Status200OK, true, data));        }        /// <summary>        /// 验证失败返回值        /// </summary>        /// <param name="context"></param>        /// <param name="metadata"></param>        /// <returns></returns>        public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata)        {            return new JsonResult(RESTfulResult(StatusCodes.Status400BadRequest, errors: metadata.ValidationResult));        }        /// <summary>        /// 特定状态码返回值        /// </summary>        /// <param name="context"></param>        /// <param name="statusCode"></param>        /// <param name="unifyResultSettings"></param>        /// <returns></returns>        public async Task OnResponseStatusCodes(HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings)        {            // 设置响应状态码            UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings);            switch (statusCode)            {                // 处理 401 状态码                case StatusCodes.Status401Unauthorized:                    await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 Unauthorized")                        , App.GetOptions<JsonOptions>()?.JsonSerializerOptions);                    break;                // 处理 403 状态码                case StatusCodes.Status403Forbidden:                    await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "403 Forbidden")                        , App.GetOptions<JsonOptions>()?.JsonSerializerOptions);                    break;                default: break;            }        }        /// <summary>        /// 返回 RESTful 风格结果集        /// </summary>        /// <param name="statusCode"></param>        /// <param name="succeeded"></param>        /// <param name="data"></param>        /// <param name="errors"></param>        /// <returns></returns>        private static RESTfulResult<object> RESTfulResult(int statusCode, bool succeeded = default, object data = default, object errors = default)        {            return new RESTfulResult<object>            {                StatusCode = statusCode,                Succeeded = succeeded,                Data = data,                Errors = errors,                Extras = UnifyContext.Take(),