Entity Framework Core SQLite provider向已存在的表中添加外键

SQLite本身不支持向已创建的表中添加外键,类似的限制还有很多,比较蛋疼,具体可以参见SQLite Limitations

项目中,如果是测试的时候,数据不是很重要的话,最方便的方法就是把已经创建的Migrations包括ModelSnapshot都删掉,重新Add-Migration重建数据库。

对于已经发布的应用,数据库不能删了创建的话,可以“曲线救国”。

假设需要给TableA添加一个需要建立外键的字段ColumnA,为了增加难度,假设TableB中的Column

B是TableA的外键。具体操作方法如下:

  1. 先在代码中TableA里添加ColumnA(不设置外键),Add-Migration,更新到线上数据库
  2. 将本地的数据库改名为database-backup,删除项目中所有Migrations和ModelSnapshot,创建一个RebuildDatabase的Migration,创建全新的数据库,从新数据库中复制TableA的Create Statement SQL语句并将改SQL语句中的表名改为TableA-New
  3. 在线上数据库中执行步骤2中的SQL语句,将创建TableA-New(已经含有外键约束了)
  4. 导出线上数据库中TableA中的数据到SQL文件中,并将该SQL文件中的表名改为TableA-New
  5. 将步骤4中的SQL文件的数据导入到线上数据库中
  6. 将线上数据库的TableA改名为TableA-Old,将TableA-New改名为TableA
  7. 因为重命名的关系,这时候TableB中的ColumnB是TableA-Old的外键,通过如下方法,将TableB中的外键约束改到TableA中:复制TableB的Create Statement,创建一个TableB-New,其中的ColumnB是TableA的外键,然后将TableB改名为TableB-Old,并将TableB-New改名为TableB(如果还有TableC中有TableB-Old的外键,通过同样的方法操作,以此类推),删除TableB-Old。(如果没有这样的TableB,则此步骤省略)
  8. 删除TableAOld
  9. 清空线上数据库__EFMigrationHistory表中的数据,并手动添加一条数据,以RebuildDatabase的Migration的文件名作为MigrationId,并输入当前的ProductVersion

收工!

自建NAS及DDNS

众所周知的原因,前段时间360云盘也倒下了,之前大部分照片、电影资源都放在360云盘上。由于国内的环境,感觉第三方云盘的可靠程度还不如自己建个NAS。前端时间研究了硬件方案,今天研究了下外网访问的方案。

固定IP肯定是拉不起,太贵了,国内运营商太黑心。只能通过DDNS,但花生壳这种我也不想用,以前试用过,速度太慢。既然是程序猿,还是自己来吧。具体方案如下:

  1. 阿里云的云解析DNS,升级付费版,将最低TTL值拉到1秒,其余都拉成最低配置,一年40.8块钱,完全可以接受。
  2. 在自己的阿里云服务器上搭建一个小站点,用于返回来访请求的公网IP地址。没有外网服务器的,可以利用ip138的服务来做,http://city.ip138.com/ip2city.asp
  3. 做一个小应用,跑在NAS上,每秒向步骤2中的站点请求获取NAS的外网IP,并通过阿里云云解析DNS的api接口,更新域名的IP地址,并记录,如果下次请求IP不变则跳过,IP变化了则更新。做好日志,运行一段时间之后看下电信的动态ip更换有没有规律,可以适当调整获取外网IP的频率。

解决阿里云CDN回源https返回503错误的问题

最近打算把www.monkeyrun.net改成全站https,使用的Let’s Encrypt的证书。然而在设置阿里云CDN的时候,阿里云CDN回源一直返回503错误,发工单,来来回回经过整整两天,终于把问题解决。容我娓娓道来。

最一开始,我先开启了阿里云的CDN,源站设置为www.monkeyrun.net,通过80端口回源,没有任何问题。

后来当时配置好证书,站点也开启了https之后,将回源端口改为443,开始出问题了,CDN资源全部返回503。而直接通过浏览器访问https的源站内容,都是没有问题的。

发工单,经过漫长的等待和提供链接等更详细的信息之后,阿里云的工作人员首先认为这个问题可能是由于我开启了防火墙或者一些安全软件导致,拦截或阻止了CDN节点的回源请求。我关闭了防火墙,问题依旧存在。

又经过漫长的等待以及转交专项处理人员处理之后,给我发了个抓的包,说是CDN回源请求被源站给RST了,让我检查我的服务器在网络层面是不是做了什么限制。看了半天抓包的数据,也不大看得懂,各种谷歌,最后感觉可能是协议不同,握手的时候有一个是TLS 1.0,有一个是TLS 1.2,谷歌了一通,被带入了另一个未知领域,尝试了各种cipher suites,随后还是无果。

