打开项目文件,在 <ItemGroup /> 中添加一个 <DotNetCliToolReference />:(注意版本号)
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.3" />
结果:

微信的 H5 页面前端引导到:
https://api.magcloud.cc/magshare/{siteId}?jump_url={jump_url}&content_url={content_url}
各参数含义见官方文档:http://doc.magcloud.net/325339
content_url
地址(一般仍为活动页面)接收参数magshareredirect
,值为 1 则跳转到自定义的引导页。自定义引导页按 iOS 和安卓显示图文和箭头,引导用户点击右上角菜单,在浏览器中打开。
在浏览器 H5 中引导打开:
{siteId}://pagejump?jump_url={jump_url}
各参数含义见第 1 步中的文档。
这里涉及一个“若未安装则引导安装”的过程,可以选择下方合适的一种方式实现:
A
标签的href
填入下载地址,onclick
事件location
到 Apponclick
事件location
到 App,再用setTimeOut
location
到下载地址若仍无法实现预期效果,可以在弹出层中放置“未安装,前往下载”和“已安装,立即打开”两个按钮,让用户自己选择
若下载地址为应用宝,那么在 iOS 中可能会出现“打开 App 后继而自动打开 App Store”的情况,可以自建中转的 App 介绍页引导到 App Store 或应用宝。
分别实现微信和 MAGAPP 客户端的分享

本文介绍 ASP.NET 的 Swagger 部署,若您使用 ASP.NET Core 应用程序,请移步 ASP.NET Core Web API Swagger 官方文档:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore
安装
NuGet 中搜索安装 Swashbuckle,作者 Richard Morris
访问
http://您的域名/swagger
配置
显示描述
以将描述文件(xml)存放到项目的 bin 目录为例:
打开项目属性,切换到“生成”选项卡
在“配置”下拉框选择“所有配置”
更改“输出路径”为:bin\
勾选“XML 文档文件”:bin\******.xml,(默认以程序集名称命名)
打开文件:App_Start/SwaggerConfig.cs
取消注释:c.IncludeXmlComments(GetXmlCommentsPath());
添加方法:
public static string GetXmlCommentsPath() { return System.IO.Path.Combine( System.AppDomain.CurrentDomain.BaseDirectory, "bin", string.Format("{0}.xml", typeof(SwaggerConfig).Assembly.GetName().Name)); }
其中 Combine 方法的第 2 个参数是项目中存放 xml 描述文件的位置,第 3 个参数即以程序集名称作为文件名,与项目属性中配置一致。
如遇到以下错误,请检查第 2、3、4 步骤中的配置(Debug / Release)
500 : {"Message":"An error has occurred."} /swagger/docs/v1
使枚举类型按实际文本作为参数值(而非转成索引数字)
打开文件:App_Start/SwaggerConfig.cs
取消注释:c.DescribeAllEnumsAsStrings();

之前已经介绍了百度编辑器在 ASP.NET 环境下的配置,本文继续补充改进其将图片等附件上传到阿里云 OSS。
首先从阿里云控制台下载相关 SDK,并阅读相关的 API 文档。链接:https://promotion.aliyun.com/ntms/act/ossdoclist.html
改造上传代码
打开文件 UploadHandler.cs,在 Process() 方法中找到:
File.WriteAllBytes(localPath, uploadFileBytes);
在其下方插入代码:
client.PutObject(bucketName, "upload/ueditor/" + savePath, localPath);
实现图标按钮上传和 HTML5 拖放上传。
PutObject() 的第 2 个参数是 OSS 中的图片路径,第 3 个参数是图片在网站目录下的磁盘路径。
打开文件 CrawlerHandler.cs,在 Fetch() 方法中找到:
File.WriteAllBytes(savePath, bytes);
在其下方插入代码:
client.PutObject(bucketName, "upload/ueditor/" + ServerUrl, savePath);
实现粘贴板图片上传。
同样注意两个参数变量。
修改路径前缀
上传成功后,编辑器会拼装路径到 HTML 代码中,这个前缀是在 config.json 文件中配置的,具体找到以 UrlPrefix
结尾的属性,如 imageUrlPrefix
,进行相应的修改即可:
"imageUrlPrefix": "//oss.xoyozo.net/upload/ueditor/", /* 图片访问路径前缀 */
路径以“//”开头能更好地兼容 https。

