2023年6月20日发(作者:)

.netcore基础知识

(转:)IoC的全名Inverse of Control,翻译成中⽂就是“控制反转”或者“控制倒置”。控制反转也好,控制倒置也罢,它体现的意思是控制权的转移,即原来控制权在A⼿中,现在需要B来接管。那么具体对于软件设计来说,IoC所谓的控制权的转移具有怎样的体现呢?要回答这个问题,就需要先了解IoC的C(Control)究竟指的是怎样⼀种控制。对于我们所在的任何⼀件事,不论其⼤⼩,其实可以分解成相应的步骤,所以任何⼀件事都有其固有的流程,IoC涉及的所谓控制可以理解为“针对流程的控制我们通过⼀个具体事例来说明传统的设计在采⽤了IoC之后针对流程的控制是如何实现反转的。⽐如说现在设计⼀个针对Web的MVC类库,我们不妨将其命名为MvcLib。简单起见,这个类库中只包含如下这个同名的静态类public static class MvcLib{ public static Task ListenAsync(Uri address); public static Task ReceiveAsync(); public static Task CreateControllerAsync(Request request); public static Task ExecuteControllerAsync(Controller controller); public static Task RenderViewAsync(View view);}MvcLib提供了如上5个⽅法帮助我们完成整个HTTP请求流程中的5个核⼼任务。具体来说,ListenAsync⽅法启动⼀个监听器并将其绑定到指定的地址进⾏HTTP请求的监听,抵达的请求通过ReceiveAsync⽅法进⾏接收,我们将接收到的请求通过⼀个Request对象来表⽰。CreateControllerAsync⽅法根据接收到的请求解析并激活请求的⽬标Controller对象。ExecuteControllerAsync⽅法执⾏激活的Controller并返回⼀个表⽰视图的View对象。RenderViewAsync最终将View对象转换成HTML并作为当前请求响应的内容返回给请求的客户端。现在我们在这个MvcLib的基础上创建⼀个真正的MVC应⽤,那么除了按照MvcLib的规范⾃定义具体的Controller和View之外,我们还需要⾃⾏控制从请求的监听与接收、Controller的激活与执⾏以及View的最终呈现在内的整个流程,这样⼀个执⾏流程反映在如下所⽰的代码中。class Program{ static async Task Main() { while (true) { Uri address = new Uri("0.0.0.0:8080/mvcapp"); await Async(address); while (true) { var request = await eAsync(); var controller = await ControllerAsync(request); var view = await eControllerAsync(controller); await ViewAsync(view); } } }}2.好莱坞法则在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项⽬的完全控制,演员只能被动式的接受电影公司的⼯作,在需要的环节中,完成⾃⼰的演出。“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞法则( HollywoodPrinciple或者 Hollywood Low),IoC完美地体现了这⼀法则。在IoC的应⽤语境中,框架就像是掌握整个电影制⽚流程的电影公司,由于它是整个⼯作流程的实际控制者,所以只有它知道哪个环节需要哪些组件。应⽤程序就像是演员,它只需要按照框架定制的规则注册这些组件就可以了,因为框架会在适当的时机字典加载并执⾏注册的组件。以熟悉的 Core MVC或者 MVC应⽤开发来说,我们只需要按照约定规则(⽐如⽬录结构和命名等)定义相应的Controller类型和View⽂件就可以了。当 (Core )MVC框架在进⾏处理请求的过程中,它会根据解析⽣成的路由参数定义为对应的Controller类型,并按照预定义的规则找到我们定义的Controller,然后⾃动创建并执⾏它。如果定义在当前Action⽅法需要呈现⼀个View,框架⾃⾝会根据预定义的⽬录约定找到我们定义的View⽂件,并对它实施动态编译和执⾏。整个流程处处体现了“框架Call应⽤”的好莱坞法则。总的来说,我们在⼀个框架的基础上进⾏应⽤开发,就相当于在⼀条调试好的流⽔线上⽣成某种商品,我们只需要在相应的环节准备对应的原材料,最终下线的就是我们希望得到的最终产品。IoC⼏乎是所有框架均具有的⼀个固有属性,从这个意义上讲,“IoC框架”的说法其实是错误的,世界上并没有什么IoC框架,或者说⼏乎所有的框架都是IoC框架。3.依赖注⼊(DI容器)IoC主要体现了这样⼀种设计思想:通过将⼀组通⽤流程的控制权从应⽤转移到框架中以实现对流程的复⽤,并按照“好莱坞法则”实现应⽤程序的代码与框架之间的交互。我们可以采⽤若⼲设计模式以不同的⽅式实现IoCDI是⼀种“对象提供型”的设计模式,在这⾥我们将提供的对象统称为“服务”、“服务对象”或者“服务实例”。在⼀个采⽤DI的应⽤中,在定义某个服务类型的时候,我们直接将依赖的服务采⽤相应的⽅式注⼊进来。按照“⾯向接⼝编程”的原则,被注⼊的最好是依赖服务的接⼝⽽⾮实现。在应⽤启动的时候,我们会对所需的服务进⾏全局注册。服务⼀般都是针对接⼝进⾏注册的,服务注册信息的核⼼⽬的是为了在后续消费过程中能够根据接⼝创建或者提供对应的服务实例。按照“好莱坞法则”,应⽤只需要定义好所需的服务,服务实例的激活和调⽤则完全交给框架来完成,⽽框架则会采⽤⼀个独⽴的“容器(Container)”来提供所需的每⼀个服务实例。我们将这个被框架⽤来提供服务的容器称为“DI容器”,也由很多⼈将其称为“IoC容器”,根据我们在《控制反转》针对IoC的介绍,我不认为后者是⼀个合理的称谓。DI容器之所以能够按照我们希望的⽅式来提供所需的服务是因为该容器是根据服务注册信息来创建的,服务注册了包含提供所需服务实例的所有信息。以Autofac框架为列