后来找到一个网站,测试SSL兼容性的,https://www.ssllabs.com/ssltest/,测试了一下网站SSL兼容性,发现不支持SNI的请求会直接close connection。于是又问阿里工作人员,得知他们CDN回源时,SSL握手不支持发送SNI。

定位到问题了,在IIS站点里面,编辑网站绑定,取消勾选“需要服务器名称指示”,问题解决!

可以愉快的开启全站https了!

更新“Microsoft.NETCore.App”到1.0.1出现502.5错误的问题

今天白天遇到一个问题,花了很长时间才解决。记录下。

问题是这样的,我是个强迫症,如果发现有可以更新的包,我肯定会去更新。

新建了一个ASP.NET Core的Api项目,发现有包可以更新,于是通过Nuget自动更新。更新完之后,出现第一个坑。

原先的

1
2
3
4
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
}

更新之后会丢失"type": "platform",变成

1
"Microsoft.NETCore.App": "1.0.1"

直接编译都会报错。这个好解决,自己手动改下。把versiontype加上。

改好之后,编译不报错了,但是在iis express上运行的时候,会出现502.5的错误,百思不得其解。Google了很长时间也没找到解决方案。后来猛然在.NET Core首页的Install .NET Core SDK中看到一个.NET Core 1.0.1 - VS 2015 Tooling Preview 2,突然感觉是不是还得安装下这个更新才能用.NET Core 1.0.1,于是下载更新,问题解决。

Windoows下Redis Sentinel的部署

虽然很久之前就了解了Redis的哨兵机制,今天第一次尝试在多个服务器上部署多个Redis实例,并且设置了哨兵用来进行自动的主从切换。

一、部署Redis

在3台服务器上分别安装了Redis,Redis on Windows下载地址:https://github.com/MSOpenTech/redis/releases

配置文件添加密码:

1
2
requirepass <密码>
masterauth <密码>

除了设置本实例的密码外,还需要输入master的密码(需要和本实例密码相同),所有实例需要设置相同的密码,以便进行主从切换。

需要注意的是,Redis从某个版本起,加入了一个protected-mode的保护模式。启动保护模式的条件是protected-mode开启,且没有设置bind,且没有设置密码。我的Redis的实例部署在多个公网服务器下,所以加密码是必须的,另外需要注释掉默认的bind 127.0.0.1,以使用公网IP。因为设置了密码,所以protected-mode就无需进行改动,直接使用默认的就可以了。但是在后面哨兵的配置中的保护模式会有一个坑。

在3个Redis实例中挑选一个作为初始的master。在另外两个实例的配置文件中,加入slaveof的配置。

二、配置Sentinel

创建哨兵的配置文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
port <端口号>
sentinel monitor redis-master <Master IP> <Master端口号> 2
sentinel down-after-milliseconds redis-master 5000
sentinel failover-timeout redis-master 900000
sentinel parallel-syncs redis-master 2
sentinel auth-pass redis-master <密码>
logfile "LogFiles/monkeyrun-sentinel.log"
protected-mode no

具体参数的含义不再赘述,可以Google。

这里需要加上最后一行protected-mode no,把哨兵的保护模式关掉。因为哨兵目前不支持设置密码,如果不加这行就会启动保护模式了,外网无法访问,会出现哨兵与哨兵之间互相认为对方不可用的情况。

三、一些命令

因为是在Windows下,有些命令可以通过批处理文件方便处理。将如下代码分别保存为.bat文件,可以直接双击执行。默认安装完会新建一个名叫Redis的服务,我不喜欢这个名字,可以先卸载这个默认的Redis服务,然后重新安装自己命名的服务。这样的好处是以后可以在一台服务器上安装多个不同用途的Redis实例,以便区分。

1、安装Redis服务

1
redis-server --service-install redis-service-monkey-run.conf --service-name redis-service-monkey-run

2、启动Redis服务

1
redis-server --service-start --service-name redis-service-monkey-run

3、停止Redis服务

1
redis-server --service-stop --service-name redis-service-monkey-run

4、卸载Redis服务

1
2
redis-server --service-stop --service-name redis-service-monkey-run
redis-server --service-uninstall --service-name redis-service-monkey-run

5、安装Sentinel服务

1
redis-server --service-install sentinel-monkey-run.conf --service-name redis-sentinel-service-monkey-run --sentinel

6、启动Sentinel服务

1
redis-server --service-start --service-name redis-sentinel-service-monkey-run

7、停止Sentinel服务

1
redis-server --service-stop --service-name redis-sentinel-service-monkey-run