传统的 LIKE 模糊查询(前置百分号)无法利用索引,特别是多个关键词 OR,或在多个字段中 LIKE,更是效率低下。本文研究对文章进行分词以提高检索的准确度和查询效率。
根据自己的编程语言选择一款合适的中文分词组件,我在 ASP.NET 平台下选择了 jieba.NET。
设想的步骤:
分别对文章标题、标签、正文进行分词,保存到一张分词表上。该表把“文章 ID”和“词语”设为联合主键,用 3 个字段记录该词语分别在标题、标签、正文中出现的次数,另外还可以按需要添加文章分类 ID、文章创建时间等字段。
当用户输入关键词进行检索时,先将关键词分词,在分词表中用 in 语法查询到所有相关的记录;
使用 group by 语法对查询结果按文章 ID 分组;
关键在排序上,理想的排序是:
a. 先按搜索关键词中不同词语的出现量排序,即:若搜索关键词分词后是 3 个词语,那么全部包含这 3 个词的文章优先,只匹配其中 2 个词语的其次;
b. 再按搜索关键词在文中累计出现的次数排序(考虑权重),即:我们先假定标题和标签的分词权重为 5(意思是一个分词在标题中出现 1 次相当于在正文中出现 5 次),那么累加每个分词在标题、标签、正文的权重次数,得分高的优先;
c. 再进一步考虑文章的发布时间,即将文章的发布时间距离最早一篇文章的发布时间(或一个较早的固定日期)相隔的天数,乘以一个系数加入到权重中,这个系数按不同文章分类(场景)不同,比如新闻类的大一点,情感类的小一点)。乘以系数时一篇文章只加权一次,不要加权到每个分词。
d. 根据需求还可以加入文章热度(阅读数)的权重。
根据上述逻辑对一个有 18 万篇文章的内容管理系统进行改造,循环所有文章进行分词统计,得到一张包含 5 千万条记录的分词表(系统中部分文章只有标题、标签和外链,没有正文,否则更多)。
由于查询中包含 in、group by、count、sum、运算等,再若分类是无级限的,即文章分类 ID 也是 in 查询,然后分页,即使创建索引,效率也只能呵呵了。
简化:
不对正文进行分词;
不按权重进行排序;
那么分词表的记录数降到 250 万条,同样用 in 查询分词,先按搜索关键词中不同词语的出现量排序,再按发布时间排序,分页后获得一页的文章 ID 集合,再去文章表中 in 获取详细信息(注意保持一页中的排序)。
添加相关索引后,查询一个包含 3 个分词的关键词仅需十几毫秒。因为 in 的内容比较离散,所以索引的利用率比较高。