框架特性1,灵活的组件实例化:Autofac⽀持⾃动装配,给定的组件类型Autofac⾃动选择使⽤构造函数注⼊或者属性注⼊,Autofac还可以基于lambda表达式创建实例,这使得容器⾮常灵活,很容易和其他的组件集成。2,资源管理的可视性:基于依赖注⼊容器构建的应⽤程序的动态性,意味着什么时候应该处理那些资源有点困难。Autofac通过容器来跟踪组件的资源管理。对于不需要清理的对象,例如,我们调⽤ExternallyOwned()⽅法告诉容器不⽤清理。细粒度的组件⽣命周期管理:应⽤程序中通常可以存在⼀个应⽤程序范围的容器实例,在应⽤程序中还存在⼤量的⼀个请求的范围的对象,例如⼀个HTTP请求,⼀个IIS⼯作者线程或者⽤户的会话结束时结束。通过嵌套的容器实例和对象的作⽤域使得资源的可视化。3,Autofac的设计上⾮常务实,这⽅⾯更多是为我们这些容器的使⽤者考虑:●组件侵⼊性为零:组件不需要去引⽤Autofac。●灵活的模块化系统:通过模块化组织你的程序,应⽤程序不⽤纠缠于复杂的XML配置系统或者是配置参数。●⾃动装配:可以是⽤lambda表达式注册你的组件,autofac会根据需要选择构造函数或者属性注⼊●XML配置⽂件的⽀持:XML配置⽂件过度使⽤时很丑陋,但是在发布的时候通常⾮常有⽤(1).属性注⼊er(c => new A { B = e() });为了⽀持循环依赖,使⽤激活的事件处理程序:er(c => new A()).OnActivated(e => ce.B = e());如果是⼀个反射组件,使⽤PropertiesAutowired()修改注册属性:erType().PropertiesAutowired();如果你有⼀个特定的属性和值需要连接,你可以使⽤WithProperty()修改:erType().WithProperty("PropertyName", propertyValue);(2).⽅法注⼊调⽤⼀个⽅法来设置⼀个组件的值的最简单的⽅法是,使⽤⼀个lambda表达式组件和正确的调⽤激活处理⽅法。er(c => { var result = new MyObjectType(); var dep = e(); Dependency(dep); return result;});如果你不能使⽤⼀个lambda表达式注册,你可以添加⼀个激活事件处理程序(activating event handler)builder .Register() .OnActivating(e => { var dep = e(); Dependency(dep); });(3).构造函数注⼊ // 创建你的builder var builder = new ContainerBuilder(); // 通常你只关⼼这个接⼝的⼀个实现 erType().As(); // 当然,如果你想要全部的服务(不常⽤),可以这么写: erType().AsSelf().As(); ⽣命周期AutoFac中的⽣命周期概念⾮常重要,AutoFac也提供了强⼤的⽣命周期管理的能⼒。AutoFac定义了三种⽣命周期:Per DependencySingle InstancePer Lifetime ScopePer Dependency为默认的⽣命周期,也被称为’transient’或’factory’,其实就是每次请求都创建⼀个新的对象[Fact] public void per_dependency() { var builder = new ContainerBuilder(); erType().InstancePerDependency(); IContainer container = (); var myClass1 = e(); var myClass2 = e(); al(myClass1,myClass2); }Single Instance也很好理解,就是每次都⽤同⼀个对象

[Fact] public void single_instance() { var builder = new ContainerBuilder(); erType().SingleInstance();

IContainer container = (); var myClass1 = e(); var myClass2 = e();

(myClass1,myClass2); }Per Lifetime Scope,同⼀个Lifetime⽣成的对象是同⼀个实例