8、卸载Sentinel服务

1
2
redis-server --service-stop --service-name redis-sentinel-service-monkey-run
redis-server --service-uninstall --service-name redis-sentinel-service-monkey-run

四、其他

验证了一下哨兵的主从切换,很爽!

睡觉!

2016年10月23日凌晨于老家

使用Visual Studio Web Deploy发布ASP.NET Core至IIS

操作系统要求

  • Windows 7及以上
  • Windows Server 2008 R2及以上

IIS配置

在服务器管理器中,通过添加角色和功能的向导,在服务器角色中勾选Web服务器(IIS),并安装。

安装.NET Core Windows Server Hosting包

  1. 安装.NET Core Windows Server Hosting,这个包会安装.NET Core运行时、.NET Core库和ASP.NET Core模块,这个模块会在IIS和Kestrel服务器之间创建反向代理。
  2. 重启服务器,或者从命令行执行net stop was /y,接着执行net start w3svc

更多关于ASP.NET Core模块以及针对该模块的配置、web.config中系统变量的设置、app_ofline.htm的使用、模块日志的激活等,请参阅ASP.NET Core Module Configuration Reference

应用程序配置

添加对Microsoft.AspNetCore.Server.IISIntegration包的依赖,添加.UseIISIntegration()WebHostBuilder()中以引入IIS集成中间件。

1
2
3
4
5
6
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

需要指出的是,添加.UseIISIntegration()并不会影响代码的可移植性。

为IISIntegration服务设置IISOptions

为了配置IISIntegration服务,需要在ConfigureServices中为IISOptions添加服务器配置。

1
2
3
services.Configure<IISOptions>(options => {
...
});
Option Setting
AutomaticAuthentication If true, the authentication middleware will alter the request user arriving and respond to generic challenges. If false, the authentication middleware will only provide identity and respond to challenges when explicitly indicated by the AuthenticationScheme.
ForwardClientCertificate If true and the MS-ASPNETCORE-CLIENTCERT request header is present, the ITLSConnectionFeature will be populated.
ForwardWindowsAuthentication If true, authentication middleware will attempt to authenticate using platform handler windows authentication. If false, authentication middleware won’t be added.

publish-iis工具

The publish-iis tool can be added to any .NET Core application and will configure the ASP.NET Core Module by creating or modifying the web.config file. The tool runs after publishing with the dotnet publish command or publishing with Visual Studio and will configure the processPath and arguments for you. If you’re publishing a web.config file by including the file in your project and listing the file in the publishOptions section of project.json, the tool will not modify other IIS settings you have included in the file.

To include the publish-iis tool in your application, add entries to the tools and scripts sections of project.json.

1
2
3
4
5
6
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"scripts": {
"postpublish": "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
}

Web Deploy配置

第一步需要确保你的服务器支持ASP.NET Core,必要的条件是:

  1. 安装了IIS 7.5+
  2. 安装了HttpPlatformHandler
  3. 安装了Web Deploy v3.6

HttpPlatformHandler是一个新的组件,用来连接IIS和ASP.NET Core应用程序,下载链接如下:

安装HttpPlatformHandler之前,需要先安装Web Deploy v3.6,可以通过Web Platform Installer(WebPI),或者直接从下载中心下载,不过推荐通过WebPI的方式下载,它提供了独立的安装并且包含了一些必要的配置。

在IIS中配置站点

  1. 在IIS管理器中新建一个网站,输入网站名、物理地址以及域名绑定的配置。
  2. 设置应用程序池的.NET CLR版本为无托管代码。
  3. 右键网站->部署->启用Web Deploy发布…,会在桌面生成一个.PublishSettings后缀的配置文件,复制出来,后续操作中需要导入到Visual Studio中。

配置数据保护

为了持久化数据保护的密钥,你必须为每个应用程序池创建注册表存储单元来存储这些密钥。需要为每个ASP.NET Core应用程序池执行这个PowerShell脚本Provisioning PowerShell

For web farm scenarios developers can configure their applications to use a UNC path to store the data protection key ring. By default this does not encrypt the key ring. You can deploy an x509 certificate to each machine and use that to encrypt the keyring. See the configuration APIs for more details.

Warning: Data Protection is used by various ASP.NET middlewares, including those used in authentication. Even if you do not specifically call any Data Protection APIs from your own code you should configure Data Protection with the deployment script or in your own code. If you do not configure data protection when using IIS by default the keys will be held in memory and discarded when your application closes or restarts. This will then, for example, invalidate any cookies written by the cookie authentication and users will have to login again.