前言:
ChatGPT 给了 3 条建议:
在应用程序中正确释放数据库连接。确保在使用完数据库连接后,将其关闭并将其返回到连接池中。您可以使用 using 语句来确保连接在使用完毕后被正确释放。
调整连接池的大小。默认情况下,连接池的最大大小为 100。如果您的应用程序需要更多的连接,则可以增加连接池的大小。您可以在连接字符串中设置 Max Pool Size 属性来调整连接池的大小。
调整连接池的超时时间。默认情况下,连接池中的连接在 30 秒钟内未使用时将被关闭。如果您的应用程序需要更长的连接时间,则可以增加连接池的超时时间。您可以在连接字符串中设置 Connection Lifetime 属性来调整连接池的超时时间。
亲测有效,尤其是第 3 条,原因是 Connection Lifetime 的默认值是 0,即没有超时限制。
—— 2023.5
一般地,我们使用 EF 连接数据库前会先初始化一个数据库上下文:
dbEntities db = new dbEntities();
虽然 ASP.NET 会在查询完毕后自动关闭该连接,但是在什么情况下回收等都是不确定的,所以会导致 MySQL 中出现很多 Sleep 的连接(执行命令 SHOW FULL PROCESSLIST
可见),占用数据库的连接数,除非主动调用 Dispose():
db.Dispose();
官方建议的写法是使用 using
语法:
using (dbEntities db = new dbEntities())
{
}
using 会自动调用 Dispose()。这样对减少连接数是很有效的,但官方提示为了提高下一次连接的速度,并不会完全关闭所有连接。
C# 8 建议写法:
using dbEntities db = new dbEntities();
在实际项目中(该项目有 500+处数据库连接)测试结果,在不执行 Dispose() 时稳定为 140 个左右连接数,使用 using 或 Dispose() 后稳定变为 40 个左右。
如果不小心在 using 外部或 Dispose() 后再次对该上下文执行查询操作会出现异常:
无法完成该操作,因为 DbContext 已释放。
或
此 ObjectContext 实例已释放,不可再用于需要连接的操作。
所以要避免出现这种情况。这里还有一种另类的解决方法(不建议),根据上下文的特性,只要在 using 内查询一次(譬如视图中需要用到的导航属性,即外键关联的表),就可以在外部使用这个属性。
(建议)在 ASP.NET MVC 或 Web API 项目中,如果一个控制器中仅在 Action 外部定义一个 DbContext,那么,只要重写该控制器的 Dispose() 方法即可:
private dbEntities db = new dbEntities();
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
上下文使用 private 修饰即可。

