无法向会话状态服务器发出会话状态请求。请确保 ASP.NET State Service (ASP.NET 状态服务)已启动,并且客户端端口与服务器端口相同。如果服务器位于远程计算机上,请检查 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\AllowRemoteConnection 的值,确保服务器接受远程请求。如果服务器位于本地计算机上,并且上面提到的注册表值不存在或者设置为 0,则状态服务器连接字符串必须使用“localhost”或“127.0.0.1”作为服务器名称。
无法连接 StateService 的原因有很多种,我遇到的情况是:
VS 中使用 IISExpress 调试项目,系统没有安装 IIS,
ASP.NET 状态服务是开启状态,
没有对系统作任何修改,无缘无故无法连接。
修改注册表、配置防火墙无效(我没有用到远程连接),
执行 aspnet_regiis -i -enables 无效,
重装 .NET Framework 4.8 Developer Pack 无效,
差点就重装系统了。
最终的解决方法是:
在“启用或关闭 Windows 功能”中勾选安装:Internet Information Services -> 万维网服务 -> 应用程序开发功能 -> ASP.NET 4.8
重启系统。

场景:每天带笔记本电脑上班,公司使用固定 IP 上网,家里网络的网段又跟公司不一样。
方法 1:两个网络环境分别使用有线连接和无线连接(鉴于笔记本无线网卡不支持 5G(802.11ac),或者有其它需求必须都使用有线连接的不适用此方法)。
方法 2:修改家里路由器的局域网 IP 地址,使其网段与公司相同(虽然仅需要一次修改,但部分路由器不允许修改局域网 IP 地址);
方法 3:使用第三方软件一键切换(有安全隐患,且可能导致电脑网络设置出现错误);
方法 4(推荐):设置电脑网络连接同时使用两个 IP,具体方法如下:
打开“以太网 属性”,双击“Internet 协议版本 4 (TCP/IPv4)”,配置其中一个网络环境的 IP 地址和网关(例如我在公司上网的 IP 是 192.168.1.5,网关是 192.168.1.1)
点击“高级”,在 IP 地址栏添加另一个网络的 IP(如 192.168.31.5),在“默认网关”处添加这个网络的网关(如 192.168.31.1)
这样设置后,不管到公司还是回到家,只要网线一插就能上网,不需要每天更换 IP 地址设置了。
(图中显示了两个网络名称)

如果你是刚入手的小米 WiFi 电力猫,那么按说明书一步一步配置即可。这里记录一下移机的重置方法。
本次移机的场景是电力猫从一台小米路由器更换到另一台小米路由器的配置,两台小米路由器 WIFI 的 SSD 不同(假设为 WIFI-A 和 WIFI-B)。
错误做法:打开米家,切换到 WIFI-B 所在的房间,点击右上角的添加新设备,根据介绍插上子猫,并按子猫的配置键 5 秒以上。连上“xiaomi-plc-xxx”,米家显示正在连接,但到达 100% 后显示连接超时。
可能的错误原因:
没有插上主猫
该电子猫已经在米家上添加过了(在 WIFI-A 所在的房间),就无法添加到其它房间了(仅猜测,未实测)
于是,同时插上主猫和子猫,并且路由器和主猫使用网线连接,什么都不设置,过一两分钟,两猫自动会变成蓝灯(表示连接成功)。这时,如果刚才那步没有重置成功的话,打开米家主界面上的“小米电力线wifi扩展器”(如果没有删除过,还在 A 房间),里面会显示网络名称为 WIFI-A,还可能没有开“WIFI漫游”。这样虽然能连接上网,但有两个 SSD,不能实现漫游。
这时,按子猫配置键 5 秒以上,并静等一分钟以上,变回蓝灯后,再打开米家,就会看到网络名称可能是 WIFI-B-Plus,这时"WIFI漫游”也能打开了,完美扩展了 WIFI。
综上实践得出:
WIFI 是由子猫负责的,所以跟 SSD 有关的配置由子猫负责;
一台设备(这里指电子猫套餐)只能添加到米家一次,但可以更改设备所在的房间。
正确的重置电力猫步骤是:
同时插上主猫和子猫
主猫连上路由器
子猫按配置键 5 秒以上,等待一分钟以上
打开米家,进入“小米电力线wifi扩展器”,打开 WIFI 漫游
以上仅在使用小米路由器情况下实测,非小米路由器未尝试。

常用获取英文的方法是查看英文版 App。
微信:WeChat
小程序:Mini Programs
公众号:Official Accounts
订阅号:Subscriptions
朋友圈:Moments
已登录的:Logged
服务通知:Service Messages
置顶:Sticky on Top / Remove from Top
发送给朋友:Share to Friends
微信号:WeChat ID
钱包:Wallet
余额:Balance
资金/零钱/基金:Funds
金额:Amount
明细:Details
账单:Transactions
资产:Assets
收入/收益:Income
财富:Fortune
登录:Log In / Login
退出登录:Log Out / Logout

