建设信用卡网站首页,手机网站代理,做cpa广告建什么网站好,重庆文化墙制作本文所需的一些预备知识可以看这里: 用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 和 用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 (2) 准备项目建立Richardson成熟度2级的POST、GET、PUT、PATCH、DELETE的RESTful API请看这里#xff1a; 用ASP.NET Core… 本文所需的一些预备知识可以看这里: 用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 和 用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 (2) 准备项目建立Richardson成熟度2级的POST、GET、PUT、PATCH、DELETE的RESTful API请看这里 用ASP.NET Core 2.0 建立规范的 REST API -- DELETE, UPDATE, PATCH 和 Log本文需要的代码 (右键另存把后缀改为zip)https://images2018.cnblogs.com/blog/986268/201806/986268-20180604151009219-514390264.jpg本代码已经更新至ASP.NET Core 2.1. (从ASP.NET Core 2.0 迁移至 ASP.NET Core 2.1: https://docs.microsoft.com/en-us/aspnet/core/migration/20_21?viewaspnetcore-2.1) 本文主要介绍一些常见情况的实现包括集合更新、翻页、排序、过滤等等。但是仍然是Richardson成熟度顶多为2级的Web API未达到RESTful API的标准和约束。 集合的更新操作 看这种更新集合的情况原来数据库里中国存了4个城市北平上海盛京海参崴而几个世纪后北平改名叫北京了盛京改名为沈阳了海参崴不属于中国了就删除了威海从县成为市就算是新增而上海保持不变。现在就是要对中国的城市进行整体性的更新操作里面会包含添加、删除、更新操作。看代码集合更新我一共分了三步进行的操作1. 把数据库中存在的但是传进来的数据里没有的城市删掉2. 把数据库中没有的而传进来的数据里有的数据进行添加操作其实这里只判断id为0即可3. 把数据库中原有和传进来的参数里也存在的数据条目进行更新。然后保存即可。先看一下原有的数据然后我们执行集合的更新执行之后再次查询集合按预期更新了。我相信大家肯定会写这段代码或者有更简单的实现方式请贴出来。但这不是重点我看到有人这样写把上面那三步代码写在了AutoMapper的配置文件里首先需要忽略Country的Cities属性的映射操作然后把那部分代码写在AfterMap里面即可这样在Action方法里面就简单了可以使用Automapper了这只是一种可选的写法而已不一定就必须放在AutoMapper的配置文件里。 翻页翻页可以避免一些性能问题不必一次性加载所有数据。所以最好默认就采用分页而且每页的条目数量必须有限制不能太大。分页信息应该使用查询字符串query stringg传递参数。格式应该这样http://localhost:5000/api/country?pageIndex12pageSize10这里我喜欢使用pageIndex这个词这也意味着页数是从0开始的当然很多人喜欢用pageNumber等词也就是说更喜欢页数从1开始这个其实随意吧。在ASP.NET Core里我要使用Linq来动态组建一个查询的表达式IQueryableT可以创建表达式树它是延迟执行的直到各种条件都判断完了并组建出最终的查询表达式之后才去执行查询数据库。这个查询表达式只有在进行迭代的时候才会查询数据库。触发迭代动作可以使用下面的方法foreach 循环ToList(), ToArray(), ToDictionary() 以及相应的异步版本ToXxxxAsync()单项查询例如 Average(), Count(), First(), FirstOrDefault(), SingleOrDefault()等等以及相应的异步版本。需要确保的是要在迭代发生之前使用Skip()和Take()以及Where()。下面我一点一点来写代码首先我们需要从参数(query string参数)传进来pageIndex和pageSize还要赋默认值以防止API的消费者没有设置pageIndex和pageSize由于pageSize的值是由API的消费者来定的所以应该在后端设定一个最大值以免API的消费者设定一个很大的值。由于所有的资源几乎都要使用翻页所以我们最好使用一个公共类来封装这些翻页相关的信息我暂时把这个类放在了Core项目里。这个公共类很简单可以为pageIndex和pageSize设定默认值也设置了一个每页的最多条目数是100这里面还有一个OrderBy属性默认值是“Id”因为翻页必须要先排序但目前这个OrderBy属性还没用上。而针对具体的资源我们可以再建立一个类继承于PaginationBase这个类就是Country的参数类由于暂时还没有什么特别的参数所以里面是空的。下面我修改一下CountryRepository可以看到我组建了这个查询的表达式并且直接出发了迭代动作返回查询结果。回到Action方法里我使用了这个参数类代替了之前的pageIndex和pageSize参数因为ASP.NET Core足够智能可以把这两个参数解析到这个类里面。下面测试一下我就不进行多次测试了这个是好用的。如果你是用的是关系型数据库的话应该可以在Log的输出媒介上看到打印出的SQL语句但我这里使用的是内存数据库所以看不到如果使用关系型数据库还是看不到SQL语句的话请配置一下返回翻页的元数据很显然只返回当前页的数据是不满足需求的至少还需要返回总页数总数等信息还有可能需要返回前一页或者后一页的链接。但是如何把这些信息连同当页的数据一起返回给API消费者呢下面的做法是可以把这些数据都返回去的{“data”: [{country1}, {country2}...],“metadata”: {prev: /api/..., ....}
}但是这样做的话就导致了响应的body不再符合Accept Header了不是资源的JSON表述了也就不是application/json了而是一种新的media type。所以如果返回这样的数据就违反了REST的规则了尽管本文代码的Richardson成熟度最多也就是2级它违反了自我描述的约束请参考本系列的预备知识文章API消费者不知道如何通过application/json这个设定的contety-type来解释响应数据了。所以说翻页的元数据并不是资源表述的一部分。我们应该使用自定义的Header例如“X-Pagination”来表述翻页元数据这个名也是比较常用的。首先我创建一个类可以存放翻页的数据可以向上面这样做这个类该类继承于ListT同时还包含PaginationBase作为属性还可以判断是否有前一页和后一页。使用静态方法创建该类的实例。这个静态方法也许会有一点点问题这里没有使用异步方法这样做是OK的但是如果使用异步方法例如source.CountAsync()和source.ToListAsync()就会有一些问题因为我需要修改CountryRepository的GetCountriesAsync方法的返回类型改成上面这个类型所以它的接口ICountryRepository也需要改而它的接口是整个项目的核心并放在Core项目里而整个项目的核心合约我个人认为应该是和具体的ORM无关的但是这里依赖于EntityFrameworkCore了ToListAsync()。所以我最后决定去掉这个静态方法这样可能会导致多写一些代码此外还添加HasPrevious和HasNext属性判断是否有前一页和后一页(暂时放在Core项目里面了)。然后修改CountryRepository然后在Action方法里我们还需要生成前一页和后一页的URI所以这里可以使用UrlHelper需要在Startup的ConfigureServices方法里面注册然后回到Controller里面建立一个方法来生成URI在这里我还建立了一个枚举PaginationResourceUriType。我还为PaginationBase添加了一个Clone()方法目的是创建出一个属性值和它相同的另一个实例因为这里有修改pageIndex属性这个操作也许Clone不是最好的办法直接new可能更合适。下面就是修改Action方法了通过之前的方法分别创建出两个链接然后把翻页相关的数据组成一个匿名类使用JSON.NET将其串行化并放到响应的自定义Header“X-Pagination”里面。而body部分还是资源的集合数据。测试一下响应的body正常的返回来了再看一下响应的Header可以看到自定义的X-Pagination Header了然后我复制一下里面的NextPageLink链接并发送该请求都没有问题。这个Action目前的Richardson成熟度已经接近3级了HATEOAS但还不是。翻页现在是到这下面要进行过滤并翻页。 过滤和搜索过滤的意思就是对集合资源附加一些条件然后筛选出结果它的URI是下面的形式http://localhost:5000/api/countries?englishNameChina所以需要在查询字符串里写上属性的名字和属性的值来表示要按这个属性的值来进行过滤当然也可以写多个过滤的条件。过滤的条件是应用于ResourceModel或叫做DtoViewModel例如CountryResource而不应用于其它级别的Model因为API消费者只知道ResourceModel它不知道内部实现的细节也就是不知道EntityModel的样子。 而搜索呢是通过一个搜索关键字来模糊的筛选集合资源可能会有多个属性针对这个关键字进行模糊筛选。搜索的URI大致是下面的形式http://localhost/api/countries?searchTermhin 上面这个URI可以理解为针对Countries资源凡是字符串类型的属性它的值包含hin的都符合条件就返回符合这个条件的结果。首先看一下过滤的实现。在Countries的GET Action方法里我使用CountryResourceParameters类作为参数所以要增加针对某个属性的过滤条件只需扩展这个类即可而增加的属性名要和ResourceModel里面的属性名一致然后是修改CountryRepository里面的方法首先要在执行分页动作之前附加过滤条件query的类型必须是IQueryableCountry才可以动态组建查询表达式所以使用了AsQueryable()方法然后分别判断两个条件并附加条件注意大小写问题和两头空格的问题最后再执行分页查询。由于添加了参数所以CreateUri的方法也需要改这个方法参数变成了CountryResourceParameters而且Clone方法克隆出来的也是CountryResourceParameters类下面测试没有问题的但是还要看看Header针对这个结果是OK的。下面我做一些数据使其拥有同样的EnglishName然后测试 OK再看看Header使用NextLink再次发送请求, 结果是OK的我就不贴图了。但是你应该注意到X-Pagination的属性名不符合camelCase命名规范所以需要在转化成JSON的时候添加一些配置然后再测试一下属性的命名符合camelcase规范了但是previousLink和nextLink里面的查询字符串的大小写依然不正确所以我干脆去掉了Clone()方法然后在CreateCountryUri的方法里直接new出来新链接的参数测试现在命名终于符合规范了。 排序之前做的翻页都需要排序暂时都是按照Id进行排序的。而实际上API消费者可能让资源按照资源的某个属性或多个属性进行正向或反向的排序。我们先从最简单的例子开始只考虑只按照某一个属性针对的是资源的属性例如CountryResource的EnglishName进行排序针对这个例子我先使用比较笨的方法。首先我假定参数类里面的OrderBy属性如果以 desc 结尾例如“EnglishName desc”那么就是按照EnglishName倒序排列而“EnglishName”就是正序排列。只需在CountryRepository里面修改代码即可 嗯很笨重的代码。先测试一下至少功能是OK的再看一下倒序也OK所以虽然代码很笨重但是针对这种简单的情况是可以应付的。下面我们对它进行第一次优化。像上面这样挨个属性的判断实在是太费劲了所以我们来分析一下OrderBy的值是字符串而OrderBy()方法里面的lambda表达式的类型是Expression具体的类型是ExpressionFuncCountry, object。这里简单讲一下万一您不知道lambda表达式的话可以看一下。lambda表达式就是匿名的函数它的类型是Func可以赋值给Func类型的变量同时我们也可以把这个lambda表达式赋值给Expression而OrderBy()这个Linq方法接收的参数类型就是ExpressionFuncCountry, object。使用Expression我们可以构建Expression Tree使用Expression Tree可以表示一些逻辑。而在运行时Linq的提供商将会解析这个Expression Tree并把这些逻辑转化为SQL语句再看上面的排序条件判断我们可以把OrderBy的字符串和Expression映射起来就像Key-Value 键值对那样这样做也许就会是代码稍微好看一些。所以你肯定会想到DictionaryK, V。所以修改后的代码如下我相信你能看懂我就不解释了下面测试总之是好用的我就不贴其他测试结果的图片了。应该把上面这段代码提取出来封装成一个方法函数并泛型化但是我暂时先不这样做。 经过第一次优化使用Dictionary代码简洁了许多但是期间还是有手动把属性名字符串转化为Expression的动作。之所以这么写是因为OrderBy仅支持Expression的参数类型如果支持字符串那就完美了。幸好有一个微软的库支持这种操作它叫做System.Linq.Dynamic.Core其作者是红衣教主啊我把它安装在了Infrastructure项目里供Repository使用。再次修改排序那部分的代码注意这里OrderBy的命名空间是System.Linq.Dynamic.Core。经过第二次优化代码已经很简洁了但是还有很多待完善的地方例如Resource Model的一个属性可能会映射到Entity Model的多个属性上Name 属性通常会映射成EntityModel的 FirstName 和 LastName属性Resource Model上的正序可能在Entity Model上就是倒序的Age 升序而Entity Model的BirthDate就是降序需要支持多属性的排序EnglishName desc, Id, ChineseName。复用 第三次优化要解决Model属性映射引起的问题。也就是说要从ResourceModel的一个属性映射到Entity Model的一个或者多个属性上而且它们之间的排列顺序可能是不同的举一个极端的例子假设ResourceModel 有个属性叫做 Rank(排名) 它所映射Entity Model的两个属性Result(成绩)和Weight(体重)假设这是举重比赛的Model排名结果(Rank)是按照成绩(Result)从高到低排序的但是如果多名选手的成绩相同则体重轻的排名靠前。也就是Rank asc - Result desc, Weight asc。用程序来说就是一个字符串“Rank asc”要映射成一个集合而集合元素的类型有两个属性Entity Model的属性名和排序的方向。所以先把集合里这种元素的类建立出来这里方向我是用的Revert这个单词表示其方向是否与Resource Model的属性方向相反即可。然后在做针对CountryResource的整套映射不过首先我考虑建立一个抽象父类里面可能有些公用的东西由于Id这个属性可能是每个相关的Model共有的所以在这个父类里我添加了Id属性的映射Id是一对一的映射排序方向相同。然后我针对CountryResource写一个派生于PropertyMapping的子类注意红框很重要比较key的时候忽略大小写。到这里Resource和Entity Model之间映射的部分差不多做完了接下来要考虑整个排序的问题做这样一个扩展方法它应用于IQueryable并把orderBy字符串和属性映射表传进来。经过一些初步检验之后把orderBy按“,”分解成字段属性的数组。然后去掉两边可能存在的空格判断是否是倒序提取出属性的名称。如果在映射表里面找不到该名称或者该名称对应的值是空那就抛出异常。然后先循环字段数组然后内层循环该字段映射的属性集合。最后通过DynamicLinq即可组建出所需的排序表达式。使用DynamicLinq的OrderBy时要注意排序条件必须反向附加不信可以试试。随后我们修改一下Repository就剩下一句话了很简洁了。但是这里需要new一个CountryPropertyMapping类这样做对单元测试就不友好了也许把它放在一个容器里取出来用更合适那么就建立一个容器该容器的Register和Resolve分别用来注册和提取映射表。下面还有个检查映射是否存在的方法fields是一个或者多个字段属性组成的字符串其格式如“EnglishName,ChineseName”它检查是否能在映射配置表MappingDictionary找到相应的Key如果找不到就验证失败。这个容器在整个应用范围内也是个容器所以需要在Startup里面注册由于它的代码可能比较多因为本身它也是个容器还有很多注册内容用的代码所以我单独写了个扩展方法该方法可以在Startup里面调用从而注册到ASP.NET Core的服务容器里然后再次修改CountryRepository:先注入了该容器服务然后从该容器中按照映射两端的Model类型取出需要的映射表 测试看起来是OK的那我们针对排序暂时先优化到这里。 排序的异常还需要考虑到如果OrderBy里面的字段在映射表里面不存在的情况所以我使用这个方法来进行判断我把这个方法放在了PropertyMappingContainer里因为PropertyMappingContainer本身实际上就是一个服务放在里还是比较合适的。这里需要注意的是fileds里面的字段可能是这种形式的“EnglishName desc”所以需要把空格和desc部分去掉。随后在Action方法里调用即可测试应该是没问题的我就不多测试了以后要实行单元测试的。 资源塑形如果某个资源的属性比较多那么客户端的API消费者可能只需要一部分属性这时就应该进行数据塑形而且这样做有可能会提升性能。数据塑形要考虑两种情况集合资源和单个资源。集合资源塑形先考虑集合资源首先我做一个扩展方法把IEnumerableT可以转化为IEnumerabledynamic这里要用到dynamicExpandoObject由于反射比较消耗资源所以在这里我一次性把需要的属性弄成PropertyInfo放到了一个集合里。如果fields是空的说明需要所有属性就把所有public和实例的property都放到集合里否则就把需要的属性放进去即可。然后循环数据源使用反射通过PropertyInfo获取该属性的值最后组成一个ExpandoObject再把这个ExpandoObject放到结果集合里面即可。接下来修改参数类因为这是个通用的东西那就是为PaginationBase添加一个Fields属性吧最后修改Action方法测试好用的。但是返回的数据并不是camelcase的这是因为JSON.net串行化的ContractResolver并不适用于Dictionary。下面来处理这个问题。打开Startup在services.AddMvc()后边添加这句话就是配置了JSON转化的ContractResolver。在测试一下现在Ok了。处理异常但如果API消费者在Fields里面提供了不存在的属性那么就应该返回Bad Request。原理上我也许可以使用ProperyMappingContainer里面的验证方法但是数据塑形并不使用映射表。而且目的不同一个是排序一个是数据塑形所以因为关注分离吧SoC。我们要做的就是给定一个Fields和一个类型需要判断Fields里面包含的字段属性在这个类型里面都存在所以还是做一个Service比较好可以注入使用。看代码这个类比较简单不多讲了别忘了在Startup里面注册。然后在Controller里面注入并使用别忘了还需要修改CreateCountryUri方法测试OK.对单个资源塑形这个跟集合的原理差不多先建立一个扩展方法再修改Action即可测试 是好用的我就不多测试了。 针对数据塑形需要注意的是尽量把Id带上否则可能无法获取相关的链接了。 今天先写到这里还有很多更深入一点的功能没有做我就不做了。到目前为止这些Web API仍然称不上是RESTful的API成熟度不够高有些约束也没达到。下一篇文章会把升级这些API以便支持HATEOAS。代码在这https://github.com/solenovex/ASP.NET-Core-2.0-RESTful-API-Tutorial项目有一些文件的拜访目录可能不对暂时先不处理。相关文章用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 (2) 准备项目用ASP.NET Core 2.0 建立规范的 REST API -- GET 和 POST用ASP.NET Core 2.0 建立规范的 REST API -- DELETE, UPDATE, PATCH 和 Log原文地址: http://www.cnblogs.com/cgzl/p/9117448.html.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com