POCO Controller 你这么厉害,ASP.NET vNext 知道吗?

简介:

POCO 是什么?

POCO(Plain Old C#/CLR Object),意为:纯老式的 C#/CLR 对象,也可以称为简单的 C#/CLR 对象,POCO 的概念来自于 POJO(Plain Old Java Object),POJO 的内在含义是指那些没有从任何类继承、也没有实现任何接口,更没有被其它框架侵入的 C# 对象。

1、为什么会有 POJO?   

  主要是 Java 的开发者被 EJB 的繁杂搞怕了,大家经过反思,又回归“纯洁老式”的 JavaBean,即有无参构造函数,每个字段都有 getter 和 setter 的 java 类。

2、POJO 的意义

  POJO 让开发者可专注于业务逻辑和脱离框架的单元测试。除此之外, 由于 POJO 并不须要继承框架的类或实现其接口,开发者能够极其灵活地搭建继承结构和建造应用。

  POJO 的意义就在于它的简单而灵活性,因为它的简单和灵活,使得 POJO 能够任意扩展,从而胜任多个场合,也就让一个模型贯穿多个层成为现实。先写一个核心 POJO,然后实现业务逻辑接口和持久化接口,就成了 Domain Model; UI 需要使用时,就实现数据绑定接口,变成 VO(View Object)。

3、POJO 与 PO、VO 的区别

  • POJO 是指简单 java 对象(Plain Old Java Objects、pure old java object 或者 plain ordinary java object)。
  • PO 是指持久对象(persistant object持久对象)。
  • VO 是指值对象或者 View 对象(Value Object、View Object)。注意,本文的 VO 特指 View Object。

  持久对象实际上必须对应数据库中的 entity,所以和 POJO 有所区别。比如说 POJO 是由 new 创建,由 GC 回收。但是持久对象是 insert 数据库创建,由数据库 delete 删除的。基本上持久对象生命周期和数据库密切相关。另外持久对象往往只能存在一个数据库 Connection 之中,Connnection 关闭以后,持久对象就不存在了,而 POJO 只要不被 GC 回收,总是存在的。

  由于存在诸多差别,因此持久对象 PO(Persistent Object)在代码上肯定和 POJO 不同,起码 PO 相对于 POJO 会增加一些用来管理数据库 entity 状态的属性和方法。而 ORM 追求的目标就是要 PO 在使用上尽量和 POJO 一致,对于程序员来说,他们可以把 PO 当做 POJO 来用,而感觉不到 PO 的存在。

4、POJO 的扩展

  POJO 仅包含最简单的字段属性,没有多余的东西,它本质上就是一个普通的 JavaBean。但是在 POJO 的基础上,能够扩展出不同的对象。

  • 为 POJO 增加了持久化的方法(Insert、Update、Delete……)之后,POJO 就变成了 PO。
  • 为 POJO 增加了数据绑定功能之后,POJO 就变成了 View Object,即 UI Model。
  • 为 POJO 增加业务逻辑的方法(比如单据审核、转帐……)之后,POJO 就变成了 Domain Model。
  • POJO 还可以当作 DTO 使用。

注:以上内容来自(http://kb.cnblogs.com/page/89750/

5、POCO VS DTO

  理解了上面关于 POCO 的概念理解,如果对 DDD(领域驱动设计)有所了解,就会发现 POCO 和 DTO 某种意义上很相似,上面关于 POCO 的扩展也有指出:POCO 可以当做 DTO 使用,但也只是某种意义上的,其实有很大的区别,可以参考:http://stackoverflow.com/questions/725348/poco-vs-dto,我理解的 POCO,就像原始人类一样,它可以演化成现在的各个人种,比如白种人、黑种人和黄种人(PO、UI Model、Domain Model等等)。

  虽然 POCO 为原始人类,但是也包含(不必须)人类的状态或行为,只是这种状态或行为是最原始的,比如狩猎为生、吃生食物等等,DTO(Data Transfer Objec)是什么?关于 DTO 的概念可以参考:http://www.cnblogs.com/xishuai/p/3691787.html,DTO 是领域驱动设计中的概念,只是数据传输对象,不包含任何的行为和状态,我个人觉得和 POCO 不是一个概念里面的东西,无法进行比较。

  如果非要对 DTO 和 POCO 进行区别的话,上面把 POCO 看做为原始人类,那 DTO 可以看做是原始人类的标本,他们俩只是长得比较像,仅此而已。

Controller 是什么?

  在 ASP.NET MVC 中 Controller(控制器)的职责是:获取 Model 数据并将 Model 传递给 View 对象,通知 View 对象显示,关于 Controller 概念的官方说明:

Controllers:Controllers are the components that handle user interaction, work with the model, and ultimately select a view to render that displays UI. In an MVC application, the view only displays information; the controller handles and responds to user input and interaction. For example, the controller handles query-string values, and passes these values to the model, which in turn queries the database by using the values.

  可以把 Controller 看做是一个产品加工厂,Model 为原材料,View 为销售平台,原材料在产品加工厂经过一定的加工处理后,得到成型的产品,然后放在销售平台上进行展示销售,从这个比喻中可以看出 Controller 的作用,就是协调 Model 和 View,那 Routing(路由)是什么?可以看做是政府的宏观调控,用来决定加工生产什么产品?由哪家加工厂生产?这些工作都是 Routing 进行宏观调控的。

  为了进一步方便理解 Controller 在 ASP.NET MVC 所起到的作用,我们分析下 ASP.NET MVC 整个的处理流程,首先,用户通过 Web 浏览器向服务器发送一条 Url 请求,这个请求被 ASP.NET MVC 的路由映射系统截获。路由映射系统按照映射规则,解析出控制器名 ControllerName、ActionName和各个参数 Parameters,然后,找寻 Controllers 目录下的 ControllerNameController.cs 这个控制器类,默认情况下,系统总是找寻 Controllers 目录下的“控制器名+ Controller ”这么一个类,然后,找寻这个类下与 ActionName 同名的方法,找到后,将 Parameters 作为参数传给这个方法,而后 Action 方法开始执行,完成后返回相应视图,默认情况下,会返回 Views 目录下与 ControllerName 同名的目录下的与 ActionName 同名的 View 文件,并且将 ViewData 传递到视图。

  大致画了下 ASP.NET MVC 的处理流程:

  上图只是大致演示了下 ASP.NET MVC 的处理流程,更专业、详细请参照《Pro ASP.NET MVC Framework》作者 Steven Sanderson 的一张完整请求处理流程图(Request-Handling Pipeline Poster):

  当我们使用 VS2012 创建 MVC3/MVC4 的时候,如果把控制器中继承的 Controller 基类去掉,结果如下:

复制代码
 1 namespace MvcApplication1.Controllers
 2 {
 3     public class HomeController
 4     {
 5         //
 6         // GET: /Home/
 7         public string Index()
 8         {
 9             return "Hello World";
10         }
11     }
12 }
复制代码

  HomeController 的 IndexAction 方法中返回一段字符串,路由配置是完全正确的,运行结果会是怎样?你可能会猜到了,对,就是这样:

  结合上面关于 ASP.NET MVC 的处理流程,你会明白为什么会报“无法找到资源”的异常?而这些同样的代码或配置,在 ASP.NET vNext 中却可以正常使用,这也就是 POCO Controllers 特性的美妙之处。

  请接着往下看。

关于 POCO Controller

  理解了 POCO 和 Controller 概念,理解 POCO Controller 就不会那么困难了,网上关于 POCO Controller 特性的资料实在是少的可怜,MSDN 暂时未找到详细的说明,如果想要深入的研究,看来只有过段时间把 ASP.NET vNext 搞熟之后了,因为 ASP.NET vNext 支持 POCO Controller 特性,这样使得代码更加简洁,难道好处只是代码简洁吗?其实不然,因为 ASP.NET vNext 开源和支持内置依赖注入(DI),所以你可以研究它的源代码,在 Controller 中编写适合自己的控制器,这样使得你的 vNext 应用程序可扩展性或性能得到进一步的提升,如果可以的话,你甚至编写属于自己的一套 Controller,这一切都是从 POCO Controller 演化而来。

  其实如果往长远一点来想,我个人觉得 POCO Controller 并不简简单单是 ASP.NET vNext 的一种特性,它也代表着 ASP.NET vNext 的一种方向,因为跨平台、开源和依赖注入,使得一切都有可能,虽然 ASP.NET vNext 现在还未正式发布,POCO Controller 也仅仅是一个探路者,但是我觉得这个信号很重要,很重要,也希望大家可以接收得到。

POCO Controller 应用

1,识别 POCO Controller

  言归正题,因为 POCO Controllers 不从 Microsoft.AspNet.Mvc.Controller 基类派生,ASP.NET vNext 如何识别 POCO Controller?关于这个问题在 stackoverflow 中提问:“How Are POCO Controllers Discovered As Controllers?”,回答如下:

There are some conventions that we use to identify a POCO controller:

  • The assembly must reference MVC(必须引用 MVC)
  • The POCO controller class must have the suffix Controller(POCO controller 必须以“Controller”后缀结尾)

  必须引用 MVC 就是在 project.json 中添加如下如下引用:

2,简单 POCO Controller

  最简单的 POCO Controller

复制代码
 1 // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
 2 
 3 namespace PocoControllerDemo.Controllers
 4 {
 5     public class HelloWorldController
 6     {
 7         // GET: /<controller>/
 8         public string Index()
 9         {
10             return "Hello World";
11         }
12     }
13 }
复制代码

  注意 HelloWorldController 并没有继承 Controller,这种写法在 ASP.NET vNext 是可以的,因为未涉及到 Controller 的核心操作如(View、Url、Action等),只是返回一个字符串(内置依赖注入映射默认配置),首先我们看下 DefaultControllerFactory 中的源码:

复制代码
 1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
 2 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 3 
 4 using System;
 5 using Microsoft.AspNet.Mvc.Core;
 6 using Microsoft.AspNet.Mvc.ModelBinding;
 7 using Microsoft.Framework.DependencyInjection;
 8 
 9 namespace Microsoft.AspNet.Mvc
10 {
11     public class DefaultControllerFactory : IControllerFactory
12     {
13         private readonly ITypeActivator _activator;
14         private readonly IServiceProvider _serviceProvider;
15 
16         public DefaultControllerFactory(IServiceProvider serviceProvider, ITypeActivator activator)
17         {
18             _serviceProvider = serviceProvider;
19             _activator = activator;
20         }
21 
22         public object CreateController(ActionContext actionContext)
23         {
24             var actionDescriptor = actionContext.ActionDescriptor as ReflectedActionDescriptor;
25             if (actionDescriptor == null)
26             {
27                 throw new ArgumentException(
28                     Resources.FormatDefaultControllerFactory_ActionDescriptorMustBeReflected(
29                         typeof(ReflectedActionDescriptor)),
30                     "actionContext");
31             }
32 
33             var controller = _activator.CreateInstance(
34                 _serviceProvider,
35                 actionDescriptor.ControllerDescriptor.ControllerTypeInfo.AsType());
36 
37             InitializeController(controller, actionContext);
38 
39             return controller;
40         }
41 
42         public void ReleaseController(object controller)
43         {
44             var disposableController = controller as IDisposable;
45 
46             if (disposableController != null)
47             {
48                 disposableController.Dispose();
49             }
50         }
51 
52         private void InitializeController(object controller, ActionContext actionContext)
53         {
54             Injector.InjectProperty(controller, "ActionContext", actionContext);
55 
56             var viewData = new ViewDataDictionary(
57                 _serviceProvider.GetService<IModelMetadataProvider>(),
58                 actionContext.ModelState);
59             Injector.InjectProperty(controller, "ViewData", viewData);
60 
61             var urlHelper = _serviceProvider.GetService<IUrlHelper>();
62             Injector.InjectProperty(controller, "Url", urlHelper);
63 
64             Injector.CallInitializer(controller, _serviceProvider);
65         }
66     }
67 }
复制代码

3,IActionResultHelper

  InitializeController 方法,用来初始化控制器,可以看出,如果我们没有添加任何的配置操作,ASP.NET vNext 会通过内置的 IoC 容器注入默认的类型映射,比如上面的 HelloWorldController。主要包含三个类型:ActionContext、ViewDataDictionary 和 IUrlHelper,从单词上可以简单看出这三种类型所包含的意义,ViewDataDictionary 视图操作的数据字典,这个在控制器返回 View 的时候会用到,这个后面有说明,我们看下在 MSDN 中关于 POCO Controller 的简单示例,项目地址:http://www.asp.net/vnext/overview/aspnet-vnext/overview,示例代码:

复制代码
 1 public class HomeController
 2 {
 3     // Helpers can be dependency injected into the controller
 4     public HomeController(IActionResultHelper resultHelper)
 5     {
 6         Result = resultHelper;
 7     }
 8 
 9     private IActionResultHelper Result { get; set; }
10 
11     public ActionResult Index()
12     {
13         return Result.Json(new { message = "Poco controllers!" });
14     }
15 }
复制代码

  执行结果:

  代码中主要对 IActionResultHelper 进行构造函数注入,你可以实现属于自己的 IActionResultHelper 实例,使用 ASP.NET vNext 内置IoC容器进行依赖注入,IActionResultHelper 是什么?IActionResultHelper is a helper for creating action results。就是说 IActionResultHelper 用来创建 Action 结果的契约,Index 方法中返回一个 Json 格式数据,返回类型为ActionResult,Result.Json 也就是 IActionResultHelper.Json,也就是说所有的一切都表明,你可以创建属于自己的 IActionResultHelper,包含自定义的 IActionResultHelper.Json 类型格式。

  以下是在 MSDN 中找到仅存的关于 POCO Controller 的说明:

The Controller class also gives you convenient access to the HTTP request context, the current principal (IPrincipal), a view bag, and other useful properties. So the Controller class is certainly useful, but it’s not required. For a light-weight controller, you might prefer a POCO controller.

4,IModelMetadataProvider、ViewDataDictionary

  以上是有关 Action 的操作,如果涉及到 View 操作,又是怎么实现?MSDN 太坑爹了,都没有详细说明,有位国外哥们和我遇到一样的问题了,详细地址:http://geekswithblogs.net/stun/archive/2014/06/04/asp.net-vnext-blog-post-series.aspx,以下来自这位国外哥们的描述:

Today, I want to start this blog post series with a teaser code snippet for those developers familiar with the ASP.NET MVC.
Getting Started with ASP.NET MVC 6 article from ASP.NET website shows how to write a lightweight POCO (plain-old CLR object) MVC Controller class in the upcoming ASP.NET MVC 6.
However, it doesn't show us how to use the IActionResultHelper interface to render a View.
This is how I wrote my POCO MVC Controller based on the https://github.com/aspnet/Home/blob/master/samples/HelloMvc/Controllers/HomeController.cs sample from Github.

  虽然在 MSDN 中关于 View 操作没有相关说明,但是这位国外哥们搞出来了,不服不行啊,所谓前人种树,后人乘凉,来看一下这位哥们的解决方案:

复制代码
 1 using Microsoft.AspNet.Mvc;
 2 using Microsoft.AspNet.Mvc.ModelBinding;
 3 using MvcSample.Web.Models;
 4 
 5 namespace MvcSample.Web
 6 {
 7     public class HomeController
 8     {
 9         IActionResultHelper html;
10         IModelMetadataProvider mmp;
11 
12         public HomeController(IActionResultHelper h, IModelMetadataProvider mmp)
13         {
14             this.html = h;
15             this.mmp = mmp;
16         }
17 
18         public IActionResult Index()
19         {
20             var viewData = new ViewDataDictionary<User>(mmp) { Model = User() };
21             return html.View("Index", viewData);
22         }
23 
24         public User User()
25         {
26             return new User { Name = "My name", Address = "My address" };
27         }
28     }
29 }
复制代码

  可以看出除了 IActionResultHelper 类型映射,还包含 IModelMetadataProvider 类型,关于 IModelMetadataProvider 可以从 DefaultControllerFactory 源码中得到一些信息,在创建 ViewDataDictionary 实例的时候,需要传入 IModelMetadataProvider 类型的参数,也就是代码中的 new ViewDataDictionary<User>(mmp) { Model = User() }; IModelMetadataProvider 需要引入 using Microsoft.AspNet.Mvc.ModelBinding,关于 IModelMetadataProvider 定义,请参照:

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Microsoft.AspNet.Mvc.ModelBinding
 5 {
 6     public interface IModelMetadataProvider
 7     {
 8         IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType);
 9         ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName);
10         ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType);
11     }
12 }
复制代码

  html.View("Index", viewData); 使用方式就像我们平常向 View 传入数据一样,如果不传入数据,但是也要传入 ViewDataDictionary 类型参数,修改 HelloWorldController 控制器代码:

复制代码
 1 using Microsoft.AspNet.Mvc;
 2 using Microsoft.AspNet.Mvc.ModelBinding;
 3 
 4 // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
 5 
 6 namespace PocoControllerDemo.Controllers
 7 {
 8     public class HelloWorldController
 9     {
10         private IActionResultHelper actionResultHelper { get; set; }
11         private IModelMetadataProvider modelMetadataProvider { get; set; }
12         // Helpers can be dependency injected into the controller
13         public HelloWorldController(IActionResultHelper resultHelper, IModelMetadataProvider metadataProvider)
14         {
15             actionResultHelper = resultHelper;
16             modelMetadataProvider = metadataProvider;
17         }
18         // GET: /<controller>/
19         public ActionResult Index()
20         {
21             //return Result.Json(new { message = "Poco controllers!" });
22             var viewData = new ViewDataDictionary(modelMetadataProvider);
23             return actionResultHelper.View("Index", viewData);
24         }
25     }
26 }
复制代码

  Index.cshtml 视图中只有 html 代码:“<h1>Hello World</h1>”,运行结果:

5,IActionDiscoveryConventions、IControllerDescriptorFactory

  既然 POCO Controller 都这么厉害了(以上内容),还能不能来点更厉害的呢?当然会有,比如扩展 EndpointActionDiscoveryConventions 和IControllerDescriptorFactory 使得控制器并不需要以“Controller”名称结尾,可以按照自己的喜好创建配置结束符,虽然功能作用不是很大,但是这些操作在 MVC3/MVC4 中连想都不敢想,也从另一方便看出 POCO Controller 可扩展型是多么的强大,怎么实现?

  首先创建 EndpointActionDiscoveryConventions 类,继承 DefaultActionDiscoveryConventions(IActionDiscoveryConventions 的默认派生类),在 IActionDiscoveryConventions 接口定义中有个重要的虚方法:IsController,参数类型为:TypeInfo,用来判断当前 typeInfo 是否为 Controller?示例代码: 

