ASP.NET MVC深入浅出系列(持续更新)
一. ASP.NET体系
从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模式,ViewState功不可没,通过的控件的拖拽和绑定,很快就可以搭建出来一个Web项目,其开发速度远胜Java、PHP,当年Web项目并不很重视体验,没有今天响应式,没有各种前端js框架,所以在当年的WebForm,微软是以引以为豪的。
该框架毕竟有时代局限性,随着前端的崛起,随着人们对项目体验的提高,随着项目的体量的增大,WebForm在当下强调松耦合、模块化、组件化的时代,略显落后,故微软于2009年推出第一个开源框架,即MVC框架,俗称ASP.Net MVC1,后面又陆续的推出 MVC2、3、4、5。现在最新的跨平台Web开发框架 Core MVC,它隶属于.Net Core框架,而非 .Net FrameWork下。
下面用一张图表示一下在Core出现之前,.Net在Web开发即ASP.NET中包含的内容。
简单分析一下:WebPages(没用过哎)、WebForms、MVC均为ASP.NET下的Web开发框架,WebAPI负责构建HTTP常规服务,SignalR负责构建实时服务。
MVC和WebAPI简单对比一下:MVC用来构建网站既关心页面又关心数据,WebAPI只关心数据;MVC通过可以通过Action的名字来接受CURD操作,而WebAPI通过HTTP的访问方式(GET、PUT、POST、DELETE)来表达不同的CURD操作。
(WebService、WCF、WebAPI、SignalR在后面会有单独的章节详细介绍)
二. WebForm和MVC对比
1. WebForm
做过WebForm开发的朋友们应该很清楚,WebForm开发形式主要分为三种:
①:服务器端控件,即拖拽控件(aspx)
②:一般处理程序(ashx) + html模板
③:html静态页 + ajax + 一般处理程序(ashx)
请求模型:
WebForm的几个臭名昭著的缺点:封装太强,导致开发不灵活;ViewState的无状态;声明周期模型复杂,对于初学者不易于理解。
2. MVC
我们经常从各种开发工程师(JAVA 、.NET、PHP)口中听到MVC,但不同人说的MVC貌似类似,但又有区别,那么MVC到底是什么呢?(类似的还有MVP和MVVM)
1. 从宏观上来说:MVC是框架分层的一种搭建思想,在最原始的项目中,没有什么框架分层之说,所有的项目代码都在一个层里,这样会导致代码冗杂,耦合性强,项目迭代升级困难,MVC是一种分层思想,将一个项目代码分为几类,分别放到不同的层里,Model层存储一些数据和业务逻辑,View层处理页面问题,Controller层用来接收人机交互指令。MVC分层思想和传统的三层(数据库访问层、业务逻辑层、表现层)还是有区别的。
MVC的核心是分离了View和Model(即页面和数据),Controller负责接收和发送指令,交给Model层处理。
2. 从.Net的角度来说,MVC是微软提供一个Web开发框架,全称ASP.NET MVC,它是传统三层中View层的一种功能细分,一种表现形式而已,与三层没有直接关系。ASP.NET MVC框架中的开发流程围绕:
① Model:存储实体类,实现系统中的业务逻辑。
② View:页面展示(Razor模板),展现给用户。
③ Controller:页面的逻辑处理,用来与用户的交互,处理接受的Http请求。然后从Model中获取相应的数据,转发给View进行页面展示。
补充:通过Controller中的Action,可以充当APP的服务端接口的作用,和WebAPI达到的效果一致,但访问方式是有区别的。
请求模型:
下面列举MVC框架的几个典型的优点:
①:松耦合,M-V-C分工明确,有利于代码的维护。
②:便于Seo优化,能自定义url的生成规则。
③:ASP.NET MVC5是个出色的表现层框架。
④:对单元测试支持友好。
三. ASP.Net MVC 框架
1. 包含的技术
ASP.NET MVC框架,这里以MVC5为例,涉及到的知识有:Model-View-Controller的使用、Area和Global的理解、路由配置、数据传递的方式、AOP思想的体现(4大过滤器),各种Result、Razor语法、HttpContext相关的五大对象、分布视图、特性的补充、Html扩展控件、Bundles压缩js和css的原理、Owin约束、管道模型及MVC的工作原理。
以及最新的Core MVC框架的相关知识。
2. 常见的约定
①:控制器命名通常以Controller结尾。
②:一个控制器对应一个View视图文件夹,常用页面都存放在里面。
四. 系列章节
第二节:各种路由约束(动态路由、静态路由、组合路由、正则约束、命名空间约束、区域内路由)
第三节:Action向View传值的四种方式(ViewData、ViewBag、TempData、Model)
第四节:MVC中AOP思想的体现(四种过滤器)并结合项目案例说明过滤器的实际用法
第五节:从源码的角度理解MVC中各种Result(ActionResult、JsonResult、JavaScriptResult等)---待补充
第八节:layout和partialView、RenderPartial和Partial、 action和RenderAction(未完成)
第十节:数据批注(DataAnnotationModel)和自定义验证(包括Model级别的验证)
第十三节:HttpHander扩展及应用(自定义扩展名、图片防盗链)
第十五节:HttpContext五大核心对象的使用(Request、Response、Application、Server、Session)
第十六节:利用MVC的扩展完成“一个后台,多套前端页面”的解决方案(未完成)
ORM系列之Entity FrameWork详解(持续更新)
一. 谈情怀
从第一次接触开发到现在(2018年),大约有六年时间了,最初阶段连接数据库,使用的是【SQL语句+ADO.NET】,那时候,什么存储过程、什么事务 统统不理解,生硬的将SQL语句传入SQLHelper类中,后来知道还要注意SQL注入,引入参数化的方式,现在回想一下,那个阶段简直Low到爆。
(PS:现在很多场景下还是需要使用ADO.NET,所以没有最好,只有最适合)
凡事都是从零开始的,正是这个阶段积累,才有了后续的发展。一次机会,接触到了微软的EF(ps:如果没有记错的话,当时用的是EF 5.0 的DBFirst模式),彻底颠覆了我的开发观念,不需要写SQL语句(那时还不清楚EF也可以调用SQL语句),Lambda表达式(并不知道Lambda还分Lambda表达式和Lambda语句),兴奋了一阵,然后进入了漫长的EF摸索和填坑阶段了(EF5.0→EF6.2→EF Core)。
如果你觉得EF不灵活,不能调用存储过程或自定义事务?如果你觉得EF在处理海量数据库的时候会很慢?如果你觉得EF满足不了你的开发需求?。。。。。。 那么也许该系列的文章会对你有些帮助。
二. ORM框架
说起DotNet领域的ORM框架,我们想到的有EF、NHibernate、FluentData、Dapper、SQLSugar等等。那么到底什么是ORM呢?
ORM(Object Relational Mapping),中文名字:对象关系映射,它是面向对象思想的进一步的发扬光大,主要实现【程序对象】到【关系型数据库】的映射。在之前使用ADO.NET开发的时候,数据库访问层(即 DAL),要写很多增加、删除、保存的方法,很是麻烦,如果引入ORM框架,它自带CRUD的API(相当于DAL层已经给你写好了),可以自动生成SQL语句,所以作为开发者而言,只需要关注对象即可。
ORM的含义:
① O:Domain Object 领域模型
② R:Relational DataBase 关系型数据库
③ M : Mapping 映射关系
ORM的三大核心原则:
① 简单性:以最简单的模式建模数据。
② 传达性:数据库结构被任何人都能理解的语言文档化。
③ 精确性:基于数据模型创建正确标准化的结构。
EF的核心架构:
① EDM(Entity Data Model):这是微软提供的一个强大的可视化工具,用来生成 O、R、M的
② 两种语法的查询:LINQ to Entities 和 Entity SQL
③ ObjectServices:Linq to Entities 和Entity Client Data Provider 之间起到转换作用
④ Entity Client Data Provider: 将Lambda转换成SQL语句
⑤ ADO.Net Data Provider: 标准的ADO.Net
⑥ DataBase:数据库
三. 从EF的入手,深入理解ORM框架
EF(Entity FrameWork),是微软推出基于ADO.NET的数据库访问技术,它是一套ORM框架, 框架中包括了实例模型、数据模型、关系映射三部分,EF的上下文为DbContext,EF在 【.Net FrameWork】版本中最新的是 6.2.0 版本,最稳定的是6.2版本,在【.Net Core】中推出 EF Core (即所谓的“EF7”,^_^,当然已经不这么叫了)。
EF包括三种模式:DBFirst、CodeFist、ModelFirst 。EF可以调用SQL语句、可以使用Linq查询、可以使用Lambda查询,EF还有很多高级属性,比如延迟加载、缓存属性等等,在该系列文章中都将逐一详细介绍。
EF也有优缺点:
优点:① EF是微软自家的产品,和VS的集成度很高,极大的提高了开发效率。
② EF中ModelFirst模式提供的模型设计器功能很强大,且采用T4模板生成的代码的功能很强大。
③ 支持多数据库开发,简单的改变配置文件,就可以切换数据库。
缺点: 在复杂的查询生成SQL脚本的效率不是很高,浪费了性能。
举例:之前在开发中,一张幼儿表里大约有60个字段左右,一个业务需要根据复杂的条件查询出来一些信息(大约5个字段左右),当时由于按开发人员的失误,写的Lambda表达式直接将所有字段查询出来,导致该业务耗时很长大约25s左右,后来在我的引导下,先去数据库中直接写SQL语句,查询所有字段,发现速度很快;然后将EF中的Lambda查询改为只查询指定的5个字段,发现速度由25s→3s左右,提升非常明显,当然这也很好的说明了EF的局限是在生成SQL的脚本效率上。
四. 系列章节
第一节: 结合EF的本地缓存属性来介绍【EF增删改操作】的几种形式。
第二节: 比较EF的Lambda查询和Linq查询写法的区别。
第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery )
第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法
第五节: EF高级属性(一) 之 本地缓存、立即加载、延迟加载(不含导航属性)
第六节: EF高级属性(二) 之延迟加载、立即加载、显式加载(含导航属性)。
第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges、DBContextTransaction、TransactionScope)。
第八节: EF的性能篇(一) 之 EF自有方法的性能测试 :
第九节: EF的性能篇(二) 之 Z.EntityFramework.Extensions程序集解决EF的性能问题 :
第十节: EF的三种追踪实体属性变化方式(DBEntityEntry、ChangeTracker、Local) :
第十一节: EF的三种模式(一) 之 DBFirst模式(SQLServer和MySQL两套方案):
第十二节: EF的三种模式(二) 之 ModelFirst模式(SQLServer为例):
第十三节: EF的三种模式(三) 之 来自数据库的CodeFirst模式 :
第十四节: EF的三种模式(四) 之 原生正宗的 CodeFirst模式的默认约定 :
第十五节: EF的CodeFirst模式通过DataAnnotations修改默认协定 :
第十六节: EF的CodeFirst模式通过Fluent API修改默认协定 :
第十七节: EF的CodeFirst模式的四种初始化策略和通过Migration进行数据的迁移 :
第十八节: EF的CodeFirst模式连接MySQL数据库的解决方案(未完成) :
第十九节: 结合【表达式目录树】来封装EF的BaseDal层的方法 :
第二十节: 深入理解并发机制以及解决方案(锁机制、EF自有机制、队列模式等):
第二十一节:ADO层次上的海量数据处理方案(SqlBulkCopy类插入和更新):
第二十二节: 以SQLServer为例介绍数据库自有的锁机制(共享锁、更新锁、排它锁等)(未完成) :
第二十三节: EF性能篇(三)之开源组件 Z.EntityFrameWork.Plus.EF6解决EF性能问题 :
第十六节:语法总结(3)(C#6.0和C#7.0新语法)
一. C# 6.0 新语法
1. 自动属性初始化可以赋值
1 /// <summary> 2 /// 自动属性初始化 3 /// </summary> 4 public class UserInfor 5 { 6 public string userId { get; set; } = "123456"; 7 8 public string userName { get; set; } = "lmr"; 9 10 } 11 { 12 Console.WriteLine("------------------------- 1. 自动属性初始化可以赋值----------------------------"); 13 UserInfor uInfor = new UserInfor(); 14 Console.WriteLine($"id={uInfor.userId},userName={uInfor.userName}"); 15 }
2. 字符串嵌入值【$配合{}使用】
特别注意:{}中如果有业务计算需要配合()使用
1 { 2 Console.WriteLine("------------------------- 2. 字符串嵌入值【$配合{}使用】----------------------------"); 3 UserInfor uInfor2 = new UserInfor(); 4 Console.WriteLine($"名字为:{uInfor2.userName}"); 5 //代表要输入一个括号{} 6 Console.WriteLine($"名字为:{{{uInfor2.userName}}}"); 7 //{}中如果有业务计算需要配合()使用 8 Console.WriteLine($"{(uInfor2.userName != "lmr" ? "小鲜肉" : "老鲜肉")}"); 9 }
3. 可以在命名空间出使用static声明静态类
1 { 2 Console.WriteLine($"-------------------------3.可以在命名空间出使用static声明静态类-------------------------"); 3 Console.WriteLine($"之前的使用方式: {Math.Pow(4, 2)}"); 4 Console.WriteLine($"导入后可直接使用方法: {Pow(4, 2)}"); 5 }
4. 空值运算符(?)
不需要判断是否为空了.
1 { 2 Console.WriteLine($"-------------------------4. 空值运算符-------------------------"); 3 int? iValue = 10; 4 Console.WriteLine(iValue?.ToString());//不需要判断是否为空 5 string name = null; 6 Console.WriteLine(name?.ToString()); 7 }
5. 对象初始化
可以直接给字典类型通过类似“索引”的形式赋值 (原先是通过Add方法赋值)
1 { 2 Console.WriteLine($"-------------------------5. 字典对象初始化-------------------------"); 3 Dictionary<string, string> dicList = new Dictionary<string, string>() 4 { 5 ["1"] = "ypf1", 6 ["2"] = "ypf2", 7 ["3"] = "ypf3" 8 }; 9 Dictionary<string, string> dicList2 = new Dictionary<string, string>() 10 { 11 { "1","lmr1"}, 12 { "2","lmr2"}, 13 { "3","lmr3"} 14 }; 15 foreach (var item in dicList) 16 { 17 Console.WriteLine("key:{0},value:{1}", item.Key.ToString(), item.Value.ToString()); 18 } 19 foreach (var item in dicList2) 20 { 21 Console.WriteLine("key:{0},value:{1}", item.Key.ToString(), item.Value.ToString()); 22 } 23 }
6. 异常过滤器增加when判断条件
只有符合when里的条件,才能进入catch,如果不满足的话,直接代码报错,不能抛异常
1 { 2 Console.WriteLine("-------------------------6. 异常过滤器增加when判断条件-------------------------"); 3 int epCheck = 100; 4 try 5 { 6 int.Parse("ypf"); 7 } 8 catch (Exception e) when (epCheck > 11) 9 { 10 Console.WriteLine(e.Message); 11 } 12 }
7. nameof表达式
把实例转换成同名的字符串
1 { 2 Console.WriteLine("-------------------------7. nameof表达式-------------------------"); 3 UserInfor userInfor = new UserInfor(); 4 Console.WriteLine(nameof(userInfor)); 5 }
8. 在catch和finally语句块里使用await(暂不介绍,不常用)
9. 在属性或方法上使用Lambada表达式
①:方法针对于只有一行的方法
②:属性通过Lambda的形式进行赋值
public class UserInfor { public string userId { get; set; } = "123456"; public string userName { get; set; } = "lmr"; /// <summary> /// Lambda类型的方法 /// </summary> public void LambdaPrint() => Console.WriteLine("我是Lambda类型的方法"); public string userSex => string.Format("男"); } { Console.WriteLine("-------------------------9.在属性或方法上使用Lambada表达式-------------------------"); UserInfor userInfor = new UserInfor(); userInfor.LambdaPrint(); Console.WriteLine($"userSex的值为:{userInfor.userSex}"); }
二. C# 7.0 新语法
1. out参数的改进
在C#7.0之前, out参数使用前必须先声明,然后传入方法中,在C#7.0后可以直接在传入方法的时候进行声明。
1 /// <summary> 2 /// out参数的改进 3 /// </summary> 4 /// <param name="x"></param> 5 /// <param name="y"></param> 6 public void DoNoting(out int x, out int y) 7 { 8 x = 1; 9 y = 2; 10 } 11 { 12 Console.WriteLine("--------------------1-out参数的改进------------------------"); 13 SevenEdition s = new SevenEdition(); 14 s.DoNoting(out int x, out int y); 15 Console.WriteLine(x + y); 16 }
2. 元组(Tuples)
①:需要通过nuget安装这个System.ValueTuple包
②:元组创建的三种方式:语法糖、Create静态方法、构造函数 (默认情况下是通过Item1、Item2 。。。)
③:指定元祖命名:可以通过左边指定,也可以通过右边指定
④:将元祖应用于方法中返回值,即一个方法可以返回多种不同类型的值,不需要封装实体即可以实现。
1 /// <summary> 2 /// 默认元组命名 3 /// </summary> 4 /// <returns></returns> 5 public (int, string, string) TupleWay1() 6 { 7 return (1, "ypf1", "ypf2"); 8 } 9 /// <summary> 10 /// 指定元祖命名 11 /// </summary> 12 /// <returns></returns> 13 public (int m, string n, string k) TupleWay2() 14 { 15 return (1, "ypf1", "ypf2"); 16 } 17 { 18 Console.WriteLine("--------------------2-元组(Tuples)------------------------"); 19 //1. 创建元组的三种形式 20 Console.WriteLine("--------------------1. 创建元组的三种形式------------------------"); 21 var tuple1 = (1, 2); //语法糖 22 var tuple2 = ValueTuple.Create("ypf", "lmr"); //Create静态方法 23 var tuple3 = new ValueTuple<int, string>(12, "ypf12"); //构造函数 24 Console.WriteLine($"tuple1的两个值为:{tuple1.Item1}和{tuple1.Item2}"); 25 Console.WriteLine($"tuple1的两个值为:{tuple2.Item1}和{tuple2.Item2}"); 26 Console.WriteLine($"tuple1的两个值为:{tuple3.Item1}和{tuple3.Item2}"); 27 28 //2. 指定元祖命名 29 Console.WriteLine("--------------------2. 指定元祖命名------------------------"); 30 (int m, string n) tuple4 = (100, "erp"); //左边命名 31 Console.WriteLine($"tuple4的两个值为:{tuple4.m}和{tuple4.n}"); 32 var tuple5 = (one: 250, two: "spz"); //右边命名 33 Console.WriteLine($"tuple5的两个值为:{tuple5.one}和{tuple5.two}"); 34 35 //3. 元祖应用于方法中返回值 36 Console.WriteLine("--------------------3. 元祖应用于方法中返回值------------------------"); 37 SevenEdition s = new SevenEdition(); 38 //默认命名 39 var result1 = s.TupleWay1(); 40 Console.WriteLine($"返回值有:{result1.Item1}、{result1.Item2}、{result1.Item3}"); 41 //指定命名 42 var result2 = s.TupleWay2(); 43 Console.WriteLine($"返回值有:{result2.m}、{result2.n}、{result2.k}"); 44 45 }
3. 局部函数
即在{}中声明一个函数,只有{}中能使用
1 { 2 Console.WriteLine("--------------------3-局部函数------------------------"); 3 DoSome(); 4 void DoSome() 5 { 6 Console.WriteLine("我是局部函数"); 7 } 8 }
4. 模式的比较和匹配
①:is的新模式。
首先补充一个概念:拆箱和装箱
拆箱是将引用类型→值类型 (object ,class均为引用类型)
装箱是将值类型→引用类型
案例:有一个object类型的变量a,如果它是int类型,则+10赋值给m,并输出m的值,下面看新老用法比较。
1 { 2 Console.WriteLine("--------------------4-模式比较(is)------------------------"); 3 object a = 9; 4 //老写法 5 if (a is int) 6 { 7 int b = (int)a; //拆箱 8 int c = b + 10; 9 Console.WriteLine($"老写法c的值为:{c}"); 10 } 11 //新写法 12 if (a is int m) //这里的a直接拆箱为m 13 { 14 int c = m + 10; 15 Console.WriteLine($"新写法c的值为:{c}"); 16 } 17 }
②:switch-case中可以自定义参数类型
传统情况,所有的case中必须是同类型的;而在C# 7.0 新版本中,case中可以不同类型,即Swich中可以传入任何类型,然后通过case中进行对应匹配,这就叫做模式匹配。
1 /// <summary> 2 /// 单一类型 3 /// </summary> 4 /// <param name="m"></param> 5 /// <returns></returns> 6 public dynamic SwitchWay1(string m) 7 { 8 dynamic data; 9 switch (m) 10 { 11 case "ypf1": 12 data = m + "lmr1"; 13 break; 14 case "ypf2": 15 data = m + "lmr2"; 16 break; 17 default: 18 data = "spz00000"; 19 break; 20 } 21 return data; 22 } 23 24 /// <summary> 25 /// 多种类型 26 /// </summary> 27 /// <param name="m"></param> 28 /// <returns></returns> 29 public dynamic SwitchWay2(object m) 30 { 31 dynamic data; 32 switch (m) 33 { 34 case int a when a > 10: 35 data = a + 10; 36 break; 37 case int b: 38 data = b + 100; 39 break; 40 case String c: 41 data = c + "mmmmmmmmmmmmmmm"; 42 break; 43 default: 44 data = "spz00000"; 45 break; 46 } 47 return data; 48 }
1 { 2 Console.WriteLine("--------------------4-模式匹配(switch-case)------------------------"); 3 SevenEdition sE = new SevenEdition(); 4 //老用法 5 var data1 = sE.SwitchWay1("ypf1"); 6 Console.WriteLine($"类型为:{data1.GetType()},值为:{data1}"); 7 //新用法 8 var data2 = sE.SwitchWay2(1); 9 Console.WriteLine($"类型为:{data2.GetType()},值为:{data2}"); 10 11 var data3 = sE.SwitchWay2(11); 12 Console.WriteLine($"类型为:{data3.GetType()},值为:{data3}"); 13 14 var data4 = sE.SwitchWay2("ypf1"); 15 Console.WriteLine($"类型为:{data4.GetType()},值为:{data4}"); 16 17 }
5. 数字文本语法的改写
比如10000000 可以写成10_000_000 ,方便识别。
{ Console.WriteLine("--------------------5-数字文本语法的改写------------------------"); long a = 10000000; long b = 10_000_000; Console.WriteLine($"a的值为:{a},b的值为:{b}"); }
6. 补充一些特殊地方也可以写异常表达式 (不详细测试了)
比如:条件表达式(? :)、null合并运算符(??)、一些Lambda
eg:private string _name = GetName() ?? throw new ArgumentNullException(nameof(GetName));
第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字
一. 各类数据结构比较及其线程安全问题
1. Array(数组):
分配在连续内存中,不能随意扩展,数组中数值类型必须是一致的。数组的声明有两种形式:直接定义长度,然后赋值;直接赋值。
缺点:插入数据慢。
优点:性能高,数据再多性能也没有影响
特别注意:Array不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,可以用ConcurrentStack这个线程安全的数组来替代Array。
1 { 2 Console.WriteLine("---------------------------01 Array(数组)-----------------------------------"); 3 //模式一:声明数组并指定长度 4 int[] array = new int[3]; 5 //数组的赋值通过下标来赋值 6 for (int i = 0; i < array.Length; i++) 7 { 8 array[i] = i + 10; 9 } 10 //数组的修改通过下标来修改 11 array[2] = 100; 12 //输出 13 for (int j = 0; j < array.Length; j++) 14 { 15 Console.WriteLine(array[j]); 16 } 17 18 //模式二:直接赋值 19 string[] array2 = new string[] { "二胖", "二狗" }; 20 }
2. ArrayList(可变长度的数组)
不必在声明的时候指定长度,即长度可变;可以存放不同的类型的元素。
致命缺点:无论什么类型存到ArrayList中都变为object类型,使用的时候又被还原成原先的类型,所以它是类型不安全的,当值类型存入的时候,会发生装箱操作,变为object引用类型,而使用的时候,又将object类型拆箱,变为原先的值类型,这尼玛,你能忍?
结论:不推荐使用,建议使用List代替!!
特别注意:ArrayList不是线程安全,在多线程中需要配合锁机制来进行。
1 { 2 Console.WriteLine("---------------------------02 ArrayList(可变长度的数组)-----------------------------------"); 3 ArrayList arrayList = new ArrayList(); 4 arrayList.Add("二胖"); 5 arrayList.Add("马茹"); 6 arrayList.Add(100); 7 for (int i = 0; i < arrayList.Count; i++) 8 { 9 Console.WriteLine(arrayList[i] + "类型为:" + arrayList[i].GetType()); 10 } 11 }
3. List<T> (泛型集合) 推荐使用
内部采用array实现,但没有拆箱和装箱的风险,是类型安全的
特别注意:List<T>不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,可以用ConcurrentBag这个线程安全的数组来替代List<T>
1 { 2 Console.WriteLine("---------------------------03 List<T> (泛型集合)-----------------------------------"); 3 List<string> arrayList = new List<string>(); 4 arrayList.Add("二胖"); 5 arrayList.Add("马茹"); 6 arrayList.Add("大胖"); 7 //修改操作 8 arrayList[2] = "葛帅"; 9 //删除操作 10 //arrayList.RemoveAt(0); 11 for (int i = 0; i < arrayList.Count; i++) 12 { 13 Console.WriteLine(arrayList[i]); 14 } 15 }
4. LinkedList<T> 链表
在内存空间中存储的不一定是连续的,所以和数组最大的区别就是,无法用下标访问。
优点:增加删除快,适用于经常增减节点的情况。
缺点:无法用下标访问,查询慢,需要从头挨个找。
特别注意:LinkedList<T>不是线程安全,在多线程中需要配合锁机制来进行。
{ Console.WriteLine("---------------------------04 ListLink<T> 链表-----------------------------------"); LinkedList<string> linkedList = new LinkedList<string>(); linkedList.AddFirst("二胖"); linkedList.AddLast("马茹"); var node1 = linkedList.Find("二胖"); linkedList.AddAfter(node1, "三胖"); //删除操作 linkedList.Remove(node1); //查询操作 foreach (var item in linkedList) { Console.WriteLine(item); } }
5. Queue<T> 队列
先进先出,入队(Enqueue)和出队(Dequeue)两个操作
特别注意:Queue<T>不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,线程安全的队列为 ConcurrentQueue。
实际应用场景:利用队列解决高并发问题(详见:http://www.cnblogs.com/yaopengfei/p/8322016.html)
1 { 2 Console.WriteLine("---------------------------05 Queue<T> 队列-----------------------------------"); 3 Queue<int> quereList = new Queue<int>(); 4 //入队操作 5 for (int i = 0; i < 10; i++) 6 { 7 quereList.Enqueue(i + 100); 8 } 9 //出队操作 10 while (quereList.Count != 0) 11 { 12 Console.WriteLine(quereList.Dequeue()); 13 } 14 }
6. Stack<T> 栈
后进先出,入栈(push)和出栈(pop)两个操作
特别注意:Stack<T>不是线程安全
1 { 2 Console.WriteLine("---------------------------06 Stack<T> 栈-----------------------------------"); 3 Stack<int> stackList = new Stack<int>(); 4 //入栈操作 5 for (int i = 0; i < 10; i++) 6 { 7 stackList.Push(i + 100); 8 } 9 //出栈操作 10 while (stackList.Count != 0) 11 { 12 Console.WriteLine(stackList.Pop()); 13 } 14 }
7. Hashtable
典型的空间换时间,存储数据不能太多,但增删改查速度非常快。
特别注意:Hashtable是线程安全的,不需要配合锁使用。
{ Console.WriteLine("---------------------------07 Hashtable-----------------------------------"); Hashtable tableList = new Hashtable(); //存储 tableList.Add("001", "马茹"); tableList["002"] = "二胖"; //查询 foreach (DictionaryEntry item in tableList) { Console.WriteLine("key:{0},value:{1}", item.Key.ToString(), item.Value.ToString()); } }
8. Dictionary<K,T>字典 (泛型的Hashtable)
增删改查速度非常快,可以用来代替实体只有id和另一个属性的时候,大幅度提升效率。
特别注意:Dictionary<K,T>不是线程安全,在多线程中需要配合锁机制来进行,如果不想使用锁,线程安全的字典为 ConcurrentDictionary。
1 { 2 Console.WriteLine("---------------------------08 Dictionary<K,T>字典-----------------------------------"); 3 Dictionary<string, string> tableList = new Dictionary<string, string>(); 4 //存储 5 tableList.Add("001", "马茹"); 6 tableList.Add("002", "二胖"); 7 tableList["002"] = "三胖"; 8 //查询 9 foreach (var item in tableList) 10 { 11 Console.WriteLine("key:{0},value:{1}", item.Key.ToString(), item.Value.ToString()); 12 } 13 }
强调:
以上8种类型,除了Hashtable是线程安全,其余都不是,都需要配合lock锁来进行,或者采用 ConcurrentXXX来替代。
详细的请见:http://www.cnblogs.com/yaopengfei/p/8322016.html
二. 四大接口比较
1. IEnumerable
是最基本的一个接口,用于迭代使用,里面有GetEnumerator方法。
2. ICollection
继承了IEnumerable接口,主要用于集合,内部有Count属性表示个数,像ArrayList、List、LinkedList均实现了该接口。
3. IList
继承了IEnumerable 和 ICollection,实现IList接口的数据接口可以使用索引访问,表示在内存上是连续分配的,比如Array、List。
4. IQueryable
这里主要和IEnumerable接口进行对比。
Enumerable里实现方法的参数是Func委托,Queryable里实现的方法的参数是Expression表达式。
实现IQueryable和IEnumabler均为延迟加载,但二者的实现方式不同,前者为迭代器模式,参数为Func委托,后者为Expression表达式目录树实现。
三. yield关键字
1. yield必须出现在IEunmerable中
2. yield是迭代器的状态机,能做到延迟查询,使用的时候才查询,可以实现按序加载
3. 例子
测试一:在 “var data1 = y.yieldWay();”加一个断点,发现直接跳过,不能进入yieldWay方法中,而在“foreach (var item in data1)”加一个断点,第一次遍历的时候就进入了yieldWay方法中,说明了yield是延迟加载的,只有使用的时候才查询。
测试二:对yieldWay和commonWay获取的数据进行遍历,通过控制台发现前者是一个一个输出,而后者是先一次性获取完,一下全部输出来,证明了yield可以做到按需加载,可以在foreach中加一个限制,比如该数据不满足>100就不输出。
1 //********************************* 下面为对比普通返回值和使用yeild返回值的方法 ************************************************ 2 3 /// <summary> 4 /// 含yield返回值的方法 5 /// </summary> 6 /// <returns></returns> 7 public IEnumerable<int> yieldWay() 8 { 9 for (int i = 0; i < 10; i++) 10 { 11 yield return this.Get(i); 12 } 13 } 14 /// <summary> 15 /// 普通方法 16 /// </summary> 17 /// <returns></returns> 18 public IEnumerable<int> commonWay() 19 { 20 int[] intArray = new int[10]; 21 for (int i = 0; i < 10; i++) 22 { 23 intArray[i] = this.Get(i); 24 } 25 return intArray; 26 } 27 28 /// <summary> 29 /// 一个获取数据的方法 30 /// </summary> 31 /// <param name="num"></param> 32 /// <returns></returns> 33 private int Get(int num) 34 { 35 Thread.Sleep(1000); 36 return num * DateTime.Now.Second; 37 }
1 Console.WriteLine("-----------------------下面是调用yield方法-----------------------"); 2 yieldDemo y = new yieldDemo(); 3 var data1 = y.yieldWay(); 4 foreach (var item in data1) 5 { 6 Console.WriteLine(item); 7 } 8 Console.WriteLine("-----------------------下面是调用普通方法-----------------------"); 9 var data2 = y.commonWay(); 10 foreach (var item in data2) 11 { 12 Console.WriteLine(item); 13 }
各种通讯连接方式
1. http https (ajax)
2. 总结一下同一个项目下 前后交互的集中提交方式 (.Net特有的 EasyUI封装的)
3. 跨域请求的几种形式
3. HttpClient
4. WebService
5. WebAPI(重点)
6. socket
7. websocket (重点)+superSocket
8. SignalR (重点)
9. nodejs中的net通讯模块
10. WCF (可以弃疗了)
设计模式篇
一. 什么是设计模式
纠结了好久,今天终于下定决心开始写设计模式系列,因为这个系列章节确实不好写,在这之前,也看了好多关于设计模式的博客、视频、书籍等,大多数用的例子要么猫啊狗啊、大雁等动物类;要么就是学生类,教师类,吐槽一下,真shit,试想一下,哪个项目中会用这些动物类教师类呢?
我也和我同行的朋友交流了一下关于设计模式,对设计模式的理解,可以分为这么几个层次:
①:根本不知道什么是设计模式。
②:听说过几种设计模式,理解不深。
③:能写出并理解几种设计模式,但不知道在项目中该怎么用。
毋庸置疑,能否灵活的运用好设计模式,是一个名开发工程师迈向架构师的必经之路,上面说的这么玄乎,那么到底什么是涉及模式呢?这里先借助金庸的武侠小说来类比一下。
作为金庸迷的我,金庸老师的“飞雪连天射白鹿,笑书神侠倚碧鸳”14部小说每一部看了都不低于3遍以上,对里面个各种武功也是了如指掌,像效果比较炫丽,威力比较大的有:“乔帮主降龙十八掌、段誉的六脉神剑、杨过的黯然销魂掌、任我行的吸星大法等等”,这些都是外家功夫,种类很多,一个人可能会多种,这就好比.Net的中MVC、EF、SignalR等等;当然也有内功心法,典型的有:”少林和尚的易筋经、张无忌的九阳神功”,这两种功夫本身并没有太大的杀伤力,但会了这种功夫,更容易融会贯通外家功夫,使外家功夫发挥出更大效果,拿到我们开发领域,“设计模式”就是内功心法,没有语言之分,它是一种模式,一种思想指导着我们开发。
那么怎么才能算精通设计模式呢?
看过《倚天屠龙记》的朋友可能会记得里面有这么一个场景:赵敏冒充明教挑战张三丰的时候,张无忌办成小道童出来救场,在对阵三个家奴的的时候,张三丰教了张无忌一套太极拳法,里面有这么一段对话:
张三丰演示完后,问张无忌:“无忌,你记住了多少”,张无忌回答说:“无忌不才,只有一小部分没有记住”;过了一会,张三丰又问道:“现在能记住多少”,无忌说:“太师傅,我已经全部忘记了”,这时,张三丰说:“无忌你可以上了”,结果显然而知,对手被打的那叫一个惨啊。
所以:设计模式的最高境界是,忘记设计模式,将23种的设计模式自然而然的融入开发中,哈哈,当然这个有点难,没有个五年以上的功力,很难达到这个层次。
二. 设计模式的内容
设计模式是一种套路,是把 “别人成功的例子” 拿过来灵活运用,我们的优秀的前辈总结出来7个设计原则和23种设计模式。
设计原则:
1. 单一职责原则 2. 里氏替换原则 3. 依赖倒置原则
4. 接口隔离原则 5. 迪米特原则(最小知道原则) 6. 开闭原则
7. 组合聚合原则
设计模式:
1. 创建型模式 :工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
2. 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
3. 行为型模式:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
解释:设计模式只是一个大概的套路,不能生搬硬套,有一些做法可能是多个设计模式的融合,有的也很难归为某个设计模式。不要为了设计模式而设计模式,没有十全十美的代码。
在上述设计模式中,大部分设计模式都用到了上面的设计思想,在设计模式中,我们会经常看到:开闭原则、里氏替换原则(多态)、面向接口编程、面向抽象编程、抽象类、override覆写这几种技术。
如果对于抽象类或者override不明白的,有必要先看一下下面的章节:
第一节:从面向对象思想(oo)开发、接口、抽象类以及二者比较
第二节:重写(new)、覆写(overwrite)、和重载(overload)
三. 系列章节
下面将由浅入深,结合实际案例来讲解设计原则和设计模式。
第二节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第三节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第四节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第五节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第六节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第七节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第八节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第九节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十一节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十二节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十三节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十四节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十五节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十六节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十七节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十八节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第十九节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十一节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十二节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十三节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十四节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十五节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十六节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十七节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十八节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第二十九节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第三十节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第三十一节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第三十二节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
第三十三节:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
(进度:一周写 2-3篇,大约2个半月 完成该系列)
第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借助TopSelf、服务类】)
一. IIS部署
比如在MVC框架中集成了Quartz.Net定时调度,此时该调度系统会随着MVC框架被挂在IIS下,IIS会进程回收,所以大部分开发都会遇到Quartz挂在IIS下一段时间不好用。
补充:IIS可以设置定时自动回收,默认回收是1740分钟,也就是29小时。IIS自动回收相当于服务器IIS重启,应用程序池内存清空,所有数据被清除,相当于IIS重启,在度量快速开发平台服务器端,为了减小数据库负担,内存中暂存了很多信息,不适合频繁的回收,因为回收会造成服务器端所有存在内存中的数据丢失,如果没有及时保存到数据库中,可能导致程序出现问题。而如果系统使用高峰时期,并不适合回收,回收可能导致几十秒IIS无响应,对于正在工作的人员来说,是一种很不好的体验,会以为是网络或者掉线等问题。
解决方案:关闭该项目在IIS上对应的进程池的回收机制。
如何关闭进程池的回收机制:选中IIS中部署的项目对应的进程池,点击【高级设置】,里面有5个核心参数:
① 发生配置更改时禁止回收:如果为True,应用程序池在发生配置更改时将不会回收。
② 固定时间间隔(分钟):超过设置的时间后,应用程序池回收,设置为:0 意味着应用程序池不回收。系统默认设置的时间是1740(29小时)。
③ 禁用重叠回收:如果为true,将发生应用程序池回收,以便在创建另一个工作进程之前退出现有工作进程
④ 请求限制:应用程序池在回收之前可以处理的最大请求数。如果值为0,则表示应用程序池可以处理的请求数没有限制。
⑤ 生成回收事件日志条目:每发生一次指定的回收事件时便产生一个事件日志条目。
总结:即使可以将IIS进程池回收关掉,仍然不建议把Quartz挂到IIS下,长时间不回收,会存在内存溢出的问题。
二. C/S程序直接运行
我们可以用控制台的形式或者Winform的形式单独做一套定时调度系统,与主框架分类,也便于维护,可以直接将exe程序或者Winform窗体程序在服务器上运行。
总结:该方法不存在回收的问题,但直接在服务器上运行,容易不小心被运维人员关掉了。
对于专业一点的人员来说,该方案,直接运行程序太Low了,所以通常是将exe程序发不成windows服务,通过服务的开启和关闭来 维护。
三. 借助topshelf来进行的windows服务部署
官网:http://topshelf-project.com/ , 这是一种通用的发布服务的方式,步骤如下:
1. 通过NuGet下载 Topshelf 的程序集
2. 配置QuartzService类,充当定时任务的服务端程序
①:构造函数中执行定时调度任务
②:Start()方法控制调度的开启 (必填)
③:Stop()方法控制调度的关闭 (必填)
④:Pause()方法暂停调度 (选填)
⑤:Continue()方法继续调度 (选填)
⑥:Shutdown() 关闭 (需要支持)
3. 在主程序中通过topshelf代码调用: HostFactory.Run 详见主程序。(在里面可以设置服务的名称、描述等)
4. 通过指令进行服务发布和卸载(查看windows服务:services.msc)
①:通过cmd命令定位到bin文件目录下(以管理员身份运行)
(eg: 先输入 d: 切换到D盘,再执行下面的命令 cd D:\06-我的开发之路\DotNet体系\04-DotNet专题篇\01-Quartz.Net定时调度\01-Code\MyQuarzt\QuartzDemo\bin\Debug )
②:QuartzDemo.exe help: 查看指令
QuartzDemo.exe install: 安装服务
QuartzDemo.exe install start : 安装服务且开启
QuartzDemo.exe uninstall :卸载服务
截止此处,大功告成,可以看到D盘中多了一个txt文件,每隔3s多一条数据
下面分享整个过程的代码部署的相应截图:
(1). Quartz代码和TopSelf调用代码:
1 public class QuartzService 2 { 3 IScheduler scheduler = null; 4 /// <summary> 5 /// 定时调度业务 6 /// </summary> 7 public QuartzService() 8 { 9 //1.创建作业调度池(Scheduler) 10 scheduler = StdSchedulerFactory.GetDefaultScheduler(); 11 12 //2.创建一个具体的作业即job (具体的job需要单独在一个文件中执行) 13 var job = JobBuilder.Create<HelloJob5>().Build(); 14 15 //3.创建并配置一个触发器即trigger 3s执行一次 16 var trigger = TriggerBuilder.Create().WithSimpleSchedule(x => x.WithIntervalInSeconds(3) 17 .RepeatForever()).Build(); 18 //4.将job和trigger加入到作业调度池中 19 scheduler.ScheduleJob(job, trigger); 20 } 21 /// <summary> 22 /// 开启任务 23 /// </summary> 24 public void Start() 25 { 26 scheduler.Start(); 27 } 28 /// <summary> 29 /// 关闭任务 30 /// </summary> 31 public void Stop() 32 { 33 //true:表示该Sheduler关闭之前需要等现在所有正在运行的工作完成才能关闭 34 //false:表示直接关闭 35 scheduler.Shutdown(true); 36 } 37 /// <summary> 38 /// 暂停调度 39 /// </summary> 40 public void Pause() 41 { 42 scheduler.PauseAll(); 43 } 44 /// <summary> 45 /// 继续调度 46 /// </summary> 47 public void Continue() 48 { 49 scheduler.ResumeAll(); 50 } 51 52 }
1 HostFactory.Run(x => //1 2 { 3 x.Service<QuartzService>(s => //2 4 { 5 s.ConstructUsing(name => new QuartzService()); //3 6 //开启和关闭 必选项 7 s.WhenStarted(tc => tc.Start()); //4 8 s.WhenStopped(tc => tc.Stop()); //5 9 10 // optional pause/continue methods if used 11 // 暂停和继续 选填 12 s.WhenPaused(tc => tc.Pause()); 13 s.WhenContinued(tc => tc.Continue()); 14 15 //// optional, when shutdown is supported 16 //s.WhenShutdown(tc => tc.Shutdown()); 17 18 }); 19 x.RunAsLocalSystem(); //6 20 x.SetDescription("测试借助TopSelf将Quartz发布成服务"); //7 21 x.SetDisplayName("QuartzService"); //8 22 x.SetServiceName("QuartzService2"); //9 23 });
(2). 定位到指定路径,并进行相应的服务部署
a. 很多指令
b. 安装服务并开启
查看服务列表:
查看服务运行结果:
删除服务:
三. 借助window服务类
这里不详细介绍(网上一堆一堆的),推荐采用上面的TopShelf的服务发布方式。