关于配置IIS,可以前往Publishing to IIS查看更多详情。下面我们来看看Visual Studio中的步骤。

通过Visual Studio发布

在配置好服务器之后,下一步就是在Visual Studio中创建一个发布配置文件。将ASP.NET Core应用程序发布到标准的IIS服务器上最简单的方法就是使用发布配置文件。如果你的服务器支持创建发布配置文件,可以下载过来,然后在Visual Studio发布对话框中导入进来。

如果发现使用Web Deploy无法部署,可能是由于数据保护没有配置好,也可以不配置,在pubxml中添加如下两行:

1
2
<AllowUntrustedCertificate>True</AllowUntrustedCertificate>
<UsePowerShell>False</UsePowerShell>

参考链接

“Bad Request - Invalid Hostname”的解决办法

最近在做一个微信端的应用,除了在本地测试之外,有时候还需要在手机上进行测试。

假设我的手机和PC在同一内网内,PC的IP是192.168.1.2,Website的端口是12345

我的第一反应是,我应该在手机上通过http://192.168.1.2:12345来访问我的站点。

然而,我得到了这样一个错误:

1
2
3
Bad Request - Invalid Hostname
------------------------------------------------
HTTP Error 400. The request hostname is invalid.

方法很简单,Visual Studio 2015的项目目录中会有一个.vs的文件夹,打开.vs\config\applicationhost.config,找到目标站点的配置节点,例如:

1
2
3
4
5
6
7
8
9
<site name="Demo.Website" id="2">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="D:\Projects\Demo\Demo.Website" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:52945:localhost" />
<binding protocol="http" bindingInformation="*:52945:*" /> <!-- 加上这一行 -->
</bindings>
</site>

网上大部分教程基本就说了这么多,然而我在这样配置了之后依然有问题。甚至有些教程是直接把localhost改成了*,也可以理解。然而,我每次这么做之后,重新启动站点的时候,总是会自动重新生成一个节点,那个里面配置的是localhost。纠结了一下午。

最后发现是权限的问题,如果想配置非localhost的绑定,VS必须以管理员权限运行才行。这样如果只配置了非localhost的绑定,就不会新建了,或者配置多条绑定也可以生效了。

遇到这个问题一直解决不了的朋友可以参考下。

另外如果站点甚至无法访问的话,可以新建一条防火墙入站规则,把端口号配置进去。

ASP.NET Core初体验

前两天试了下ASP.NET Core MVC,很好用。微软整合了大量前端工具,npm、Bower都可以很方便地使用了,甚至对Grunt、Gulp这类的工具都有集成一些任务管理器,这对前端来说,是一件鼓舞人心的事。

ASP.NET Core MVC的推荐目录结构也进行了调整,新增了wwwroot这样一个静态目录,js、css、图片都可以放这里面,而Bower管理的第三方前端库则会自动下载到wwwroot里面的lib目录下。作为强迫症的我,wwwroot这个目录必须全部是自动生成的。通过Gulp,可以很轻松的实现这一点。继承原先的目录结构习惯,在解决方案下建立Scripts、Styles、Images文件夹,里面用来放原始的js、less和图片,然后通过Gulp进行合并、压缩、复制到wwwroot目录下,这样wwwroot这个目录就可以在git里面排除掉了。完美。而在ASP.NET Core的项目目录下默认的.gitignore文件里,微软其实是已经有这样的想法:

1
2
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

在代码层面,和之前差别不大,基本上M、V、C的代码都可以直接拿过来用。在bundle上有一些变化,然而我是直接删掉了默认的bundle配置,既然可以方便地使用Gulp了,为啥不用呢?

在View中,新增了environment的语法,可以通过environment标签来控制开发环境和生产环境的不同输出,主要是用来控制css、js这些文件的引用,在开发环境下使用未压缩的文件,在生产环境下使用压缩过的文件。还提供了cdn的方式,可以配置多个链接,优先使用CDN的链接,通过asp-fallback-test来检查CDN的链接是否可用,不可用的话再切换为本地的链接。和之前Bundle里面的方式差不多,只是使用起来更简单了。现在css、js这些文件的缓存控制也比以前更简单,只需要加上asp-append-version="true"即可在文件名后面自动加上版本号后缀。