复制代码
 1 using System;
 2 using System.Reflection;
 3 using Microsoft.AspNet.Mvc;
 4 
 5 namespace PocoControllerDemo
 6 {
 7     public class EndpointActionDiscoveryConventions : DefaultActionDiscoveryConventions
 8     {
 9         public override bool IsController(TypeInfo typeInfo)
10         {
11             var isController = base.IsController(typeInfo);
12             return isController || typeInfo.Name.EndsWith("Endpoint", StringComparison.OrdinalIgnoreCase);
13         }
14     }
15 }
复制代码

  可以看出 IsController 控制器判断,以“Endpoint”为结束符的 TypeInfo 返回 True,表示当前 TypeInfo 为 Controller。仅仅实现 Controller 判断还是不够的,还要对 IControllerDescriptorFactory 进行配置,示例代码:

复制代码
 1 using System;
 2 using System.Reflection;
 3 using Microsoft.AspNet.Mvc;
 4 
 5 namespace PocoControllerDemo
 6 {
 7     public class EndpointControllerDescriptorFactory : IControllerDescriptorFactory
 8     {
 9         public ControllerDescriptor CreateControllerDescriptor(TypeInfo type)
10         {
11             var descriptor = new ControllerDescriptor(type);
12 
13             if (descriptor.Name.EndsWith("Endpoint", StringComparison.Ordinal))
14             {
15                 var nameProp = descriptor.GetType().GetProperty("Name");
16                 var oldName = nameProp.GetValue(descriptor, null) as string;
17                 nameProp.SetValue(descriptor, oldName.ToLowerInvariant().Replace("endpoint", string.Empty));
18             }
19             return descriptor;
20         }
21     }
22 }
复制代码

  HelloWorldEndpoint 控制器代码:

复制代码
 1 using Microsoft.AspNet.Mvc;
 2 using Microsoft.AspNet.Mvc.ModelBinding;
 3 
 4 // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
 5 
 6 namespace PocoControllerDemo.Controllers
 7 {
 8     public class HelloWorldEndpoint
 9     {
10         private IActionResultHelper actionResultHelper { get; set; }
11         private IModelMetadataProvider modelMetadataProvider { get; set; }
12         // Helpers can be dependency injected into the controller
13         public HelloWorldEndpoint(IActionResultHelper resultHelper, IModelMetadataProvider metadataProvider)
14         {
15             actionResultHelper = resultHelper;
16             modelMetadataProvider = metadataProvider;
17         }
18         // GET: /<controller>/
19         public ActionResult Index()
20         {
21             //return Result.Json(new { message = "Poco controllers!" });
22             var viewData = new ViewDataDictionary(modelMetadataProvider);
23             return actionResultHelper.View("Index", viewData);
24         }
25     }
26 }
复制代码

  注意 HelloWorldEndpoint 并不是 HelloWorldController,Startup 添加 IActionDiscoveryConventions 和 IControllerDescriptorFactory 类型配置:

复制代码
1             app.UseServices(services =>
2             {
3                 services.AddMvc();
4                 services.AddTransient<IActionDiscoveryConventions, EndpointActionDiscoveryConventions>();
5                 services.AddTransient<IControllerDescriptorFactory, EndpointControllerDescriptorFactory>();
6             });
复制代码

  运行截图:



本文转自田园里的蟋蟀博客园博客,原文链接:http://www.cnblogs.com/xishuai/p/3789335.html,如需转载请自行联系原作者

相关文章
|
开发框架 IDE .NET
.NET 6 之 ABP vNext 初体验
本文将介绍在 .net6 平台的 asp.net core webapi 框架中,如何使用 abp vnext 框架进行模块化开发,重在思想理解。
610 1
.NET 6 之 ABP vNext 初体验
|
开发框架 前端开发 .NET
ASP.NET Core MVC 之控制器(Controller)
ASP.NET Core MVC 之控制器(Controller)  操作(action)和操作结果(action result)是 ASP.NET MVC 构建应用程序的一个基础部分。   在 ASP.NET MVC 中,控制器用于定义和聚合一组操作。
1255 0
|
前端开发 .NET API
如何解决Asp.Net MVC和WebAPI的Controller名称不能相同的问题
原文:如何解决Asp.Net MVC和WebAPI的Controller名称不能相同的问题 1、问题描述 假如有一个文章的业务(Article),我们在 Controllers文件夹中创建MVC Controller和Api Controller,各个Controller中都有相同的获取文章内容的Action(Content)。
1437 0
|
.NET API 开发框架
从零开始学习 asp.net core 2.1 web api 后端api基础框架(四)-创建Controller
原文:从零开始学习 asp.net core 2.1 web api 后端api基础框架(四)-创建Controller 版权声明:本文为博主原创文章,未经博主允许不得转载。
1218 0
|
JavaScript 前端开发 .NET
【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--请求处理函数装饰器注册篇(5/8)【controller+action】
文章目录 前情概要 上篇文章把action的注册讲完了,但是我们的处理函数没有指定可接受的httpmethod,也没有别名上面的。下面我们使用typescript的特性之一装饰器来实现一把这个特性。 在控制器和处理函数的注册篇中有说到的第三,第四个参数就在这里排上用场拉。
1099 0
|
缓存 JavaScript 前端开发
【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--控制器和处理函数的注册篇(4/8)【controller+action】
文章目录 前情概要 前边的文章把一些基本的前置任务都完成了。接下就是比较重要的处理函数action是如何自动发现和注册的拉,也就是入口函数RouteHandler(也是我们的第一个express中间件)里面的一些细节。
923 0