这是一篇可能解决困扰了 .NET 程序猿多年的文章!
首先,使用 Visual Studio 2019 创建一个 ASP.NET WEB 应用程序,直接选用了 MVC 模板。
直接发布项目,默认的设置如下图:
对于小型项目,按默认设置发布基本可满足正常运行,首次运行打开第一个页面基本在 5~6 秒(视服务器配置),其它页面的首次打开也基本在 1~2 秒完成,非首次瞬间打开。
一旦项目功能变得复杂,文件增多,会导致发布后首次运行打开第一个页面 30 秒以上,其它页面的首次打开 10 秒左右,非首次瞬间打开。
这是因为项目在发布时没有进行预编译,而是在用户访问网页时动态编译,一旦应用程序池回收,或项目文件改动,都会重新编译,再次经历缓慢的“第一次”,这是不能忍的。
于是咱们来研究一下“预编译”。
在发布页面勾选“在发布期间预编译”,这时发布会在“输出”窗口显示正在执行编译命令(编译时间会比较长):
该选项对发布后的文件结构和内容影响不大,因此对首次执行效率的提升也不大,重要的在后面。
在“高级预编译设置”窗口中:
我们针对预编译选项和合并选项分别做测试。
不勾选“允许更新预编译站点”,会致 bin 目录中生成许多 .compiled 文件,而所有 .cshtml 文件的内容都是:
这是预编译工具生成的标记文件,不应删除!
如果是 Web From 网站,.aspx/.ashx 等文件内容同上。
尝试打开网站页面,你会发现,除了项目启动后的第一个页面仍然需要 1~2 秒(无 EF),其余每个页面的首次都是瞬间打开的(EF 的首次慢不在本文讨论范围)。这让我对预编译有一种相见恨晚的感觉!
这里偷偷地告诉你,把 Views 目录删掉也不影响网页正常打开哦~为什么不让删,咱也不敢问,咱也不敢删。
目的达到了,有一些后遗症需要解决,比如 bin 目录内杂乱无章。
选“不合并。为每个页面和控件创建单独的程序集”,结果是 bin 多出许多 App_Web_*.dll 文件。
选“将所有输出合并到单个程序集”,填写程序集名称。这时会发现“输出”窗口执行了命令:
如果程序集名称跟已有名称冲突,会发生错误:
合并程序集时出错: ILMerge.Merge: The target assembly '******' lists itself as an external reference.
查看 bin 目录,.compiled 文件还在,但 App_Web_*.dll 合并成了一个程序集。FTP 的“不合并”也是把所有 App_Web_*.dll 上传一遍,所以不存在相对于“合并”的增量发布优势。
是不是勾了“视为库组件删除(AppCode.compiled 文件)”.compiled 文件就会不见?意外之外,情理之中,bin 没有什么变化。
继续选择“将各个文件夹输出合并到其自己的程序集”,呃,bin 中文件数不降反增。
最后选择“将所有页和控件输出合并到单个程序集”,同样程序集名称不能冲突。结局令人失望,bin 中仍然一大堆 .compiled 和 App_Web_*.dll。
整理成表格:
1 | 允许更新预编译站点 aspnet_compiler.exe -v / -p \Source -u \TempBuildDir 重复发布后 bin 目录文件数:不会增多(页面不进行预编译) |
2 | 不允许更新预编译站点,不合并 aspnet_compiler.exe -v / -p \Source \TempBuildDir 重复发布后 bin 目录文件数:.compiled 不会增多,App_Web_*.dll 增多 |
3 | 不允许更新预编译站点,不合并。为每个页面和控件创建单独的程序集 aspnet_compiler.exe -v / -p \Source -c -fixednames \TempBuildDir 重复发布后 bin 目录文件数:不会增多(编译后的文件名固定,FTP 方式的部分 App_Web_*.dll 文件可能实现增量上传) |
4 | 不允许更新预编译站点,将所有输出合并到单个程序集,不勾选“视为库组件” aspnet_compiler.exe -v / -p \Source -c \TempBuildDir aspnet_merge.exe \TempBuildDir -o 程序集名称 -copyattrs AssemblyInfo.dll -a 重复发布后 bin 目录文件数:不会增多(.compiled 固定名称,App_Web_*.dll 合并为一个) |
5 | 不允许更新预编译站点,将所有输出合并到单个程序集,勾选“视为库组件” aspnet_compiler.exe -v / -p \Source \TempBuildDir aspnet_merge.exe \TempBuildDir -o 程序集名称 -copyattrs AssemblyInfo.dll -a -r 重复发布后 bin 目录文件数:不会增多(.compiled 固定名称,App_Web_*.dll 合并为一个) |
6 | 不允许更新预编译站点,将每个文件夹输出合并到其自己的程序集,前缀 aspnet_compiler.exe -v / -p \Source -c \TempBuildDir aspnet_merge.exe \TempBuildDir -prefix 前缀 -copyattrs AssemblyInfo.dll -a 重复发布后 bin 目录文件数:.compiled 不会增多,App_Web_*.dll 增多 |
7 | 不允许更新预编译站点,将所有页和控件输出合并到单个程序集 aspnet_compiler.exe -v / -p \Source -c \TempBuildDir aspnet_merge.exe \TempBuildDir -w 程序集名称 -copyattrs AssemblyInfo.dll -a 重复发布后 bin 目录文件数:.compiled 不会增多,App_Web_*.dll 增多 |
bin 目录下,页面的程序集文件(App_Web_*.dll)增多的原因是编译后的文件名不固定,发布到会导致残留许多无用的程序集文件。
相比较,如果第 3 种能实现 FTP 稳定的增量上传的话是比较完美的(还有一个缺点是:如果页面有删除,目标 bin 内对应该页面的 .compiled 和 .dll 不会删除,这跟“允许更新预编译站点”是一个情况),那么第 4 种 或第 5 种也是不错的选择(同样有缺点:执行合并比较慢)。
测试一个有 300 个页面的项目,compiler 用时约 2 分钟,merge 用时约 5 分钟,发布用时约 4 分钟。
这里有个槽点,执行预编译和合并是佛系的,CPU 和磁盘使用率永远保持在最低水平。
所以如果是要经常修改的项目,那么建议选择“不合并”的第 3 种发布方式:
“视为库组件(删除 AppCode.compiled 文件)”:移除主代码程序集(App_Code 文件夹中的代码)的 .compiled 文件。 如果应用程序包含对主代码程序集的显式类型引用,则不要使用此选项。
补充:
不允许更新预编译站点发布后,因为前端页面没有内容,因此无法单独修改发布(单独发布一个页面后,在访问时不会生效!),只能全站发布,耗时较长;bin 目录有变动将导致使用 InProc 方式存储的 Session 丢失。
预编译的另一个优点是可以检查 .aspx / .cshtml 页面 C# 部分的错误(特别是命名空间和路径引用)。
改为预编译发布后,可以将服务器上残留的 .master / .ascx / .asax 删除,但不能删除 .aspx / .ashx /.config 等。
VS 发布到 FTP 经常会遗漏一些页面文件,不合并时会遗漏独立文件,合并后也会遗漏合并后的 .dll 的文件,合并的好处就是方便检查是否完全上传发布。
慎选“在发布前删除所有现有文件”!一旦勾选发布,世界就清静了,所有网友上传的图片附件以及网站运行产生的其它文件,消失得无影无踪。不管发布到文件系统还是发布到 FTP 都一样。当然,如果是先发布到文件系统,再通过 FileZilla 等 FTP 软件上传到服务器的,建议勾选此项。
.NET Core 应用程序部署:https://docs.microsoft.com/zh-cn/dotnet/core/deploying/index
由于上传文件时 bin 目录文件较多,理论上 bin 目录内的文件有任何改动都会重启应用池,而且 VS 是单线程上传的,导致期间网站访问缓慢,服务器 CPU 升高,我的做法是:发布到文件系统,再使用专用 FTP 工具上传,上传用时约半分钟(如果大小不同或源文件较新则覆盖文件)。还嫌慢?那就打包上传,解压覆盖。
关于本文研究对象的官方解释:高级预编译设置对话框
出现“未能加载文件或程序集“***”或它的某一个依赖项。试图加载格式不正确的程序。”的问题时,先用改用 Debug 方式发布会报详细错误,一般是 .aspx 等客户端页面有 C# 语法问题,注意提示报错的是 /obj/ 目录下的克隆文件,应更改原文件。排除错误后关闭所有页面,再使用 Release 方式发布。