项目部署上面,确实遇到了一个坑。我是通过Web Deploy来自动部署的,花了半天时间才终于搞定。

  1. IIS里应用程序池中的.NET CLR版本要选择“无托管代码”

  2. 安装.NET Core Windows Server Hosting,这里有个大坑,安装完之后要执行一下iisreset,我没有执行这一步,导致出现了HTTP Error 502.5 - Process Failure的问题,从事件查看器里面看到的错误日志是:Failed to start process with commandline '"dotnet" .\****.dll', ErrorCode = '0x80070002'.。遇到同样问题的朋友可以试试iisreset或者重启机器。

  3. 安装HttpPlatformHandler

  4. 还有个就是我当时用Web Delpoy往服务器部署的时候,文件总是推不上去,后来Google了一下,在pubxml里面加上了以下两行:

    1
    2
    <AllowUntrustedCertificate>True</AllowUntrustedCertificate>
    <UsePowerShell>False</UsePowerShell>
  5. 下载Provisioning PowerShell script,在服务器上使用PowerShell运行,输入应用程序池的名称即可。

Plan B

今天从外面回来的路上,我在想,我的Plan B是什么。

直到刚刚,看到阮一峰老师新文章《你的B计划在哪里?》,才发现,我之前所想的那些Plan B其实并不算Plan B,而是基于Plan A的一些妥协与调整。真正的Plan B应该是那些实施起来非常艰苦,不到走投无路自己没办法下定决心去做的计划,但他往往是所有的Plan A都失败的情况下,能救命的计划。

前段时间很幸运接到了一个非常好的项目,APP的外包开发,要求签完合同两个月内完成。这对我来说,是个挑战,也是个机遇。一直很想转型,做前端也挺久了,然而现在的我越来越发现,在公司的这些项目对我而言没有任何挑战性,纯粹是堆时间。而且业务变动及其不稳定,加上一直被需求赶着走,甚至没有时间停下来整顿代码、理清逻辑,导致目前代码中的业务逻辑错综复杂,特别多的坑,搞得我心力交瘁。

我之前是下定了决心,如果项目能谈下来,我可以从公司离职,全职去搞这个外包。一来,Money肯定比工资多,二来也是个锻炼自己、强迫自己去学习与转型的机会,三来,自己可以找到更感兴趣的地方,或许可以找回曾经的那种感觉。项目完成之后,如果有新项目那就接着干,如果找不到新的项目,再找工作也不迟。这是我最初的Plan A。

今天下午和益达见面,他希望我能够出来和他一起创业,有股份,但工资会比现在少很多。以他的项目以及他投资人的项目来看,其实并不会占用我太多的时间,在他们有需求的时候帮他们搞,在他们没需求的时候,我依旧可以做那个APP的外包。于我而言,虽然属于自己的时间少了,但是相比Plan A,我多了一些保底的收入,计划的安全性上更高了一点,2个月之后,即便没有新项目,没有去找新工作,也已然有保底工资,可以多一些缓冲的时间,多做一些选择和考虑。算是Plan A的一个比较中庸的变种。

然而,这些都是在那个APP项目能够谈拢的情况下的计划。如果那个APP的项目没能谈下来。那可就真的要考虑Plan B了。

留在原公司,是一个非常保守的方案,只是为了工资,没有自己的时间,没有乐趣,提升空间不大,前途渺茫。这是一个我很不情愿的Plan B。

和益达一起创业,给他们提供一些技术输出的同时换来一个办公场所+保底工资,牺牲了一部分自己的时间,但是依旧有不少时间做自己喜欢的事,可以接一些外包在手上干。规避了接不到活,又没工资的风险。融到钱也能有不错的回报。这似乎是一个比较安全稳妥的Plan B。

还有一个很激进的Plan,我心里很想这么干,但是需要很大的勇气。那就是直接自己出来做,接外包干。完全属于自己的时间,挑自己感兴趣的项目做,没有保底工资。是对Plan A的颠覆。但是可以获得自由、兴趣、成就感,算是人生职业生涯的一个转型。从此不再需要被项目催着走,不再会觉得加班没有补贴很亏。完全自己支配时间,由兴趣拉动,自己主动前进。当然,外包项目在金钱上的回报也比工资要大得多。如果手头能够一直有项目做,那自然是最好的,人既自由了,快乐了,钱也多了。而风险也不言而喻,就是不够稳定,一旦没有项目做,在金钱上,就是损失。这是一个我最向往,然而却未必能够有勇气去做的Plan B,但这会是我的目标。

之前对react native总是跃跃欲试,一直苦于没有时间和精力去真正静下心来开始学。这两天,花了很多时间去研究,总算真正开始了,也找到了久违的兴趣和好奇心。大爱react native,有种相见恨晚的感觉。2016年react native一定会成为焦点,越来越多的项目会尝试通过react native来实现。而我感觉很幸运,自己能够在这样的时间点,有这样的机会能够开始投身这个领域。

我想,是时候尝试一种新的生活了!

加油!

by Bean

凌晨于闵行