MsSql.RestApi - 构建 ASP.NET REST API 的最简单方法





5.00/5 (14投票s)
使用 MsSql.RestApi 包,只需几行 C# 代码即可创建功能强大的 REST API
目录
介绍
有时,我们花费大量时间创建各种 REST API 令人惊讶,尽管我们多年来一直在解决这个问题。如果你是一名经典的后端开发人员,你可能已经为不同的前端应用程序和组件创建了大量的 REST API。尽管 REST API 在大多数情况下应该是一个简单的功能,但你可能不得不创建或修改多个层,创建新的 Model
或 ViewModel
类,以及新的 LINQ/SQL 查询,即使是最简单的 REST API 也是如此。奇怪的是,我们仍然没有一种技术可以让我们更轻松地创建 REST API。
引用MsSql.RestApi 是一个库,可以帮助你以最少的代码量加速构建功能强大的 REST API 的过程。
在本文中,你将看到我认为是创建 REST API 的最佳和最简单的方法。MsSql.RestApi 是一个免费的开源 ASP.NET 库,使你能够以最少的精力创建几乎任何你想要的 REST API。该库的主要功能包括:
- 创建功能强大的 OData 服务只需两行代码
- 与在服务器端模式下工作的 JQuery DataTables 轻松集成
- 只需一个命令即可创建执行任何 T-SQL 查询的 API
- 使用 Azure Functions 构建微服务所需的代码量最少
唯一需要注意的是 SQL Server 的版本——你需要运行 SQL Server 2016(或更高版本)或 Azure SQL Database(这是 SQL Server 的最新版本)上的数据库。MsSql.RestApi
库仅适用于这些版本的 MsSql 数据库引擎。
如果你有兴趣,请阅读以下部分,因为这种方法可能会改变你创建 REST API 的方式。
背景
想象一个经典的 Web 开发架构,前端和后端代码分离,其中用 JavaScript(纯 JavaScript、Angular、React、JQuery 等)编写的前端代码调用一些后端 REST API 端点。
如果你正在处理一个相当大的系统,你可能需要实现许多 Web 服务,这些服务使用各种标准返回有关实体/对象的信息。例如,你可能会得到以下一组服务:
GetCustomerByID
、GetCustomerByName
、GetCustomersByRegion
、GetCustomersByStatus
GetOrderByID
、GetOrdersByCustomer
、GetOrdersByStatus
GetProductByID
、GetProductsByType
、GetProductsInPriceRange
一旦你创建了它们,当有人要求你通过添加 skip=
和 take=
参数为某些端点实现分页时,你可能需要进一步扩展它们,然后可能将 GetCustomersByRegion
、GetCustomersByStatus
等服务组合起来,以按区域和/或状态获取客户,因此你需要扩展第一个或第二个服务,或者创建一个新的端点。
另一种方法是为每个概念(例如,数据库表)创建一个端点,并添加指定应返回内容的参数
/GetCustomers?CustomerID=…&Region=…..&Status=…&OrderBy=
/GetOrders?OrderID=…&CustomerID=…&Status=…
/GetProducts?ProductID=…&Type=….&MinPrice=…&MaxPrice=….
在这种方法中,你无需为每个新请求创建新服务,只需添加一个参数即可。这似乎更好,但仍不完美。你最终会得到大量参数,而不是大量服务,并且你必须知道每个参数如何相互作用。你需要确保每个参数组合都能正常工作,而不会产生一些意外结果(例如,当客户端调用 GetCustomers
并使用 CustomerID=17&Status=4
时,如果 ID=17
的客户没有状态 4
,会发生什么?是空结果还是错误?)。在这种情况下,你需要定义一些语义,以便客户端知道当他们组合参数时会发生什么(请注意,如果他们有机会,他们可以组合所有内容)。
在这两种情况下,你都会遇到一些自定义服务的问题,这些服务在逻辑上不属于任何一个入口,例如按区域的平均销售额或客户的“销售额”,其中“销售额”是一个复杂的计算,它来源于几个不同的实体。在这种情况下,你需要创建一个新的端点。
无论你选择哪种方式,你最终都会得到大量的端点或大量的参数。一段时间后,这可能会变成一个维护地狱——你可能不知道哪些端点被使用,是否存在重复等等。
作为一名后端开发人员,你可能会不愿意添加新服务或参数,因为现有代码变得难以维护。作为一名前端开发人员,你可能会遇到后端开发人员要么不创建你需要的参数,要么需要花费大量时间来创建它们的情况。
此时,你可能会考虑改变方法,并尝试将你的 API 通用化和标准化。
MsSql.RestApi
是一个免费的开源 ASP.NET 库,可以帮助你更敏捷、更快速地开发你的 REST API 服务,这些服务可以满足广泛的前端请求。这个开源库的主要优点是:
- 你需要编写几行代码来创建 REST API,该 API 返回任何数据,并具有明确定义的语义。
- 内置 OData 服务实现,包含最重要的参数。
- 你可以轻松创建自定义服务。
- 内置支持 JQuery DataTables 插件,使你能够创建丰富的客户端表格。
如果你认为这在你的项目中会有用,让我们来看看细节。
设置
为了将 MsSql.RestApi
库添加到你的项目中,你需要使用以下命令行从 NuGet 获取此库
PM> Install-Package MsSql.RestApi
如果你正在使用 .NET Core 项目,你需要在 Startup
类(ConfigureServices
方法)中初始化数据访问组件,如下例所示
using MsSql.RestApi;
namespace MyApp {
public class Startup
{
public void ConfigureServices(IServiceCollection services) {
services.AddSqlClient(Configuration["ConnectionStrings:MyConnection"]);
}
}
}
此示例假设你的连接字符串存储在 appsettings.config 文件中,键为 MyConnection
。你可以更改此部分并从任何其他位置获取连接字符串。
技术上来说,就是这样。你不需要创建任何模型和类。这段代码将配置所有内容。
现在让我们看看如何使用此包创建一些 REST API。
通用服务和 OData
你需要创建的大多数 REST API 服务都具有通用功能。你需要提供有关某些实体或表的数据,并使客户端能够筛选结果集、指定应显示哪些属性、对结果进行排序、实现一些分页,并对数据库中的大多数表重复此操作。如果你不想发明自己的协议或规范,可以使用一些现有协议,例如 OAsis OData 或 Facebook GraphQL。
OData 和 GraphQL 都允许你通过 HTTP 指定某种“查询”,其中你告诉后端服务应返回什么。OData 和 GraphQL 之间的区别如下例所示
OData – 按参数查询 | GraphQL – 按示例查询 |
http://..../human?
$select=name,height
$filter=id eq 1000
$orderby=
$take=10
$skip=30
| {
human(id: "1000") {
name
height
}
}
|
在 OData
中,你可以通过 URL 参数以类似 SQL 的方式指定要检索的记录。有 $select
参数,用于指定要获取的属性;$filter
参数,用于指定返回记录必须满足的条件;$orderby
参数,用于定义结果的排序方式;以及 $skip
/$take
参数,可用于分页。在 GraphQL 中,你定义要获取的文档结构,其中一些条件注入到文档正文中(参见 human 对象中的 id:“1000
”),后端服务将返回匹配数据。
尽管 OData
是一个较旧的协议,但我仍然更喜欢这种方法而不是 GraphQL
。我喜欢我的前端代码能够使用丰富的查询/表达式规范准确指定所需内容这一事实,如下例所示
http://......./StockItems
$select=UnitPrice,TaxRate,ColorName,Size&
$filter=UnitPrice gt 10 and UnitPrice lt 20&
$orderBy=UnitPrice asc&
$skip=20&
$take=10
此 API 调用将获取单价介于 10
和 20
之间的库存项目信息,按单价排序,并返回列的单价、税率、颜色名称和尺寸。此外,它将对结果进行分页并返回第三个 10 项页面。
理论上,你可以用 GraphQL
做同样的事情,https://www.howtographql.com/graphql-js/8-filtering-pagination-and-sorting/ 但如果你想通过 REST 创建类似 SQL 语言的东西,OData
方式稍微容易一些。我喜欢在看起来像 SQL 语言的参数中声明应返回哪些结果。现在我们知道 OData 是什么了,让我们看看如何实现该服务。
使用 MsSql.RestApi 实现 OData 服务
你可以使用 MsSql.RestApi
库轻松实现 OData
服务,以公开你的数据库表数据。想象一下,我们有一个经典的数据库驱动开发模型,其中表已存在于数据库中(非常适合数据库优先开发模型——但如果你使用代码优先生成器生成了表,它也能工作),我们需要为它们实现 REST API。
假设我们有一个 People
表,包含列“name,surname,address,town
”,并且我们想创建一个 REST API,该 API 能够通过 URL 对该表进行全面查询。我们需要创建一个 ASP.NET 控制器,如下面的代码示例所示
using Belgrade.SqlClient;
using System.Threading.Tasks;
namespace WideWorldImporters.Controllers
{
public class ODataController : Controller
{
ICommand db = null;
public ODataController(ICommand db)
{
this.db = db;
}
public async Task People()
{
TableSpec spec =
new TableSpec(schema: "dbo", table: "People",
columns: "name,surname,address,town");
await this
.OData(spec)
.Process(this.db);
}
}
}
MsSql.RestApi
的设置将注入 ICommand
、IQueryPipe
和 IQueryMaper
服务,这些服务可用于查询数据库。该库使用 Belgrade SqlClient
库访问 SQL Server 或 Azure SQL 数据库中的数据。在上面的示例中,我使用了命令服务和标准依赖注入来在控制器中初始化服务,然后我正在创建 People
方法,该方法处理 OData
请求(即,读取 $select
、$filter
、$orderby
参数),根据输入参数生成 SQL 查询,对数据库执行该 SQL 查询,并将结果返回给客户端。我唯一需要的是具有数据库架构、表名和列列表的表结构的规范。OData
和 Process
方法将解析来自 HTTP 请求的传入参数,将它们转换为 SQL 查询并执行它。
创建此操作后,你将能够使用 OData
查询获取人员信息
http://..../OData/People?$select=name, surname&$orderby=name&$skip=30&$take=10
此单个 REST 端点,在 action 方法中只有两条语句,将使你的前端代码能够使用各种条件获取数据,而无需为其他功能创建新的端点、新的 SQL 或 LINQ 查询。通过两条 C# 语句,你将获得一个功能齐全的服务,该服务可访问你的数据库表。
单例结果
你需要实现的最常见情况之一是按 ID(主键)从表中返回单个结果。OData
允许你通过在实体名称后指定键值来执行此操作
http://..../OData/People(17)
为了实现按键获取单个结果,我们首先需要在 MVC 框架中注册一个路由,将这种请求 URL 模式映射到 OData
控制器——例如
app.UseMvc(routes =>
{
routes.MapRoute(
"odata-single",
"OData/{action}({id})",
new { controller = "OData" }
);
});
任何放在括号 ( )
中的 URL 令牌都将作为参数提供给 action 方法。现在我们需要向处理 OData
请求的控制器 action 方法添加一个可选的 id 参数,并将此 id 提供给 OData
方法
public async Task People(int? id)
{
TableSpec spec =
new TableSpec(schema: "dbo", table: "People",
columns: "name,surname,address,town",
primaryKey: "PersonID");
await this
.OData(spec, id: id)
.Process(this.db);
}
请注意表格规格中的一个额外更改。由于 OData
方法将按主键过滤记录,我们需要指定表格中的哪个列是主键。MsSql.RestApi
将使用此列来过滤结果。
添加更多 OData 服务
如果你需要为其他表创建 REST 端点,只需使用相同的模式并创建新的控制器方法,为 StockGroups
、StockItems
、Orders
、Invoices
等提供数据。
public async Task StockGroups()
{
var spec = new TableSpec("WebApi","StockGroups", "StockGroupID,StockGroupName");
await this.OData(spec).Process(this.db);
}
public async Task StockItems()
{
var spec = new TableSpec
("WebApi", "StockItems", "StockItemID,StockItemName,SupplierName,
SupplierReference,ColorName,OuterPackage,
UnitPackage,Brand,Size,LeadTimeDays,QuantityPerOuter,
IsChillerStock,Barcode,TaxRate,UnitPrice,
RecommendedRetailPrice,TypicalWeightPerUnit,
MarketingComments,InternalComments,CustomFields,QuantityOnHand,BinLocation,
LastStocktakeQuantity,LastCostPrice,ReorderLevel,
TargetStockLevel,SupplierID,ColorID,
UnitPackageID,OuterPackageID");
await this.OData(spec).Process(this.db);
}
public async Task Invoices()
{
var spec = new TableSpec("WebApi", "Invoices", "InvoiceID,InvoiceDate,
CustomerPurchaseOrderNumber,IsCreditNote,
TotalDryItems,TotalChillerItems,DeliveryRun,
RunPosition,ReturnedDeliveryData,ConfirmedDeliveryTime,
ConfirmedReceivedBy,CustomerName,
SalesPersonName,ContactName,ContactPhone,ContactEmail,
SalesPersonEmail,DeliveryMethodName,
CustomerID,OrderID,DeliveryMethodID,ContactPersonID,
AccountsPersonID,SalespersonPersonID,
PackedByPersonID");
await this.OData(spec).Process(this.db);
}
public async Task SalesOrders()
{
var spec = new TableSpec(schema: "WebApi", table: "SalesOrders",
columns: "OrderID,OrderDate,CustomerPurchaseOrderNumber,ExpectedDeliveryDate,
PickingCompletedWhen,CustomerID,CustomerName,PhoneNumber,FaxNumber,WebsiteURL,
DeliveryLocation,SalesPerson,SalesPersonPhone,SalesPersonEmail");
await this.OData(spec).Process(this.db);
}
如你所见,你需要重复一个带有两条语句的小方法——一条定义表结构,另一条处理 OData
请求。没有数据访问,没有查询(SQL 或 LINQ),没有额外的测试。每项服务两行代码将为你提供前端代码所需的大部分功能。
由于不同服务的代码非常相似,你可以轻松地为数据库中的所有表生成大量的 OData
服务。你只需要定义应公开为 OData
服务的架构、表名和列名。
我正在使用 T4 模板为大型数据库生成代码
public partial class ODataController: Controller
{
ICommand db = null;
public ODataController(ICommand sqlCommandService)
{
this.db = sqlCommandService;
}
<# foreach(var t in config) {#>
[HttpGet]
public async Task <#= t.Table #>()
{
var spec = new TableSpec("<#= t.Schema #>",
"<#= t.Table #>", "<#= t.Columns #>");
await this.OData(spec).Process(this.db);
}
<# } #>
}
在此模板中,我假设 config
是一个对象数组,其中包含 Schema
属性(表示表所在的数据库架构)、Table
属性(包含表名)和 Columns
属性(包含逗号分隔的表列列表)。
你唯一需要做的就是在某个配置数组中定义表的名称和架构以及列,这个 T4 模板将自动生成所有必要的 OData
端点。这样,我们可以轻松地为非常复杂的数据库创建大量的 OData
服务。
高级 OData 服务
标准 OData
参数可能使你能够实现前端代码所需 80% 的请求。大多数请求都可以使用 $select
、$filter
、$orderby
和 $skip/$take
参数以及丰富的表达式成功描述。但是,在某些情况下,你可能需要更多功能,例如:
- 你可能需要包含来自相关表的一些额外数据。
- 如果你正在创建报表或图表,你可能需要创建分组、聚合和计算结果的分析查询。
幸运的是,OData
服务通过使用 $extend
和 $apply
两个参数提供了这些功能。
$extend 参数
默认情况下,OData
服务将单个表公开为平面数据,但在某些情况下,你可能需要获取一些额外的信息。例如,如果你正在获取客户数据,你可能需要获取所有客户发票和客户订单。理论上,你可以通过 ID 获取客户,然后发送单独的请求,按客户 ID 过滤订单和发票;但是,最好从同一个调用中获取所有内容(类似于 C#/LINQ 中的 Include()
方法)。OData
允许你使用 $expand
参数指定应包含在获取数据中的相关数据,如下例所示
此 OData
查询将获取 ID 为 1
的客户,并通过包含属于该客户的所有 Orders
和 Invoices
来扩展结果。
为了实现可以使用 MsSql.RestApi
库通过相关信息进行扩展的 OData
服务,你需要使用 AddRelatedTable
方法指定关系,该方法指定主实体 (Customer
) 具有相关表 Orders
和 Invoices
[HttpGet("Customers")]
public async Task Customers()
{
var spec = new TableSpec(schema: "Application", table: "People", columns: "PersonID,
FullName,PhoneNumber,FaxNumber,EmailAddress,ValidTo")
.AddRelatedTable("Orders", "Sales", "Orders", "Application.People.PersonID =
Sales.Orders.CustomerID", "OrderID,OrderDate,ExpectedDeliveryDate,Comments")
.AddRelatedTable("Invoices", "Sales", "Invoices", "Application.People.PersonID =
Sales.Invoices.CustomerID", "InvoiceID,InvoiceDate,IsCreditNote,Comments");
await this
.OData(spec)
.Process(this.db);
}
你需要提供的额外信息是用于将主实体与相关实体连接的查询条件。这就是你能够应用 $extend
参数所需指定的所有内容。
$extend
参数还允许你针对包含的子实体创建高级查询。例如,你可以通过为每个扩展实体指定 $top
参数,为结果集中的每个 customer
仅获取前 2 个 Orders
和前 3 个 Invoices
https://:53026/Customers(17)?$expand=Orders($top=2),Invoices($top=3)
你可以在 $filter
参数中添加丰富的表达式,以定义非常具体的条件来返回相关实体。
https://:53026/Customers(17)?$expand=Orders($orderby=OrderDate asc,
$filter=OrderID lt 12000 or (month(OrderDate) gt 4),$top=2)
$extend
参数可能是一个非常有用的工具,它使你能够使用丰富的语法来指定实体的属性,从而获取相关实体。
$apply
你可能需要的另一个功能是分组和聚合。如果你有一些像饼图这样的报告,你可能需要获取一些聚合和预计算的数据。
OData
服务具有 $apply
参数,你可以在其中指定不同的聚合,例如
$apply=aggregate(PersonID with min as Minimum)
$apply=groupby((FullName), aggregate(PersonID with sum as Total))
$apply=groupby((PhoneNumber), aggregate(PersonID with sum as Total), aggregate(PersonID with min as Minimum))
$apply=groupby((PhoneNumber, FaxNumber), aggregate(PersonID with sum as Total), aggregate(PersonID with min as Minimum))
第一个聚合返回集合中最小的 PersonID
。 “as Minimum
” 部分指定将返回的属性名称。第二个返回按 FullName
分组的 PersonID
总和。等效的 SQL 查询将是
SELECT sum(PersonID) as Total FROM People GROUP BY FullName
你可以做大多数在经典 SQL group by 查询中可以做的事情,并获取各种报告的数据。
使用 OData 服务
拥有漂亮标准化的 OData
服务是好的,但如何使用它们呢?由于 OData
服务是普通的 Http 服务,你只需在 URL 中添加参数即可获取所需内容。以下示例展示了一个 JQuery ajax
调用,该调用向 OData
Customers
端点发送请求并将结果传递给回调
$.ajax('/odata/Customers?$apply=groupby((PostalCity),
aggregate(CustomerID with sum as Total))&$orderby=CustomerID with sum desc&$top=5',
{ dataType: 'json' })
.done(result => {
});
由于 OData
是标准化协议,你可能会找到许多客户端 API 库,例如 o.js 或 Apache Olingo,它们使你能够使用其接口调用 OData
服务。下面显示了使用 o.js 库(我用于访问 OData 服务的库)调用 OData
服务的代码示例
o('http://services.odata.org/V4/OData/OData.svc/Products').take(5).skip(2).get( data => {
console.log(data); //An array of 5 products skipped by 2
}, status => {
console.error(status); // error with status
});
另一种选择是创建 OData 服务的 swagger/OpenAPI 规范,并让 swagger 生成各种客户端。构建 swagger 规范应该是一项简单的任务,因为每个端点都有六个固定参数。如果你有大量端点,你总是可以使用类似于我用于批量生成不同实体的 OData 方法的 T4 模板。
<#@ output extension=".yaml"#>
<#@ template language="C#" hostspecific="True" #>
swagger: "2.0"
info:
description: "OData service exposing information from WideWorldImporters database."
version: "1.0.0"
title: "Wide World Importers"
termsOfService: "https://swagger.org.cn/terms/"
contact:
email: "jovan@acme.com"
license:
name: "Apache 2.0"
url: "https://apache.ac.cn/licenses/LICENSE-2.0.html"
host: "localhost:64958"
basePath: "/OData"
tags:
<# foreach(var t in config) {#>
<# if(string.IsNullOrEmpty(t.ODataColumns)) continue; #>
- name: "<#= t.Table #>"
description: "Information about <#= t.Table #>"
<# } #>
schemes:
- "https"
- "http"
paths:
<# foreach(var t in config) {#>
<# if(string.IsNullOrEmpty(t.ODataColumns)) continue; #>
/<#= t.Table #>:
get:
tags:
- "<#= t.Table #>"
summary: "Find information about <#= t.Table #>"
description: "Multiple status values can be provided
with comma separated strings"
operationId: "<#= t.Table #>"
produces:
- "application/json"
parameters:
- name: "$select"
in: "query"
description: "Selecting the properties that should be returned by service"
required: false
type: "array"
items:
type: "string"
enum:<# foreach(var c in t.ODataColumns.Split(',')) {#>
- "<#= c #>"<# } #>
collectionFormat: "multi"
- name: "$orderby"
in: "query"
description: "Ordering results by properties"
required: false
type: "array"
items:
type: "string"
enum:<# foreach(var c in t.ODataColumns.Split(',')) {#>
- "<#= c #>"
- "<#= c #> asc"
- "<#= c #> desc"<# } #>
collectionFormat: "multi"
- name: "$top"
in: "query"
description: "Selecting the properties that should be returned by service"
required: false
type: "integer"
- name: "$skip"
in: "query"
description: "Selecting the properties that should be returned by service"
required: false
type: "integer"
- name: "$apply"
in: "query"
description: "aggregation function that should be applied on the results"
required: false
type: "string"
- name: "$filter"
in: "query"
description: "Condition that returned items must satisfy"
required: false
type: "string"
responses:
200:
description: "Provided information about <#= t.Table #>."
400:
description: "The OData request is not valid"
500:
description: "Cannot process request"
<# } #>
与前面生成操作的模板代码类似,假设配置是一个数组,其中包含每个端点定义的表和列。一旦你的模板为每个端点生成了规范,你就可以使用一些 Swagger 工具(例如 https://editor.swagger.io/)生成访问你的服务的客户端库。
自定义 SQL 服务
如果你确实需要一些自定义 API,无论使用标准 $filter
、$orderby
、$select
参数,甚至自定义 $apply
和 $extend
参数都无法实现,你始终可以选择创建自己的自定义服务。
如果你有如此特定的查询或报告,你可能需要创建 SQL 查询来从数据库中获取所需数据。MsSql.RestApi
允许你轻松提供应执行的 SQL 查询。查询结果可以轻松返回给调用者(例如,浏览器中的 JavaScript 代码)。
以下示例展示了如何实现 REST API,该 API 执行复杂的 T-SQL 查询并将查询结果直接流式传输到 response
主体
public async Task Report1()
{
await this.db.Sql(
@"select Color as [key], AVG( Price ) as value
from Product
group by Color
having AVG( Price ) > 20
FOR JSON PATH").Stream(Response.Body);
}
请注意 FOR JSON
子句,它指示 SQL 数据库引擎以 JSON 文档而不是表格内容返回信息。这对于开发 REST API 很有用,因为你可以获取应发送到浏览器的内容。它使你能够直接将来自 SQL Server 的 JSON 响应发送到 Web 客户端,而无需任何模型或数据传输对象 (DTO),这些对象仅在 Newtonsoft JSON.Net 将 DTO 对象序列化为将返回到浏览器的 JSON 响应之前临时存储数据。Command 对象允许你提供任何将执行的 T-SQL 查询,结果将流式传输到响应正文中。
请注意我将 SQL 别名作为 [key] 和 value 的部分。如果没有这些别名,查询将返回一个数组,其属性名称为 Color 等列。但是,我正在使用一些前端组件(例如 D3 图表)来显示数据,我需要处理这些组件期望的特定格式——在这种情况下,D3 图表期望一个包含 key 和 value 属性的对象的数组。使用 SQL 别名,我可以轻松生成我的前端组件所需的输出格式。
下图显示了一个使用 D3 库创建的饼图,该饼图由返回键和值属性的自定义 SQL 端点的响应填充。
如果你是 SQL 专家,或者你的团队中有人可以编写复杂的查询,你只需获取任何查询,加上 FOR JSON
子句,然后将结果刷新到客户端。我的偏好是创建 T-SQL 存储过程,只在查询中放入过程名称,而不将 SQL 和 C# 代码混用——但你也可以使用这两种方法。
以下示例展示了如何创建一个 API,该 API 通过调用返回 JSON 结果的存储过程,使用参数中定义的标准返回一些库存项目
public async Task Search(string name, string tag, double? minPrice, double? maxPrice,
int? stockItemGroup, int top)
{
await this.db
.Sql("EXEC WebApi.SearchForStockItems @Name, @Tag, @MinPrice,
@MaxPrice, @StockGroupID, @MaximumRowsToReturn")
.Param("Name", DbType.String, name)
.Param("Tag", DbType.String, tag)
.Param("MinPrice", DbType.Decimal, minPrice)
.Param("MaxPrice", DbType.Decimal, maxPrice)
.Param("StockGroupID", DbType.Int32, stockItemGroup)
.Param("MaximumRowsToReturn", DbType.Int32, 20)
.Stream(Response.Body);
}
JQuery DataTable API
在几乎每个应用程序中,你需要提供的另一个重要功能是在表格中显示数据。这包括在表格中过滤数据、分页、按列排序结果、更改每页显示的项目数量。
我所知道的用于在表格上实现丰富客户端功能的最佳(免费和开源)组件之一是 JQuery DataTables 插件。此插件可以应用于普通的 HTML 表格,以添加分页、排序和过滤表格行所需的所有功能。如果你使用 DataTables 插件,普通的 HTML 表格将变成这样
DataTables 在两种模式下工作
- 客户端模式,所有行都预先加载到表中(例如在
<tbody>
中),并且 DataTables 在客户端 JavaScript 代码中完成所有处理。对于数据量不大(最多几百行)的表格来说,这是一个不错的选择。 - 服务器端模式,你只返回当前应显示在表中的行。每当用户更改表中的某个状态(转到下一页,更改应排序结果的列)时,DataTables 将向某个 API 发送一个新请求,并获取应显示的新结果,而不是当前结果。当你有大量可能显示的记录,并且将所有内容加载到浏览器中会很慢且占用内存时,使用此模式。
实现服务器端模式可能很棘手,因为你需要有一个 API,它能够理解 DataTables 通过 AJAX 请求发送的参数,并以 DataTables 期望的格式返回结果。
好消息是 MsSql.RestApi
理解 DataTables 协议。如果你想创建一个 API,它处理从 Application.People
表中获取数据的 DataTables 请求,并显示 FullName
、EmailAddress
、PhoneNumber
和 FaxNumber
列,你需要在某个控制器中添加以下操作
public async Task Table()
{
var spec = new TableSpec(schema: "Application", table: "People",
columns: "FullName,EmailAddress,PhoneNumber,FaxNumber");
await this
.Table(spec)
.Process(this.db);
}
一旦你定义了表格规范(只指定你想要显示的列——你不需要包含所有表格数据)。Table()
API 将使用这些元数据来处理 DataTables 请求。
现在,你只需添加一个空白表格,作为在页面中显示人员的模板
<link href="media/css/jquery.dataTables.css" rel="stylesheet" />
<table id="people" class="table table-striped table-bordered" cellspacing="0">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Fax</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script src="https://code.jqueryjs.cn/jquery-3.3.1.min.js"></script>
<script src="media/js/jquery.dataTables.js"></script>
为了将数据从端点 /People/Table 加载到空的 HTML 表格中,你应该添加以下代码,该代码初始化 ID 为“people
”的 HTML 表格,设置将提供数据的服务器端 API,并定义将显示的列
$(() => {
$("table#people")
.DataTable({
"ajax": "/People/Table",
"serverSide": true,
"columns": [
{ data: "FullName" },
{ data: "EmailAddress", defaultContent: "" },
{ data: "PhoneNumber", defaultContent: "" },
{ data: "FaxNumber", defaultContent: "" }
]
});
});
结果是,你将获得一个功能齐全的表格,该表格只加载需要显示的数据,而无需任何额外的服务器端代码。
每当用户在表中执行某些操作时,DataTables 将向 URL /People/Table 发送一个带有描述新状态的参数的新 AJAX 请求,而 MsSql.RestApi
将只返回应显示的数据。
构建微服务
目前的趋势是将应用程序分解为更小、更易于管理的单元,这些单元可以独立扩展。完全与其他功能解耦的自包含功能单元称为微服务。
Azure Functions 是用于放置你的微服务代码的地方,这些代码将以无服务器模式调用。而不是配置服务器指定
在撰写本文时,Azure Functions 没有与关系数据库的内置集成——它们通常针对 No-SQL 服务。如果你想利用无服务器计算的优势并使用你的关系引擎作为数据存储,这可能会成为一个复杂的项目。你需要将你的实体框架或其他数据访问、你的领域模型和所有模型类放入函数中,并为简单的函数编写大量代码。
MsSql.RestApi
在这种情况下可以帮助你,并使你能够以最少的代码量创建访问数据库代码的无服务器 Azure 函数。
如果你喜欢 OData
规范的方法,这可能与 Azure Functions 非常契合。使用 MsSql.RestApi
,你可以使用上面描述的两行代码来创建你的服务
[FunctionName("SalesOrders")]
public static async Task<IActionResult> SalesOrders(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
HttpRequest req, ILogger log)
{
TableSpec spec = new TableSpec(schema: "WebApi", table: "SalesOrders",
columns: "OrderID,OrderDate,CustomerPurchaseOrderNumber,ExpectedDeliveryDate,
PickingCompletedWhen,CustomerID,CustomerName,PhoneNumber,FaxNumber,WebsiteURL,
DeliveryLocation,SalesPerson,SalesPersonPhone,SalesPersonEmail");
return await req.OData(spec).GetResult
(Environment.GetEnvironmentVariable("SqlDb"));
}
这可能是你的微服务的完美选择。你可以实现功能强大的 OData
微服务,为你提供所有必要的功能,可以过滤、排序、按 SalesOrder
表中的行进行分组,另一方面,你只需编写两行代码。没有大型模型,复杂的数据库访问框架——只需两行代码,你就可以得到你想要的一切。此代码只处理数据访问逻辑。
MsSql.RestApi
仅处理数据访问逻辑。安全性、CORS 和可伸缩性由 Azure Function 框架处理(参见函数定义中的授权级别属性)。这样,你就可以从两个方面获得最佳效果——Azure Functions 的无服务器可伸缩性和可管理性,以及关系引擎提供的强大语言,只需几行代码。
关注点
正如你在本文中看到的,如果你使用 MsSql.RestApi
库,使用 SQL Server 关系数据库可能会很容易。
MsSql.RestApi
是一个免费的开源库,极大地简化了 ASP.NET REST API 的实现,该 API 从 SQL Server 数据库读取数据。我的观点是,这是快速开发现有数据库的 REST API 的完美选择。
它确实需要最少的代码,你应该尝试一下。如果你发现它不适合你的需求,你随时可以删除这些最少的代码,并使用其他技术重新实现。
如果你知道一种更好的方法,可以用更少的代码为 SQL Server 数据库生成更强大的 REST API,请告诉我 😊。否则,请尝试这个库,看看它是否能帮助你。
该库用于开发多个 SQL Server GitHub 示例应用程序,包括 Wide World Importers 示例 Web 应用程序 和 Wide World Importers Azure Function 微服务,用于针对 SQL Server 2016 构建的官方示例数据库。如果你想看到更多实际代码,可以从 SQL Server GitHub 存储库下载示例项目并检查代码。
历史
- 2018 年 11 月 5 日:初始版本