第一种方式,直接执行上一页的原生方法:
var pages = getCurrentPages();
if (pages.length > 1) {
var prePage = pages[pages.length - 2];
prePage.onPullDownRefresh();
}
第二种方式,在上一页的 onShow() 中对数据进行更新。
推荐使用第一种方式,但只能调用原生方法,不能直接调用 methods 中的自定义方法(详见)。第二种方式的缺点是,有时候不需要在返回上一页时更新数据,但它仍会执行。

当使用在线编辑器编辑一篇文章(或从 Word 复制)后,会得到包含 HTML 标签的字符串内容,可以直接将它输出到页面上而不需要进行 HTML 编码。
但是,当我们需要改变图片大小时,我们发现有些图片的尺寸是直接使用 style 属性固定的,除了用 JS 进行后期处理,我们可以在服务端对 <img /> 进行修正。
这个场景会在小程序开发的时候遇到。
我们可以在客户端用 JS 进行处理,也可以在服务端用类似的方法处理(使用正则表达式)。参此文
这里使用 HtmlAgilityPack 通过递归节点来处理:
/// <summary>
/// 给所有指定节点添加样式
/// </summary>
/// <param name="html"></param>
/// <param name="tag">节点名称(小写),如:img</param>
/// <param name="styles">要添加的样式,如:max-width:100%;</param>
/// <returns></returns>
public static string AddStyleToHtmlNode(string html, string tag, string styles)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
for (var i = 0; i < doc.DocumentNode.ChildNodes.Count; i++)
{
doc.DocumentNode.ChildNodes[i] = AddStyleToHtmlNode(doc.DocumentNode.ChildNodes[i], tag, styles);
}
return doc.DocumentNode.OuterHtml;
}
private static HtmlNode AddStyleToHtmlNode(HtmlNode node, string tag, string styles)
{
if (node.Name == tag)
{
var style = node.GetAttributeValue("style", null);
node.SetAttributeValue("style", ((string.IsNullOrWhiteSpace(style) ? "" : style.Trim() + ";") + styles).Replace(";;", ";"));
}
for (var i = 0; i < node.ChildNodes.Count; i++)
{
node.ChildNodes[i] = AddStyleToHtmlNode(node.ChildNodes[i], tag, styles);
}
return node;
}
直接调用:
Content = zStringHTML_190126.AddStyleToHtmlNode(Content, "img", "max-width:100%;height:auto;");