当使用在线编辑器编辑一篇文章(或从 Word 复制)后,会得到包含 HTML 标签的字符串内容,可以直接将它输出到页面上而不需要进行 HTML 编码。
但是,当我们需要改变图片大小时,我们发现有些图片的尺寸是直接使用 style 属性固定的,除了用 JS 进行后期处理,我们可以在服务端对 <img /> 进行修正。
这个场景会在小程序开发的时候遇到。
网上一般使用正则表达式直接将 <img
替换成 <img style="max-width: 100%; height: auto;"
,缺点是如果该 <img /> 本身就带有 style 属性,那么会出现一个标签两个 style,很多情况导致这两个样式同时失效,所以我们应针对有 style 和无 style 分别处理。
// 把 <img src="a.jpg" style="display: block;" /> 替换成 <img src="a.jpg" style="display: block;;max-width:100%;height:auto;" />
Content = Content.replace(/(\<img\s+[^>]*style\s*\=\s*['"][^'"]*)(['"])/gi, '$1;max-width:100%;height:auto;$2');
// 把 <img src="b.jpg" /> 替换成 <img src="b.jpg" style="max-width:100%;height:auto;" />
Content = Content.replace(/(\<img\s+((?!style).)+?)(\/?>)/gi, '$1 style="max-width:100%;height:auto;" $3');
复制以上代码时,半角空格可能会变成全角空格,请注意修正。
当有 style 时,我们将 max-width: 100%; height: auto;
追加在原样式之后,以重写原样式。这里没有直接判断原样式是否以 ;
结尾,而是直接追加 ;
,这并不会影响实现展示效果。
在判断没有 style 用到正则表达式的“断言”,参:https://blog.csdn.net/xuyangxinlei/article/details/81359366

