【ASP.NET Core】EF Core - “影子属性” 深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1
【ASP.NET Core】EF Core - “影子属性”
有朋友说老周近来博客更新较慢,确实有些慢,因为有些 bug 要研究,另外就是老周把部分内容转到直播上面,所以写博客的内容减少了一点。
老周觉得,视频直播可能会好一些,虽然我的水平一般,不过直播时,老周可以现场演示,可能会比看博客效果要好(因为现场演示,有时候会有失误,没办法,水平有限)。还有一个,就是.NET 的资料其实很多,毕竟也发展了十几年了,有些东西如果别人都写过了,那我也不好意思重复了。.NET Core 尽管是跨平台版本,但核心依然是.net 基础,我们不需要全新去学习,只要掌握一些新的变化就可以了。目前比较期待 .NET Core 3 的正式发布,等正式上线了,老周再挑一些有意义的内容写一下。
此外,老周也可能会写一写其他方面的博客,比如 Python、GO、Ruby、Typescript 等。老周并不是只会玩.NET ,只不过老周是主攻 .NET,在接触 .NET 之前,老周就学过很多东西,比如古老的 QBasic、Pascal ,老周在上初中时就学过。后来向 VB、C、C++ 进攻,顺便把 Ruby、Python、PB 也调戏一下,后来有一段时间,Delphi 和 E 语言也挺流行的,所以顺便也玩了两把。
再后来,学过 Java 和 PHP,抛 Java 而投 .NET 是因为 Java 太复杂,效率不高,没有深度把玩的兴趣。现在所谓的 Python 很热门纯属是商业炒作,Python 又不是什么新玩意儿,很古老了,当然相对于 C 来说,是新了一点,究其特点,就是一种脚本语言(虽然有人死要说它不是脚本语言)。现在网上更有些无知小辈,以为自己会写几行 Python 代码就到处去蹭热点,告诉你,老周当年学各种编程语言时,说不定你还没出生呢。所以,如果你真心喜欢 Python 的话,你用心去学就是了(其实老周也喜欢用 Python 来做图表),不必理会商业炒作。
记得去年 C 语言也被商业炒作了几个月,再往前几年,Javascript 和 Web 前端也被拼命炒作,说得好像 js 是万能的似的,吓得老周都不敢写前端了。最近几年,IT 界开始怀旧了,各种远古生物都被挖出来了,可能是现在计算机行业已经没什么可以创新的原因吧。现在说得较多的是人工智障,这个可以用,也可以不用,反正不痛不痒,算不上生产力革命(至少其震动效果比不上当年 Office 问世时对企业生产的影响大)。不过,人工智障在某些辅助领域还是有用的,比如现在有些小区的智能门,应用效果还可以。但是,漏洞也是百出的,总之,人类可以用它来进行辅助,但不能过于依赖它。它不能解决所有问题。
许多科幻小说都会说人类会被机器人消灭。机器人也是人创造出来的,机器不可能比人强,也不可能灭掉人类(除非机器人比奥特曼里面的超兽还牛逼)。如果人类真的智力在衰减,那么根源还是在人类自己。说白了就是,只有可能是人类自己灭掉自己。你也不用觉得太恐怖,其实只要你不要太依赖机器就好,不要失去你的本能和思考方式就行。
就像我们码农,老周也一样,天天跟计算机打交道,但老周一直坚持:用电脑,但不依赖电脑,多做些机器不能做的事。再加一句:科学只能解决数学和工程问题,而人的问题,需要哲学和美学来解决。
=============================================================================
好了,以上的都是 F 话,下面咱们聊正题。今天咱们耍一下 EF Core 中的影子属性。这个词翻译版本 TMD 多,有翻译为“卷影属性”的,现在的文档又改为“阴影属性”,这不好听,太有心理阴影了,故而,老周觉得,叫“影子属性”好一些。
不管叫什么,你只要知道它是个啥就行。老周喜欢一句话总结,所以,来一句话:
模型类中没有定义的,但数据表中存在的属性——即模型类与数据表中没有对应关系的属性。
老周就用一个简单的示例来说明一下吧。这个示例也是老周在视频直播时用的。
假设,有个模型类,叫 Student。
public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
但我想要一个属性,用来记录数据记录被写进数据库的时间,即还有一个属性,叫 InsertTime,不过,这个属性在 Student 类中是没有定义的,但在数据表中是有这一字段的。
因此,在从 DbContext 类派生时,需要重写 OnModelCreating 方法,通过 Model Builder 来定义这个“隐藏”的属性。
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Property<DateTime>("InsertTime"); }
这个 InsertTime 属性就成了影子属性了。
下面是 DbContext 派生类的完整代码,我放出来是方便你去抄袭的,放心吧,无版权税的,尽管抄。
public class MyContext : DbContext { public DbSet<Student> Students { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"server=(localdb)\MSSQLLocalDB;database=TestDb"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Property<DateTime>("InsertTime"); } }
现在,到 Startup 类中,注册一下服务,让自定义的数据库上下文可以进行依赖注入。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddDbContext<MyContext>(); }
刚刚在 MyContext 类中已经配置连接字符串了,所以注册服务时就不用指定连接字符串了。
按老周前面所写的博文,接下来是创建数据迁移,不过,这一次老周使用的是直接在运行的时候创建数据库,方法也是很简单的,找到 Main 入口点,把里面的代码改一下(项目模板默认生成的代码不进行大改)。
public static void Main(string[] args) { var host = CreateWebHostBuilder(args).Build(); using(IServiceScope scope= host.Services.CreateScope()) { MyContext c = scope.ServiceProvider.GetService<MyContext>(); c.Database.EnsureCreated(); } host.Run(); }
也就是在 host 运行之前,创建一个“作用域”级别的服务实例,创建数据库,这个实例是临时使用,不遵循服务容器的生命周期规则。EnsureCreated 方法会检测数据库是否存在,如果不存在,就创建,然后返回 true;如果数据库已经存在,不做任何处理并返回 false。
如果你不打算写入一些初始数据,可以不在乎方法的返回值,如果要写入初始数据,可以 if 一下,如果方法返回 true,就写一下数据进去(true 表示新数据库)。
接下来,咱们用一个 API 控制器来测试一下。
[Route("api/[action]")] public class TestController : Controller { readonly MyContext context; public TestController(MyContext c) => context = c; [HttpPost] public ActionResult AddNew([FromBody]Student stu) { …… } [HttpGet] public JArray GetList() { …… } }
数据库上下文的实例因为已经注册到服务容器中,所以通过构造函数可以得到其实例引用。这个控制器有两个 action,AddNew 方法用来提交数据,以 POST 方式访问。
[HttpPost] public ActionResult AddNew([FromBody]Student stu) { // 添加实体 context.Students.Add(stu); // 设置影子属性 context.Entry(stu).Property<DateTime>("InsertTime").CurrentValue = DateTime.Now; context.SaveChanges(); return Ok("操作成功"); }
记得,参数要加上 FromBody 特性,因为它要从 HTTP 消息正文中提取,上次我直播时就是忘了写这个,所以提交不到数据。
这里要注意影子属性的赋值方法,因为它没有在 Student 类中公开,你不能通过访问成员来设置它,只能先通过 CurrentValue 属性来设置。
然后还有一个 GetList 方法,以 GET 方式来访问。有来返回所有 Student 数据。此处我用 JArray 以 JSON 数组格式返回。
[HttpGet] public JArray GetList() { var q = from s in context.Students select new { s.Id, s.Name, s.Age, // 读取影子属性值 InsertTime = EF.Property<DateTime>(s, "InsertTime") }; JArray arr = new JArray(); foreach (var s in q) { JObject obj = new JObject(); obj.Add("id", new JValue(s.Id)); obj.Add("name", new JValue(s.Name)); obj.Add("age", new JValue(s.Age)); obj.Add("add_time", new JValue(s.InsertTime)); arr.Add(obj); } return arr; }
读取影子属性的方法是用 EF.Property 静态方法,第一个参数是实体模型类的实例,第二个参数是影子属性的名字。这个方法只能在 LINQ 树中使用,如果不在 LINQ 中用会发生异常。这里还有一个问题,就是在 select 子句中用 new 返回的匿名类型无法创建 JObject 对象,所以只好手动去构建。
现在可以测一下了,首先提交一下数据。
{ "name":"李三跳", "age":52 }
接着获取一下数据列表。
[ { "id": 1, "name": "什么鬼", "age": 29, "add_time": "2018-11-26T11:15:38.2012809" }, { "id": 2, "name": "陈大扣", "age": 83, "add_time": "2018-11-26T11:21:07.5374308" }, { "id": 3, "name": "李三跳", "age": 52, "add_time": "2018-11-26T12:41:51.758849" } ]
好了,示例就完成了。
影子属性的典型用法就像刚刚这个例子这样,可以用来记录数据的添加时间或者更新时间,但这种数据,一般不需要在实体模型中公开。
深入浅出经典面试题:从浏览器中输入URL到页面加载发生了什么 - Part 1
背景
“从浏览器中输入URL到页面加载的发生了什么“,这是一道经典的面试题,涉及到的知识面非常多,但作为一个自认为对网络知识掌握的比较好的老码农来说,回答这个问题自然不在话下。如果这道题目如果在面试出现,对我来说就是送分题啊。尽管如此,我还是愿意花一些时间根据我自己的理解回答一下这个题目,看我自己到底掌握的有多深,同时也把自己的知识梳理一下。
这让我想起另外一件往事,这道题有点类似于“在手机上浏览器上输入一个URL,手机做了一些什么”,我当时学习通信里的核心网时就给自己提出过这个问题。
我非常愿意将这个面试题的答案共享出来,一是希望得到大家的意见,二是也希望对那些不是特别熟的人起到一些帮助。
因为文章较长,我将它们分为三大部份,后面会提供该文章的完整PDF版本,供大家下载。
从本文里学到什么?
正如前面所说,这篇文章涉及到的知识面非常丰富,我相信您绝对可以从本文里学到很多基础知识,还有一些高级话题。
- DNS的解析原理,常用命令,端口等
- TCP/IP模型,三次握手,四次挥手。
- HTTP/HTTPS的原理和解析。
- 浏览器render一个页面
- Web安全性问题
- 抓包,分析TCP模型,三次握手,SSL/TLS,让学起来不再枯燥。
- 其他一些高级话题。
自认为是目前写的最详细的一篇文章了,因为里面有理论,有实战,应该会起到一个比较好的效果。
总概: 几大步骤
总的来说,当你输入在浏览器里输入一个URL到页面加载,发生的顺序如下:
- DNS查询
- TCP连接
- 发送HTTP请求
- Server处理HTTP请求并返回HTTP报文
- 浏览器解析并render页面
- HTTP连接断开
DNS查询
DNS解析流程
假设输入的URL是包含域名的,那肯定会涉及到DNS解析。当然,如果URL仅仅是IP,那就不会涉及到DNS的。域名的出现是为了方便记忆,因为域名比IP好记。我们这里假设URL包含域名。
解析的步骤大致如下图:
DNS的优化
DNS负载均衡
DNS Record(记录)
记录类型
|
含义简介
|
A(Address)
|
指定域名对应的IPv4地址
|
AAAA
|
指定域名对应的IPv6地址
|
NS(Name Server)
|
指定该域名由哪个DNS服务器来进行解析
|
MX(Mail Exchanger)
|
邮件交换记录,用于电子邮件系统发邮件时根据收信人的地址后缀来定位邮件服务器
|
CNAME
|
别名记录,多个域名映射到同一台计算机(如同一主机提供mail和www服务)
|
TXT
|
主机名或域名的说明
|
TTL(Time-To-Live)
|
DNS服务器中保存的时间
|
PTR
|
将一个主机地址映射到对应的域名
|
HINFO
|
说明映射到特定 DNS 主机名的 CPU 类型和操作系统类型
|
DNS常用命令和工具
[warren]$ dig
; <<>> DiG 9.9.5-3ubuntu0.16-Ubuntu <<>>
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57775
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;. IN NS
;; ANSWER SECTION:
. 16265 IN NS a.root-servers.net.
. 16265 IN NS c.root-servers.net.
. 16265 IN NS j.root-servers.net.
. 16265 IN NS b.root-servers.net.
. 16265 IN NS i.root-servers.net.
. 16265 IN NS d.root-servers.net.
. 16265 IN NS k.root-servers.net.
. 16265 IN NS f.root-servers.net.
. 16265 IN NS l.root-servers.net.
. 16265 IN NS h.root-servers.net.
. 16265 IN NS m.root-servers.net.
. 16265 IN NS g.root-servers.net.
. 16265 IN NS e.root-servers.net.
;; Query time: 0 msec
;; SERVER: 208.113.157.202#53(208.113.157.202)
;; WHEN: Thu Nov 29 18:04:06 PST 2018
;; MSG SIZE rcvd: 239
|
dig www.jd.com
; <<>> DiG 9.10.6 <<>> www.jd.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2675
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.jd.com. IN A
;; ANSWER SECTION:
www.jd.com. 300 IN CNAME www.jd.com.gslb.qianxun.com.
www.jd.com.gslb.qianxun.com. 300 IN CNAME www.jdcdn.com.
www.jdcdn.com. 300 IN A 61.174.55.1
;; Query time: 3 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Fri Nov 30 10:07:37 CST 2018
;; MSG SIZE rcvd: 106
|
nslookup www.jd.com
Server: 192.168.1.1
Address: 192.168.1.1#53
Non-authoritative answer:
www.jd.com canonical name = www.jd.com.gslb.qianxun.com.
www.jd.com.gslb.qianxun.com canonical name = www.jdcdn.com.
Name: www.jdcdn.com
Address: 61.174.55.1
|
DNS抓包分析
DNS标准和协议
DNS 10问
TCP连接
TCP/IP模型
不多解释,OSI的7/6/5层和TCP/TP的应用层对应,2/1层和链路层对应。在实际的应用中,主要还是TCP/IP概念模型,后面的内容主要讲它。
TCP/IP抓包分析
TCP三次握手与四次挥手
HTTPS证书
TCP/IP其他
TCP/IP 10问
- TCP/IP的4层模型了解吗?每层有哪些常见协议?
- TCP/IP的三次握手了解吗?四次挥手是什么,了解多少?
- HTTP和HTTPS在TCP握手上有什么不同?SSL/TLS握手流程了解吗?
- SSL/TLS的版本有哪些?当前浏览器支持哪些版本?
- SNI了解多少?如果SNI没有,该如何校验证书?
- TCP与UDP区别在哪里?
- 为什么TCP经常会组装包?如何保证包的完整性?
- TCP滑动窗口原理是什么?TCP有哪些状态?
- MAC地址的是如何定义的?(这个问题太Edge了)
- SSL/TLS证书和端口有关系吗?为什么?
今天把TCP/IP, SSL/TLS介绍完了,下一部分是最后一部份了,左右介绍HTTP和Broswer的机制。