一、显示信息的命令
console.log("normal"); // 用于输出普通信息 console.info("information"); // 用于输出提示性信息 console.error("error"); // 用于输出错误信息 console.warn("warn"); // 用于输出警示信息
二、点位符:字符(%s)、整数(%d或%i)、浮点数(%f)和对象(%o);
console.log("%s","string"); //字符(%s) console.log("%d年%d月%d日",2016,8,29); //整数(%d或%i) console.log("圆周率是%f",3.1415926); //浮点数(%f) var dog = {}; dog.name = "大毛"; dog.color = "黄色"; dog.sex = "母狗"; console.log("%o",dog); //对象(%o)
三、信息分组 (console.group(),console.groupEnd())
console.group("第一组信息"); console.log("第一组第一条:我的博客"); console.log("第一组第二条:CSDN"); console.groupEnd(); console.group("第二组信息"); console.log("第二组第一条:程序爱好者QQ群"); console.log("第二组第二条:欢迎你加入"); console.groupEnd();
四、将对象以树状结构展现 (console.dir()可以显示一个对象所有的属性和方法)
var info = { name : "Alan", age : "27", grilFriend : "nothing", getName : function(){ return this.name; } } console.dir(info);
五、显示某个节点的内容 (console.dirxml()用来显示网页的某个节点(node)所包含的html/xml代码)
var node = document.getElementById("info"); node.innerHTML += "<p>追加的元素显示吗</p>"; console.dirxml(node);
六、判断变量是否是真 (console.assert()用来判断一个表达式或变量是否为真,只有表达式为false时,才输出一条相应作息,并且抛出一个异常)
var testObj = false; console.assert(testObj, '当testObj为false时才输出!');
七、计时功能 (console.time()和console.timeEnd(),用来显示代码的运行时间)
console.time("控制台计时器"); for(var i = 0; i < 10000; i++){ for(var j = 0; j < 10000; j++){} } console.timeEnd("控制台计时器");
八、性能分析performance profile (就是分析程序各个部分的运行时间,找出瓶颈所在,使用的方法是console.profile()和console.proileEnd();)
function All(){ // alert(11); for(var i = 0; i < 10; i++){ funcA(100); } funcB(1000); } function funcA(count){ for(var i = 0; i < count; i++){}; } function funcB(count){ for(var i = 0; i < count; i++){}; } console.profile("性能分析器"); All(); console.profileEnd();
详细的信息在chrome控制台里的"profile"选项里查看
九、console.count()统计代码被执行的次数
function myFunction(){ console.count("myFunction 被执行的次数"); } myFunction(); //myFunction 被执行的次数: 1 myFunction(); //myFunction 被执行的次数: 2 myFunction(); //myFunction 被执行的次数: 3
十、keys和values,要在浏览器里输入
十一、console.table表格显示方法
var mytable = [ { name: "Alan", sex : "man", age : "27" }, { name: "Wu", sex : "gril", age : "28" }, { name: "Tao", sex : "man and gril", age : "29" } ] console.table(mytable);
十二、Chrome 控制台中原生支持类jQuery的选择器,也就是说你可以用$加上熟悉的css选择器来选择DOM节。
$("body"); //选择body节点
十三、copy通过此命令可以将在控制台获取到的内容复制到剪贴板
copy(document.body); //复制body copy(document.getElementById("info")); //复制某id元素的的节点
十四、$_命令返回最近一次表达式执行的结果,$0-$4代表了最近5个你选择过的DOM节点
十五、利用控制台输出文字,图片,以%c开头,后面的文字就打印的信息,后面一个参数就是样式属性;
console.log("请在邮件中注明%c 来自:console","font-size:16px;color:red;font-weight:bold;");
uni-app 的 input,当 type = "text" 时,设置 confirm-type = "next" 可以提供键盘的“下一项”,通过 @confirm 触发设置下一个文本框的焦点,即可实现只操作键盘即可完成表单填写。
我们以从 a 组件跳转到 b 组件为例:
<input v-model="a" type="text" confirm-type="next" @confirm="moveNext('b')" />
uni-app 的组件只有属性和事件,不提供方法,那么我们只能设置提供了 focus 属性的组件的焦点。
<input v-model="b" :focus="focusList['b']" />
moveNext() 方法实现:
moveNext(dom){
this.focusList[dom] = true;
}
当然 focusList 必须先定义并初始化,否则设置 focus 为 true 将失效:
data() {
return {
a: '',
b: '',
focusList: {
b: false
}
}
}
几个注意点:
confirm-type = "next" 仅支持微信小程序
“下一项”按钮效果是一次性的,使用一次后再次在 a 上触摸“下一项”将不再跳转到 b,即使在 moveNext() 中先设置为 false 再设置为 true 也没用
其它组件也可以通过自带的方法实现跳转到带 focus 属性的组件(如在 picker 的 @change 中设置 this.focusList['b'] = true)
限制于 confirm-type = "next" 只当 type = "text" 时有效,因此非文本键盘(如数字键盘)就不能提供“下一项”功能
textarea 组件的右下角始终是“换行”,由其右上角的“完成”按钮来实现“下一项”功能
textarea 组件在“下一项”跳转过程中,组件可能会被 tabBar 遮挡(几率很大)

在学习和使用 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 编码

本文作为 多平台用户登录模块设计 的扩展设计,即以手机号作为用户的唯一凭证。
官方文档的小程序登录时序:
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
(图片摘自 2018.10.30)
session_key 是密钥,仅保存于开发者服务器,用于将小程序通过前端接口获取到的数据解密来验证其真实性。详见 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
小程序与服务器是通过自定义登录态来识别用户身份的,以下简称口令(token)。
由于微信未告知 session_key 的有效期,那么为保证小程序发起业务请求成功,token 须要永久有效,但这会带来安全隐患。
如果设置 token 的有效期(如 7200 秒),那么小程序发起业务请求后,服务器必须把 token 的验证结果告知小程序,若失效则重新登录。
微信授权登录(含绑定手机号码)流程图
子流程:授权登录
子流程:绑定手机号
为保证数据安全,针对每个须要授权登录的业务请求,服务器都都会检验 token 的有效性。如果小程序同时发起多个业务请求,并几乎同时收到 token 过期,那么会同时发起多个重新登录流程,服务器多次 code2Session,重新生成多个 token 返回到小程序,那么小程序最终保存的 token 可能不是服务器上认为的最新的一个 token。这样,如果程序设计为获取到 token 继而重新发起业务请求,可能会进入死循环。解决的方法是在同时发起多个业务请求之前先向服务器验证一次 token 的有效性,再发起多个业务请求时就不会出现都过期的情况了。