这是一个数据可视化项目,基于D3.js。能够将历史数据排名转化为动态柱状图图表。
先来看看效果:
https://xoyozo.net/Demo/BarGraph
作者 Jannchie 见齐还提供了官方视频教程:
https://www.bilibili.com/video/av28087807
不过由于开源项目的不断更新,该教程的部分内容已失效,本文针对 2018-12-25 版本总结了一些常用的配置说明,仅供参考。
从 GitHub 下载源代码,在 src 目录中可以看到 4 个文件:
bargraph.html --> 运行示例页面
config.js --> 配置文件
stylesheet.css --> 样式表
visual.js --> 核心文件
作者并没有将代码封装为插件的方式,所以我们是通过修改 config.js 配置文件的方式应用到自己的项目中的。
visual.js 虽然是核心文件,但作者将部分示例中的代码也包含其中,但并不影响我们直接在自己的项目中引用。
以下是 config.js 中的主要属性:
属性 | 说明 | 参考值 |
encoding | 数据源(csv、json)等的文件编码 | GBK / UTF-8 等 |
max_number | 每个时间节点最多显示的条目数 | 10 |
showMessage | 控制是否显示顶部附加信息文字 | true / false |
auto_sort | 时间自动排序(详细含义、作用及限制见代码中注释) | true / false |
timeFormat | 时间格式,显示于图表右下角的时间 | %Y、%Y-%M-%D 等 |
reverse | 是否倒序 | true / false |
divide_by | 类型根据什么字段区分?如果是 name,则关闭类型显示 | |
divide_color_by | 颜色根据什么字段区分? 须要注意的是,如果配置成 name,则各条颜色不同(因为 name 值各异),如果配置成 type 等,那么相同 type 值的条颜色相同。 | 字段名 |
color | 指定部分或所有条的颜色,该项与 divide_color_by 设置有关。 | 以 divide_color_by = 'name' 为例,"中国"是其中的一项 name 值,那么该项将显示 #D62728 色: color: { '中国': '#D62728' } |
changeable_color | 若 true 则颜色的深浅将根据数据的增长率实时改变 | |
itemLabel | 左边文字 | |
typeLabel | 右边文字 | |
item_x | ||
interval_time | 时间点间隔时间 | 2 |
text_y | ||
text_x | ||
offset | ||
display_barInfo | 如果希望不显示,则可以设置较大的值,单位像素 | |
use_counter | ||
step | ||
format | 格式化数值 | |
left_margin | ||
right_margin | ||
top_margin | ||
bottom_margin | ||
dateLabel_x | ||
dateLabel_y | ||
allow_up | ||
enter_from_0 | ||
big_value | ||
use_semilogarithmic_coordinate | ||
long | ||
wait | 数据加载完成后开始播放前的等待时间 | 0 |
update_rate |

当我们用 ASP.NET Web API 作服务端接口时,如果客户端以 GET 方式请求的 URL 中,由于拼接错误导致“?”和“&”并列出现,会发生 400 Bad Request,那么需要在 IIS 中使用 URL 重写模块来纠正。
请求 URL 如:
http://www.abc.com/api/obj?&foo=bar
需要重写为:
http://www.abc.com/api/obj?foo=bar
打开 IIS 管理器中对应网站的 URL 重写,
添加空白规则。
匹配的 URL 不包含域名,如上述 URL 则匹配的范围是:
api/obj
不包含我们要查找的字符串 ?&,所以此处模式只能匹配所有:
.*
又因为重定向路径中需要包含这部分,所以用括号包裹,即:
(.*)
继续添加条件匹配:
条件输入:{QUERY_STRING},表示匹配查询字符串,
匹配的部分是“?”后面的字符串:
&foo=bar
那么可以填写模式:
^\&.*
因为 & 后面的部分仍然需要使用,所以用括号包裹:
^\&(.*)
“操作”中选择类型“重定向”,URL 填写:
{R:1}?{C:1}
其中 {R:1} 指代 api/obj,{C:1} 指代 foo=bar
取消“附加查询字符串”前面的勾
重定向类型在测试时可以选择 302 或 307,没问题后选择 301。
完成。
最终我们可以在网站的配置文件 web.config 中看到:
<rewrite>
<rules>
<rule name="uni-app-h5-replace-q" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{QUERY_STRING}" pattern="^\&(.*)" />
</conditions>
<action type="Redirect" url="{R:1}?{C:1}" appendQueryString="false" redirectType="Temporary" />
</rule>
</rules>
</rewrite>
我们要把这段代码复制到开发环境源代码的 web.config 中,以免发布网站时将我们设置好的配置覆盖掉。
