用基于模型和接口的T4来生成RESTful服务
\
关键要点
\
- 许多REST服务中都包含重复的模式; \
- 如果能自动生成这些模式相关的代码就可以节省很多时间; \
- Visual Studio的T4和EnvDTE具有强大的生成代码功能,不需要更多工具辅助; \
- 也可以用相似技术生成对WCF和数据库的调用;
\
在Visual Studio中,T4文本模板用文字和控制逻辑的混合物来生成文本文件。控制逻辑是用Visual C#或者Visual Basic语言写成的代码块。在Visual Studio 2015 Update 2及以后版本中,也可以在T4模板指令中使用C# V6.0的新功能。生成的文件可以是任意类型的文本,包括网页、资源文件、甚至是任何编程语言的源代码。在微软公司内部T4应用得很广泛。他们用它来生成MVC视图、控制器、EntityFramework上下文等等。\
对于那些想要根据已有的模式或模型生成代码,或者写最少的重复性代码的开发者来说,都可以尝试使用T4。我们可以用T4生成代码来简单地封装对业务逻辑或者任何其它服务的调用,也可以增加日志功能、实现缓存机制、基于某些模型来创建请求/响应类、甚至实现业务逻辑……等等。\
REST服务通常都被简单地作为业务逻辑的封装器,因此我们就可以使用T4来自动地为我们的接口和模型生成REST/WCF或者任意其它服务。这样就可以把开发者解放出来,让他们有更多的时间去专心处理用C#和SQL实现的业务逻辑。\
用例
\
假如我们准备开发一个简单的服务,来处理GET、批量GET、Insert和Update方法。产品实体包含下面这些属性:
public partial class Product\ {\ [Key]\ public int Id { get; set; }\ public string Name { get; set; }\ public string Number { get; set; }\ public int ProductGroupId { get; set; }\ public decimal? ListPrice { get; set; }\ public decimal? Size { get; set; }\ public decimal? Weight { get; set; }\\ public ProductGroup ProductGroup { get; set; }\ }\
\
需求要求可以通过产品的名字、数量、价格范围等方面来查找过滤产品。当插入一条产品记录时,我们会把除了Id和数量之外的所有属性都写上。这些全是自动生成的,所以我们也不能更改它们。为了提高性能,用户也可以指定是否需要添加ProductGroup对象。如果不需要联接操作或者隔离ProductGroup的查询,就可以不添加。\
为了帮助大家理解,我在这里画了一张图来展示我在这篇文章中要用到的架构:\
\\
批量GET方法
\
如上文所述,我们需要通过产品的名字、数量或价格范围等做过滤,这通常叫做“过滤类”或者“查询对象”,如果全用手工实现的话就太枯燥了。\
但通过T4、EnvDTE和模型的属性,我们也可以自动创建新的过滤类。比如,我们可以设置模型的如下属性:
public partial class Product\ {\ ...\ [Filter(FilterEnum.GreatherThanOrEqual)]\ public string Name { get; set; }\ [Filter(FilterEnum.Equal | FilterEnum.List)]\ public string Number { get; set; }\ [Filter(FilterEnum.GreatherThanOrEqual | FilterEnum.LowerThanOrEqual)]\ public decimal? ListPrice { get; set; }\ ...\ }\
\
使用T4可以自动生成包含这些属性的类:
public partial class ProductSearchObject : BaseSearchObject\u0026lt;ProductAdditionalSearchRequestData\u0026gt;\{\//some code ommited (private members and attributes)\ public virtual System.String NameGTE { get; set; }\ public virtual System.String Number { get; set; }\ public virtual IList\u0026lt;String\u0026gt; NumberList { get {return mNumberList;} set { mNumberList = value; }}\ public virtual System.Nullable\u0026lt;System.Decimal\u0026gt; ListPriceGTE { get; set; }\ public virtual System.Nullable\u0026lt;System.Decimal\u0026gt; ListPriceLTE { get; set; }\}\
\
要是使用EntityFramework的话,我们就可以轻松生成业务处理逻辑,就是包含基于这个查询对象和模型的LINQ查询。要这样做,首先要定义接口和准备使用的属性。比如:
[DefaultServiceBehaviour(DefaultImplementationEnum.EntityFramework, \"products\")]\ public interface IProductService : ICRUDService\u0026lt;Product, ProductSearchObject, ProductAdditionalSearchRequestData, ProductInsertRequest, ProductUpdateRequest\u0026gt;\ {\\ }\
\
做完这一步,T4就知道了默认的实现应该是怎样,然后就可以生成基于查询对象的检索逻辑了:
protected override void AddFilterFromGeneratedCode(ProductSearchObject search, ref System.Linq.IQueryable\u0026lt;Product\u0026gt; query)\ {\//call to partial method\ base.AddFilterFromGeneratedCode(search, ref query);\ if(!string.IsNullOrWhiteSpace(search.NameGTE))\ {\ query = query.Where(x =\u0026gt; x.Name.StartsWith(search.NameGTE));\ }\ if(!string.IsNullOrWhiteSpace(search.Number))\ {\ query = query.Where(x =\u0026gt; x.Number == search.Number);\ }\ if(search.NumberList != null \u0026amp;\u0026amp; search.NumberList.Count \u0026gt; 0)\ {\ query = query.Where(x =\u0026gt; search.NumberList.Contains(x.Number));\ }\ if(search.ListPriceGTE.HasValue)\ {\ query = query.Where(x =\u0026gt; x.ListPrice \u0026gt;= search.ListPriceGTE);\ }\ if(search.ListPriceLTE.HasValue)\ {\ query = query.Where(x =\u0026gt; x.ListPrice \u0026lt;= search.ListPriceLTE);\ }\ }\
\
可以把我们的默认实现注册到IoC框架中:
public partial class ServicesRegistration : IServicesRegistration\ {\ public int Priority {get; set; }\ public ServicesRegistration()\ {\ Priority = 0; //This is root, If you want to override this. Add new class with higher priority\ }\ public void Register(UnityContainer container)\ { container.RegisterType\u0026lt;IProductService,ProductService\u0026gt;(new HierarchicalLifetimeManager());\ }\ }\
\
如果用这种方法构建,我们还可以非常容易地用另一个更高优先级的类来重载这种注册过程,以此来替换这种实现。\
在生成REST API时,T4还会根据接口中的属性信息来决定要为哪些属性生成获取函数。比如,在IProductService接口中我们可以为相应属性这样添加函数:
[DefaultMethodBehaviour(BehaviourEnum.Get)]\PagedResult\u0026lt;TEntity\u0026gt; GetPage(TSearchObject search);\
\
既然我们知道了有哪些函数可以用于获取数据,我们就可以为REST服务生成代码了:
[RoutePrefix(\"products\")]\public partial class productsController : System.Web.Http.ApiController\{\ [Dependency]\ public IProductService Service { get; set; }\ [Route(\"\")]\ [ResponseType(typeof(PagedResult\u0026lt;Product\u0026gt;))]\ [HttpGet]\ public System.Web.Http.IHttpActionResult GetPage ([FromUri] ProductSearchObject search)\ {\ //call to partial method\ var result = Service.GetPage(search);\ return Ok(result);\ }\}\
\
如前文所述,我们希望客户端可以按需要请求ProductGroup这种附加信息,要具备这个功能,只要给ProductGroup属性加上[LazyLoading]指令就可以了。
public partial class Product\ {\ //ommited code\\ [LazyLoading]\ public ProductGroup ProductGroup { get; set; }\ }\
\
加上[LazyLoading]指令之后,T4就会给新创建的类中加上IsProductGroupLoadingEnabled变量。
public partial class ProductAdditionalSearchRequestData : A.Core.Model.BaseAdditionalSearchRequestData\{\ public virtual bool? IsProductGroupLoadingEnabled { get; set; }\}\
\
在底层使用EntityFramework会生成如下代码:
protected override void AddInclude(ProductSearchObject search, ref System.Linq.IQueryable\u0026lt;Product\u0026gt; query)\{\if(search.AdditionalData.IsProductGroupLoadingEnabled.HasValue \u0026amp;\u0026amp; search.AdditionalData.IsProductGroupLoadingEnabled == true)\{ search.AdditionalData.IncludeList.Add(\"ProductGroup\"); \ }\ base.AddInclude(search, ref query); //calls EF .Include method\}\
\
Insert方法
\
插入对象的属性列表常常与完整的模型不同。比如,那些自动生成的主键就不应该由客户端传入,因为它们应该被忽略掉。这个例子当然很明显,但还是有些字段会非常容易出问题。\
比如ProductGroup属性,如果我们把它也包含到插入对象之中,那大家就会有误解,以为客户端应该用这个函数调用去创建或者更新一个ProductGroup。所以最好是提供一个明确的插入对象,而不要重用完整模型。\
为了避免用重复性地手工劳动来创建这些代码,我们仍然可以用指令来要求它为我们生成需要的属性,比如:
[Entity]\ public partial class Product\ {\ [Key]\ public int Id { get; set; }\\ [Filter(FilterEnum.GreatherThanOrEqual)]\ [RequestField(\"Insert\")]\ public string Name { get; set; }\\ [Filter(FilterEnum.Equal | FilterEnum.List)]\ public string Number { get; set; }\\ [RequestField(\"Insert\")]\ public int ProductGroupId { get; set; }\\ [RequestField(\"Insert\")]\ [Filter(FilterEnum.GreatherThanOrEqual | FilterEnum.LowerThanOrEqual)]\ public decimal? ListPrice { get; set; }\\ [RequestField(\"Insert\")]\ public decimal? Size { get; set; }\\ [RequestField(\"Insert\")]\ public decimal? Weight { get; set; }\\ [LazyLoading]\ public ProductGroup ProductGroup { get; set; }\ }\
\
上面的信息可以生成下面的代码,即ProductInsertRequest类:
public partial class ProductInsertRequest\{\ public System.String Name { get; set; }\ public System.Int32 ProductGroupId { get; set; }\ public System.Nullable\u0026lt;System.Decimal\u0026gt; ListPrice { get; set; }\ public System.Nullable\u0026lt;System.Decimal\u0026gt; Size { get; set; }\ public System.Nullable\u0026lt;System.Decimal\u0026gt; Weight { get; set; }\}\
\
和以前一样,我们要修改一下接口,这样T4就知道哪些函数是负责处理插入请求的。我们可以为合适的函数加上属性,比如:
[DefaultMethodBehaviour(BehaviourEnum.Insert)]\ TEntity Insert(TInsert request, bool saveChanges = true);\
\
有了这些模型和接口的信息,T4就可以生成我们想要的REST API代码了:
[Route(\"\")]\ [ResponseType(typeof(Product))]\ [HttpPost]\ public HttpResponseMessage Insert([FromBody] ProductInsertRequest request)\ {\ var result = Service.Insert(request); \var response = Request.CreateResponse\u0026lt;Product\u0026gt;(HttpStatusCode.Created, result);\ return response;\ }\
\
Update方法
\
原理也和插入函数一样。在这里我们要为元组的属性加上[RequestField(\"Update\")]指令,这样就可以为ProductUpdateRequest生成合适的属性。然后再为相应的接口加上指令来让T4知道哪个函数是要处理Update的。\
加上这些指令后,T4就可以为REST服务生成更新数据的函数了:
[Route(\"{id}\")]\ [ResponseType(typeof(A.Core.Model.Product))]\ [HttpPut]\ public HttpResponseMessage Update([FromUri] Int32 id, [FromBody]ProductUpdateRequest request)\ {\ //can return \"Not Found\" if Update throws NotFoundException\ var result = Service.Update(id,request); \ var response = Request.CreateResponse\u0026lt;Product\u0026gt;(HttpStatusCode.OK, result);\ return response;\ }\
\
结论
\
从文中可以看出,我们可以用T4来生成代码,帮助我们节省很多写重复性代码的时间。生成的代码易读性也很好,和自己写的一样。用相同的办法,我们也可以生成代码来在服务级别缓存结果和增加日志功能。\
这种技术的另一种用途是同时生成REST和WCF服务代码,当你的客户端既要支持浏览器也要支持C#时这就很有用。\
在我的工作经历中,我曾经用T4和EnvDTE来为公司的项目生成完整的CRUD REST服务代码,包括数据库调用和单元测试等。几分钟就搞定了,而不是几小时。\
如果你想了解更多内容,可以通过GitHub,或者直接通过LinkedIn联系我。\
关于作者
\
Amel Musić的职业生涯早年间是为证券公司开发解决方案,主要是银行和政府部门,他的专长是基于MS SQL Server和.NET平台来开发和优化解决方案。在这些系统上做了几年设计之后,他开始学习使用T4和面向切面编程,这让他得以减少重复性工作,专心处理客户的业务需求。
\
阅读英文原文:Creating RESTful Services with T4 Based on Model and Interfaces
用基于模型和接口的T4来生成RESTful服务相关推荐
- Xilinx基于模型的设计工具—Model Composer
Model Composer 是一款基于模型的设计工具,不仅能够在 MathWorks Simulink® 环境中进行快速设计探索,而且还可通过自动代码生成加速基于 Xilinx 全可编程器件的生产. ...
- 面向航空航天工业领域的基于模型的仿真验证工具SkyEye
我们一直致力于提供给航空航天制造商一套全数字的优化方案--面向航空航天工业领域的基于模型的仿真验证工具SkyEye.我们的仿真验证技术可用于开发高复杂度和高保真度的模型,对发动机,飞机的飞控进行预测性 ...
- 「基于模型的系统工程」的发展历程
节选自<「基于模型的系统工程」的发展历程>,因篇幅有限,完整报告文末领取. 当下,人们热衷于讨论基于计算机的建模.模型.数据库和敏捷设计方法.然而,很少有人会耐心地审视和理解大量的技术创新 ...
- 《基于模型的软件开发》——1.2 结构化开发
本节书摘来自华章计算机<基于模型的软件开发>一书中的第1章,第1.2节,作者:[美]H. S.莱曼(H. S. Lahman)著, 更多章节内容可以访问云栖社区"华章计算机&qu ...
- 【腾讯TMQ】基于模型的自动化测试工具——GraphWalker
一.概述 GraphWalker就是一个基于测试模型的用例生成工具.它主要应用于FSM, EFSM模型.可以用来它可以直接读取FSM, EFSM图形模型.json模型.生成测试用例. 二.背景知识 要 ...
- 通过基于模型的系统工程简化复杂性案例研究
复杂的系统一贯存在,甚至是智慧超群的工程师也总是面临着制造业的重重考验.然而近几十年来,大多数公司必须应对的复杂性水平有了显著提高,且复杂性提高的速度也正在不断加快.例如自动驾驶汽车.自动驾驶城市飞行 ...
- Dedecms 5.7sp1文章模型栏目接口使用手册
Dedecms5.7sp1文章模型栏目接口使用手册 一.简介 1.本接口应用于Dedecms5.7SP1(20120621)版普通文章模型栏目文章发布: 2.由于数据量大时DEDE生成栏目HTML时的 ...
- 基于python的接口录制平台的设计与开发
背景: 要写论文,我选择的题目是"基于python的应用研究与开发",我打算把公司的hrun-接口录制平台,从设计,到技术分析,到代码实现,进行系统的总结下. (继续完善中...) ...
- MATLAB/SimulinkSTM32CubeMXKeil工具链完成基于模型的设计开发(一)
前言 入职一段时间了,需要不断充实自己的技能树,现在汽车行业控制器的开发大都基于"V"流程,采用基于模型开发的方式,此系列文章作为自己的学习记录 一.MBD概述 基于模型的设计(M ...
最新文章
- TensorFlow模型的签名推荐与快速上线\n
- MYSQL注入天书之order by后的injection
- 研发项目管理中需注意的人性弱点(Z)
- Arduino_esp32_WiFi代码
- 利用C#实现分布式数据库查询
- java-web前端 CSS
- P1155 双栈排序
- CentOS 7.2下ELK分析Nginx日志生产实战(高清多图)
- Intellij IDEA的配置
- opencv python 鼠标响应操作
- 我的凸优化学习之路(转)
- 代码管理学:遇到技术难题,要知道找谁
- python计算加权平均分
- 三个小李子讲述安卓动画用法
- 可能是因为该宏在此工作簿中不可用,或者所有的宏都被禁用
- 小白易学-ps印章制作图文教程+百余个视频教程,见者有份
- 深入理解Java虚拟机(周志明第三版)- 第十一章:后端编译与优化
- 精确率、准确率、召回率
- leetcode报错:reference binding to misaligned address 0xbebebebebebec0ba for type ‘int‘, which requir 4
- 解码阿里健康财报背后的“阵形”变化
热门文章
- linux 类似winscp_mac如何连接远程linux,类似windows上的putty和winscp
- android 读取内部存储文件格式,Android中的数据储存之文件存储
- 5单个编译总会编译全部_5分钟读懂JavaScript预编译
- python空值填充_pandas | DataFrame基础运算以及空值填充
- multipart request_Request和Response
- Java程序员三年的工作经验,却不如一个新人的工资高???
- iOS 直播专题3-前置处理
- EJS 什么是EJS后缀文件 EJS怎么用
- 【iOS官方文档翻译】UICollectionView与UICollectionViewFlowLayout
- iOS解决键盘阻挡输入框