[Fact] public void per_lifetime_scope() { var builder = new ContainerBuilder(); erType().InstancePerLifetimeScope();

IContainer container = (); var myClass1 = e(); var myClass2 = e();

ILifetimeScope inner = ifetimeScope(); var myClass3 = e(); var myClass4 = e();

(myClass1,myClass2); al(myClass2,myClass3); (myClass3,myClass4); }[Fact] public void life_time_and_dispose() { var builder = new ContainerBuilder(); erType(); using (IContainer container = ()) { var outInstance = e(new NamedParameter("name", "out")); using(var inner = ifetimeScope()) { var inInstance = e(new NamedParameter("name", "in")); }//inInstance dispose here }//out dispose here } 4.过滤器(转:)下图展⽰了 Core MVC框架默认实现的过滤器的执⾏顺序:Authorization Filters:⾝份验证过滤器,处在整个过滤器通道的最顶层。对应的类型为: 对应的接⼝有同步和异步两个版本: 、 urce Filters:资源过滤器。因为所有的请求和响应都将经过这个过滤器,所以在这⼀层可以实现类似缓存的功能。对应的接⼝有同步和异步两个版本: 、 on Filters:⽅法过滤器。在控制器的Action⽅法执⾏之前和之后被调⽤,⼀个很常⽤的过滤器。对应的接⼝有同步和异步两个版本: 、 ption Filters:异常过滤器。当Action⽅法执⾏过程中出现了未处理的异常,将会进⼊这个过滤器进⾏统⼀处理,也是⼀个很常⽤的过滤器。对应的接⼝有同步和异步两个版本: 、 lt Filters:返回值过滤器。当Action⽅法执⾏完成的结果在组装或者序列化前后被调⽤。对应的接⼝有同步和异步两个版本: 、 5.中间件(转) Core,管道模型流程图IHttpModule和IHttpHandler不复存在,取⽽代之的是⼀个个中间件(Middleware)。Server将接收到的请求直接向后传递,依次经过每⼀个中间件进⾏处理,然后由最后⼀个中间件处理并⽣成响应内容后回传,再反向依次经过每个中间件,直到由Server发送出去。中间件就像⼀层⼀层的“滤⽹”,过滤所有的请求和相应。这⼀设计⾮常适⽤于“请求-响应”这样的场景——消息从管道头流⼊最后反向流出。接下来将演⽰在 Core⾥如何实现中间件功能。 IHttpModule和IHttpHandler不复存在,取⽽代之的是⼀个个中间件(Middleware)。Server将接收到的请求直接向后传递,依次经过每⼀个中间件进⾏处理,然后由最后⼀个中间件处理并⽣成响应内容后回传,再反向依次经过每个中间件,直到由Server发送出去。中间件就像⼀层⼀层的“滤⽹”,过滤所有的请求和相应。这⼀设计⾮常适⽤于“请求-响应”这样的场景——消息从管道头流⼊最后反向流出。接下来将演⽰在 Core⾥如何实现中间件功能。Middleware Middleware⽀持Run、Use和Map三种⽅法进⾏注册,下⾯将展⽰每⼀种⽅法的使⽤⽅式。 Run⽅法所有需要实现的⾃定义管道都要在 的 Configure ⽅法⾥添加注册。public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async context => { await sync("Hello World!"); }); // 添加MVC中间件 //(); }启动调试,访问地址 localhost:5000/ ,页⾯显⽰Hello World!字样再次添加⼀个Run⽅法public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async context => { await sync("Hello World!"); }); (async context => { await sync("Hello World too!"); }); // 添加MVC中间件 //(); }启动调试,再次访问发现页⾯上只有Hello World!字样。原因是:Run的这种⽤法表⽰注册的此中间件为管道内的最后⼀个中间件,由它处理完请求后直接返回。Use⽅法

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async (context, next) => { await sync("Hello World!"); }); // 添加MVC中间件 //(); }启动调试,访问页⾯同样显⽰Hello World!字样。我们发现使⽤Use⽅法替代Run⽅法,⼀样可以实现同样的功能。再次添加⼀个Use⽅法,将原来的Use⽅法内容稍作调整,尝试实现页⾯显⽰两个Hello World!字样。public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async (context, next) => { await sync("Hello World!"); await next(); }); (async (context, next) => { await sync("Hello World too!"); }); // 添加MVC中间件 //(); }将两个Use⽅法换个顺序,稍微调整⼀下内容,再次启动调试,访问页⾯,发现字样输出顺序也发⽣了变化。public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g();

// 添加⾃定义中间件 (async (context, next) => { await sync("Hello World too!"); await next(); }); (async (context, next) => { await sync("Hello World!"); }); // 添加MVC中间件 //(); }从上⾯的例⼦可以发现,通过Use⽅法注册的中间件,如果不调⽤next⽅法,效果等同于Run⽅法。当调⽤next⽅法后,此中间件处理完后将请求传递下去,由后续的中间件继续处理。当注册中间件顺序不⼀样时,处理的顺序也不⼀样,这⼀点很重要,当注册的⾃定义中间件数量较多时,需要考虑哪些中间件先处理请求,哪些中间件后处理请求。另外,我们可以将中间件单独写成独⽴的类,通过UseMiddleware⽅法同样可以完成注册。下⾯将通过独⽴的中间件类重写上⾯的演⽰功能。新建两个中间件类: 、 g ;using ;namespace wares{ public class HelloworldMiddleware { private readonly RequestDelegate _next; public HelloworldMiddleware(RequestDelegate next){ _next = next; } public async Task Invoke(HttpContext context){ await sync("Hello World!"); await _next(context); } }}g ;using ;namespace wares{ public class HelloworldTooMiddleware { private readonly RequestDelegate _next; public HelloworldTooMiddleware(RequestDelegate next){ _next = next; } public async Task Invoke(HttpContext context){ await sync("Hello World too!"); } }}修改 的Configure⽅法内容public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 dleware(); dleware(); // 添加MVC中间件 //(); }启动调试,访问页⾯,可以看到同样的效果。Map⽅法Map⽅法主要通过请求路径和其他⾃定义条件过滤来指定注册的中间件,看起来更像⼀个路由。修改 的Configure⽅法内容,增加静态⽅法MapTestpublic void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 ("/test", MapTest); // 添加MVC中间件 //(); } private static void MapTest(IApplicationBuilder app){ (async context => { await sync("Url is " + ng()); }); }启动调试,访问路径 localhost:5000/test ,页⾯显⽰如下内容

但是访问其他路径时,页⾯没有内容显⽰。从这个可以看到,Map⽅法通过类似路由的机制,将特定的Url地址请求引导到固定的⽅法⾥,由特定的中间件处理。另外,Map⽅法还可以实现多级Url“路由”,其实就是Map⽅法的嵌套使⽤// 添加⾃定义中间件 ("/level1", lv1App => { ("/level1.1", lv11App => { // /level1/level1.1 });

("/level1.2", lv12App => { // /level1/level1.2 }); });也可以通过MapWhen⽅法使⽤⾃定义条件进⾏“路由”public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 n(context => { return nsKey("a"); }, MapTest); // 添加MVC中间件 //(); } private static void MapTest(IApplicationBuilder app) { (async context => { await sync($"Url is {ng()}{}"); }); }启动调试,访问路径 localhost:5000/path?a=1&b=2 ,页⾯显⽰如下内容只有当请求参数中含有a时,页⾯才正常显⽰内容。其他内置的中间件 Core框架内置了⼏个中间件 6.路由(转)定义/* * API 定义如下 * GET api/menu 获取菜单列表 * POST api/menu 添加模块 * PUT api/menu 修改模块 * PATCH api/menu 修改菜单信息 * DELETE api/menu 删除模块 */2.后台代码 :MenuModelContext _Context = new MenuModelContext();///

/// 获取列表/// /// [HttpGet]public IEnumerable Get(){ List list = _(); return list;}/// /// 添加模块对象/// /// /// [HttpPost]public bool Add(Model m){ try { e = ; _(m); _anges(); return true; } catch (Exception ex) { return false; }}/// /// 修改模块/// /// /// [HttpPut]public bool Update(Model m){ try { Model oldModel = _(d); ame = ame; mber = mber; _anges(); return true; } catch (Exception ex) { return false; }}/// /// 修改菜单对象/// /// /// [HttpPatch]public IActionResult Update(Menu m){ try { Menu oldMenu = _(); if (oldMenu == null) return NotFound("你要访问的菜单不存在或已经删除"); me = me; mber = mber; d = d; _anges(); return Ok(); } catch (Exception ex) { return Content(e); }}/// /// 删除模块/// /// /// [HttpDelete]public IActionResult Delete(string ids){ try { if (OrEmpty(ids)) throw new Exception("获取id参数失败"); List idList = (',').Select(q => 32(q)).ToList(); List list = _(q => ns(d)).ToList(); _Range(list); int count = _anges(); //使⽤OkObjectResult 前台jquery⾃动解析为object对象,不需要进⾏反序列化处理 //返回的不是字符串 return Ok(new { msg = $"删除成功,总共删除{count}条数据" }); } catch (Exception ex) { //使⽤ContentResult返回字符串处理 return Content(e); }} ajax代码//Get获取列表数据function testOne() { $.get(('menu'), {}, function (data) { (data); var menu1 = new Vue({ el: '#menu1', data: { result: data } }); })}testOne();//添加菜单var example2 = new Vue({ el: '#example-2', data: { name:'添加菜单' }, //在 methods 对象中定义⽅法 methods: { addMenu: function (event) { //使⽤ Post提交添加菜单 var _this = this; = '正在提交...'; $.post(('menu'), { ModelName: '测试菜单5', SortNumber:5 }, function (data) { (data); _ = '提交成功'; }); }, updateModel: function (event) { //使⽤put提交修改模块 var _this = this; $.ajax({ url: ('menu'), type: 'put', data: { ModelId: '4', ModelName: '模块abc', SortNumber: 4 }, success: function (data) { (data); if (data == true) alert('修改成功'); else alert('修改失败'); } }); } }});//修改菜单、删除模块var btngroup1 = new Vue({ el: '#btngroup1', data: { name: '修改菜单', name1: '删除模块' }, methods: { updateMenu: function (e) { var _this = this; //使⽤patch ⽅式修改菜单 $.ajax({ url: ('menu'), type:'patch', data: { MenuID: 1, MenuName: '测试菜单One', SortNumber: 100, ModelID:2 }, success: function (data) { (data); } }); }, deleteMenu: function (e) { //使⽤delete 提交⽅式删除模块 $.ajax({ url: ('menu'), type: 'delete', data: { ids:[1003] }, success: function (data) { (data); }, error: function (data) { (data); } }); } }});根据HttpMethod的Template路由⽰例定义/** API 定义* GET api/menu/{id} 获取指定ID的菜单对象* GET api/menu/getmodel 获取模块列表内容*/[HttpGet("{id}")]public IActionResult Get(int ID){ Menu m = _(ID); if (m == null) return NotFound(); return Json(m);}//特别说明:路由中的Template的值不可以包含斜杠/[HttpGet("getmodel")]

public IActionResult GetModel(){ List list = _(); return Json(list);}Jquery 的ajax代码//其他Get⽅式测试var btngroup2 = new Vue({ el: '#btngroup2', data: { name: '获取菜单对象', name1: '获取模块列表', item: {} //Vue的对象绑定,没有的情况下需要⼀个空对象,不然报错 }, methods: { getMenu: function (e) { var _this = this; //链接地址格式 :localhost:50000/api/menu/1/ $.get(('menu','1'), { }, function (data) { (data); _ = data; }); }, getModel: function (e) { var _this = this; $.get(('menu', 'getmodel'), {}, function (data) { (data); }) } }}); p ///

/// 此⽅法由运⾏时调⽤。使⽤此⽅法向容器添加服务。 /// /// For more information on how to configure your application, visit /fwlink/?LinkID=398940 /// DI容器 public IServiceProvider ConfigureServices(IServiceCollection services) { //添加MVC ( // 配置异常过滤器 config => { (typeof(CustomExceptionFilter)); } ) // 设置json序列化⽅式 .AddJsonOptions(mvcJsonOptions => { //忽略循环引⽤ nceLoopHandling = ; //不使⽤驼峰样式的key ctResolver = new DefaultContractResolver(); //设置时间格式 rmatString = ddHHmmss; }) .SetCompatibilityVersion(n_2_1); s(options => { icy("AllowSpecificOrigins", builder => { nyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); // 注册服务并且实例化AutoFac替换默认容器 var containerBuilder = new ContainerBuilder(); //实例化 AutoFac 容器

// 注册⽤户服务 erType().As(); te(services); ApplicationContainer = (); return new AutofacServiceProvider(ApplicationContainer); //第三⽅IOC接管 core内置DI容器 } ///

/// 此⽅法由运⾏时调⽤。使⽤此⽅法配置HTTP请求管道。 /// /// app /// env public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //if (lopment()) //{ // eloperExceptionPage(); //} //eptionHandler( // options => { // ( // async context => // { // Code = (int); // tType = "text/html"; // var ex = (); // if (ex != null) // { // var err = $"

Error: {e}

{race }"; // await sync(err).ConfigureAwait(false); // } // }); // } //); //启动跨域 s("AllowSpecificOrigins"); //注⼊MVC路由 (); }

2023年6月20日发(作者:)

.netcore基础知识

(转:)IoC的全名Inverse of Control,翻译成中⽂就是“控制反转”或者“控制倒置”。控制反转也好,控制倒置也罢,它体现的意思是控制权的转移,即原来控制权在A⼿中,现在需要B来接管。那么具体对于软件设计来说,IoC所谓的控制权的转移具有怎样的体现呢?要回答这个问题,就需要先了解IoC的C(Control)究竟指的是怎样⼀种控制。对于我们所在的任何⼀件事,不论其⼤⼩,其实可以分解成相应的步骤,所以任何⼀件事都有其固有的流程,IoC涉及的所谓控制可以理解为“针对流程的控制我们通过⼀个具体事例来说明传统的设计在采⽤了IoC之后针对流程的控制是如何实现反转的。⽐如说现在设计⼀个针对Web的MVC类库,我们不妨将其命名为MvcLib。简单起见,这个类库中只包含如下这个同名的静态类public static class MvcLib{ public static Task ListenAsync(Uri address); public static Task ReceiveAsync(); public static Task CreateControllerAsync(Request request); public static Task ExecuteControllerAsync(Controller controller); public static Task RenderViewAsync(View view);}MvcLib提供了如上5个⽅法帮助我们完成整个HTTP请求流程中的5个核⼼任务。具体来说,ListenAsync⽅法启动⼀个监听器并将其绑定到指定的地址进⾏HTTP请求的监听,抵达的请求通过ReceiveAsync⽅法进⾏接收,我们将接收到的请求通过⼀个Request对象来表⽰。CreateControllerAsync⽅法根据接收到的请求解析并激活请求的⽬标Controller对象。ExecuteControllerAsync⽅法执⾏激活的Controller并返回⼀个表⽰视图的View对象。RenderViewAsync最终将View对象转换成HTML并作为当前请求响应的内容返回给请求的客户端。现在我们在这个MvcLib的基础上创建⼀个真正的MVC应⽤,那么除了按照MvcLib的规范⾃定义具体的Controller和View之外,我们还需要⾃⾏控制从请求的监听与接收、Controller的激活与执⾏以及View的最终呈现在内的整个流程,这样⼀个执⾏流程反映在如下所⽰的代码中。class Program{ static async Task Main() { while (true) { Uri address = new Uri("0.0.0.0:8080/mvcapp"); await Async(address); while (true) { var request = await eAsync(); var controller = await ControllerAsync(request); var view = await eControllerAsync(controller); await ViewAsync(view); } } }}2.好莱坞法则在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项⽬的完全控制,演员只能被动式的接受电影公司的⼯作,在需要的环节中,完成⾃⼰的演出。“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞法则( HollywoodPrinciple或者 Hollywood Low),IoC完美地体现了这⼀法则。在IoC的应⽤语境中,框架就像是掌握整个电影制⽚流程的电影公司,由于它是整个⼯作流程的实际控制者,所以只有它知道哪个环节需要哪些组件。应⽤程序就像是演员,它只需要按照框架定制的规则注册这些组件就可以了,因为框架会在适当的时机字典加载并执⾏注册的组件。以熟悉的 Core MVC或者 MVC应⽤开发来说,我们只需要按照约定规则(⽐如⽬录结构和命名等)定义相应的Controller类型和View⽂件就可以了。当 (Core )MVC框架在进⾏处理请求的过程中,它会根据解析⽣成的路由参数定义为对应的Controller类型,并按照预定义的规则找到我们定义的Controller,然后⾃动创建并执⾏它。如果定义在当前Action⽅法需要呈现⼀个View,框架⾃⾝会根据预定义的⽬录约定找到我们定义的View⽂件,并对它实施动态编译和执⾏。整个流程处处体现了“框架Call应⽤”的好莱坞法则。总的来说,我们在⼀个框架的基础上进⾏应⽤开发,就相当于在⼀条调试好的流⽔线上⽣成某种商品,我们只需要在相应的环节准备对应的原材料,最终下线的就是我们希望得到的最终产品。IoC⼏乎是所有框架均具有的⼀个固有属性,从这个意义上讲,“IoC框架”的说法其实是错误的,世界上并没有什么IoC框架,或者说⼏乎所有的框架都是IoC框架。3.依赖注⼊(DI容器)IoC主要体现了这样⼀种设计思想:通过将⼀组通⽤流程的控制权从应⽤转移到框架中以实现对流程的复⽤,并按照“好莱坞法则”实现应⽤程序的代码与框架之间的交互。我们可以采⽤若⼲设计模式以不同的⽅式实现IoCDI是⼀种“对象提供型”的设计模式,在这⾥我们将提供的对象统称为“服务”、“服务对象”或者“服务实例”。在⼀个采⽤DI的应⽤中,在定义某个服务类型的时候,我们直接将依赖的服务采⽤相应的⽅式注⼊进来。按照“⾯向接⼝编程”的原则,被注⼊的最好是依赖服务的接⼝⽽⾮实现。在应⽤启动的时候,我们会对所需的服务进⾏全局注册。服务⼀般都是针对接⼝进⾏注册的,服务注册信息的核⼼⽬的是为了在后续消费过程中能够根据接⼝创建或者提供对应的服务实例。按照“好莱坞法则”,应⽤只需要定义好所需的服务,服务实例的激活和调⽤则完全交给框架来完成,⽽框架则会采⽤⼀个独⽴的“容器(Container)”来提供所需的每⼀个服务实例。我们将这个被框架⽤来提供服务的容器称为“DI容器”,也由很多⼈将其称为“IoC容器”,根据我们在《控制反转》针对IoC的介绍,我不认为后者是⼀个合理的称谓。DI容器之所以能够按照我们希望的⽅式来提供所需的服务是因为该容器是根据服务注册信息来创建的,服务注册了包含提供所需服务实例的所有信息。以Autofac框架为列

框架特性1,灵活的组件实例化:Autofac⽀持⾃动装配,给定的组件类型Autofac⾃动选择使⽤构造函数注⼊或者属性注⼊,Autofac还可以基于lambda表达式创建实例,这使得容器⾮常灵活,很容易和其他的组件集成。2,资源管理的可视性:基于依赖注⼊容器构建的应⽤程序的动态性,意味着什么时候应该处理那些资源有点困难。Autofac通过容器来跟踪组件的资源管理。对于不需要清理的对象,例如,我们调⽤ExternallyOwned()⽅法告诉容器不⽤清理。细粒度的组件⽣命周期管理:应⽤程序中通常可以存在⼀个应⽤程序范围的容器实例,在应⽤程序中还存在⼤量的⼀个请求的范围的对象,例如⼀个HTTP请求,⼀个IIS⼯作者线程或者⽤户的会话结束时结束。通过嵌套的容器实例和对象的作⽤域使得资源的可视化。3,Autofac的设计上⾮常务实,这⽅⾯更多是为我们这些容器的使⽤者考虑:●组件侵⼊性为零:组件不需要去引⽤Autofac。●灵活的模块化系统:通过模块化组织你的程序,应⽤程序不⽤纠缠于复杂的XML配置系统或者是配置参数。●⾃动装配:可以是⽤lambda表达式注册你的组件,autofac会根据需要选择构造函数或者属性注⼊●XML配置⽂件的⽀持:XML配置⽂件过度使⽤时很丑陋,但是在发布的时候通常⾮常有⽤(1).属性注⼊er(c => new A { B = e() });为了⽀持循环依赖,使⽤激活的事件处理程序:er(c => new A()).OnActivated(e => ce.B = e());如果是⼀个反射组件,使⽤PropertiesAutowired()修改注册属性:erType().PropertiesAutowired();如果你有⼀个特定的属性和值需要连接,你可以使⽤WithProperty()修改:erType().WithProperty("PropertyName", propertyValue);(2).⽅法注⼊调⽤⼀个⽅法来设置⼀个组件的值的最简单的⽅法是,使⽤⼀个lambda表达式组件和正确的调⽤激活处理⽅法。er(c => { var result = new MyObjectType(); var dep = e(); Dependency(dep); return result;});如果你不能使⽤⼀个lambda表达式注册,你可以添加⼀个激活事件处理程序(activating event handler)builder .Register() .OnActivating(e => { var dep = e(); Dependency(dep); });(3).构造函数注⼊ // 创建你的builder var builder = new ContainerBuilder(); // 通常你只关⼼这个接⼝的⼀个实现 erType().As(); // 当然,如果你想要全部的服务(不常⽤),可以这么写: erType().AsSelf().As(); ⽣命周期AutoFac中的⽣命周期概念⾮常重要,AutoFac也提供了强⼤的⽣命周期管理的能⼒。AutoFac定义了三种⽣命周期:Per DependencySingle InstancePer Lifetime ScopePer Dependency为默认的⽣命周期,也被称为’transient’或’factory’,其实就是每次请求都创建⼀个新的对象[Fact] public void per_dependency() { var builder = new ContainerBuilder(); erType().InstancePerDependency(); IContainer container = (); var myClass1 = e(); var myClass2 = e(); al(myClass1,myClass2); }Single Instance也很好理解,就是每次都⽤同⼀个对象

[Fact] public void single_instance() { var builder = new ContainerBuilder(); erType().SingleInstance();

IContainer container = (); var myClass1 = e(); var myClass2 = e();

(myClass1,myClass2); }Per Lifetime Scope,同⼀个Lifetime⽣成的对象是同⼀个实例

[Fact] public void per_lifetime_scope() { var builder = new ContainerBuilder(); erType().InstancePerLifetimeScope();

IContainer container = (); var myClass1 = e(); var myClass2 = e();

ILifetimeScope inner = ifetimeScope(); var myClass3 = e(); var myClass4 = e();

(myClass1,myClass2); al(myClass2,myClass3); (myClass3,myClass4); }[Fact] public void life_time_and_dispose() { var builder = new ContainerBuilder(); erType(); using (IContainer container = ()) { var outInstance = e(new NamedParameter("name", "out")); using(var inner = ifetimeScope()) { var inInstance = e(new NamedParameter("name", "in")); }//inInstance dispose here }//out dispose here } 4.过滤器(转:)下图展⽰了 Core MVC框架默认实现的过滤器的执⾏顺序:Authorization Filters:⾝份验证过滤器,处在整个过滤器通道的最顶层。对应的类型为: 对应的接⼝有同步和异步两个版本: 、 urce Filters:资源过滤器。因为所有的请求和响应都将经过这个过滤器,所以在这⼀层可以实现类似缓存的功能。对应的接⼝有同步和异步两个版本: 、 on Filters:⽅法过滤器。在控制器的Action⽅法执⾏之前和之后被调⽤,⼀个很常⽤的过滤器。对应的接⼝有同步和异步两个版本: 、 ption Filters:异常过滤器。当Action⽅法执⾏过程中出现了未处理的异常,将会进⼊这个过滤器进⾏统⼀处理,也是⼀个很常⽤的过滤器。对应的接⼝有同步和异步两个版本: 、 lt Filters:返回值过滤器。当Action⽅法执⾏完成的结果在组装或者序列化前后被调⽤。对应的接⼝有同步和异步两个版本: 、 5.中间件(转) Core,管道模型流程图IHttpModule和IHttpHandler不复存在,取⽽代之的是⼀个个中间件(Middleware)。Server将接收到的请求直接向后传递,依次经过每⼀个中间件进⾏处理,然后由最后⼀个中间件处理并⽣成响应内容后回传,再反向依次经过每个中间件,直到由Server发送出去。中间件就像⼀层⼀层的“滤⽹”,过滤所有的请求和相应。这⼀设计⾮常适⽤于“请求-响应”这样的场景——消息从管道头流⼊最后反向流出。接下来将演⽰在 Core⾥如何实现中间件功能。 IHttpModule和IHttpHandler不复存在,取⽽代之的是⼀个个中间件(Middleware)。Server将接收到的请求直接向后传递,依次经过每⼀个中间件进⾏处理,然后由最后⼀个中间件处理并⽣成响应内容后回传,再反向依次经过每个中间件,直到由Server发送出去。中间件就像⼀层⼀层的“滤⽹”,过滤所有的请求和相应。这⼀设计⾮常适⽤于“请求-响应”这样的场景——消息从管道头流⼊最后反向流出。接下来将演⽰在 Core⾥如何实现中间件功能。Middleware Middleware⽀持Run、Use和Map三种⽅法进⾏注册,下⾯将展⽰每⼀种⽅法的使⽤⽅式。 Run⽅法所有需要实现的⾃定义管道都要在 的 Configure ⽅法⾥添加注册。public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async context => { await sync("Hello World!"); }); // 添加MVC中间件 //(); }启动调试,访问地址 localhost:5000/ ,页⾯显⽰Hello World!字样再次添加⼀个Run⽅法public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async context => { await sync("Hello World!"); }); (async context => { await sync("Hello World too!"); }); // 添加MVC中间件 //(); }启动调试,再次访问发现页⾯上只有Hello World!字样。原因是:Run的这种⽤法表⽰注册的此中间件为管道内的最后⼀个中间件,由它处理完请求后直接返回。Use⽅法

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async (context, next) => { await sync("Hello World!"); }); // 添加MVC中间件 //(); }启动调试,访问页⾯同样显⽰Hello World!字样。我们发现使⽤Use⽅法替代Run⽅法,⼀样可以实现同样的功能。再次添加⼀个Use⽅法,将原来的Use⽅法内容稍作调整,尝试实现页⾯显⽰两个Hello World!字样。public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 (async (context, next) => { await sync("Hello World!"); await next(); }); (async (context, next) => { await sync("Hello World too!"); }); // 添加MVC中间件 //(); }将两个Use⽅法换个顺序,稍微调整⼀下内容,再次启动调试,访问页⾯,发现字样输出顺序也发⽣了变化。public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g();

// 添加⾃定义中间件 (async (context, next) => { await sync("Hello World too!"); await next(); }); (async (context, next) => { await sync("Hello World!"); }); // 添加MVC中间件 //(); }从上⾯的例⼦可以发现,通过Use⽅法注册的中间件,如果不调⽤next⽅法,效果等同于Run⽅法。当调⽤next⽅法后,此中间件处理完后将请求传递下去,由后续的中间件继续处理。当注册中间件顺序不⼀样时,处理的顺序也不⼀样,这⼀点很重要,当注册的⾃定义中间件数量较多时,需要考虑哪些中间件先处理请求,哪些中间件后处理请求。另外,我们可以将中间件单独写成独⽴的类,通过UseMiddleware⽅法同样可以完成注册。下⾯将通过独⽴的中间件类重写上⾯的演⽰功能。新建两个中间件类: 、 g ;using ;namespace wares{ public class HelloworldMiddleware { private readonly RequestDelegate _next; public HelloworldMiddleware(RequestDelegate next){ _next = next; } public async Task Invoke(HttpContext context){ await sync("Hello World!"); await _next(context); } }}g ;using ;namespace wares{ public class HelloworldTooMiddleware { private readonly RequestDelegate _next; public HelloworldTooMiddleware(RequestDelegate next){ _next = next; } public async Task Invoke(HttpContext context){ await sync("Hello World too!"); } }}修改 的Configure⽅法内容public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 dleware(); dleware(); // 添加MVC中间件 //(); }启动调试,访问页⾯,可以看到同样的效果。Map⽅法Map⽅法主要通过请求路径和其他⾃定义条件过滤来指定注册的中间件,看起来更像⼀个路由。修改 的Configure⽅法内容,增加静态⽅法MapTestpublic void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 ("/test", MapTest); // 添加MVC中间件 //(); } private static void MapTest(IApplicationBuilder app){ (async context => { await sync("Url is " + ng()); }); }启动调试,访问路径 localhost:5000/test ,页⾯显⽰如下内容

但是访问其他路径时,页⾯没有内容显⽰。从这个可以看到,Map⽅法通过类似路由的机制,将特定的Url地址请求引导到固定的⽅法⾥,由特定的中间件处理。另外,Map⽅法还可以实现多级Url“路由”,其实就是Map⽅法的嵌套使⽤// 添加⾃定义中间件 ("/level1", lv1App => { ("/level1.1", lv11App => { // /level1/level1.1 });

("/level1.2", lv12App => { // /level1/level1.2 }); });也可以通过MapWhen⽅法使⽤⾃定义条件进⾏“路由”public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { // 添加⽇志⽀持 sole(); ug();

// 添加NLog⽇志⽀持 g(); // 添加⾃定义中间件 n(context => { return nsKey("a"); }, MapTest); // 添加MVC中间件 //(); } private static void MapTest(IApplicationBuilder app) { (async context => { await sync($"Url is {ng()}{}"); }); }启动调试,访问路径 localhost:5000/path?a=1&b=2 ,页⾯显⽰如下内容只有当请求参数中含有a时,页⾯才正常显⽰内容。其他内置的中间件 Core框架内置了⼏个中间件 6.路由(转)定义/* * API 定义如下 * GET api/menu 获取菜单列表 * POST api/menu 添加模块 * PUT api/menu 修改模块 * PATCH api/menu 修改菜单信息 * DELETE api/menu 删除模块 */2.后台代码 :MenuModelContext _Context = new MenuModelContext();///

/// 获取列表/// /// [HttpGet]public IEnumerable Get(){ List list = _(); return list;}/// /// 添加模块对象/// /// /// [HttpPost]public bool Add(Model m){ try { e = ; _(m); _anges(); return true; } catch (Exception ex) { return false; }}/// /// 修改模块/// /// /// [HttpPut]public bool Update(Model m){ try { Model oldModel = _(d); ame = ame; mber = mber; _anges(); return true; } catch (Exception ex) { return false; }}/// /// 修改菜单对象/// /// /// [HttpPatch]public IActionResult Update(Menu m){ try { Menu oldMenu = _(); if (oldMenu == null) return NotFound("你要访问的菜单不存在或已经删除"); me = me; mber = mber; d = d; _anges(); return Ok(); } catch (Exception ex) { return Content(e); }}/// /// 删除模块/// /// /// [HttpDelete]public IActionResult Delete(string ids){ try { if (OrEmpty(ids)) throw new Exception("获取id参数失败"); List idList = (',').Select(q => 32(q)).ToList(); List list = _(q => ns(d)).ToList(); _Range(list); int count = _anges(); //使⽤OkObjectResult 前台jquery⾃动解析为object对象,不需要进⾏反序列化处理 //返回的不是字符串 return Ok(new { msg = $"删除成功,总共删除{count}条数据" }); } catch (Exception ex) { //使⽤ContentResult返回字符串处理 return Content(e); }} ajax代码//Get获取列表数据function testOne() { $.get(('menu'), {}, function (data) { (data); var menu1 = new Vue({ el: '#menu1', data: { result: data } }); })}testOne();//添加菜单var example2 = new Vue({ el: '#example-2', data: { name:'添加菜单' }, //在 methods 对象中定义⽅法 methods: { addMenu: function (event) { //使⽤ Post提交添加菜单 var _this = this; = '正在提交...'; $.post(('menu'), { ModelName: '测试菜单5', SortNumber:5 }, function (data) { (data); _ = '提交成功'; }); }, updateModel: function (event) { //使⽤put提交修改模块 var _this = this; $.ajax({ url: ('menu'), type: 'put', data: { ModelId: '4', ModelName: '模块abc', SortNumber: 4 }, success: function (data) { (data); if (data == true) alert('修改成功'); else alert('修改失败'); } }); } }});//修改菜单、删除模块var btngroup1 = new Vue({ el: '#btngroup1', data: { name: '修改菜单', name1: '删除模块' }, methods: { updateMenu: function (e) { var _this = this; //使⽤patch ⽅式修改菜单 $.ajax({ url: ('menu'), type:'patch', data: { MenuID: 1, MenuName: '测试菜单One', SortNumber: 100, ModelID:2 }, success: function (data) { (data); } }); }, deleteMenu: function (e) { //使⽤delete 提交⽅式删除模块 $.ajax({ url: ('menu'), type: 'delete', data: { ids:[1003] }, success: function (data) { (data); }, error: function (data) { (data); } }); } }});根据HttpMethod的Template路由⽰例定义/** API 定义* GET api/menu/{id} 获取指定ID的菜单对象* GET api/menu/getmodel 获取模块列表内容*/[HttpGet("{id}")]public IActionResult Get(int ID){ Menu m = _(ID); if (m == null) return NotFound(); return Json(m);}//特别说明:路由中的Template的值不可以包含斜杠/[HttpGet("getmodel")]

public IActionResult GetModel(){ List list = _(); return Json(list);}Jquery 的ajax代码//其他Get⽅式测试var btngroup2 = new Vue({ el: '#btngroup2', data: { name: '获取菜单对象', name1: '获取模块列表', item: {} //Vue的对象绑定,没有的情况下需要⼀个空对象,不然报错 }, methods: { getMenu: function (e) { var _this = this; //链接地址格式 :localhost:50000/api/menu/1/ $.get(('menu','1'), { }, function (data) { (data); _ = data; }); }, getModel: function (e) { var _this = this; $.get(('menu', 'getmodel'), {}, function (data) { (data); }) } }}); p ///

/// 此⽅法由运⾏时调⽤。使⽤此⽅法向容器添加服务。 /// /// For more information on how to configure your application, visit /fwlink/?LinkID=398940 /// DI容器 public IServiceProvider ConfigureServices(IServiceCollection services) { //添加MVC ( // 配置异常过滤器 config => { (typeof(CustomExceptionFilter)); } ) // 设置json序列化⽅式 .AddJsonOptions(mvcJsonOptions => { //忽略循环引⽤ nceLoopHandling = ; //不使⽤驼峰样式的key ctResolver = new DefaultContractResolver(); //设置时间格式 rmatString = ddHHmmss; }) .SetCompatibilityVersion(n_2_1); s(options => { icy("AllowSpecificOrigins", builder => { nyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); // 注册服务并且实例化AutoFac替换默认容器 var containerBuilder = new ContainerBuilder(); //实例化 AutoFac 容器

// 注册⽤户服务 erType().As(); te(services); ApplicationContainer = (); return new AutofacServiceProvider(ApplicationContainer); //第三⽅IOC接管 core内置DI容器 } ///

/// 此⽅法由运⾏时调⽤。使⽤此⽅法配置HTTP请求管道。 /// /// app /// env public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //if (lopment()) //{ // eloperExceptionPage(); //} //eptionHandler( // options => { // ( // async context => // { // Code = (int); // tType = "text/html"; // var ex = (); // if (ex != null) // { // var err = $"

Error: {e}

{race }"; // await sync(err).ConfigureAwait(false); // } // }); // } //); //启动跨域 s("AllowSpecificOrigins"); //注⼊MVC路由 (); }