这是一篇可能解决困扰了 .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 方式发布。
2025年9月补充:因预编译发布后生成的文件时间都是系统最新时间,通过 FTP 上传到服务器时都会覆盖文件,可能导致杀毒软件进程 MsMpEng.exe 占用 CPU 过高。尝试选择不进行预编译,即取消“发布期间预编译”前的勾,这样发布的的文件时间都是文件本身的最后修改时间,重复发布不会更新这个时间,而且 bin 目录中也不会出现大量编译后的文件。这样通过 FTP 上传时只要设置“大小不同且文件较新则覆盖”就不会重复上传文件了,杀毒软件的工作量就能大大减少。缺点就是本文开头说的,每个页面的第一次打开会比较慢,而是非常慢。所以仍然需要勾选“发布期间预编译”,同时勾上“允许更新预编译站点”,这样 bin 目录中不会发现很多文件(合并不合并都一样),打开每个页面的第一次耗时也能在接受范围内。
前提:
若需获取用户 unionid,则小程序必须已绑定到微信开放平台。
小程序调用 wx.login(),将 code 发送到开发者服务器
开发者服务器请求微信接口 code2Session,获得 session_key、openid,有机会获得 unionid(参 UnionID 机制说明)。因此无需任何用户授权即可获取 openid
访问须要已获取用户信息的页面时,可通过小程序端使用 button + getUserInfo 的方式请求用户授权
访问须要已获取手机号码的页面时,可通过小程序端使用 button + getPhoneNumber 的方式请求用户授权
code2session、getUserInfo、getPhoneNumber 等接口返回数据存入数据库 dt_weapp_user
服务端的任何接口均返回口令状态包(含自定义会话口令等信息),小程序端应保存于 storage
请求 getUserInfo 后应判断 detail.errMsg 是否为 getUserInfo:ok,请求 getPhoneNumber 后应判断 detail.errMsg 是否为 getPhoneNumber:ok
授权窗口的弹出情况参此文
口令状态包是用来传递到客户端用来标识用户唯一凭证的,包含字段:会话口令(自定义用户凭证)、会话口令有效时间(秒)、是否已获取 unionid 或昵称(视业务需求)、是否已获取手机号码、uid(可选),以及其他用来标识业务身份字段。服务端可根据会话口令从数据库中获取该用户的所有已知信息。
一般情况下,当已获取手机号码时,uid 必有值,否则,uid 必为空。因此 uid 不是必须传递到客户端的。
通过 code2Session 获得的 session_key、以及 openid、unionid、手机号码等机密信息不允许包含在口令状态包中并缓存在客户端,客户端需要用到手机号码时单独调用接口获取。
若业务不要求以手机号码作为用户唯一标识,那么口令状态包中不需要包含是否已获取手机号码,仅 uid 即可。在客户端访问须要业务用户登录的页面时,也仅判断 uid 有值即可。
流程图:

为更好地适应 2017.7.19 发布的《小程序内用户帐号登录规范调整和优化建议》,开发者服务器提供的所有业务逻辑接口的返回值中都应包含口令状态包,约定有以下相关的返回值和处理动作(返回值按个人喜好来定):
| 口令状态 | 接口返回 | ASP.NET Web API 语句 | 处理动作 |
| access_token 空或失效 | 401 状态码 | return Unauthorized(); | login() + code2Session,获取 openid,生成 access_token |
| access_token 有效,但未获取 uniond 或用户信息 | 401 状态码,http 头 WWW-Authenticate 中标注 getUserInfo | return Unauthorized(new AuthenticationHeaderValue("getUserInfo")); | getUserInfo() |
| access_token 有效,但未获取手机号码(即未找到业务用户) | 401 状态码,http 头 WWW-Authenticate 中标注 getPhoneNumber | return Unauthorized(new AuthenticationHeaderValue("getPhoneNumber")); | getPhoneNumber() |
WWW-Authenticate 有大小写的问题,千万别入坑,参:https://xoyozo.net/Blog/Details/header-www-authenticate
返回内容是根据业务逻辑在服务端决定的,客户端在处理每个接口返回值时对返回内容作相应的处理即可。
微信小程序 login 后,在用 code 换 session 时,如果小程序绑定了微信开放平台,应该会返回 unionid。
文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/code2Session.html。
但实际上部分用户仍然是没有 unionid 的,把上述接口返回的数据记录下来如:

仔细阅读官方 UnionID 机制说明,通过用户授权 wx.getUserInfo 才会保证有 unionid 返回,否则只会在“用户关注了绑定同一微信开放平台的同主体的公众号”等情况下才会返回 unionid。
确保获取 unionid 流程参此文。
在 Godday 上搜索域名,会引导购买一个叫“域名购买服务”的产品(英文版上是人民币 472 元,新加坡简体中文版上的新加坡元换算成人民币更贵),稍不注意就会以为是域名本身,其实是让 Godday 联系域名所有者实现交易的服务费。付款后可设置你希望购买该域名的价格范围。
发现入坑后立刻问度娘,发现好多人跟我一样被坑。
于是按 https://sg.godaddy.com/zh/contact-us 上的 400 电话打过去,客服 MM 说原则上是不退款的哦。后来又得知我没有设置过域名报价,她说可以试试提交一下退款申请。听了十几分钟的音乐后,客服提交完毕,告诉我,如果审批成功,2 天内会收到邮件,15 天内会收到退款。
等了 48 小时没有收到邮件。第 3 天晚上,直接收到退款。
一般地,我们会设置网站中的图片文件仅允许本网站页面调用,或者开放地址栏直接打开,但不允许其它网站调用。
这就需要在 Web 服务器上判断图片的来源(Referer)。
但是,当网站页面是 https,而图片地址是 http 时,考虑到安全性,浏览器将不带 Referer,这时如果图片允许在地址栏直接打开(即 Referer 为空),就起不到防盗链的作用了。
所以图片服务器在处理防盗链时,尽量不要允许 Referer 为空时调用。这时如果要单独允许另一个网站来调用图片,应该使用一致的 protocol。
HTTP Error 502.5 - ANCM Out-Of-Process Startup Failure
Common causes of this issue:
The application process failed to start
The application process started but then stopped
The application process started but failed to listen on the configured port
Troubleshooting steps:
Check the system event log for error messages
Enable logging the application process' stdout messages
Attach a debugger to the application process and inspect
For more information visit: https://go.microsoft.com/fwlink/?LinkID=808681
发现装错了东西,服务器上应该安装 dotnet-hosting-*.*.*-win.exe,而非 dotnet-runtime-*.*.*-win-x64.exe
注册苹果开发者账号和发布 App 会填写一些联系邮箱,一旦账号或应用违规,苹果方也只会以解决方案中心或邮件的形式通知开发者,一旦超过时限,可能会采取一些强制措施,影响到我们的 App 正常上架运营,本文罗列了一些相关的电子邮箱,如有不全请指正。
在注册 Apple ID (https://appleid.apple.com/account/manage)时会填写 3 个邮箱,一个用于登录用户名,一个作为常规联系,第 3 个是用于救援(估计是常规联系邮箱失效时才会用到)。

在 App Store Connect (https://appstoreconnect.apple.com/)中发布新版本 App 时会填写两个邮箱,一个是 App 的联系邮箱,一个是审核联系邮箱。

当我们在联系审核团队(https://developer.apple.com/contact/app-store/)时,可能会填写一个邮箱,用于针对当前问题进行反馈。
苹果的“联系我们”(https://developer.apple.com/contact/)
其它一些主动提交的信息,用于反馈,如:投诉侵犯知识产权(https://www.apple.com/legal/internet-services/itunes/appstorenotices/#?lang=zh)
当使用在线编辑器编辑一篇文章(或从 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 之前,最好先对 RESTful 有所了解。它是一种软件架构风格、设计风格,而不是标准。
推荐视频教程:https://www.imooc.com/learn/811
讲师:会飞的鱼Xia
需时:2小时25分
REST API 测试工具:
在 Chrome 网上应用店中搜索:Restlet Client
或网站 https://client.restlet.com
>>> 资源路径
SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
其中 PATH 中的资源名称应该使用复数名词,举例:
GET https://xoyozo.net/api/Articles/{id}
POST https://xoyozo.net/api/Articles
>>> HTTP verb(对应 CURD 操作):
| 方法 | 功能 | ASP.NET Web API 接口返回类型(一般的) |
|---|---|---|
| GET | 取一个或多个资源 | T 或 IEnumerable<T> |
| POST | 增加一个资源 | T |
| PUT | 修改一个资源 | T |
| DELETE | 删除一个资源 | void |
| PATCH | 更新一个资源(资源的部分属性) | |
| HEAD | 获取报头 | |
| OPTIONS | 查询服务器性能或资源相关选项和需求 |
>>> 过滤信息:
如分页、搜索等
>>> 常用状态码:
| 200 | OK | 指示请求已成功 |
| 201 | Created | 资源创建成功(常见用例是一个 PUT 请求的结果) |
| 202 | Accepted | 该请求已被接收但尚未起作用。它是非承诺的,这意味着HTTP中没有办法稍后发送指示处理请求结果的异步响应。 |
| 204 | No Content | 成功 PUT(修改)或 DELETE(删除)一个资源后返回空内容(ASP.NET Web API 中将接口返回类型设置为 void 即可) |
| 400 | Bad Request | 服务器无法理解请求(如必填项未填、内容超长、分页大小超限等),幂等 |
| 401 | Unauthorized | 未授权(如用户未登录时执行了须要登录才能执行的操作),通俗地讲,是服务器告诉你,“你没有通过身份验证 - 根本没有进行身份验证或验证不正确 - 但请重新进行身份验证并再试一次。”这是暂时的,服务器要求你在获得授权后再试一次。 |
| 403 | Forbidden | 服务器理解请求但拒绝授权(是永久禁止的并且与应用程序逻辑相关联(如不正确的密码、尝试删除其它用户发表的文章)、IP 被禁止等),通俗地讲,是服务器告诉你,“我很抱歉。我知道你是谁 - 我相信你说你是谁 - 但你只是没有权限访问此资源。” |
| 404 | Not Found | 找不到请求的资源(指资源不存在,如果找到但无权访问则应返回 403) |
| 405 | Method Not Allowed | 请求方法被禁用(HTTP 动词) |
| 500 | Internal Server Error | 服务器遇到阻止它履行请求的意外情况(如数据库连接失败) |
| 503 | Service Unavailable | 服务器尚未准备好处理请求(常见原因是服务器因维护或重载而停机,这是临时的,可用于在单线程处理事务时遇到被锁定时的响应,如抽奖活动、抢楼活动,防止因并发导致逻辑错误) |
>> 错误处理:
C# 例:throw new HttpResponseException(HttpStatusCode.NotFound);
PHP 例:throw new Exception('文章不存在', 404);
>>> 返回结果:
JSON/XML
不要返回密码等机密字段
>>> 其它:
身份验证窗口(浏览器弹窗,类似 alert,非页面表单),明文传输,安全性不高,不建议使用。实现:
在 Headers 中添加 Authorization:Basic “用户名:密码”的 Base64 编码

